-
Notifications
You must be signed in to change notification settings - Fork 0
/
test.luau
439 lines (410 loc) · 14.3 KB
/
test.luau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
local cell = require("cell")
local formula = require("formula")
local batch = require("batch")
local subscribe = require("subscribe")
local passedTests = {}
local failedTests = {}
local function test(name: string, body: () -> ())
local suc = xpcall(body, function(msg)
table.insert(failedTests, { name = name, msg = msg })
end)
if suc then
table.insert(passedTests, name)
end
end
test("Cells can be created and read from", function()
local c = cell(1)
assert(c.value == 1)
end)
test("Cells can be written to", function()
local c = cell(1)
assert(c.value == 1)
c.value = 2
assert(c.value == 2)
end)
test("Cells can be converted to a string", function()
assert(tostring(cell("Hello, World!")) == "Hello, World!")
end)
test("Derived cells can be created and read from", function()
local f = formula(function()
return "Hello, World!"
end)
assert(f.value == "Hello, World!")
end)
test("Derived cells are updated if any cell it refers to changes", function()
local c = cell(8)
local f = formula(function()
return "The lucky number is " .. c.value
end)
assert(f.value == "The lucky number is 8", "Formula should have been evaluated")
c.value = 64
assert(f.value == "The lucky number is 64", "Formula should have been re-evaluated")
end)
test("Derived cells can depend on other derived cells", function()
local c = cell(8)
local f1 = formula(function()
return "The lucky number is " .. c.value
end)
local f2 = formula(function()
return f1.value .. "!"
end)
assert(f2.value == "The lucky number is 8!", "Formula should have been evaluated")
c.value = 64
assert(f2.value == "The lucky number is 64!", "Formula should have been re-evaluated")
end)
test("Derived cells dependencies can change", function()
local switchCell = cell(false)
local c1 = cell(1)
local c2 = cell(2)
local timesRan = 0
local f = formula(function()
timesRan += 1
if switchCell.value then
return "foxes - " .. c1.value
else
return "kitties - " .. c2.value
end
end)
assert(f.value == "kitties - 2", "Formula should have been evaluated")
assert(timesRan == 1, "Formula should have run once, but ran " .. timesRan .. " times")
c1.value = 3
assert(f.value == "kitties - 2", "Formula should not have been re-evaluated")
assert(timesRan == 1, "Formula should have only ran once, but ran " .. timesRan .. " times")
c2.value = 4
assert(f.value == "kitties - 4", "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice, but ran " .. timesRan .. " times")
switchCell.value = true
assert(f.value == "foxes - 3", "Formula should have been re-evaluated")
assert(timesRan == 3, "Formula should have only ran thrice, but ran " .. timesRan .. " times")
c2.value = 5
assert(f.value == "foxes - 3", "Formula should not have been re-evaluated")
assert(timesRan == 3, "Formula should have only ran thrice, but ran " .. timesRan .. " times")
end)
test("Derived dervied cells dependencies can change", function()
local switchCell = cell(false)
local c1 = cell(1)
local c2 = cell(2)
local f1 = formula(function()
return c1.value .. "!"
end)
local f2 = formula(function()
return c2.value .. "!"
end)
local timesRan = 0
local f = formula(function()
timesRan += 1
if switchCell.value then
return "foxes - " .. f1.value
else
return "kitties - " .. f2.value
end
end)
assert(f.value == "kitties - 2!", "Formula should have been evaluated")
assert(timesRan == 1, "Formula should have run once, but ran " .. timesRan .. " times")
c1.value = 3
assert(f.value == "kitties - 2!", "Formula should not have been re-evaluated")
assert(timesRan == 1, "Formula should have only ran once, but ran " .. timesRan .. " times")
c2.value = 4
assert(f.value == "kitties - 4!", "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice, but ran " .. timesRan .. " times")
switchCell.value = true
assert(f.value == "foxes - 3!", "Formula should have been re-evaluated")
assert(timesRan == 3, "Formula should have only ran thrice, but ran " .. timesRan .. " times")
c2.value = 5
assert(f.value == "foxes - 3!", "Formula should not have been re-evaluated")
assert(timesRan == 3, "Formula should have only ran thrice, but ran " .. timesRan .. " times")
end)
test("Updates to derived cells are topologically sorted", function()
local c = cell({ "Hello,", "World!" })
local firstWord = formula(function()
return c.value[1]
end)
local secondWord = formula(function()
return c.value[2]
end)
local timesRan = 0
local computed = formula(function()
timesRan += 1
return string.format("%s %s", firstWord.value, secondWord.value)
end)
assert(computed.value == "Hello, World!", "Formula should have been evaluated")
assert(timesRan == 1, "Formula should have run once")
-- Changing the value of the cell will only recompute the formula once
c.value = { "foo", "bar" }
assert(computed.value == "foo bar", "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice, but ran " .. timesRan .. " times")
end)
test("Updates can be batched", function()
local c = cell(1)
local timesRan = 0
local f = formula(function()
timesRan += 1
return c.value + 1
end)
assert(f.value == 2, "Formula should have been evaluated")
batch(function()
c.value += 1
c.value = 3
c.value = 4
end)
assert(f.value == 5, "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice")
end)
test("Batches can be nested", function()
local c = cell(1)
local timesRan = 0
local f = formula(function()
timesRan += 1
return c.value + 1
end)
assert(f.value == 2, "Formula should have been evaluated")
batch(function()
c.value = 2
batch(function()
c.value = 3
c.value = 4
end)
c.value = 5
end)
assert(f.value == 6, "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice")
end)
test("Batch affecting multiple dependencies of a cell will only update it once", function()
local c1 = cell("foo")
local c2 = cell("bar")
local timesRan = 0
local f = formula(function()
timesRan += 1
return c1.value .. c2.value
end)
assert(f.value == "foobar", "Formula should have been evaluated")
assert(timesRan == 1, "Formula should have run once, but ran " .. timesRan .. " times")
-- The formula will only be recomputed at the end of the batch block
batch(function()
c1.value = "first"
c2.value = "second"
end)
assert(f.value == "firstsecond", "Formula should have been re-evaluated")
assert(timesRan == 2, "Formula should have only ran twice, but ran " .. timesRan .. " times")
end)
test("Unused derived cells should be garbage collected", function()
local c = cell(1)
local t = setmetatable({}, { __mode = "v" })
t[1] = formula(function()
return c.value
end)
assert(t[1].value == 1, "Formula should have been evaluated")
collectgarbage()
assert(t[1] == nil, "Formula should have been garbage collected")
end)
test("Dependencies shouldn't be garbaged collected unexpectedly", function()
local c = cell(1)
local f = formula(function()
return c.value
end)
assert(f.value == 1, "Formula should have been evaluated")
collectgarbage()
c.value = 2
assert(f.value == 2, "Formula should have been re-evaluated")
end)
test("Setting a cell to the same value shouldn't trigger a re-evaluation", function()
local c = cell(1)
local timesRan = 0
local f = formula(function()
timesRan += 1
return c.value
end)
assert(f.value == 1, "Formula should have been evaluated")
assert(timesRan == 1, "Formula should have run once, but ran " .. timesRan .. " times")
c.value = 1
assert(f.value == 1, "Formula should not have been re-evaluated")
assert(timesRan == 1, "Formula should have only ran once, but ran " .. timesRan .. " times")
end)
test("Unreferenced cells get garbaged collected", function()
local t = setmetatable({ cell(1) }, { __mode = "v" })
assert(t[1], "Cell should exist")
collectgarbage()
assert(t[1] == nil, "Cell should have been garbage collected")
end)
test("Cells should be garbage collected when all dependent cells are garbage collected", function()
local c = cell(1)
local f1 = formula(function()
return c.value
end)
local f2 = formula(function()
return c.value
end)
local t = setmetatable({ c, f1, f2 }, { __mode = "v" })
assert(t[1], "Root cell should exist")
assert(t[2], "First derived cell should exist")
assert(t[3], "Second derived cell should exist")
f1 = nil
collectgarbage()
assert(t[1], "Root cell should exist")
assert(t[2] == nil, "First derived cell should have been garbage collected")
assert(t[3], "Second derived cell should exist")
f2 = nil
collectgarbage()
assert(t[1], "Cell should exist")
assert(t[3] == nil, "Second derived cell should have been garbage collected")
c = nil
collectgarbage()
assert(t[1] == nil, "Root cell should have been garbage collected")
end)
test("Cells shouldn't be garbage collected while a formula relies on them", function()
local c2 = cell(1)
local f
do
local c1 = cell(1)
f = formula(function()
return c1.value + c2.value
end)
end
collectgarbage()
c2.value = 2 -- make sure this doesn't error
end)
test("subscribe() lets you subscribe to when cells change", function()
local c = cell(1)
local timesRan = 0
local last = nil
subscribe(function()
timesRan += 1
last = c.value
end)
assert(timesRan == 1, "Subscription should have run once, but ran " .. timesRan .. " times")
assert(last == 1, "Subscription should have been called with the initial value")
c.value = 2
assert(timesRan == 2, "Subscription should have only ran twice, but ran " .. timesRan .. " times")
assert(last == 2, "Subscription should have been called with the new value")
-- This test only really makes sense if the test above passes
test("Subscriptions don't get garbage collected unexpectedly", function()
collectgarbage() -- Make sure that subscriptions don't get garbage collected
c.value = 3
assert(timesRan == 3, "Subscription should have only ran thrice, but ran " .. timesRan .. " times")
end)
end)
test("Loose cells should get garbage collected (formula that depends on two cells)", function()
local c1 = cell(1)
local c2 = cell(2)
local f1 = formula(function()
return c1.value + c2.value
end)
local t = setmetatable({ c1, c2, f1 }, { __mode = "v" })
c1, c2, f1 = nil
collectgarbage()
assert(t[1] == nil, "First cell should have been garbage collected")
assert(t[2] == nil, "Second cell should have been garbage collected")
assert(t[3] == nil, "First formula should have been garbage collected")
end)
test("Subscriptions can be unsubscribed", function()
local c = cell(1)
local timesRan = 0
local last = nil
local unsub = subscribe(function()
timesRan += 1
last = c.value
end)
assert(timesRan == 1, "Subscription should have run once, but ran " .. timesRan .. " times")
assert(last == 1, "Subscription should have been called with the initial value")
c.value = 2
assert(timesRan == 2, "Subscription should have only ran twice, but ran " .. timesRan .. " times")
assert(last == 2, "Subscription should have been called with the new value")
unsub()
c.value = 3
assert(timesRan == 2, "Subscription should have only ran twice, but ran " .. timesRan .. " times")
assert(last == 2, "Subscription should have been called with the new value")
end)
test("Subscriptions are garbage collected when all it's dependencies are garbage collected", function()
local c1 = cell(1)
local c2 = cell(2)
do
local f1 = formula(function()
return c1.value + c2.value
end)
subscribe(function()
return c1.value + c2.value + f1.value
end)
end
local t = setmetatable({ c1, c2 }, { __mode = "v" })
c1 = nil
collectgarbage()
assert(t[1], "First cell should exist")
assert(t[2], "Second cell should exist")
c2.value = 2
c2 = nil
collectgarbage()
assert(t[1] == nil, "First cell should have been garbage collected")
assert(t[2] == nil, "Second cell should have been garbage collected")
end)
test("Root cells should keep subscriptions alive", function()
local c1 = cell(1)
local timesRan = 0
do
local c1 = c1 -- Capture c1
local f1 = formula(function()
return c1.value * 2
end)
subscribe(function()
timesRan += 1
return f1.value
end)
end
local t = setmetatable({ c1 }, { __mode = "v" })
assert(timesRan == 1, "Subscription should have only ran once, but ran " .. timesRan .. " times")
collectgarbage()
c1.value = 2
assert(timesRan == 2, "Subscription should have only ran twice, but ran " .. timesRan .. " times")
c1 = nil
collectgarbage()
assert(t[1] == nil, "First cell should have been garbage collected")
end)
test("Subscriptions dependencies can change", function()
local switchCell = cell(false)
local c1 = cell(1)
local c2 = cell(2)
local timesRan = 0
local value
local unsub = subscribe(function()
timesRan += 1
if switchCell.value then
value = "foxes - " .. c1.value
else
value = "kitties - " .. c2.value
end
end)
assert(value == "kitties - 2", "Subscription should have been evaluated")
assert(timesRan == 1, "Subscription should have run once, but ran " .. timesRan .. " times")
c1.value = 3
assert(value == "kitties - 2", "Subscription should not have been re-evaluated")
assert(timesRan == 1, "Subscription should have only ran once, but ran " .. timesRan .. " times")
c2.value = 4
assert(value == "kitties - 4", "Subscription should have been re-evaluated")
assert(timesRan == 2, "Subscription should have only ran twice, but ran " .. timesRan .. " times")
switchCell.value = true
assert(value == "foxes - 3", "Subscription should have been re-evaluated")
assert(timesRan == 3, "Subscription should have only ran thrice, but ran " .. timesRan .. " times")
c2.value = 5
assert(value == "foxes - 3", "Subscription should not have been re-evaluated")
assert(timesRan == 3, "Subscription should have only ran thrice, but ran " .. timesRan .. " times")
unsub()
c1.value = 6
assert(value == "foxes - 3", "Subscription should not have been re-evaluated")
assert(timesRan == 3, "Subscription should have only ran thrice, but ran " .. timesRan .. " times")
end)
local red = "\27[31m"
local green = "\27[32m"
local bold = "\27[1m"
local reset = "\27[0m"
local underline = "\27[4m"
for _, passedTest in passedTests do
print(green .. underline .. passedTest .. reset)
end
for _, failedTest in failedTests do
print(red .. underline .. failedTest.name .. reset)
print(failedTest.msg)
end
if #failedTests > 0 then
error(red .. bold .. #failedTests .. " tests failed" .. reset, 0)
else
print(green .. bold .. "All tests passed!" .. reset)
end