@quenty/metricutils 2.4.3 → 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 +16 -54
- 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 +47 -16
- 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,362 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnit.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 DAY = MetricUnit.days(1):InSeconds()
|
|
12
|
+
local MONTH = MetricUnit.months(1):InSeconds()
|
|
13
|
+
local YEAR = MetricUnit.years(1):InSeconds()
|
|
14
|
+
|
|
15
|
+
local describe = Jest.Globals.describe
|
|
16
|
+
local expect = Jest.Globals.expect
|
|
17
|
+
local it = Jest.Globals.it
|
|
18
|
+
|
|
19
|
+
type MetricUnit = MetricUnit.MetricUnit
|
|
20
|
+
|
|
21
|
+
local DAYS_A_YEAR = YEAR / DAY
|
|
22
|
+
|
|
23
|
+
describe("MetricUnit constructors and readers", function()
|
|
24
|
+
it("reads every time unit back", function()
|
|
25
|
+
expect(MetricUnit.seconds(90):InSeconds()).toBeCloseTo(90, 9)
|
|
26
|
+
expect(MetricUnit.minutes(90):In("minute")).toBeCloseTo(90, 9)
|
|
27
|
+
expect(MetricUnit.hours(36):In("hour")).toBeCloseTo(36, 9)
|
|
28
|
+
expect(MetricUnit.days(3):In("day")).toBe(3)
|
|
29
|
+
expect(MetricUnit.weeks(2):In("week")).toBe(2)
|
|
30
|
+
expect(MetricUnit.months(18):In("month")).toBeCloseTo(18, 9)
|
|
31
|
+
expect(MetricUnit.years(2):In("year")).toBe(2)
|
|
32
|
+
end)
|
|
33
|
+
|
|
34
|
+
it("counts a year and a month by their definitions", function()
|
|
35
|
+
expect(MetricUnit.hours(36):In("day")).toBeCloseTo(1.5, 9)
|
|
36
|
+
expect(MetricUnit.seconds(86_400):In("day")).toBeCloseTo(1, 9)
|
|
37
|
+
expect(MetricUnit.minutes(60):In("hour")).toBeCloseTo(1, 9)
|
|
38
|
+
expect(MetricUnit.weeks(1):In("day")).toBe(7)
|
|
39
|
+
expect(MetricUnit.years(1):In("day")).toBe(DAYS_A_YEAR)
|
|
40
|
+
expect(MetricUnit.months(12):In("year")).toBeCloseTo(1, 9)
|
|
41
|
+
end)
|
|
42
|
+
|
|
43
|
+
it("makes twelve averaged months exactly one averaged year", function()
|
|
44
|
+
expect(MetricUnit.months(12) == MetricUnit.years(1)).toBe(true)
|
|
45
|
+
expect(MetricUnit.years(1) / 12 == MetricUnit.months(1)).toBe(true)
|
|
46
|
+
expect(MONTH * 12).toBe(YEAR)
|
|
47
|
+
end)
|
|
48
|
+
|
|
49
|
+
it("reads every length unit back", function()
|
|
50
|
+
expect(MetricUnit.millimeters(5):In("millimeter")).toBeCloseTo(5, 9)
|
|
51
|
+
expect(MetricUnit.new(5, "centimeter"):In("centimeter")).toBeCloseTo(5, 9)
|
|
52
|
+
expect(MetricUnit.meters(500):In("meter")).toBeCloseTo(500, 9)
|
|
53
|
+
expect(MetricUnit.kilometers(2):In("kilometer")).toBe(2)
|
|
54
|
+
expect(MetricUnit.meters(500):In("kilometer")).toBeCloseTo(0.5, 9)
|
|
55
|
+
expect(MetricUnit.new(100, "centimeter"):In("meter")).toBeCloseTo(1, 9)
|
|
56
|
+
expect(MetricUnit.millimeters(10):In("centimeter")).toBeCloseTo(1, 9)
|
|
57
|
+
end)
|
|
58
|
+
|
|
59
|
+
it("reads every memory unit back, a kilobyte being 1,000 bytes", function()
|
|
60
|
+
expect(MetricUnit.bytes(512):In("byte")).toBe(512)
|
|
61
|
+
expect(MetricUnit.kilobytes(1_600):In("kilobyte")).toBe(1_600)
|
|
62
|
+
expect(MetricUnit.megabytes(1.6):In("megabyte")).toBeCloseTo(1.6, 9)
|
|
63
|
+
expect(MetricUnit.kilobytes(1_600):In("megabyte")).toBeCloseTo(1.6, 9)
|
|
64
|
+
expect(MetricUnit.kilobytes(1):In("byte")).toBe(1_000)
|
|
65
|
+
end)
|
|
66
|
+
|
|
67
|
+
it("reads every mass unit back", function()
|
|
68
|
+
expect(MetricUnit.grams(250):In("gram")).toBeCloseTo(250, 9)
|
|
69
|
+
expect(MetricUnit.kilograms(3):In("kilogram")).toBe(3)
|
|
70
|
+
expect(MetricUnit.tonnes(2):In("tonne")).toBe(2)
|
|
71
|
+
expect(MetricUnit.tonnes(2):In("kilogram")).toBe(2_000)
|
|
72
|
+
expect(MetricUnit.grams(500):In("kilogram")).toBeCloseTo(0.5, 9)
|
|
73
|
+
end)
|
|
74
|
+
|
|
75
|
+
it("reads every rate back", function()
|
|
76
|
+
expect(MetricUnit.perSecond(2):In("perSecond")).toBeCloseTo(2, 9)
|
|
77
|
+
expect(MetricUnit.perHour(2):In("perHour")).toBeCloseTo(2, 9)
|
|
78
|
+
expect(MetricUnit.perDay(2):In("perDay")).toBe(2)
|
|
79
|
+
expect(MetricUnit.new(2, "perWeek"):In("perWeek")).toBeCloseTo(2, 9)
|
|
80
|
+
expect(MetricUnit.new(2, "perMonth"):In("perMonth")).toBeCloseTo(2, 9)
|
|
81
|
+
expect(MetricUnit.perYear(0.03):In("perYear")).toBeCloseTo(0.03, 12)
|
|
82
|
+
expect(MetricUnit.perYear(DAYS_A_YEAR):In("perDay")).toBeCloseTo(1, 9)
|
|
83
|
+
expect(MetricUnit.perDay(1):In("perHour")).toBeCloseTo(1 / 24, 9)
|
|
84
|
+
end)
|
|
85
|
+
|
|
86
|
+
it("errors when a reader meets another dimension, naming both", function()
|
|
87
|
+
expect(function()
|
|
88
|
+
MetricUnit.kilometers(1):In("day")
|
|
89
|
+
end).toThrow('In("day") reads time, not length')
|
|
90
|
+
expect(function()
|
|
91
|
+
MetricUnit.days(1):In("perYear")
|
|
92
|
+
end).toThrow('In("perYear") reads 1/time, not time')
|
|
93
|
+
expect(function()
|
|
94
|
+
(MetricUnit.kilometers(1) / MetricUnit.days(1)):In("kilometer")
|
|
95
|
+
end).toThrow('In("kilometer") reads length, not length/time')
|
|
96
|
+
expect(function()
|
|
97
|
+
MetricUnit.kilograms(1):In("kilobyte")
|
|
98
|
+
end).toThrow('In("kilobyte") reads memory, not mass')
|
|
99
|
+
end)
|
|
100
|
+
|
|
101
|
+
it("errors when a constructor is given something other than a number", function()
|
|
102
|
+
expect(function()
|
|
103
|
+
MetricUnit.kilometers("2" :: any)
|
|
104
|
+
end).toThrow("MetricUnit.kilometers takes a number, not string")
|
|
105
|
+
end)
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
describe("MetricUnit composition", function()
|
|
109
|
+
it("reads km a year as km a day", function()
|
|
110
|
+
local speed = MetricUnit.kilometers(20) / MetricUnit.years(1)
|
|
111
|
+
expect(speed:In(MetricUnit.kilometers(1) / MetricUnit.days(1))).toBeCloseTo(20 / DAYS_A_YEAR, 12)
|
|
112
|
+
expect(MetricUnit.isKind(speed, "speed")).toBe(true)
|
|
113
|
+
end)
|
|
114
|
+
|
|
115
|
+
it("reads a speed in km/h with In", function()
|
|
116
|
+
local speed = MetricUnit.meters(10) / MetricUnit.seconds(1)
|
|
117
|
+
expect(speed:In(MetricUnit.kilometers(1) / MetricUnit.hours(1))).toBeCloseTo(36, 9)
|
|
118
|
+
end)
|
|
119
|
+
|
|
120
|
+
it("errors when In meets another dimension", function()
|
|
121
|
+
expect(function()
|
|
122
|
+
MetricUnit.kilometers(1):In(MetricUnit.days(1))
|
|
123
|
+
end).toThrow("In cannot read length in time")
|
|
124
|
+
end)
|
|
125
|
+
|
|
126
|
+
it("cancels to dimensionless and reads it with In", function()
|
|
127
|
+
local ratio = MetricUnit.kilometers(3) / MetricUnit.meters(1_500)
|
|
128
|
+
expect(ratio:In(MetricUnit.none(1))).toBeCloseTo(2, 9)
|
|
129
|
+
expect(MetricUnit.sameDimension(ratio, MetricUnit.none(1))).toBe(true)
|
|
130
|
+
end)
|
|
131
|
+
|
|
132
|
+
it("makes a rate from a number over a time", function()
|
|
133
|
+
local rate = 0.06 / MetricUnit.years(1)
|
|
134
|
+
expect(MetricUnit.isKind(rate, "rate")).toBe(true)
|
|
135
|
+
expect(rate:In("perYear")).toBeCloseTo(0.06, 12)
|
|
136
|
+
expect(MetricUnit.fuzzyEq(rate, MetricUnit.perYear(0.06))).toBe(true)
|
|
137
|
+
end)
|
|
138
|
+
|
|
139
|
+
it("multiplies a rate by a time to a dimensionless count", function()
|
|
140
|
+
local count = MetricUnit.perDay(3) * MetricUnit.weeks(1)
|
|
141
|
+
expect(count:In(MetricUnit.none(1))).toBeCloseTo(21, 9)
|
|
142
|
+
end)
|
|
143
|
+
end)
|
|
144
|
+
|
|
145
|
+
describe("MetricUnit operators", function()
|
|
146
|
+
it("scales by a number on either side", function()
|
|
147
|
+
expect((MetricUnit.years(1) / 4):In("day")).toBeCloseTo(DAYS_A_YEAR / 4, 9)
|
|
148
|
+
expect((2 * MetricUnit.kilometers(3)):In("kilometer")).toBe(6)
|
|
149
|
+
expect((MetricUnit.kilometers(3) * 2):In("kilometer")).toBe(6)
|
|
150
|
+
end)
|
|
151
|
+
|
|
152
|
+
it("inverts a unit divided into a number", function()
|
|
153
|
+
local rate = 2 / MetricUnit.days(4)
|
|
154
|
+
expect(rate:In("perDay")).toBeCloseTo(0.5, 9)
|
|
155
|
+
end)
|
|
156
|
+
|
|
157
|
+
it("adds and subtracts one dimension", function()
|
|
158
|
+
expect((MetricUnit.kilometers(1) + MetricUnit.meters(500)):In("meter")).toBeCloseTo(1_500, 9)
|
|
159
|
+
expect((MetricUnit.years(1) - MetricUnit.days(0.25)):In("day")).toBeCloseTo(DAYS_A_YEAR - 0.25, 9)
|
|
160
|
+
end)
|
|
161
|
+
|
|
162
|
+
it("errors adding or subtracting different dimensions, naming both", function()
|
|
163
|
+
expect(function()
|
|
164
|
+
local _ = MetricUnit.kilometers(1) + MetricUnit.days(1)
|
|
165
|
+
end).toThrow("cannot add length and time")
|
|
166
|
+
expect(function()
|
|
167
|
+
local _ = MetricUnit.kilometers(1) - MetricUnit.perDay(1)
|
|
168
|
+
end).toThrow("cannot subtract length and 1/time")
|
|
169
|
+
end)
|
|
170
|
+
|
|
171
|
+
it("errors adding a plain number other than 0 to a dimensioned unit", function()
|
|
172
|
+
expect(function()
|
|
173
|
+
local _ = MetricUnit.kilometers(1) + 2
|
|
174
|
+
end).toThrow("cannot add length and the plain number 2")
|
|
175
|
+
end)
|
|
176
|
+
|
|
177
|
+
it("adds a plain number to a dimensionless unit", function()
|
|
178
|
+
expect((MetricUnit.none(1) + 2):In(MetricUnit.none(1))).toBe(3)
|
|
179
|
+
end)
|
|
180
|
+
|
|
181
|
+
it("takes a literal 0 as zero of the other side", function()
|
|
182
|
+
local function growth(x: boolean): MetricUnit | number
|
|
183
|
+
return if x then MetricUnit.perYear(0.05) else 0
|
|
184
|
+
end
|
|
185
|
+
expect((MetricUnit.perYear(0.01) + growth(false)):In("perYear")).toBeCloseTo(0.01, 12)
|
|
186
|
+
expect((growth(false) + MetricUnit.perYear(0.01)):In("perYear")).toBeCloseTo(0.01, 12)
|
|
187
|
+
expect((MetricUnit.kilometers(2) - 0):In("kilometer")).toBe(2)
|
|
188
|
+
expect((0 - MetricUnit.kilometers(2)):In("kilometer")).toBe(-2)
|
|
189
|
+
end)
|
|
190
|
+
|
|
191
|
+
it("negates", function()
|
|
192
|
+
expect((-MetricUnit.kilometers(2)):In("kilometer")).toBe(-2)
|
|
193
|
+
end)
|
|
194
|
+
|
|
195
|
+
it("compares one dimension", function()
|
|
196
|
+
expect(MetricUnit.kilometers(1) < MetricUnit.meters(1_500)).toBe(true)
|
|
197
|
+
expect(MetricUnit.kilometers(1) <= MetricUnit.meters(1_000)).toBe(true)
|
|
198
|
+
expect(MetricUnit.kilometers(2) > MetricUnit.meters(1_500)).toBe(true)
|
|
199
|
+
expect(MetricUnit.hours(24) == MetricUnit.days(1)).toBe(true)
|
|
200
|
+
expect(MetricUnit.hours(23) == MetricUnit.days(1)).toBe(false)
|
|
201
|
+
end)
|
|
202
|
+
|
|
203
|
+
it("errors comparing different dimensions, naming both", function()
|
|
204
|
+
expect(function()
|
|
205
|
+
local _ = MetricUnit.kilometers(1) < MetricUnit.days(1)
|
|
206
|
+
end).toThrow("cannot compare length and time")
|
|
207
|
+
expect(function()
|
|
208
|
+
local _ = MetricUnit.kilometers(1) <= MetricUnit.kilograms(1)
|
|
209
|
+
end).toThrow("cannot compare length and mass")
|
|
210
|
+
expect(function()
|
|
211
|
+
local _ = MetricUnit.kilometers(1) == MetricUnit.days(1)
|
|
212
|
+
end).toThrow("cannot compare length and time")
|
|
213
|
+
end)
|
|
214
|
+
|
|
215
|
+
it("is frozen, every operation giving a new value", function()
|
|
216
|
+
local a = MetricUnit.kilometers(1)
|
|
217
|
+
local b = a + MetricUnit.kilometers(1)
|
|
218
|
+
expect(table.isfrozen(a :: any)).toBe(true)
|
|
219
|
+
expect(table.isfrozen(b :: any)).toBe(true)
|
|
220
|
+
expect(a:In("kilometer")).toBe(1)
|
|
221
|
+
expect(function()
|
|
222
|
+
(a :: any)._amount = 5
|
|
223
|
+
end).toThrow()
|
|
224
|
+
end)
|
|
225
|
+
end)
|
|
226
|
+
|
|
227
|
+
describe("MetricUnit helpers", function()
|
|
228
|
+
it("takes the min, max and clamp of one dimension, a literal 0 among them", function()
|
|
229
|
+
expect(MetricUnit.min(MetricUnit.kilometers(2), MetricUnit.meters(500)):In("meter")).toBeCloseTo(500, 9)
|
|
230
|
+
expect(
|
|
231
|
+
MetricUnit.max(MetricUnit.kilometers(2), MetricUnit.meters(500), MetricUnit.kilometers(3)):In("kilometer")
|
|
232
|
+
).toBe(3)
|
|
233
|
+
expect(MetricUnit.max(0, MetricUnit.kilometers(-2)):In("kilometer")).toBe(0)
|
|
234
|
+
expect(MetricUnit.min(0, MetricUnit.kilometers(2)):In("kilometer")).toBe(0)
|
|
235
|
+
expect(MetricUnit.clamp(MetricUnit.kilometers(5), 0, MetricUnit.kilometers(3)):In("kilometer")).toBe(3)
|
|
236
|
+
expect(MetricUnit.clamp(MetricUnit.kilometers(-5), 0, MetricUnit.kilometers(3)):In("kilometer")).toBe(0)
|
|
237
|
+
end)
|
|
238
|
+
|
|
239
|
+
it("errors taking the min of different dimensions", function()
|
|
240
|
+
expect(function()
|
|
241
|
+
MetricUnit.min(MetricUnit.kilometers(1), MetricUnit.days(1))
|
|
242
|
+
end).toThrow("cannot compare")
|
|
243
|
+
end)
|
|
244
|
+
|
|
245
|
+
it("takes the absolute value", function()
|
|
246
|
+
expect(MetricUnit.abs(MetricUnit.kilometers(-2)):In("kilometer")).toBe(2)
|
|
247
|
+
end)
|
|
248
|
+
|
|
249
|
+
it("takes the square root, halving exponents", function()
|
|
250
|
+
local area = MetricUnit.kilometers(3) * MetricUnit.kilometers(3)
|
|
251
|
+
expect(MetricUnit.sqrt(area):In("kilometer")).toBeCloseTo(3, 9)
|
|
252
|
+
local spread = MetricUnit.kilometers(2) * MetricUnit.kilometers(2) / MetricUnit.days(1)
|
|
253
|
+
expect(MetricUnit.sqrt(spread * MetricUnit.days(4)):In("kilometer")).toBeCloseTo(4, 9)
|
|
254
|
+
end)
|
|
255
|
+
|
|
256
|
+
it("errors taking the square root of an odd exponent", function()
|
|
257
|
+
expect(function()
|
|
258
|
+
MetricUnit.sqrt(MetricUnit.kilometers(4))
|
|
259
|
+
end).toThrow("length to the power 0.5 has a fractional exponent")
|
|
260
|
+
end)
|
|
261
|
+
|
|
262
|
+
it("raises to a whole power", function()
|
|
263
|
+
local volume = MetricUnit.pow(MetricUnit.meters(2), 3)
|
|
264
|
+
expect(volume:In(MetricUnit.pow(MetricUnit.meters(1), 3))).toBeCloseTo(8, 9)
|
|
265
|
+
expect(MetricUnit.pow(MetricUnit.days(2), -1):In("perDay")).toBeCloseTo(0.5, 9)
|
|
266
|
+
expect(MetricUnit.pow(MetricUnit.days(2), 0):In(MetricUnit.none(1))).toBe(1)
|
|
267
|
+
expect(function()
|
|
268
|
+
MetricUnit.pow(MetricUnit.days(2), 0.5)
|
|
269
|
+
end).toThrow("whole power")
|
|
270
|
+
end)
|
|
271
|
+
|
|
272
|
+
it("tells units, zeros, signs and dimensions apart", function()
|
|
273
|
+
expect(MetricUnit.isUnit(MetricUnit.kilometers(1))).toBe(true)
|
|
274
|
+
expect(MetricUnit.isUnit(1)).toBe(false)
|
|
275
|
+
expect(MetricUnit.isUnit({})).toBe(false)
|
|
276
|
+
expect(MetricUnit.isZero(MetricUnit.kilometers(0))).toBe(true)
|
|
277
|
+
expect(MetricUnit.isZero(0)).toBe(true)
|
|
278
|
+
expect(MetricUnit.isZero(MetricUnit.kilometers(1))).toBe(false)
|
|
279
|
+
expect(MetricUnit.sign(MetricUnit.kilometers(-3))).toBe(-1)
|
|
280
|
+
expect(MetricUnit.sign(MetricUnit.kilometers(0))).toBe(0)
|
|
281
|
+
expect(MetricUnit.sameDimension(MetricUnit.kilometers(1), MetricUnit.meters(1))).toBe(true)
|
|
282
|
+
expect(MetricUnit.sameDimension(MetricUnit.kilometers(1), MetricUnit.days(1))).toBe(false)
|
|
283
|
+
expect(MetricUnit.sameDimension(MetricUnit.kilometers(1), 0)).toBe(true)
|
|
284
|
+
expect(MetricUnit.sameDimension(MetricUnit.kilometers(1), 2)).toBe(false)
|
|
285
|
+
end)
|
|
286
|
+
|
|
287
|
+
it("compares within a tolerance", function()
|
|
288
|
+
expect(MetricUnit.fuzzyEq(MetricUnit.hours(24), MetricUnit.days(1))).toBe(true)
|
|
289
|
+
expect(MetricUnit.fuzzyEq(MetricUnit.kilometers(1), MetricUnit.meters(1_001))).toBe(false)
|
|
290
|
+
expect(MetricUnit.fuzzyEq(MetricUnit.kilometers(1), MetricUnit.meters(1_001), MetricUnit.meters(2))).toBe(true)
|
|
291
|
+
expect(function()
|
|
292
|
+
MetricUnit.fuzzyEq(MetricUnit.kilometers(1), MetricUnit.days(1))
|
|
293
|
+
end).toThrow("cannot compare length and time")
|
|
294
|
+
end)
|
|
295
|
+
|
|
296
|
+
it("checks dimensions for API boundaries", function()
|
|
297
|
+
expect(MetricUnit.isKind(MetricUnit.days(1), "time")).toBe(true)
|
|
298
|
+
expect(MetricUnit.isKind(MetricUnit.kilometers(1), "time")).toBe(false)
|
|
299
|
+
expect(MetricUnit.isKind(1, "time")).toBe(false)
|
|
300
|
+
expect(MetricUnit.isKind(MetricUnit.kilometers(1), "length")).toBe(true)
|
|
301
|
+
expect(MetricUnit.isKind(MetricUnit.perYear(1), "rate")).toBe(true)
|
|
302
|
+
expect(MetricUnit.isKind(MetricUnit.kilometers(1) / MetricUnit.days(1), "speed")).toBe(true)
|
|
303
|
+
expect(MetricUnit.isKind(MetricUnit.perYear(1), "speed")).toBe(false)
|
|
304
|
+
expect(MetricUnit.isKind(MetricUnit.kilobytes(1), "memory")).toBe(true)
|
|
305
|
+
expect(MetricUnit.isKind(MetricUnit.kilograms(1), "mass")).toBe(true)
|
|
306
|
+
end)
|
|
307
|
+
end)
|
|
308
|
+
|
|
309
|
+
describe("MetricUnit.expect", function()
|
|
310
|
+
it("returns the amount in SI base units", function()
|
|
311
|
+
expect(MetricUnit.expect(MetricUnit.kilometers(0.5), "length", "reach")).toBe(500)
|
|
312
|
+
expect(MetricUnit.expect(MetricUnit.years(1), "time", "every")).toBe(YEAR)
|
|
313
|
+
expect(MetricUnit.expect(MetricUnit.new(60, "perMinute"), "rate", "rate")).toBeCloseTo(1, 9)
|
|
314
|
+
expect(MetricUnit.expect(MetricUnit.kilometers(36) / MetricUnit.hours(1), "speed", "speed")).toBeCloseTo(10, 9)
|
|
315
|
+
expect(MetricUnit.expect(MetricUnit.kilobytes(2), "memory", "memory")).toBe(2_000)
|
|
316
|
+
expect(MetricUnit.expect(MetricUnit.kilometers(1) * MetricUnit.kilometers(1), { length = 2 }, "area")).toBe(1e6)
|
|
317
|
+
end)
|
|
318
|
+
|
|
319
|
+
it("takes a literal 0 as zero of any dimension", function()
|
|
320
|
+
expect(MetricUnit.expect(0, "length", "reach")).toBe(0)
|
|
321
|
+
expect(MetricUnit.expect(0, "rate", "rate")).toBe(0)
|
|
322
|
+
end)
|
|
323
|
+
|
|
324
|
+
it("names the slot, the dimension expected and what was given", function()
|
|
325
|
+
expect(function()
|
|
326
|
+
MetricUnit.expect(MetricUnit.days(1), "length", "reach")
|
|
327
|
+
end).toThrow("MetricUnit.expect: reach expects length, got time")
|
|
328
|
+
expect(function()
|
|
329
|
+
MetricUnit.expect(2, "rate", "rate")
|
|
330
|
+
end).toThrow("MetricUnit.expect: rate expects 1/time, got the plain number 2")
|
|
331
|
+
expect(function()
|
|
332
|
+
MetricUnit.expect("far" :: any, "length", "reach")
|
|
333
|
+
end).toThrow("MetricUnit.expect: reach expects length, got string")
|
|
334
|
+
expect(function()
|
|
335
|
+
MetricUnit.expect(MetricUnit.kilometers(1), "speed", "speed")
|
|
336
|
+
end).toThrow("speed expects length/time, got length")
|
|
337
|
+
end)
|
|
338
|
+
|
|
339
|
+
it("errors on an unknown dimension name", function()
|
|
340
|
+
expect(function()
|
|
341
|
+
MetricUnit.expect(MetricUnit.kilometers(1), "distance" :: any, "reach")
|
|
342
|
+
end).toThrow('no kind named "distance"')
|
|
343
|
+
end)
|
|
344
|
+
end)
|
|
345
|
+
|
|
346
|
+
describe("MetricUnit data", function()
|
|
347
|
+
it("round trips through plain data", function()
|
|
348
|
+
local speed = MetricUnit.kilometers(20) / MetricUnit.years(1)
|
|
349
|
+
local data = MetricUnit.toData(speed)
|
|
350
|
+
expect(data).toEqual({ amount = 20_000 / YEAR, dimensions = { length = 1, time = -1 } })
|
|
351
|
+
expect(table.isfrozen(data)).toBe(false)
|
|
352
|
+
local back = MetricUnit.fromData(data)
|
|
353
|
+
expect(back == speed).toBe(true)
|
|
354
|
+
expect(MetricUnit.isKind(back, "speed")).toBe(true)
|
|
355
|
+
end)
|
|
356
|
+
|
|
357
|
+
it("errors on data that is not a unit", function()
|
|
358
|
+
expect(function()
|
|
359
|
+
MetricUnit.fromData({ amount = 1, dimensions = { length = 0.5 } })
|
|
360
|
+
end).toThrow("MetricUnit.fromData takes")
|
|
361
|
+
end)
|
|
362
|
+
end)
|
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--[=[
|
|
3
|
-
Provides ways to convert studs to metric and back.
|
|
3
|
+
Provides ways to convert studs to metric and back, at 3.57 studs to the metre as these functions have always
|
|
4
|
+
counted them. Each is a plain multiplication, by a factor [MetricUnit] works out once, so they are cheap enough
|
|
5
|
+
to call every frame; for typed values, or Roblox's own 28 cm stud, use [MetricUnit] directly.
|
|
4
6
|
@class MetricUtils
|
|
5
7
|
]=]
|
|
6
8
|
|
|
9
|
+
local require = require(script.Parent.loader).load(script)
|
|
10
|
+
|
|
11
|
+
local MetricUnit = require("MetricUnit")
|
|
12
|
+
local MetricUnitSystems = require("MetricUnitSystems")
|
|
13
|
+
|
|
7
14
|
local MetricUtils = {}
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
type Factors = {
|
|
17
|
+
secondsOfSoundPerStud: number,
|
|
18
|
+
kilometersPerStud: number,
|
|
19
|
+
metersPerStud: number,
|
|
20
|
+
kphPerStudPerSecond: number,
|
|
21
|
+
mphPerStudPerSecond: number,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
local factors: Factors? = nil
|
|
25
|
+
|
|
26
|
+
local function getFactors(): Factors
|
|
27
|
+
local found = factors
|
|
28
|
+
if found then
|
|
29
|
+
return found
|
|
30
|
+
end
|
|
31
|
+
local studs = MetricUnitSystems.roblox({ name = "metricUtils", metersPerStud = 1 / 3.57 })
|
|
32
|
+
local stud = studs:New(1, "stud")
|
|
33
|
+
local studPerSecond = studs:New(1, "studPerSecond")
|
|
34
|
+
local speedOfSoundInDryAirAt20C = MetricUnit.new(343, "meterPerSecond")
|
|
35
|
+
local computed: Factors = table.freeze({
|
|
36
|
+
secondsOfSoundPerStud = (stud / speedOfSoundInDryAirAt20C):InSeconds(),
|
|
37
|
+
kilometersPerStud = stud:In("kilometer"),
|
|
38
|
+
metersPerStud = stud:In("meter"),
|
|
39
|
+
kphPerStudPerSecond = studPerSecond:In("kilometerPerHour"),
|
|
40
|
+
mphPerStudPerSecond = studPerSecond:In("milePerHour"),
|
|
41
|
+
})
|
|
42
|
+
factors = computed
|
|
43
|
+
return computed
|
|
44
|
+
end
|
|
13
45
|
|
|
14
46
|
--[=[
|
|
15
47
|
Computes sound delay for the given studs
|
|
@@ -17,8 +49,7 @@ local KPH_TO_MPH = 0.621371192
|
|
|
17
49
|
@return number -- seconds
|
|
18
50
|
]=]
|
|
19
51
|
function MetricUtils.getSoundDelaySeconds(studs: number): number
|
|
20
|
-
|
|
21
|
-
return meters / SPEED_OF_SOUND_IN_METERS_PER_SECOND
|
|
52
|
+
return studs * getFactors().secondsOfSoundPerStud
|
|
22
53
|
end
|
|
23
54
|
|
|
24
55
|
--[=[
|
|
@@ -27,7 +58,7 @@ end
|
|
|
27
58
|
@return number -- kilometers
|
|
28
59
|
]=]
|
|
29
60
|
function MetricUtils.studsToKilometers(studs: number): number
|
|
30
|
-
return studs
|
|
61
|
+
return studs * getFactors().kilometersPerStud
|
|
31
62
|
end
|
|
32
63
|
|
|
33
64
|
--[=[
|
|
@@ -36,7 +67,7 @@ end
|
|
|
36
67
|
@return number -- meters
|
|
37
68
|
]=]
|
|
38
69
|
function MetricUtils.studsToMeters(studs: number): number
|
|
39
|
-
return studs
|
|
70
|
+
return studs * getFactors().metersPerStud
|
|
40
71
|
end
|
|
41
72
|
|
|
42
73
|
--[=[
|
|
@@ -45,17 +76,17 @@ end
|
|
|
45
76
|
@return number -- kph
|
|
46
77
|
]=]
|
|
47
78
|
function MetricUtils.studsPerSecondToKph(studsPerSecond: number): number
|
|
48
|
-
return studsPerSecond
|
|
79
|
+
return studsPerSecond * getFactors().kphPerStudPerSecond
|
|
49
80
|
end
|
|
50
81
|
|
|
51
82
|
--[=[
|
|
52
83
|
Convert from studs per a second to meters per a second
|
|
53
84
|
|
|
54
85
|
@param studsPerSecond number
|
|
55
|
-
@return number --
|
|
86
|
+
@return number -- meters per a second
|
|
56
87
|
]=]
|
|
57
88
|
function MetricUtils.studsPerSecondToMetersPerSecond(studsPerSecond: number): number
|
|
58
|
-
return studsPerSecond
|
|
89
|
+
return studsPerSecond * getFactors().metersPerStud
|
|
59
90
|
end
|
|
60
91
|
|
|
61
92
|
--[=[
|
|
@@ -64,16 +95,16 @@ end
|
|
|
64
95
|
@return number -- mph
|
|
65
96
|
]=]
|
|
66
97
|
function MetricUtils.studsPerSecondToMph(studsPerSecond: number): number
|
|
67
|
-
return studsPerSecond
|
|
98
|
+
return studsPerSecond * getFactors().mphPerStudPerSecond
|
|
68
99
|
end
|
|
69
100
|
|
|
70
101
|
--[=[
|
|
71
|
-
Convert
|
|
102
|
+
Convert kph to studs per a second
|
|
72
103
|
@param kph number
|
|
73
104
|
@return number -- studs per a second
|
|
74
105
|
]=]
|
|
75
106
|
function MetricUtils.kphToStudsPerSecond(kph: number): number
|
|
76
|
-
return kph
|
|
107
|
+
return kph / getFactors().kphPerStudPerSecond
|
|
77
108
|
end
|
|
78
109
|
|
|
79
110
|
--[=[
|
|
@@ -82,7 +113,7 @@ end
|
|
|
82
113
|
@return number -- studs per a second
|
|
83
114
|
]=]
|
|
84
115
|
function MetricUtils.mphToStudsPerSecond(mph: number): number
|
|
85
|
-
return mph
|
|
116
|
+
return mph / getFactors().mphPerStudPerSecond
|
|
86
117
|
end
|
|
87
118
|
|
|
88
119
|
return MetricUtils
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUtils.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUtils = require("MetricUtils")
|
|
10
|
+
|
|
11
|
+
local describe = Jest.Globals.describe
|
|
12
|
+
local expect = Jest.Globals.expect
|
|
13
|
+
local it = Jest.Globals.it
|
|
14
|
+
|
|
15
|
+
describe("MetricUtils.getSoundDelaySeconds", function()
|
|
16
|
+
it("takes one second for sound to cross 343 meters", function()
|
|
17
|
+
expect(MetricUtils.getSoundDelaySeconds(343 * 3.57)).toBeCloseTo(1, 9)
|
|
18
|
+
end)
|
|
19
|
+
|
|
20
|
+
it("is zero at zero distance", function()
|
|
21
|
+
expect(MetricUtils.getSoundDelaySeconds(0)).toBe(0)
|
|
22
|
+
end)
|
|
23
|
+
end)
|
|
24
|
+
|
|
25
|
+
describe("MetricUtils.studsToMeters", function()
|
|
26
|
+
it("converts at 3.57 studs per meter", function()
|
|
27
|
+
expect(MetricUtils.studsToMeters(357)).toBeCloseTo(100, 9)
|
|
28
|
+
expect(MetricUtils.studsToMeters(-3.57)).toBeCloseTo(-1, 9)
|
|
29
|
+
expect(MetricUtils.studsToMeters(0)).toBe(0)
|
|
30
|
+
end)
|
|
31
|
+
end)
|
|
32
|
+
|
|
33
|
+
describe("MetricUtils.studsToKilometers", function()
|
|
34
|
+
it("converts studs to kilometers", function()
|
|
35
|
+
expect(MetricUtils.studsToKilometers(3570)).toBeCloseTo(1, 9)
|
|
36
|
+
expect(MetricUtils.studsToKilometers(0)).toBe(0)
|
|
37
|
+
end)
|
|
38
|
+
end)
|
|
39
|
+
|
|
40
|
+
describe("MetricUtils.studsPerSecondToMetersPerSecond", function()
|
|
41
|
+
it("converts a stud velocity to meters per second", function()
|
|
42
|
+
expect(MetricUtils.studsPerSecondToMetersPerSecond(35.7)).toBeCloseTo(10, 9)
|
|
43
|
+
end)
|
|
44
|
+
end)
|
|
45
|
+
|
|
46
|
+
describe("MetricUtils.studsPerSecondToKph", function()
|
|
47
|
+
it("converts a stud velocity to kilometers per hour", function()
|
|
48
|
+
expect(MetricUtils.studsPerSecondToKph(3.57)).toBeCloseTo(3.6, 9)
|
|
49
|
+
end)
|
|
50
|
+
end)
|
|
51
|
+
|
|
52
|
+
describe("MetricUtils.studsPerSecondToMph", function()
|
|
53
|
+
it("converts a stud velocity to miles per hour", function()
|
|
54
|
+
expect(MetricUtils.studsPerSecondToMph(3.57 * 0.44704)).toBeCloseTo(1, 9)
|
|
55
|
+
end)
|
|
56
|
+
end)
|
|
57
|
+
|
|
58
|
+
describe("MetricUtils.kphToStudsPerSecond", function()
|
|
59
|
+
it("converts kilometers per hour to a stud velocity", function()
|
|
60
|
+
expect(MetricUtils.kphToStudsPerSecond(3.6)).toBeCloseTo(3.57, 9)
|
|
61
|
+
end)
|
|
62
|
+
|
|
63
|
+
it("inverts studsPerSecondToKph", function()
|
|
64
|
+
expect(MetricUtils.kphToStudsPerSecond(MetricUtils.studsPerSecondToKph(16))).toBeCloseTo(16, 9)
|
|
65
|
+
end)
|
|
66
|
+
end)
|
|
67
|
+
|
|
68
|
+
describe("MetricUtils.mphToStudsPerSecond", function()
|
|
69
|
+
it("converts miles per hour to a stud velocity", function()
|
|
70
|
+
expect(MetricUtils.mphToStudsPerSecond(1)).toBeCloseTo(3.57 * 0.44704, 9)
|
|
71
|
+
end)
|
|
72
|
+
|
|
73
|
+
it("inverts studsPerSecondToMph", function()
|
|
74
|
+
expect(MetricUtils.mphToStudsPerSecond(MetricUtils.studsPerSecondToMph(16))).toBeCloseTo(16, 9)
|
|
75
|
+
end)
|
|
76
|
+
end)
|