@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,153 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.Display.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
|
+
local function preferenceOf(value: MetricUnit.MetricUnit): string?
|
|
16
|
+
local system = value:GetDisplaySystem()
|
|
17
|
+
local unit = value:GetDisplayUnit()
|
|
18
|
+
return if unit then unit.id elseif system then system:GetName() else nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
-- Formatting puts a no-break space between a number and its unit; these compare it as a plain space
|
|
22
|
+
local function plain(text: string): string
|
|
23
|
+
return (string.gsub(text, "\u{00A0}", " "))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
local function format(value: any, options: MetricUnit.FormatOptions?): string
|
|
27
|
+
return plain(MetricUnit.format(value, options))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
local function mismatches(cases: { { any } }): { string }
|
|
31
|
+
local found = {}
|
|
32
|
+
for _, case in cases do
|
|
33
|
+
local text = string.gsub(tostring(case[1]), "\u{00A0}", " ")
|
|
34
|
+
if text ~= case[2] then
|
|
35
|
+
table.insert(found, string.format("expected %q, got %q", case[2], text))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
return found
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe("MetricUnit display preference", function()
|
|
42
|
+
it("prefers imperial units or a pinned unit only when asked", function()
|
|
43
|
+
local distance = MetricUnit.kilometers(5)
|
|
44
|
+
expect(format(distance)).toBe("5 km")
|
|
45
|
+
expect(format(distance:ToUSCustomary())).toBe("3.107 mi")
|
|
46
|
+
expect(format(distance:As("mile"))).toBe("3.107 mi")
|
|
47
|
+
expect(format(distance:As("meter"))).toBe("5,000 m")
|
|
48
|
+
expect(format(MetricUnit.meters(28):As("stud"))).toBe("100 studs")
|
|
49
|
+
expect(format(distance:ToUSCustomary():ToMetric())).toBe("5 km")
|
|
50
|
+
expect(preferenceOf(distance:ToUSCustomary())).toBe("usCustomary")
|
|
51
|
+
expect(preferenceOf(distance)).toBeNil()
|
|
52
|
+
end)
|
|
53
|
+
|
|
54
|
+
it("never takes a measurement system from the locale", function()
|
|
55
|
+
local distance = MetricUnit.kilometers(5)
|
|
56
|
+
expect(format(distance, { locale = "en-us" })).toBe("5 km")
|
|
57
|
+
expect(format(distance, { locale = "en-us", unitDisplay = "long" })).toBe("5 kilometers")
|
|
58
|
+
expect(format(MetricUnit.celsius(20), { locale = "en-us" })).toBe("20 °C")
|
|
59
|
+
end)
|
|
60
|
+
|
|
61
|
+
it("lets an explicit unit option win over the preference", function()
|
|
62
|
+
expect(format(MetricUnit.kilometers(5):ToUSCustomary(), { unit = "meter" })).toBe("5,000 m")
|
|
63
|
+
end)
|
|
64
|
+
|
|
65
|
+
it("errors pinning a unit of another dimension", function()
|
|
66
|
+
expect(function()
|
|
67
|
+
MetricUnit.kilometers(5):As("kilogram")
|
|
68
|
+
end).toThrow('MetricUnit:As("kilogram") is mass, not length')
|
|
69
|
+
end)
|
|
70
|
+
|
|
71
|
+
it("never changes the amount, comparison, reading or saved data", function()
|
|
72
|
+
local distance = MetricUnit.kilometers(5)
|
|
73
|
+
local imperial = distance:ToUSCustomary()
|
|
74
|
+
expect(imperial == distance).toBe(true)
|
|
75
|
+
expect(imperial:In("kilometer")).toBe(5)
|
|
76
|
+
expect(MetricUnit.expect(imperial, "length", "reach")).toBe(5000)
|
|
77
|
+
expect(MetricUnit.toData(imperial)).toEqual({ amount = 5000, dimensions = { length = 1 } })
|
|
78
|
+
expect(preferenceOf(MetricUnit.fromData(MetricUnit.toData(imperial)))).toBeNil()
|
|
79
|
+
end)
|
|
80
|
+
|
|
81
|
+
it("carries through arithmetic of one dimension when the sides agree", function()
|
|
82
|
+
local imperial = MetricUnit.kilometers(5):ToUSCustomary()
|
|
83
|
+
expect(preferenceOf((imperial + MetricUnit.kilometers(1)))).toBe("usCustomary")
|
|
84
|
+
expect(preferenceOf((MetricUnit.kilometers(1) + imperial))).toBe("usCustomary")
|
|
85
|
+
expect(preferenceOf((imperial + MetricUnit.kilometers(1):ToMetric()))).toBeNil()
|
|
86
|
+
expect(preferenceOf(MetricUnit.max(imperial, MetricUnit.kilometers(1)))).toBe("usCustomary")
|
|
87
|
+
expect(preferenceOf(MetricUnit.sum({ imperial, MetricUnit.kilometers(1) }))).toBe("usCustomary")
|
|
88
|
+
expect(preferenceOf(-imperial)).toBe("usCustomary")
|
|
89
|
+
end)
|
|
90
|
+
|
|
91
|
+
it("keeps a pinned unit through scaling, and its system through a change of dimension", function()
|
|
92
|
+
local pinned = MetricUnit.kilometers(5):As("mile")
|
|
93
|
+
expect(preferenceOf((pinned * 2))).toBe("mile")
|
|
94
|
+
expect(preferenceOf((2 * pinned))).toBe("mile")
|
|
95
|
+
expect(preferenceOf((pinned / 2))).toBe("mile")
|
|
96
|
+
expect(preferenceOf((pinned + MetricUnit.kilometers(1)))).toBe("mile")
|
|
97
|
+
expect(preferenceOf(pinned / MetricUnit.hours(1))).toBe("usCustomaryAndImperial")
|
|
98
|
+
expect(format(MetricUnit.kilometers(5):ToUSCustomary() / MetricUnit.hours(1))).toBe("3.107 mph")
|
|
99
|
+
-- A stud's system shows studs per second for the speed it makes
|
|
100
|
+
expect(preferenceOf(MetricUnit.meters(1):As("stud") / MetricUnit.seconds(1))).toBe("roblox")
|
|
101
|
+
end)
|
|
102
|
+
end)
|
|
103
|
+
|
|
104
|
+
describe("MetricUnit tostring", function()
|
|
105
|
+
it("is the English short format with four significant digits", function()
|
|
106
|
+
expect(mismatches({
|
|
107
|
+
{ MetricUnit.days(3.5), "3 days, 12 hours" },
|
|
108
|
+
{ MetricUnit.years(2), "2 years" },
|
|
109
|
+
{ MetricUnit.minutes(90), "1 hour, 30 minutes" },
|
|
110
|
+
{ MetricUnit.seconds(30), "30 seconds" },
|
|
111
|
+
{ MetricUnit.kilometers(2), "2 km" },
|
|
112
|
+
{ MetricUnit.meters(500), "500 m" },
|
|
113
|
+
{ MetricUnit.millimeters(4), "4 mm" },
|
|
114
|
+
{ MetricUnit.kilobytes(1_600), "1.6 MB" },
|
|
115
|
+
{ MetricUnit.bytes(12), "12 B" },
|
|
116
|
+
{ MetricUnit.tonnes(3), "3 t" },
|
|
117
|
+
{ MetricUnit.grams(20), "20 g" },
|
|
118
|
+
{ MetricUnit.kilometers(0), "0 m" },
|
|
119
|
+
{ MetricUnit.days(0), "0 seconds" },
|
|
120
|
+
{ MetricUnit.perYear(0.03), "0.03/yr" },
|
|
121
|
+
{ MetricUnit.perDay(5), "5/d" },
|
|
122
|
+
{ MetricUnit.perSecond(2), "2/s" },
|
|
123
|
+
{ MetricUnit.new(60, "hertz"), "60/s" },
|
|
124
|
+
{ MetricUnit.kilometers(18) / MetricUnit.hours(1), "5 m/s" },
|
|
125
|
+
{ MetricUnit.kilometers(20) / MetricUnit.years(1), "2.282 m/h" },
|
|
126
|
+
{ MetricUnit.new(1_600, "kilobytePerSecond"), "1.6 MB/s" },
|
|
127
|
+
{ MetricUnit.bytes(20) / MetricUnit.days(1), "20 B/d" },
|
|
128
|
+
{ MetricUnit.milliseconds(250), "250 milliseconds" },
|
|
129
|
+
{ MetricUnit.new(40, "microsecond"), "40 microseconds" },
|
|
130
|
+
{ MetricUnit.kilometers(3) * MetricUnit.kilometers(2), "6 km²" },
|
|
131
|
+
{ MetricUnit.new(50, "squareMeter"), "50 m²" },
|
|
132
|
+
{ MetricUnit.new(2.5, "liter"), "2.5 L" },
|
|
133
|
+
{ MetricUnit.new(3, "cubicMeter"), "3 m³" },
|
|
134
|
+
{ MetricUnit.new(9.81, "meterPerSecondSquared"), "9.81 m/s²" },
|
|
135
|
+
{ MetricUnit.new(1_500, "newton"), "1.5 kN" },
|
|
136
|
+
{ MetricUnit.new(1, "kilowattHour"), "3.6 MJ" },
|
|
137
|
+
{ MetricUnit.new(2_500, "watt"), "2.5 kW" },
|
|
138
|
+
{ MetricUnit.new(1, "bar"), "100 kPa" },
|
|
139
|
+
{ MetricUnit.new(1_000, "kilogramPerCubicMeter"), "1,000 kg/m³" },
|
|
140
|
+
{ MetricUnit.new(2, "gigabyte"), "2 GB" },
|
|
141
|
+
{ MetricUnit.new(5, "milligram"), "5 mg" },
|
|
142
|
+
{ MetricUnit.kilograms(2) / MetricUnit.kilometers(1), "0.002 kg/m" },
|
|
143
|
+
{ MetricUnit.kilograms(2) * MetricUnit.new(3, "meterPerSecond"), "6 m·kg/s" },
|
|
144
|
+
{ MetricUnit.none(0.25), "0.25" },
|
|
145
|
+
{ MetricUnit.years(10_000), "10,000 years" },
|
|
146
|
+
{ MetricUnit.celsius(5), "5 °C" },
|
|
147
|
+
{ MetricUnit.celsius(5) - MetricUnit.celsius(2), "3 K" },
|
|
148
|
+
{ MetricUnit.meters(1 / 3), "33.33 cm" },
|
|
149
|
+
{ MetricUnit.kilometers(5):ToUSCustomary(), "3.107 mi" },
|
|
150
|
+
{ MetricUnit.instant(90), "epoch + 1 minute, 30 seconds" },
|
|
151
|
+
})).toEqual({})
|
|
152
|
+
end)
|
|
153
|
+
end)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.Electrical.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
|
+
local function format(value: any): string
|
|
16
|
+
return (string.gsub(MetricUnit.format(value), "\u{00A0}", " "))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
local function parse(text: string, kind: any?): MetricUnit.MetricUnit
|
|
20
|
+
local value, err = MetricUnit.parse(text, kind)
|
|
21
|
+
if not value then
|
|
22
|
+
error(string.format("could not parse %q: %s", text, tostring(err)))
|
|
23
|
+
end
|
|
24
|
+
return value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
describe("MetricUnit circuits", function()
|
|
28
|
+
it("follows Ohm's law and the power law", function()
|
|
29
|
+
local voltage = MetricUnit.new(12, "volt")
|
|
30
|
+
local resistance = MetricUnit.new(4.7, "kilohm")
|
|
31
|
+
local current = voltage / resistance
|
|
32
|
+
expect(current:GetKind()).toBe("current")
|
|
33
|
+
expect(current:In("milliampere")).toBeCloseTo(2.5532, 4)
|
|
34
|
+
expect((current * resistance):In("volt")).toBeCloseTo(12, 9)
|
|
35
|
+
expect((voltage * current):GetKind()).toBe("power")
|
|
36
|
+
expect((voltage * current):In("watt")).toBeCloseTo(0.030638, 6)
|
|
37
|
+
end)
|
|
38
|
+
|
|
39
|
+
it("makes seconds from RC and L/R time constants", function()
|
|
40
|
+
local tau = MetricUnit.new(10, "kilohm") * MetricUnit.new(100, "nanofarad")
|
|
41
|
+
expect(tau:GetKind()).toBe("time")
|
|
42
|
+
expect(tau:In("millisecond")).toBeCloseTo(1, 9)
|
|
43
|
+
local inductive = MetricUnit.new(10, "millihenry") / MetricUnit.new(1, "ohm")
|
|
44
|
+
expect(inductive:In("millisecond")).toBeCloseTo(10, 9)
|
|
45
|
+
end)
|
|
46
|
+
|
|
47
|
+
it("relates charge, capacitance and stored energy", function()
|
|
48
|
+
local capacitance = MetricUnit.new(470, "microfarad")
|
|
49
|
+
local voltage = MetricUnit.new(5, "volt")
|
|
50
|
+
local charge = capacitance * voltage
|
|
51
|
+
expect(charge:GetKind()).toBe("charge")
|
|
52
|
+
expect(charge:In("coulomb")).toBeCloseTo(0.00235, 9)
|
|
53
|
+
expect((charge / MetricUnit.seconds(1)):In("milliampere")).toBeCloseTo(2.35, 9)
|
|
54
|
+
local energy = 0.5 * capacitance * MetricUnit.pow(voltage, 2)
|
|
55
|
+
expect(energy:In("joule")).toBeCloseTo(0.005875, 9)
|
|
56
|
+
end)
|
|
57
|
+
|
|
58
|
+
it("relates flux, flux density, inductance and conductance", function()
|
|
59
|
+
local flux = MetricUnit.new(2, "volt") * MetricUnit.seconds(3)
|
|
60
|
+
expect(flux:GetKind()).toBe("magneticFlux")
|
|
61
|
+
expect(flux:In("weber")).toBe(6)
|
|
62
|
+
expect((flux / MetricUnit.new(2, "squareMeter")):In("tesla")).toBe(3)
|
|
63
|
+
expect((flux / MetricUnit.new(2, "ampere")):In("henry")).toBe(3)
|
|
64
|
+
expect((1 / MetricUnit.new(50, "ohm")):In("siemens")).toBeCloseTo(0.02, 9)
|
|
65
|
+
end)
|
|
66
|
+
|
|
67
|
+
it("names every electrical kind", function()
|
|
68
|
+
expect(MetricUnit.new(1, "ampere"):GetKind()).toBe("current")
|
|
69
|
+
expect(MetricUnit.new(1, "coulomb"):GetKind()).toBe("charge")
|
|
70
|
+
expect(MetricUnit.new(1, "volt"):GetKind()).toBe("voltage")
|
|
71
|
+
expect(MetricUnit.new(1, "ohm"):GetKind()).toBe("resistance")
|
|
72
|
+
expect(MetricUnit.new(1, "siemens"):GetKind()).toBe("conductance")
|
|
73
|
+
expect(MetricUnit.new(1, "farad"):GetKind()).toBe("capacitance")
|
|
74
|
+
expect(MetricUnit.new(1, "henry"):GetKind()).toBe("inductance")
|
|
75
|
+
expect(MetricUnit.new(1, "weber"):GetKind()).toBe("magneticFlux")
|
|
76
|
+
expect(MetricUnit.new(1, "tesla"):GetKind()).toBe("magneticFluxDensity")
|
|
77
|
+
end)
|
|
78
|
+
end)
|
|
79
|
+
|
|
80
|
+
describe("MetricUnit electrical formatting", function()
|
|
81
|
+
it("picks the prefix a component is labelled with", function()
|
|
82
|
+
expect(format(MetricUnit.new(4700, "ohm"))).toBe("4.7 kΩ")
|
|
83
|
+
expect(format(MetricUnit.new(0.1, "microfarad"))).toBe("100 nF")
|
|
84
|
+
expect(format(MetricUnit.new(0.0015, "ampere"))).toBe("1.5 mA")
|
|
85
|
+
expect(format(MetricUnit.new(3.3, "volt"))).toBe("3.3 V")
|
|
86
|
+
expect(format(MetricUnit.new(2200, "microhenry"))).toBe("2.2 mH")
|
|
87
|
+
expect(format(MetricUnit.new(0.05, "tesla"))).toBe("50 mT")
|
|
88
|
+
expect(format(MetricUnit.new(2, "volt") / MetricUnit.seconds(1))).toBe("2 V/s")
|
|
89
|
+
end)
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
describe("MetricUnit electrical parsing", function()
|
|
93
|
+
it("reads component values as they are written", function()
|
|
94
|
+
expect(parse("4.7kΩ"):In("ohm")).toBeCloseTo(4700, 9)
|
|
95
|
+
expect(parse("4.7 kilohms"):In("ohm")).toBeCloseTo(4700, 9)
|
|
96
|
+
expect(parse("100nF"):In("nanofarad")).toBeCloseTo(100, 9)
|
|
97
|
+
expect(parse("10 uF"):In("microfarad")).toBeCloseTo(10, 9)
|
|
98
|
+
expect(parse("10 µF"):In("microfarad")).toBeCloseTo(10, 9)
|
|
99
|
+
expect(parse("12 V"):In("volt")).toBe(12)
|
|
100
|
+
expect(parse("2 amps"):In("ampere")).toBe(2)
|
|
101
|
+
end)
|
|
102
|
+
|
|
103
|
+
it("reads the Greek mu and the ohm sign as the micro sign and omega", function()
|
|
104
|
+
expect(parse("10 \u{03BC}F"):In("microfarad")).toBeCloseTo(10, 9)
|
|
105
|
+
expect(parse("5 \u{2126}"):In("ohm")).toBe(5)
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
it("keeps C and F as temperatures unless a charge or capacitance is expected", function()
|
|
109
|
+
expect(parse("20 C"):GetKind()).toBe("temperature")
|
|
110
|
+
expect(parse("20 F"):GetKind()).toBe("temperature")
|
|
111
|
+
expect(parse("20 C", "charge"):In("coulomb")).toBe(20)
|
|
112
|
+
expect(parse("20 F", "capacitance"):In("farad")).toBe(20)
|
|
113
|
+
end)
|
|
114
|
+
end)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.Math.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 MetricUnitSystems = require("MetricUnitSystems")
|
|
11
|
+
|
|
12
|
+
local describe = Jest.Globals.describe
|
|
13
|
+
local expect = Jest.Globals.expect
|
|
14
|
+
local it = Jest.Globals.it
|
|
15
|
+
|
|
16
|
+
describe("MetricUnit.lerp", function()
|
|
17
|
+
it("interpolates differences and absolutes alike", function()
|
|
18
|
+
expect(MetricUnit.lerp(MetricUnit.meters(0), MetricUnit.meters(10), 0.25):In("meter")).toBe(2.5)
|
|
19
|
+
local temperature = MetricUnit.lerp(MetricUnit.celsius(10), MetricUnit.celsius(20), 0.5)
|
|
20
|
+
expect(temperature:IsAbsolute()).toBe(true)
|
|
21
|
+
expect(temperature:In("celsius")).toBeCloseTo(15, 9)
|
|
22
|
+
local instant = MetricUnit.lerp(MetricUnit.instant(0), MetricUnit.instant(100), 0.5)
|
|
23
|
+
expect(instant:GetKind()).toBe("instant")
|
|
24
|
+
expect(instant:InSeconds()).toBe(50)
|
|
25
|
+
end)
|
|
26
|
+
|
|
27
|
+
it("errors between different dimensions", function()
|
|
28
|
+
expect(function()
|
|
29
|
+
MetricUnit.lerp(MetricUnit.meters(1), MetricUnit.seconds(1), 0.5)
|
|
30
|
+
end).toThrow("cannot subtract")
|
|
31
|
+
end)
|
|
32
|
+
end)
|
|
33
|
+
|
|
34
|
+
describe("MetricUnit rounding", function()
|
|
35
|
+
it("rounds, floors and ceils to a unit", function()
|
|
36
|
+
expect(MetricUnit.round(MetricUnit.seconds(95), "minute"):In("minute")).toBe(2)
|
|
37
|
+
expect(MetricUnit.floor(MetricUnit.seconds(95), "minute"):In("minute")).toBe(1)
|
|
38
|
+
expect(MetricUnit.ceil(MetricUnit.seconds(61), "minute"):In("minute")).toBe(2)
|
|
39
|
+
expect(MetricUnit.round(MetricUnit.meters(1234), "kilometer"):In("kilometer")).toBe(1)
|
|
40
|
+
end)
|
|
41
|
+
|
|
42
|
+
it("snaps to a multiple of a step", function()
|
|
43
|
+
expect(MetricUnit.round(MetricUnit.meters(12.4), MetricUnit.meters(5)):In("meter")).toBe(10)
|
|
44
|
+
expect(MetricUnit.round(MetricUnit.meters(12.6), MetricUnit.meters(5)):In("meter")).toBe(15)
|
|
45
|
+
expect(function()
|
|
46
|
+
MetricUnit.round(MetricUnit.meters(1), MetricUnit.meters(-5))
|
|
47
|
+
end).toThrow("takes a positive step of length")
|
|
48
|
+
expect(function()
|
|
49
|
+
MetricUnit.round(MetricUnit.meters(1), MetricUnit.seconds(1))
|
|
50
|
+
end).toThrow("takes a positive step of length, not time")
|
|
51
|
+
end)
|
|
52
|
+
|
|
53
|
+
it("rounds a temperature on its scale and an instant from its epoch", function()
|
|
54
|
+
local warm = MetricUnit.round(MetricUnit.celsius(21.6), "celsius")
|
|
55
|
+
expect(warm:IsAbsolute()).toBe(true)
|
|
56
|
+
expect(warm:In("celsius")).toBeCloseTo(22, 9)
|
|
57
|
+
local instant = MetricUnit.round(MetricUnit.instant(95), "minute")
|
|
58
|
+
expect(instant:GetKind()).toBe("instant")
|
|
59
|
+
expect(instant:InSeconds()).toBe(120)
|
|
60
|
+
end)
|
|
61
|
+
|
|
62
|
+
it("keeps the display preference and errors on an unknown unit", function()
|
|
63
|
+
expect(MetricUnit.round(MetricUnit.kilometers(1.4):ToUSCustomary(), "mile"):GetDisplaySystem()).toBe(
|
|
64
|
+
MetricUnitSystems.US_CUSTOMARY
|
|
65
|
+
)
|
|
66
|
+
expect(function()
|
|
67
|
+
MetricUnit.round(MetricUnit.meters(1), "klicks" :: any)
|
|
68
|
+
end).toThrow('MetricUnit.round knows no unit "klicks"')
|
|
69
|
+
end)
|
|
70
|
+
end)
|
|
71
|
+
|
|
72
|
+
describe("MetricUnit aggregation", function()
|
|
73
|
+
it("sums values of one dimension", function()
|
|
74
|
+
expect(MetricUnit.sum({ MetricUnit.meters(1), MetricUnit.kilometers(1) }):In("meter")).toBe(1001)
|
|
75
|
+
expect(MetricUnit.sum({ 1, 2 }):GetBaseAmount()).toBe(3)
|
|
76
|
+
expect(function()
|
|
77
|
+
MetricUnit.sum({ MetricUnit.celsius(1), MetricUnit.celsius(2) })
|
|
78
|
+
end).toThrow("cannot add two absolute temperatures")
|
|
79
|
+
expect(function()
|
|
80
|
+
MetricUnit.sum({})
|
|
81
|
+
end).toThrow("takes a list of at least one value")
|
|
82
|
+
end)
|
|
83
|
+
|
|
84
|
+
it("averages values, absolutes included", function()
|
|
85
|
+
expect(MetricUnit.mean({ MetricUnit.meters(1), MetricUnit.meters(3) }):In("meter")).toBe(2)
|
|
86
|
+
local temperature = MetricUnit.mean({ MetricUnit.celsius(10), MetricUnit.celsius(20) })
|
|
87
|
+
expect(temperature:IsAbsolute()).toBe(true)
|
|
88
|
+
expect(temperature:In("celsius")).toBeCloseTo(15, 9)
|
|
89
|
+
expect(MetricUnit.mean({ MetricUnit.instant(10), MetricUnit.instant(20) }):InSeconds()).toBe(15)
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
it("takes the smallest and largest of a list through min and max", function()
|
|
93
|
+
local list = { MetricUnit.meters(1), MetricUnit.meters(5), MetricUnit.meters(3) }
|
|
94
|
+
expect(MetricUnit.max(table.unpack(list)):In("meter")).toBe(5)
|
|
95
|
+
expect(MetricUnit.min(table.unpack(list)):In("meter")).toBe(1)
|
|
96
|
+
end)
|
|
97
|
+
end)
|
|
98
|
+
|
|
99
|
+
describe("MetricUnit rounding an absolute value by a step", function()
|
|
100
|
+
it("rounds a temperature on the scale it is shown in", function()
|
|
101
|
+
local step = MetricUnit.difference(5, "celsius")
|
|
102
|
+
expect(MetricUnit.round(MetricUnit.celsius(21.6), step):In("celsius")).toBeCloseTo(20, 9)
|
|
103
|
+
expect(MetricUnit.round(MetricUnit.celsius(23), step):In("celsius")).toBeCloseTo(25, 9)
|
|
104
|
+
local fahrenheitStep = MetricUnit.difference(10, "fahrenheit")
|
|
105
|
+
expect(MetricUnit.round(MetricUnit.fahrenheit(68), fahrenheitStep):In("fahrenheit")).toBeCloseTo(70, 9)
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
it("rounds an instant from its epoch", function()
|
|
109
|
+
local rounded = MetricUnit.round(MetricUnit.instant(95), MetricUnit.minutes(1))
|
|
110
|
+
expect(rounded:In("second")).toBeCloseTo(120, 9)
|
|
111
|
+
end)
|
|
112
|
+
end)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.Roblox.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
|
+
describe("MetricUnit and the Roblox engine", function()
|
|
16
|
+
it("converts Roblox mass units and stud accelerations through the documented stud and RMU", function()
|
|
17
|
+
-- A Roblox mass unit is a cubic stud of water
|
|
18
|
+
expect(MetricUnit.new(1, "robloxMassUnit"):In("kilogram")).toBeCloseTo(21.952, 9)
|
|
19
|
+
expect(MetricUnit.new(196.2, "studPerSecondSquared"):In("meterPerSecondSquared")).toBeCloseTo(196.2 * 0.28, 9)
|
|
20
|
+
-- An engine force is a mass times an acceleration, so it needs no unit of its own
|
|
21
|
+
local force = MetricUnit.new(1, "robloxMassUnit") * MetricUnit.new(1, "studPerSecondSquared")
|
|
22
|
+
expect(force:GetKind()).toBe("force")
|
|
23
|
+
expect(force:In("newton")).toBeCloseTo(21.952 * 0.28, 9)
|
|
24
|
+
end)
|
|
25
|
+
|
|
26
|
+
it("reads a velocity's speed in studs per second", function()
|
|
27
|
+
if typeof(Vector3) ~= "table" and typeof(Vector3) ~= "userdata" then
|
|
28
|
+
return -- Vector3 exists in Roblox only
|
|
29
|
+
end
|
|
30
|
+
local speed = MetricUnit.fromStudVelocity(Vector3.new(3, 4, 0))
|
|
31
|
+
expect(speed:In("studPerSecond")).toBeCloseTo(5, 9)
|
|
32
|
+
expect(speed:In("meterPerSecond")).toBeCloseTo(5 * 0.28, 9)
|
|
33
|
+
end)
|
|
34
|
+
end)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.SI.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
|
+
|
|
12
|
+
local describe = Jest.Globals.describe
|
|
13
|
+
local expect = Jest.Globals.expect
|
|
14
|
+
local it = Jest.Globals.it
|
|
15
|
+
|
|
16
|
+
local function format(value: any, options: any?): string
|
|
17
|
+
return (string.gsub(MetricUnit.format(value, options), "\u{00A0}", " "))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
local function parse(text: string, kind: any?): MetricUnit.MetricUnit
|
|
21
|
+
local value, err = MetricUnit.parse(text, kind)
|
|
22
|
+
if not value then
|
|
23
|
+
error(string.format("could not parse %q: %s", text, tostring(err)))
|
|
24
|
+
end
|
|
25
|
+
return value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe("MetricUnit SI prefixes", function()
|
|
29
|
+
it("makes every SI prefix of a prefixable unit", function()
|
|
30
|
+
expect(#MetricUnitDefinitions.getPrefixes()).toBe(32)
|
|
31
|
+
for _, prefix in MetricUnitDefinitions.getPrefixes() do
|
|
32
|
+
local unit = MetricUnitDefinitions.getUnit(prefix.name .. "volt")
|
|
33
|
+
local isBinary = string.find(prefix.symbol, "i", 1, true) ~= nil
|
|
34
|
+
if isBinary then
|
|
35
|
+
expect(unit).toBeNil()
|
|
36
|
+
else
|
|
37
|
+
assert(unit, prefix.name .. "volt")
|
|
38
|
+
expect(unit.symbol).toBe(prefix.symbol .. "V")
|
|
39
|
+
expect(unit.factor).toBe(prefix.factor)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
expect(MetricUnit.new(3, "nanometer" :: any):In("meter")).toBeCloseTo(3e-9, 20)
|
|
43
|
+
expect(MetricUnit.new(1, "quettameter" :: any):In("meter")).toBe(1e30)
|
|
44
|
+
end)
|
|
45
|
+
|
|
46
|
+
it("prefixes the gram, so the kilogram is one SI base unit", function()
|
|
47
|
+
local kilogram = assert(MetricUnitDefinitions.getUnit("kilogram"))
|
|
48
|
+
expect(kilogram.factor).toBe(1)
|
|
49
|
+
expect(kilogram.prefix).toEqual({ name = "kilo", base = "gram" })
|
|
50
|
+
expect(MetricUnitDefinitions.getUnit("kilokilogram" :: any)).toBeNil()
|
|
51
|
+
expect(MetricUnit.new(5, "microgram"):In("milligram")).toBeCloseTo(0.005, 12)
|
|
52
|
+
end)
|
|
53
|
+
|
|
54
|
+
it("gives bytes binary prefixes and only multiples", function()
|
|
55
|
+
expect(MetricUnit.new(1, "mebibyte" :: any):In("byte")).toBe(1024 ^ 2)
|
|
56
|
+
expect(MetricUnit.new(1, "terabyte"):In("gigabyte")).toBe(1000)
|
|
57
|
+
expect(MetricUnitDefinitions.getUnit("millibyte" :: any)).toBeNil()
|
|
58
|
+
expect(MetricUnitDefinitions.getUnit("kibibit" :: any)).toBeNil()
|
|
59
|
+
end)
|
|
60
|
+
|
|
61
|
+
it("names prefixed units in English, eliding the vowel before ohm", function()
|
|
62
|
+
expect(format(MetricUnit.new(3, "nanometer" :: any), { unit = "nanometer", unitDisplay = "long" })).toBe(
|
|
63
|
+
"3 nanometers"
|
|
64
|
+
)
|
|
65
|
+
expect(format(MetricUnit.new(1, "kilohm"), { unit = "kilohm", unitDisplay = "long" })).toBe("1 kilohm")
|
|
66
|
+
expect(format(MetricUnit.new(2, "megohm"), { unit = "megohm", unitDisplay = "long" })).toBe("2 megohms")
|
|
67
|
+
expect(format(MetricUnit.new(2, "millihenry"), { unit = "millihenry", unitDisplay = "long" })).toBe(
|
|
68
|
+
"2 millihenries"
|
|
69
|
+
)
|
|
70
|
+
expect(format(MetricUnit.new(2, "kilometer"), { unit = "kilometer", unitDisplay = "long", locale = "en-gb" })).toBe(
|
|
71
|
+
"2 kilometres"
|
|
72
|
+
)
|
|
73
|
+
end)
|
|
74
|
+
|
|
75
|
+
it("parses any prefix by symbol, name, or u for micro", function()
|
|
76
|
+
expect(parse("5 GHz"):In("hertz")).toBe(5e9)
|
|
77
|
+
expect(parse("3 nm"):In("nanometer" :: any)).toBeCloseTo(3, 9)
|
|
78
|
+
expect(parse("2 megawatts"):In("watt")).toBe(2e6)
|
|
79
|
+
expect(parse("10 us"):In("microsecond")).toBeCloseTo(10, 9)
|
|
80
|
+
expect(parse("4 uA"):In("microampere")).toBeCloseTo(4, 9)
|
|
81
|
+
expect(parse("250 ml"):In("liter")).toBeCloseTo(0.25, 9)
|
|
82
|
+
expect(parse("2 dL"):In("deciliter")).toBeCloseTo(2, 9)
|
|
83
|
+
expect(parse("3 milliamps"):In("ampere")).toBeCloseTo(0.003, 9)
|
|
84
|
+
expect(parse("5 M"):GetKind()).toBe("time")
|
|
85
|
+
end)
|
|
86
|
+
end)
|
|
87
|
+
|
|
88
|
+
describe("MetricUnit amount of substance and light", function()
|
|
89
|
+
it("converts mass, moles and concentration", function()
|
|
90
|
+
local waterMolarMass = MetricUnit.new(18.015, "gramPerMole")
|
|
91
|
+
local moles = MetricUnit.grams(36.03) / waterMolarMass
|
|
92
|
+
expect(moles:GetKind()).toBe("amount")
|
|
93
|
+
expect(moles:In("mole")).toBeCloseTo(2, 9)
|
|
94
|
+
local concentration = moles / MetricUnit.new(4, "liter")
|
|
95
|
+
expect(concentration:GetKind()).toBe("molarConcentration")
|
|
96
|
+
expect(concentration:In("millimolePerLiter")).toBeCloseTo(500, 6)
|
|
97
|
+
expect((moles / MetricUnit.seconds(2)):In("katal")).toBeCloseTo(1, 9)
|
|
98
|
+
end)
|
|
99
|
+
|
|
100
|
+
it("reads liquid volume from mass through a density", function()
|
|
101
|
+
local water = MetricUnit.new(1000, "kilogramPerCubicMeter")
|
|
102
|
+
local volume = MetricUnit.kilograms(2) / water
|
|
103
|
+
expect(volume:In("liter")).toBeCloseTo(2, 9)
|
|
104
|
+
expect(format(volume)).toBe("2 L")
|
|
105
|
+
expect((MetricUnit.new(330, "milliliter") * water):In("gram")).toBeCloseTo(330, 9)
|
|
106
|
+
expect(MetricUnit.new(1, "usGallon"):In("centiliter")).toBeCloseTo(378.5411784, 6)
|
|
107
|
+
end)
|
|
108
|
+
|
|
109
|
+
it("derives lumens and lux from the candela and the steradian", function()
|
|
110
|
+
local flux = MetricUnit.new(100, "candela") * MetricUnit.new(2, "steradian")
|
|
111
|
+
expect(flux:GetKind()).toBe("luminousFlux")
|
|
112
|
+
expect(flux:In("lumen")).toBe(200)
|
|
113
|
+
local illuminance = flux / MetricUnit.new(4, "squareMeter")
|
|
114
|
+
expect(illuminance:GetKind()).toBe("illuminance")
|
|
115
|
+
expect(illuminance:In("lux")).toBe(50)
|
|
116
|
+
expect(MetricUnit.pow(MetricUnit.radians(1), 2):In("steradian")).toBe(1)
|
|
117
|
+
end)
|
|
118
|
+
|
|
119
|
+
it("shares dimensions where SI does, showing the first unit unless asked", function()
|
|
120
|
+
local dose = MetricUnit.new(2, "joule") / MetricUnit.kilograms(1)
|
|
121
|
+
expect(dose:GetKind()).toBe("absorbedDose")
|
|
122
|
+
expect(format(dose)).toBe("2 Gy")
|
|
123
|
+
expect(format(dose:As("sievert"))).toBe("2 Sv")
|
|
124
|
+
expect(MetricUnit.new(5, "becquerel"):In("hertz")).toBe(5)
|
|
125
|
+
expect(format(MetricUnit.new(3, "millisievert"))).toBe("3 mGy")
|
|
126
|
+
end)
|
|
127
|
+
end)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.Spring.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local LinearValue = require("LinearValue")
|
|
10
|
+
local MetricUnit = require("MetricUnit")
|
|
11
|
+
local Spring = require("Spring")
|
|
12
|
+
|
|
13
|
+
local describe = Jest.Globals.describe
|
|
14
|
+
local expect = Jest.Globals.expect
|
|
15
|
+
local it = Jest.Globals.it
|
|
16
|
+
|
|
17
|
+
local function manualClock(): (() -> number, (number) -> ())
|
|
18
|
+
local now = 0
|
|
19
|
+
return function()
|
|
20
|
+
return now
|
|
21
|
+
end, function(seconds: number)
|
|
22
|
+
now += seconds
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe("MetricUnit in a Spring", function()
|
|
27
|
+
it("animates a length to its target", function()
|
|
28
|
+
local clock, advance = manualClock()
|
|
29
|
+
local spring = Spring.new(MetricUnit.meters(0), clock)
|
|
30
|
+
spring.Speed = 20
|
|
31
|
+
spring.Target = MetricUnit.meters(10)
|
|
32
|
+
|
|
33
|
+
advance(0.05)
|
|
34
|
+
local midway = (spring.Position :: any):In("meter")
|
|
35
|
+
expect(midway > 0 and midway < 10).toBe(true)
|
|
36
|
+
-- Spring's clock is a plain number, so its velocity is in the position's units per second
|
|
37
|
+
expect((spring.Velocity :: any):GetKind()).toBe("length")
|
|
38
|
+
|
|
39
|
+
advance(10)
|
|
40
|
+
expect((spring.Position :: any):In("meter")).toBeCloseTo(10, 6)
|
|
41
|
+
expect((spring.Velocity :: any):In("meter")).toBeCloseTo(0, 6)
|
|
42
|
+
end)
|
|
43
|
+
|
|
44
|
+
it("starts from a spring made with no initial value", function()
|
|
45
|
+
local clock, advance = manualClock()
|
|
46
|
+
local spring = Spring.new(nil :: any, clock)
|
|
47
|
+
spring.Target = MetricUnit.new(5, "volt")
|
|
48
|
+
advance(30)
|
|
49
|
+
expect((spring.Position :: any):In("volt")).toBeCloseTo(5, 6)
|
|
50
|
+
end)
|
|
51
|
+
|
|
52
|
+
it("takes an impulse in the position's units, and refuses one of another dimension", function()
|
|
53
|
+
local clock, advance = manualClock()
|
|
54
|
+
local spring = Spring.new(MetricUnit.meters(0), clock)
|
|
55
|
+
spring:Impulse(MetricUnit.meters(4))
|
|
56
|
+
advance(0.01)
|
|
57
|
+
expect((spring.Position :: any):In("meter") > 0).toBe(true)
|
|
58
|
+
expect(function()
|
|
59
|
+
spring:Impulse(MetricUnit.new(4, "meterPerSecond") :: any)
|
|
60
|
+
end).toThrow()
|
|
61
|
+
end)
|
|
62
|
+
|
|
63
|
+
it("skips time and snaps to a target without animating", function()
|
|
64
|
+
local clock = manualClock()
|
|
65
|
+
local spring = Spring.new(MetricUnit.celsius(0) - MetricUnit.celsius(0), clock)
|
|
66
|
+
spring:SetTarget(MetricUnit.difference(20, "celsius"), true)
|
|
67
|
+
expect((spring.Position :: any):In("kelvin")).toBe(20)
|
|
68
|
+
spring.Target = MetricUnit.difference(0, "celsius")
|
|
69
|
+
spring:TimeSkip(60)
|
|
70
|
+
expect((spring.Position :: any):In("kelvin")).toBeCloseTo(0, 6)
|
|
71
|
+
end)
|
|
72
|
+
|
|
73
|
+
it("springs an absolute value through its offset from a reference", function()
|
|
74
|
+
expect(function()
|
|
75
|
+
Spring.new(MetricUnit.celsius(20))
|
|
76
|
+
end).toThrow("cannot multiply an absolute temperature")
|
|
77
|
+
|
|
78
|
+
local clock, advance = manualClock()
|
|
79
|
+
local reference = MetricUnit.celsius(0)
|
|
80
|
+
local spring = Spring.new(MetricUnit.celsius(15) - reference, clock)
|
|
81
|
+
spring.Target = MetricUnit.celsius(25) - reference
|
|
82
|
+
advance(60)
|
|
83
|
+
expect((reference + spring.Position :: any):In("celsius")).toBeCloseTo(25, 6)
|
|
84
|
+
end)
|
|
85
|
+
end)
|
|
86
|
+
|
|
87
|
+
describe("MetricUnit and LinearValue", function()
|
|
88
|
+
it("passes through LinearValue untouched, since it is linear already", function()
|
|
89
|
+
local value = MetricUnit.meters(3)
|
|
90
|
+
expect(LinearValue.isLinear(value)).toBe(false)
|
|
91
|
+
expect(LinearValue.toLinearIfNeeded(value)).toBe(value)
|
|
92
|
+
expect(LinearValue.fromLinearIfNeeded(value)).toBe(value)
|
|
93
|
+
end)
|
|
94
|
+
end)
|