@quenty/metricutils 2.4.4 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +80 -2
- package/deploy.nevermore.json +10 -0
- package/package.json +22 -4
- package/src/Shared/Definitions/MetricUnitDefinitions.lua +1082 -0
- package/src/Shared/Definitions/MetricUnitDefinitions.spec.lua +55 -0
- package/src/Shared/Definitions/MetricUnitDimensionUtils.lua +164 -0
- package/src/Shared/Definitions/MetricUnitDimensionUtils.spec.lua +49 -0
- package/src/Shared/Definitions/MetricUnitKinds.lua +237 -0
- package/src/Shared/Definitions/MetricUnitKinds.spec.lua +51 -0
- package/src/Shared/Formatting/MetricUnitDisplayUnitUtils.lua +129 -0
- package/src/Shared/Formatting/MetricUnitFormatUtils.lua +924 -0
- package/src/Shared/Formatting/MetricUnitFormatUtils.spec.lua +280 -0
- package/src/Shared/Formatting/RxMetricUnitFormatUtils.lua +62 -0
- package/src/Shared/Localization/MetricUnitFallbackStrings.lua +289 -0
- package/src/Shared/Localization/MetricUnitFallbackStrings.spec.lua +82 -0
- package/src/Shared/Localization/MetricUnitTranslator/ar.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/de.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/en.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/es.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/fr.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/id.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/init.lua +92 -0
- package/src/Shared/Localization/MetricUnitTranslator/it.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ja.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ko.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/pl.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/pt.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ru.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/th.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/tr.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/vi.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/zh-cn.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/zh-tw.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator.spec.lua +309 -0
- package/src/Shared/Localization/PluralCategoryUtils.lua +103 -0
- package/src/Shared/Localization/PluralCategoryUtils.spec.lua +77 -0
- package/src/Shared/Logarithmic/DecibelUtils.lua +72 -0
- package/src/Shared/Logarithmic/DecibelUtils.spec.lua +35 -0
- package/src/Shared/MetricUnit.Angles.spec.lua +80 -0
- package/src/Shared/MetricUnit.Customary.spec.lua +104 -0
- package/src/Shared/MetricUnit.Derived.spec.lua +173 -0
- package/src/Shared/MetricUnit.Display.spec.lua +153 -0
- package/src/Shared/MetricUnit.Electrical.spec.lua +114 -0
- package/src/Shared/MetricUnit.Math.spec.lua +112 -0
- package/src/Shared/MetricUnit.Roblox.spec.lua +34 -0
- package/src/Shared/MetricUnit.SI.spec.lua +127 -0
- package/src/Shared/MetricUnit.Spring.spec.lua +94 -0
- package/src/Shared/MetricUnit.Temperature.spec.lua +212 -0
- package/src/Shared/MetricUnit.Time.spec.lua +202 -0
- package/src/Shared/MetricUnit.Units.spec.lua +210 -0
- package/src/Shared/MetricUnit.lua +1730 -0
- package/src/Shared/MetricUnit.spec.lua +362 -0
- package/src/Shared/MetricUtils.lua +46 -15
- package/src/Shared/MetricUtils.spec.lua +76 -0
- package/src/Shared/Parsing/MetricUnitParseUtils.lua +799 -0
- package/src/Shared/Parsing/MetricUnitParseUtils.spec.lua +189 -0
- package/src/Shared/Systems/MetricUnitSystem.lua +322 -0
- package/src/Shared/Systems/MetricUnitSystem.spec.lua +181 -0
- package/src/Shared/Systems/MetricUnitSystems.lua +225 -0
- package/src/jest.config.lua +3 -0
- package/src/node_modules.project.json +7 -0
- package/test/default.project.json +25 -0
- package/test/scripts/Server/ServerMain.server.lua +14 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnitParseUtils.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUnit = require("MetricUnit")
|
|
10
|
+
|
|
11
|
+
local describe = Jest.Globals.describe
|
|
12
|
+
local expect = Jest.Globals.expect
|
|
13
|
+
local it = Jest.Globals.it
|
|
14
|
+
|
|
15
|
+
type MetricUnit = MetricUnit.MetricUnit
|
|
16
|
+
|
|
17
|
+
local function parse(text: string, kind: any?, options: any?): MetricUnit
|
|
18
|
+
local value, err = MetricUnit.parse(text, kind, options)
|
|
19
|
+
if not value then
|
|
20
|
+
error(string.format("could not parse %q: %s", text, tostring(err)))
|
|
21
|
+
end
|
|
22
|
+
return value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
local function parseError(text: string, kind: any?, options: any?): string?
|
|
26
|
+
local value, err = MetricUnit.parse(text, kind, options)
|
|
27
|
+
expect(value).toBeNil()
|
|
28
|
+
return err
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe("MetricUnit.parse", function()
|
|
32
|
+
it("reads a number and a unit, with or without a space", function()
|
|
33
|
+
expect(parse("5 km"):In("meter")).toBe(5000)
|
|
34
|
+
expect(parse("5km"):In("meter")).toBe(5000)
|
|
35
|
+
expect(parse("2.5 kilometers"):In("meter")).toBe(2500)
|
|
36
|
+
expect(parse("2.5 Kilometres"):In("meter")).toBe(2500)
|
|
37
|
+
expect(parse("1.5 MiB"):In("byte")).toBe(1572864)
|
|
38
|
+
expect(parse("1 KB"):In("byte")).toBe(1000)
|
|
39
|
+
expect(parse("60 mph"):In("milePerHour")).toBeCloseTo(60, 9)
|
|
40
|
+
expect(parse("100 kbps"):In("kilobitPerSecond")).toBeCloseTo(100, 9)
|
|
41
|
+
expect(parse("45%"):In("percent")).toBeCloseTo(45, 9)
|
|
42
|
+
expect(parse("45°"):In("degree")).toBeCloseTo(45, 9)
|
|
43
|
+
expect(parse("100 studs"):In("meter")).toBeCloseTo(28, 9)
|
|
44
|
+
end)
|
|
45
|
+
|
|
46
|
+
it("reads composed units", function()
|
|
47
|
+
expect(parse("5 m/s"):In("meterPerSecond")).toBe(5)
|
|
48
|
+
expect(parse("9.81 m/s²"):In("meterPerSecondSquared")).toBeCloseTo(9.81, 9)
|
|
49
|
+
expect(parse("9.81 m/s^2"):In("meterPerSecondSquared")).toBeCloseTo(9.81, 9)
|
|
50
|
+
expect(parse("1.6 MB/s"):In("kilobytePerSecond")).toBeCloseTo(1600, 6)
|
|
51
|
+
expect(parse("5/d"):In("perDay")).toBeCloseTo(5, 9)
|
|
52
|
+
local momentum = parse("6 m·kg/s")
|
|
53
|
+
expect(momentum:GetDimensions()).toEqual({ length = 1, mass = 1, time = -1 })
|
|
54
|
+
expect(momentum:GetBaseAmount()).toBe(6)
|
|
55
|
+
expect(parse("2 m^4"):GetDimensions()).toEqual({ length = 4 })
|
|
56
|
+
expect(parse("5 ft/s²"):In("meterPerSecondSquared")).toBeCloseTo(5 * 0.3048, 9)
|
|
57
|
+
expect(parse("3 ft·lb"):GetDimensions()).toEqual({ length = 1, mass = 1 })
|
|
58
|
+
expect(parse("2 kg*m/s"):GetDimensions()).toEqual({ length = 1, mass = 1, time = -1 })
|
|
59
|
+
end)
|
|
60
|
+
|
|
61
|
+
it("reads a temperature scale as absolute unless a difference is expected", function()
|
|
62
|
+
local warm = parse("20 °C")
|
|
63
|
+
expect(warm:IsAbsolute()).toBe(true)
|
|
64
|
+
expect(warm:In("celsius")).toBeCloseTo(20, 9)
|
|
65
|
+
expect(parse("20C"):In("celsius")).toBeCloseTo(20, 9)
|
|
66
|
+
expect(parse("-3 °F"):In("fahrenheit")).toBeCloseTo(-3, 9)
|
|
67
|
+
local rise = parse("20 C", "temperatureDifference")
|
|
68
|
+
expect(rise:IsAbsolute()).toBe(false)
|
|
69
|
+
expect(rise:In("kelvin")).toBeCloseTo(20, 9)
|
|
70
|
+
end)
|
|
71
|
+
|
|
72
|
+
it("adds up several parts of one dimension", function()
|
|
73
|
+
expect(parse("1h30m"):In("minute")).toBe(90)
|
|
74
|
+
expect(parse("1 hour, 30 minutes"):In("minute")).toBe(90)
|
|
75
|
+
expect(parse("1 hour and 30 minutes"):In("minute")).toBe(90)
|
|
76
|
+
expect(parse("5 ft 3 in"):In("inch")).toBeCloseTo(63, 9)
|
|
77
|
+
expect(function()
|
|
78
|
+
parse("20 C 5 F")
|
|
79
|
+
end).toThrow("Write a temperature as one number and unit")
|
|
80
|
+
end)
|
|
81
|
+
|
|
82
|
+
it("reads clock text and ISO durations", function()
|
|
83
|
+
expect(parse("1:30"):InSeconds()).toBe(90)
|
|
84
|
+
expect(parse("1:30:00"):InSeconds()).toBe(5400)
|
|
85
|
+
expect(parse("-0:45"):InSeconds()).toBe(-45)
|
|
86
|
+
expect(parse("PT1H30M"):InSeconds()).toBe(5400)
|
|
87
|
+
expect(string.find(parseError("P1Y") or "", "calendar spans", 1, true) ~= nil).toBe(true)
|
|
88
|
+
end)
|
|
89
|
+
|
|
90
|
+
it("resolves ambiguous units by kind, then by the other parts, then by SI symbol", function()
|
|
91
|
+
expect(parse("5 m"):In("meter")).toBe(5)
|
|
92
|
+
expect(parse("5 m", "time"):In("minute")).toBe(5)
|
|
93
|
+
expect(parse("1m30s"):In("second")).toBe(90)
|
|
94
|
+
expect(parse("2 M"):In("month")).toBe(2)
|
|
95
|
+
expect(parseError("5 Mb")).toBe('Unknown unit "Mb"')
|
|
96
|
+
expect(parse("5 MB"):In("megabyte")).toBe(5)
|
|
97
|
+
end)
|
|
98
|
+
|
|
99
|
+
it("reads bare numbers by kind and default unit", function()
|
|
100
|
+
expect(parse("5"):GetKind()).toBe("none")
|
|
101
|
+
expect(parse("5", "none"):GetBaseAmount()).toBe(5)
|
|
102
|
+
expect(parseError("5", "length")).toBe('Add a unit, such as "5 m"')
|
|
103
|
+
expect(parseError("45", "angle")).toBe('Add a unit, such as "45°"')
|
|
104
|
+
expect(parse("5", "length", { defaultUnit = "meter" }):In("meter")).toBe(5)
|
|
105
|
+
expect(parse("5", "instant", { defaultUnit = "second" }):IsAbsolute()).toBe(true)
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
it("returns a message to show instead of erroring", function()
|
|
109
|
+
expect(parseError("")).toBe("Enter a value")
|
|
110
|
+
expect(parseError("abc")).toBe('Could not read "abc"')
|
|
111
|
+
expect(parseError("5 klicks")).toBe('Unknown unit "klicks"')
|
|
112
|
+
expect(parseError("5 kg", "length")).toBe('Expected length, but "5 kg" is mass')
|
|
113
|
+
expect(parseError("5 m", "temperature")).toBe('Expected temperature, but "5 m" is length')
|
|
114
|
+
end)
|
|
115
|
+
|
|
116
|
+
it("reads numbers with the locale's decimal mark", function()
|
|
117
|
+
expect(parse("1,5 km", nil, { locale = "de-de" }):In("meter")).toBe(1500)
|
|
118
|
+
expect(parse("1 234,5 m", nil, { locale = "de-de" }):In("meter")).toBeCloseTo(1234.5, 9)
|
|
119
|
+
expect(parse("1,234.5 m"):In("meter")).toBeCloseTo(1234.5, 9)
|
|
120
|
+
end)
|
|
121
|
+
|
|
122
|
+
it("reads back what short formatting writes", function()
|
|
123
|
+
local values: { MetricUnit } = {
|
|
124
|
+
MetricUnit.meters(2500),
|
|
125
|
+
MetricUnit.kilometers(0.75),
|
|
126
|
+
MetricUnit.millimeters(3),
|
|
127
|
+
MetricUnit.meters(1234.5),
|
|
128
|
+
MetricUnit.new(50, "squareMeter"),
|
|
129
|
+
MetricUnit.new(0.25, "liter"),
|
|
130
|
+
MetricUnit.new(3, "cubicMeter"),
|
|
131
|
+
MetricUnit.bytes(512),
|
|
132
|
+
MetricUnit.megabytes(1.6),
|
|
133
|
+
MetricUnit.new(8, "bit"),
|
|
134
|
+
MetricUnit.kilograms(80),
|
|
135
|
+
MetricUnit.tonnes(2),
|
|
136
|
+
MetricUnit.grams(20),
|
|
137
|
+
MetricUnit.new(1500, "newton"),
|
|
138
|
+
MetricUnit.new(4184, "joule"),
|
|
139
|
+
MetricUnit.new(2500, "watt"),
|
|
140
|
+
MetricUnit.new(101325, "pascal"),
|
|
141
|
+
MetricUnit.new(997, "kilogramPerCubicMeter"),
|
|
142
|
+
MetricUnit.new(5, "meterPerSecond"),
|
|
143
|
+
MetricUnit.new(36, "kilometerPerHour"),
|
|
144
|
+
MetricUnit.new(1600, "kilobytePerSecond"),
|
|
145
|
+
MetricUnit.perDay(5),
|
|
146
|
+
MetricUnit.perSecond(2),
|
|
147
|
+
MetricUnit.new(60, "hertz"),
|
|
148
|
+
MetricUnit.new(9.81, "meterPerSecondSquared"),
|
|
149
|
+
MetricUnit.celsius(21.5),
|
|
150
|
+
MetricUnit.celsius(-3),
|
|
151
|
+
MetricUnit.degrees(45),
|
|
152
|
+
MetricUnit.new(90, "degreePerSecond"),
|
|
153
|
+
MetricUnit.new(2, "radianPerSecondSquared"),
|
|
154
|
+
MetricUnit.none(0.45),
|
|
155
|
+
MetricUnit.minutes(90),
|
|
156
|
+
MetricUnit.years(1),
|
|
157
|
+
MetricUnit.days(1.5),
|
|
158
|
+
MetricUnit.milliseconds(250),
|
|
159
|
+
MetricUnit.new(40, "microsecond"),
|
|
160
|
+
MetricUnit.seconds(0),
|
|
161
|
+
MetricUnit.kilograms(2) * MetricUnit.new(3, "meterPerSecond"),
|
|
162
|
+
MetricUnit.pow(MetricUnit.meters(2), 4),
|
|
163
|
+
}
|
|
164
|
+
for _, value in values do
|
|
165
|
+
local text = MetricUnit.format(value)
|
|
166
|
+
local parsed = parse(text)
|
|
167
|
+
expect(MetricUnit.format(parsed)).toBe(text)
|
|
168
|
+
expect(parsed:GetKind()).toBe(value:GetKind())
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
-- A temperature difference reads back with its kind, since "5 K" alone is an absolute temperature
|
|
172
|
+
local rise = MetricUnit.difference(5, "celsius")
|
|
173
|
+
expect(MetricUnit.format(parse(MetricUnit.format(rise), "temperatureDifference"))).toBe("5\u{00A0}K")
|
|
174
|
+
end)
|
|
175
|
+
end)
|
|
176
|
+
|
|
177
|
+
describe("MetricUnit.parse negative text in several parts", function()
|
|
178
|
+
it("applies the leading minus sign to every part", function()
|
|
179
|
+
expect(parse("-1 hour, 30 minutes"):In("minute")).toBeCloseTo(-90, 9)
|
|
180
|
+
expect(parse("-5 ft 3 in"):In("inch")).toBeCloseTo(-63, 9)
|
|
181
|
+
expect(parse("-1h30m"):In("second")).toBeCloseTo(-5400, 9)
|
|
182
|
+
expect(parse("1 hour, 30 minutes"):In("minute")).toBeCloseTo(90, 9)
|
|
183
|
+
end)
|
|
184
|
+
|
|
185
|
+
it("reads back what the formatter writes for a negative duration", function()
|
|
186
|
+
local written = MetricUnit.format(-MetricUnit.minutes(90))
|
|
187
|
+
expect(parse(written):In("minute")).toBeCloseTo(-90, 9)
|
|
188
|
+
end)
|
|
189
|
+
end)
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
A system of units: the units it declares, the units a value is shown in when it prefers this system, and the
|
|
4
|
+
system it falls back to for everything else. Metric is the base: every unit is sized in SI units, and a value
|
|
5
|
+
made from a metric unit prefers nothing. US customary, British imperial and Roblox studs are systems
|
|
6
|
+
([MetricUnitSystems]), and a game can declare its own, such as studs at another scale.
|
|
7
|
+
|
|
8
|
+
A value made from a system's unit shows itself in that system, so studs read back as studs. To show any value
|
|
9
|
+
in a system, convert it with [MetricUnit.ToSystem] or ask the system to [MetricUnitSystem.Format] it.
|
|
10
|
+
|
|
11
|
+
```lua
|
|
12
|
+
local MINI = MetricUnitSystems.roblox({ name = "robloxMini", metersPerStud = 0.028 })
|
|
13
|
+
|
|
14
|
+
local height = MINI:New(5, "stud") -- 14 cm
|
|
15
|
+
MetricUnit.format(height) -- "5 studs"
|
|
16
|
+
MINI:In(height, "stud") -- 5
|
|
17
|
+
height:In("meter") -- 0.14
|
|
18
|
+
|
|
19
|
+
MetricUnitSystems.IMPERIAL:Format(MetricUnit.new(2, "liter")) -- "3.52 imp pt"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Every amount is held in SI units, so values made through different systems add, compare and convert freely.
|
|
23
|
+
|
|
24
|
+
@class MetricUnitSystem
|
|
25
|
+
]=]
|
|
26
|
+
|
|
27
|
+
local require = require(script.Parent.loader).load(script)
|
|
28
|
+
|
|
29
|
+
local MetricUnitDefinitions = require("MetricUnitDefinitions")
|
|
30
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
31
|
+
|
|
32
|
+
type UnitDefinition = MetricUnitDefinitions.UnitDefinition
|
|
33
|
+
|
|
34
|
+
local MetricUnitSystem = {}
|
|
35
|
+
MetricUnitSystem.ClassName = "MetricUnitSystem"
|
|
36
|
+
MetricUnitSystem.__index = MetricUnitSystem
|
|
37
|
+
|
|
38
|
+
-- MetricUnit and its formatter require the well-known systems, so this requires them back only when called
|
|
39
|
+
local function requireWhenCalled(moduleName: string): any
|
|
40
|
+
return (require :: any)(moduleName)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local SYSTEM_BY_OWN_UNIT: { [UnitDefinition]: MetricUnitSystem } = setmetatable({} :: any, { __mode = "kv" })
|
|
44
|
+
|
|
45
|
+
--[=[
|
|
46
|
+
What a system is made from.
|
|
47
|
+
|
|
48
|
+
- `units` are the units it declares, from [MetricUnitDefinitions.createUnits]. They take the place of a
|
|
49
|
+
well-known unit of the same id, so a system can rescale `stud` and keep its names and translations.
|
|
50
|
+
- `displayUnits` lists, for each dimension, the unit ids to choose from by size, largest first.
|
|
51
|
+
- `pointDisplayUnits` does the same for absolute values, such as `{ "celsius" }` for temperatures.
|
|
52
|
+
- `fallback` is the system asked for any dimension this one does not list.
|
|
53
|
+
|
|
54
|
+
@interface MetricUnitSystemConfig
|
|
55
|
+
.name string
|
|
56
|
+
.units { UnitDefinition }?
|
|
57
|
+
.displayUnits { { string } }?
|
|
58
|
+
.pointDisplayUnits { { string } }?
|
|
59
|
+
.fallback MetricUnitSystem?
|
|
60
|
+
@within MetricUnitSystem
|
|
61
|
+
]=]
|
|
62
|
+
export type MetricUnitSystemConfig = {
|
|
63
|
+
name: string,
|
|
64
|
+
units: { UnitDefinition }?,
|
|
65
|
+
displayUnits: { { string } }?,
|
|
66
|
+
pointDisplayUnits: { { string } }?,
|
|
67
|
+
fallback: MetricUnitSystem?,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type MetricUnitSystem = typeof(setmetatable(
|
|
71
|
+
{} :: {
|
|
72
|
+
_name: string,
|
|
73
|
+
_units: { UnitDefinition },
|
|
74
|
+
_unitsById: { [string]: UnitDefinition },
|
|
75
|
+
_displayUnitIds: { { string } },
|
|
76
|
+
_pointDisplayUnitIds: { { string } },
|
|
77
|
+
_resolved: { displayUnits: { { UnitDefinition } }?, pointDisplayUnits: { { UnitDefinition } }? },
|
|
78
|
+
_fallback: MetricUnitSystem?,
|
|
79
|
+
},
|
|
80
|
+
{} :: typeof({ __index = MetricUnitSystem })
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
--[=[
|
|
84
|
+
A system from `config`. See [MetricUnitSystemConfig].
|
|
85
|
+
|
|
86
|
+
@param config MetricUnitSystemConfig
|
|
87
|
+
@return MetricUnitSystem
|
|
88
|
+
]=]
|
|
89
|
+
function MetricUnitSystem.new(config: MetricUnitSystemConfig): MetricUnitSystem
|
|
90
|
+
assert(type(config) == "table", "Bad config")
|
|
91
|
+
assert(type(config.name) == "string", "Bad config.name")
|
|
92
|
+
assert(config.fallback == nil or MetricUnitSystem.isSystem(config.fallback), "Bad config.fallback")
|
|
93
|
+
|
|
94
|
+
local units: { UnitDefinition } = config.units or {}
|
|
95
|
+
local unitsById: { [string]: UnitDefinition } = {}
|
|
96
|
+
for _, unit in units do
|
|
97
|
+
unitsById[unit.id] = unit
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
local self: MetricUnitSystem = setmetatable({} :: any, MetricUnitSystem)
|
|
101
|
+
for _, unit in units do
|
|
102
|
+
SYSTEM_BY_OWN_UNIT[unit] = self
|
|
103
|
+
end
|
|
104
|
+
self._name = config.name
|
|
105
|
+
self._units = table.freeze(table.clone(units))
|
|
106
|
+
self._unitsById = table.freeze(unitsById)
|
|
107
|
+
self._fallback = config.fallback
|
|
108
|
+
self._displayUnitIds = table.freeze(table.clone(config.displayUnits or {}))
|
|
109
|
+
self._pointDisplayUnitIds = table.freeze(table.clone(config.pointDisplayUnits or {}))
|
|
110
|
+
self._resolved = {}
|
|
111
|
+
return table.freeze(self :: any)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
--[=[
|
|
115
|
+
The system that declared `unit` in its config, or nil for a well-known unit.
|
|
116
|
+
|
|
117
|
+
@param unit UnitDefinition
|
|
118
|
+
@return MetricUnitSystem?
|
|
119
|
+
]=]
|
|
120
|
+
function MetricUnitSystem.getSystemDeclaring(unit: UnitDefinition): MetricUnitSystem?
|
|
121
|
+
return SYSTEM_BY_OWN_UNIT[unit]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
--[=[
|
|
125
|
+
Whether `value` is a [MetricUnitSystem].
|
|
126
|
+
|
|
127
|
+
@param value any
|
|
128
|
+
@return boolean
|
|
129
|
+
]=]
|
|
130
|
+
function MetricUnitSystem.isSystem(value: any): boolean
|
|
131
|
+
return type(value) == "table" and getmetatable(value) == MetricUnitSystem
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
--[=[
|
|
135
|
+
The system's name, such as `"imperial"`.
|
|
136
|
+
|
|
137
|
+
@return string
|
|
138
|
+
]=]
|
|
139
|
+
function MetricUnitSystem.GetName(self: MetricUnitSystem): string
|
|
140
|
+
return self._name
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
--[=[
|
|
144
|
+
The system asked for the dimensions this one does not list, or nil.
|
|
145
|
+
|
|
146
|
+
@return MetricUnitSystem?
|
|
147
|
+
]=]
|
|
148
|
+
function MetricUnitSystem.GetFallback(self: MetricUnitSystem): MetricUnitSystem?
|
|
149
|
+
return self._fallback
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
--[=[
|
|
153
|
+
The units this system declares itself, not counting its fallbacks.
|
|
154
|
+
|
|
155
|
+
@return { UnitDefinition }
|
|
156
|
+
]=]
|
|
157
|
+
function MetricUnitSystem.GetUnits(self: MetricUnitSystem): { UnitDefinition }
|
|
158
|
+
return self._units
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
--[=[
|
|
162
|
+
The unit `unit` names in this system: its own, then its fallbacks', then the well-known one. A unit given
|
|
163
|
+
itself is returned as it is. The result can be passed anywhere a unit is named, and keeps this system's scale.
|
|
164
|
+
|
|
165
|
+
```lua
|
|
166
|
+
height:As(MINI:GetUnit("stud"))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
@param unit UnitLike
|
|
170
|
+
@return UnitDefinition?
|
|
171
|
+
]=]
|
|
172
|
+
function MetricUnitSystem.GetUnit(self: MetricUnitSystem, unit: string | UnitDefinition): UnitDefinition?
|
|
173
|
+
if type(unit) ~= "string" then
|
|
174
|
+
return MetricUnitDefinitions.getUnit(unit)
|
|
175
|
+
end
|
|
176
|
+
local system: MetricUnitSystem? = self
|
|
177
|
+
while system do
|
|
178
|
+
local own = system._unitsById[unit]
|
|
179
|
+
if own then
|
|
180
|
+
return own
|
|
181
|
+
end
|
|
182
|
+
system = system._fallback
|
|
183
|
+
end
|
|
184
|
+
return MetricUnitDefinitions.getUnit(unit)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
--[=[
|
|
188
|
+
The units a value of `dimensions` is shown in when it prefers this system, largest first: this system's, then
|
|
189
|
+
its fallbacks', or nil when none lists the dimension. An absolute value takes the point display units first.
|
|
190
|
+
|
|
191
|
+
@param dimensions Dimensions
|
|
192
|
+
@param absolute boolean?
|
|
193
|
+
@return { UnitDefinition }?
|
|
194
|
+
]=]
|
|
195
|
+
function MetricUnitSystem.GetDisplayUnits(
|
|
196
|
+
self: MetricUnitSystem,
|
|
197
|
+
dimensions: MetricUnitDimensionUtils.Dimensions,
|
|
198
|
+
absolute: boolean?
|
|
199
|
+
): { UnitDefinition }?
|
|
200
|
+
local system: MetricUnitSystem? = self
|
|
201
|
+
while system do
|
|
202
|
+
local displayUnits, pointDisplayUnits = system:_getDisplayUnitLists()
|
|
203
|
+
local found = (absolute and MetricUnitSystem._findByDimensions(pointDisplayUnits, dimensions))
|
|
204
|
+
or MetricUnitSystem._findByDimensions(displayUnits, dimensions)
|
|
205
|
+
if found then
|
|
206
|
+
return found
|
|
207
|
+
end
|
|
208
|
+
system = system._fallback
|
|
209
|
+
end
|
|
210
|
+
return nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
--[=[
|
|
214
|
+
`amount` of this system's `unit`, such as `MINI:New(5, "stud")`.
|
|
215
|
+
|
|
216
|
+
@param amount number
|
|
217
|
+
@param unit UnitLike
|
|
218
|
+
@return MetricUnit
|
|
219
|
+
]=]
|
|
220
|
+
function MetricUnitSystem.New(self: MetricUnitSystem, amount: number, unit: string | UnitDefinition): any
|
|
221
|
+
local MetricUnit = requireWhenCalled("MetricUnit")
|
|
222
|
+
return MetricUnit.new(amount, self:_requireUnit(unit, "New"))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
--[=[
|
|
226
|
+
How many of this system's `unit` `value` is, such as `MINI:In(height, "stud")`.
|
|
227
|
+
|
|
228
|
+
@param value MetricUnit
|
|
229
|
+
@param unit UnitLike
|
|
230
|
+
@return number
|
|
231
|
+
]=]
|
|
232
|
+
function MetricUnitSystem.In(self: MetricUnitSystem, value: any, unit: string | UnitDefinition): number
|
|
233
|
+
return value:In(self:_requireUnit(unit, "In"))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
--[=[
|
|
237
|
+
Text for players with `value` in this system's units where it has them, as [MetricUnit.format] writes it.
|
|
238
|
+
|
|
239
|
+
```lua
|
|
240
|
+
MetricUnitSystems.IMPERIAL:Format(MetricUnit.kilograms(80)) -- "12.598 st"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
@param value MetricUnit | MetricUnitData
|
|
244
|
+
@param options MetricUnitFormatOptions?
|
|
245
|
+
@return string
|
|
246
|
+
]=]
|
|
247
|
+
function MetricUnitSystem.Format(self: MetricUnitSystem, value: any, options: any?): string
|
|
248
|
+
local MetricUnitFormatUtils = requireWhenCalled("MetricUnitFormatUtils")
|
|
249
|
+
return MetricUnitFormatUtils._formatInSystem(value, self, options)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
--[=[
|
|
253
|
+
Reads `text` as [MetricUnit.parse] does, with this system's units: `MINI:Parse("5 studs")` is five of its
|
|
254
|
+
studs.
|
|
255
|
+
|
|
256
|
+
@param text string
|
|
257
|
+
@param kind (KindName | Dimensions)?
|
|
258
|
+
@param options ParseOptions?
|
|
259
|
+
@return MetricUnit?
|
|
260
|
+
@return string? -- why it could not be read
|
|
261
|
+
]=]
|
|
262
|
+
function MetricUnitSystem.Parse(self: MetricUnitSystem, text: string, kind: any?, options: any?): (any?, string?)
|
|
263
|
+
local MetricUnit = requireWhenCalled("MetricUnit")
|
|
264
|
+
return MetricUnit._parseInSystem(text, kind, options, self)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
function MetricUnitSystem._requireUnit(
|
|
268
|
+
self: MetricUnitSystem,
|
|
269
|
+
unit: string | UnitDefinition,
|
|
270
|
+
verb: string
|
|
271
|
+
): UnitDefinition
|
|
272
|
+
local definition = self:GetUnit(unit)
|
|
273
|
+
if not definition then
|
|
274
|
+
error(string.format("MetricUnitSystem:%s: %q knows no unit %q", verb, self._name, tostring(unit)), 3)
|
|
275
|
+
end
|
|
276
|
+
return definition
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
function MetricUnitSystem._findByDimensions(
|
|
280
|
+
lists: { { UnitDefinition } },
|
|
281
|
+
dimensions: MetricUnitDimensionUtils.Dimensions
|
|
282
|
+
): { UnitDefinition }?
|
|
283
|
+
for _, units in lists do
|
|
284
|
+
if MetricUnitDimensionUtils.equal(units[1].dimensions, dimensions) then
|
|
285
|
+
return units
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
return nil
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
function MetricUnitSystem._getDisplayUnitLists(self: MetricUnitSystem): ({ { UnitDefinition } }, { { UnitDefinition } })
|
|
292
|
+
local resolved = self._resolved
|
|
293
|
+
local displayUnits = resolved.displayUnits
|
|
294
|
+
local pointDisplayUnits = resolved.pointDisplayUnits
|
|
295
|
+
if displayUnits and pointDisplayUnits then
|
|
296
|
+
return displayUnits, pointDisplayUnits
|
|
297
|
+
end
|
|
298
|
+
local newDisplayUnits = self:_resolveDisplayUnits(self._displayUnitIds)
|
|
299
|
+
local newPointDisplayUnits = self:_resolveDisplayUnits(self._pointDisplayUnitIds)
|
|
300
|
+
resolved.displayUnits = newDisplayUnits
|
|
301
|
+
resolved.pointDisplayUnits = newPointDisplayUnits
|
|
302
|
+
return newDisplayUnits, newPointDisplayUnits
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
function MetricUnitSystem._resolveDisplayUnits(self: MetricUnitSystem, lists: { { string } }): { { UnitDefinition } }
|
|
306
|
+
local resolved: { { UnitDefinition } } = {}
|
|
307
|
+
for _, ids in lists do
|
|
308
|
+
local units: { UnitDefinition } = {}
|
|
309
|
+
for _, id in ids do
|
|
310
|
+
local unit = self:GetUnit(id)
|
|
311
|
+
if not unit then
|
|
312
|
+
error(string.format("MetricUnitSystem %q knows no unit %q", self._name, id), 4)
|
|
313
|
+
end
|
|
314
|
+
table.insert(units, unit)
|
|
315
|
+
end
|
|
316
|
+
assert(#units > 0, "Empty display unit list")
|
|
317
|
+
table.insert(resolved, table.freeze(units))
|
|
318
|
+
end
|
|
319
|
+
return table.freeze(resolved)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
return MetricUnitSystem
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnitSystem.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUnit = require("MetricUnit")
|
|
10
|
+
local MetricUnitDefinitions = require("MetricUnitDefinitions")
|
|
11
|
+
local MetricUnitSystem = require("MetricUnitSystem")
|
|
12
|
+
local MetricUnitSystems = require("MetricUnitSystems")
|
|
13
|
+
|
|
14
|
+
local describe = Jest.Globals.describe
|
|
15
|
+
local expect = Jest.Globals.expect
|
|
16
|
+
local it = Jest.Globals.it
|
|
17
|
+
|
|
18
|
+
local MINI = MetricUnitSystems.roblox({ name = "robloxMini", metersPerStud = 0.028 })
|
|
19
|
+
|
|
20
|
+
local function plain(text: string): string
|
|
21
|
+
return (string.gsub(text, "\u{00A0}", " "))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local function format(value: any, options: any?): string
|
|
25
|
+
return plain(MetricUnit.format(value, options))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe("MetricUnitSystems", function()
|
|
29
|
+
it("names the well-known systems and chains their fallbacks", function()
|
|
30
|
+
expect(MetricUnitSystems.METRIC:GetName()).toBe("metric")
|
|
31
|
+
expect(MetricUnitSystems.US_CUSTOMARY:GetFallback()).toBe(MetricUnitSystems.US_CUSTOMARY_AND_IMPERIAL)
|
|
32
|
+
expect(MetricUnitSystems.IMPERIAL:GetFallback()).toBe(MetricUnitSystems.US_CUSTOMARY_AND_IMPERIAL)
|
|
33
|
+
expect(MetricUnitSystems.US_CUSTOMARY_AND_IMPERIAL:GetFallback()).toBe(MetricUnitSystems.METRIC)
|
|
34
|
+
expect(MetricUnitSystems.ROBLOX:GetFallback()).toBe(MetricUnitSystems.METRIC)
|
|
35
|
+
expect(MetricUnitSystem.isSystem(MetricUnitSystems.ROBLOX)).toBe(true)
|
|
36
|
+
expect(MetricUnitSystem.isSystem({})).toBe(false)
|
|
37
|
+
end)
|
|
38
|
+
|
|
39
|
+
it("finds the system that declares a unit", function()
|
|
40
|
+
expect(MetricUnitSystems.getSystemOfUnit(assert(MetricUnitDefinitions.getUnit("imperialPint")))).toBe(
|
|
41
|
+
MetricUnitSystems.IMPERIAL
|
|
42
|
+
)
|
|
43
|
+
expect(MetricUnitSystems.getSystemOfUnit(assert(MINI:GetUnit("stud")))).toBe(MINI)
|
|
44
|
+
expect(MetricUnitSystems.getSystemOfUnit(assert(MetricUnitDefinitions.getUnit("second")))).toBeNil()
|
|
45
|
+
end)
|
|
46
|
+
end)
|
|
47
|
+
|
|
48
|
+
describe("a value shows itself in the system of the unit it was made from", function()
|
|
49
|
+
it("prefers nothing when made from a metric unit", function()
|
|
50
|
+
expect(MetricUnit.kilometers(8):GetDisplaySystem()).toBeNil()
|
|
51
|
+
expect(format(MetricUnit.kilometers(8))).toBe("8 km")
|
|
52
|
+
end)
|
|
53
|
+
|
|
54
|
+
it("prefers the unit's system otherwise", function()
|
|
55
|
+
expect(format(MetricUnit.new(5, "mile"))).toBe("5 mi")
|
|
56
|
+
expect(format(MetricUnit.new(30, "milePerUsGallon"))).toBe("30 mpg (US)")
|
|
57
|
+
expect(format(MetricUnit.new(2, "imperialPint"))).toBe("2 imp pt")
|
|
58
|
+
expect(format(MetricUnit.studs(10))).toBe("10 studs")
|
|
59
|
+
end)
|
|
60
|
+
|
|
61
|
+
it("converts to another system for display", function()
|
|
62
|
+
local distance = MetricUnit.new(5, "mile")
|
|
63
|
+
expect(format(distance:ToMetric())).toBe("8.047 km")
|
|
64
|
+
expect(format(MetricUnit.kilometers(8):ToImperial())).toBe("4.971 mi")
|
|
65
|
+
end)
|
|
66
|
+
end)
|
|
67
|
+
|
|
68
|
+
describe("MetricUnitSystem:Format", function()
|
|
69
|
+
it("shows any value in the system", function()
|
|
70
|
+
expect(plain(MetricUnitSystems.IMPERIAL:Format(MetricUnit.new(2, "liter")))).toBe("3.52 imp pt")
|
|
71
|
+
expect(plain(MetricUnitSystems.US_CUSTOMARY:Format(MetricUnit.new(2, "liter")))).toBe("2.113 US qt")
|
|
72
|
+
expect(plain(MetricUnitSystems.METRIC:Format(MetricUnit.new(5, "mile")))).toBe("8.047 km")
|
|
73
|
+
expect(plain(MINI:Format(MetricUnit.meters(0.14)))).toBe("5 studs")
|
|
74
|
+
end)
|
|
75
|
+
|
|
76
|
+
it("falls back for dimensions the system does not list", function()
|
|
77
|
+
expect(plain(MINI:Format(MetricUnit.seconds(90)))).toBe("1 minute, 30 seconds")
|
|
78
|
+
expect(plain(MetricUnitSystems.IMPERIAL:Format(MetricUnit.new(5, "volt")))).toBe("5 V")
|
|
79
|
+
end)
|
|
80
|
+
end)
|
|
81
|
+
|
|
82
|
+
describe("MetricUnitSystems.roblox", function()
|
|
83
|
+
it("reads stud amounts in and out at a game's own scale", function()
|
|
84
|
+
local height = MINI:New(5, "stud")
|
|
85
|
+
expect(height:In("meter")).toBeCloseTo(0.14, 9)
|
|
86
|
+
expect(MINI:In(height, "stud")).toBeCloseTo(5, 9)
|
|
87
|
+
expect(format(height)).toBe("5 studs")
|
|
88
|
+
expect(format(height:ToMetric())).toBe("14 cm")
|
|
89
|
+
end)
|
|
90
|
+
|
|
91
|
+
it("scales speeds, accelerations and the mass unit with the stud", function()
|
|
92
|
+
expect(MINI:New(10, "studPerSecond"):In("meterPerSecond")).toBeCloseTo(0.28, 9)
|
|
93
|
+
expect(MINI:New(1, "studPerSecondSquared"):In("meterPerSecondSquared")).toBeCloseTo(0.028, 9)
|
|
94
|
+
expect(MINI:New(1, "robloxMassUnit"):In("gram")).toBeCloseTo(21.952, 9)
|
|
95
|
+
if typeof(Vector3) == "userdata" or typeof(Vector3) == "table" then
|
|
96
|
+
local speed = MINI:New(Vector3.new(30, 40, 0).Magnitude, "studPerSecond")
|
|
97
|
+
expect(speed:In("meterPerSecond")).toBeCloseTo(1.4, 9)
|
|
98
|
+
end
|
|
99
|
+
end)
|
|
100
|
+
|
|
101
|
+
it("keeps Roblox's own studs apart from the game's", function()
|
|
102
|
+
local height = MINI:New(5, "stud")
|
|
103
|
+
expect(height:In("stud")).toBeCloseTo(0.5, 9)
|
|
104
|
+
expect((MetricUnit.studs(10) + MINI:New(100, "stud")):In("meter")).toBeCloseTo(5.6, 9)
|
|
105
|
+
end)
|
|
106
|
+
|
|
107
|
+
it("keeps its studs through arithmetic", function()
|
|
108
|
+
local height = MINI:New(5, "stud")
|
|
109
|
+
expect(format(height * 2)).toBe("10 studs")
|
|
110
|
+
expect(format(height / MetricUnit.seconds(1))).toBe("5 studs/s")
|
|
111
|
+
expect(format(MetricUnit.round(MINI:New(5.4, "stud"), "stud"))).toBe("5 studs")
|
|
112
|
+
end)
|
|
113
|
+
|
|
114
|
+
it("pins a value to one of its units", function()
|
|
115
|
+
local stud = assert(MINI:GetUnit("stud"))
|
|
116
|
+
expect(format(MetricUnit.meters(0.14):As(stud))).toBe("5 studs")
|
|
117
|
+
expect(MetricUnit.meters(0.14):As(stud):GetDisplayUnit()).toBe(stud)
|
|
118
|
+
expect(MetricUnit.meters(0.14):In(stud)).toBeCloseTo(5, 9)
|
|
119
|
+
end)
|
|
120
|
+
|
|
121
|
+
it("parses studs at its scale", function()
|
|
122
|
+
local value = assert(MINI:Parse("5 studs", "length"))
|
|
123
|
+
expect(value:In("meter")).toBeCloseTo(0.14, 9)
|
|
124
|
+
expect(format(value)).toBe("5 studs")
|
|
125
|
+
local plainStuds = assert(MetricUnit.parse("5 studs", "length"))
|
|
126
|
+
expect(plainStuds:In("meter")).toBeCloseTo(1.4, 9)
|
|
127
|
+
local bare = assert(MINI:Parse("5", "length", { defaultUnit = "stud" }))
|
|
128
|
+
expect(bare:In("meter")).toBeCloseTo(0.14, 9)
|
|
129
|
+
end)
|
|
130
|
+
|
|
131
|
+
it("shows the game's stud amounts in a player's chosen system", function()
|
|
132
|
+
local distance = MINI:New(10000, "stud")
|
|
133
|
+
expect(plain(MetricUnitSystems.METRIC:Format(distance))).toBe("280 m")
|
|
134
|
+
expect(plain(MetricUnitSystems.US_CUSTOMARY:Format(distance))).toBe("918.635 ft")
|
|
135
|
+
expect(format(distance)).toBe("10,000 studs")
|
|
136
|
+
end)
|
|
137
|
+
end)
|
|
138
|
+
|
|
139
|
+
describe("MetricUnitSystem.new", function()
|
|
140
|
+
local SPACE = MetricUnitSystem.new({
|
|
141
|
+
name = "space",
|
|
142
|
+
units = MetricUnitDefinitions.createUnits("length", "space", {
|
|
143
|
+
{ id = "lightYear" :: any, factor = 9.4607e15, symbol = "ly", name = "light-year" },
|
|
144
|
+
{ id = "parsec" :: any, factor = 3.0857e16, symbol = "pc", name = "parsec", prefixes = "si" },
|
|
145
|
+
}),
|
|
146
|
+
displayUnits = { { "parsec", "lightYear", "kilometer" } },
|
|
147
|
+
fallback = MetricUnitSystems.METRIC,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it("declares units only it knows", function()
|
|
151
|
+
expect(MetricUnitDefinitions.getUnit("lightYear" :: any)).toBeNil()
|
|
152
|
+
expect(SPACE:New(2, "lightYear"):In("kilometer")).toBeCloseTo(1.89214e13, -8)
|
|
153
|
+
expect(SPACE:In(SPACE:New(1, "kiloparsec"), "parsec")).toBeCloseTo(1000, 9)
|
|
154
|
+
expect(SPACE:New(3, "meter"):In("meter")).toBe(3)
|
|
155
|
+
expect(function()
|
|
156
|
+
MetricUnit.new(1, "lightYear" :: any)
|
|
157
|
+
end).toThrow('MetricUnit.new knows no unit "lightYear"')
|
|
158
|
+
expect(function()
|
|
159
|
+
SPACE:New(1, "furlong")
|
|
160
|
+
end).toThrow('"space" knows no unit "furlong"')
|
|
161
|
+
end)
|
|
162
|
+
|
|
163
|
+
it("shows and writes its own units in English", function()
|
|
164
|
+
local distance = SPACE:New(4.2, "lightYear")
|
|
165
|
+
expect(format(distance)).toBe("1.288 pc")
|
|
166
|
+
expect(format(distance, { unit = "lightYear", unitDisplay = "long" })).toBe("4.2 light-years")
|
|
167
|
+
expect(plain(SPACE:Format(MetricUnit.kilograms(3)))).toBe("3 kg")
|
|
168
|
+
end)
|
|
169
|
+
|
|
170
|
+
it("parses its own units", function()
|
|
171
|
+
local value = assert(SPACE:Parse("4.2 ly", "length"))
|
|
172
|
+
expect(SPACE:In(value, "lightYear")).toBeCloseTo(4.2, 9)
|
|
173
|
+
end)
|
|
174
|
+
|
|
175
|
+
it("errors on a display unit it cannot find, when it first shows a value", function()
|
|
176
|
+
local broken = MetricUnitSystem.new({ name = "broken", displayUnits = { { "furlong" } } })
|
|
177
|
+
expect(function()
|
|
178
|
+
broken:Format(MetricUnit.meters(1))
|
|
179
|
+
end).toThrow('MetricUnitSystem "broken" knows no unit "furlong"')
|
|
180
|
+
end)
|
|
181
|
+
end)
|