@rbxts/planck 0.2.5 → 0.3.0-alpha.2

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.
@@ -0,0 +1,660 @@
1
+ --!nonstrict
2
+ local ReplicatedStorage = game:GetService("ReplicatedStorage")
3
+
4
+ local root = script.Parent.Parent
5
+
6
+ local Phase = require(root.Phase)
7
+ local Scheduler = require(root.Scheduler)
8
+
9
+ local JestGlobals = require(ReplicatedStorage.DevPackages.JestGlobals)
10
+
11
+ local describe = JestGlobals.describe
12
+ local expect = JestGlobals.expect
13
+ local test = JestGlobals.test
14
+
15
+ describe("Initializer Systems", function()
16
+ describe("Basic Initialization", function()
17
+ test("standard system", function()
18
+ expect.assertions(3)
19
+
20
+ local runCount = 0
21
+
22
+ local function standardSystem()
23
+ runCount += 1
24
+ end
25
+
26
+ local myScheduler = Scheduler.new()
27
+ local myPhase = Phase.new("myPhase")
28
+
29
+ myScheduler:insert(myPhase):addSystem(standardSystem, myPhase)
30
+
31
+ myScheduler:runPhase(myPhase)
32
+ expect(runCount).toBe(1)
33
+ expect(myScheduler._systemInfo[standardSystem].initialized).toBe(
34
+ true
35
+ )
36
+
37
+ myScheduler:runPhase(myPhase)
38
+ expect(runCount).toBe(2)
39
+ end)
40
+
41
+ test("initializer system", function()
42
+ expect.assertions(7)
43
+
44
+ local initCount = 0
45
+ local runCount = 0
46
+
47
+ local function initializerSystem()
48
+ initCount += 1
49
+
50
+ return function()
51
+ runCount += 1
52
+ end
53
+ end
54
+
55
+ local myScheduler = Scheduler.new()
56
+ local myPhase = Phase.new("myPhase")
57
+
58
+ myScheduler:insert(myPhase):addSystem(initializerSystem, myPhase)
59
+
60
+ myScheduler:runPhase(myPhase)
61
+ expect(initCount).toBe(1)
62
+ expect(runCount).toBe(1)
63
+ expect(myScheduler._systemInfo[initializerSystem].initialized).toBe(
64
+ true
65
+ )
66
+
67
+ myScheduler:runPhase(myPhase)
68
+ expect(initCount).toBe(1)
69
+ expect(runCount).toBe(2)
70
+
71
+ myScheduler:runPhase(myPhase)
72
+ expect(initCount).toBe(1)
73
+ expect(runCount).toBe(3)
74
+ end)
75
+
76
+ test("initializer with cleanup", function()
77
+ expect.assertions(5)
78
+
79
+ local initCount = 0
80
+ local runCount = 0
81
+ local cleanupCount = 0
82
+
83
+ local function initializerWithCleanup()
84
+ initCount += 1
85
+
86
+ return function()
87
+ runCount += 1
88
+ end, function()
89
+ cleanupCount += 1
90
+ end
91
+ end
92
+
93
+ local myScheduler = Scheduler.new()
94
+ local myPhase = Phase.new("myPhase")
95
+
96
+ myScheduler
97
+ :insert(myPhase)
98
+ :addSystem(initializerWithCleanup, myPhase)
99
+
100
+ myScheduler:runPhase(myPhase)
101
+ expect(initCount).toBe(1)
102
+ expect(runCount).toBe(1)
103
+ expect(myScheduler._systemInfo[initializerWithCleanup].cleanup).toBeTruthy()
104
+
105
+ myScheduler:runPhase(myPhase)
106
+ expect(runCount).toBe(2)
107
+
108
+ myScheduler:removeSystem(initializerWithCleanup)
109
+ expect(cleanupCount).toBe(1)
110
+ end)
111
+
112
+ test("init once", function()
113
+ expect.assertions(2)
114
+
115
+ local initCount = 0
116
+ local runCount = 0
117
+
118
+ local function initOnceSystem()
119
+ initCount += 1
120
+ return function()
121
+ runCount += 1
122
+ end
123
+ end
124
+
125
+ local myScheduler = Scheduler.new()
126
+ local myPhase = Phase.new("myPhase")
127
+
128
+ myScheduler:insert(myPhase):addSystem(initOnceSystem, myPhase)
129
+
130
+ myScheduler:runPhase(myPhase)
131
+ myScheduler:runPhase(myPhase)
132
+ myScheduler:runPhase(myPhase)
133
+ myScheduler:runPhase(myPhase)
134
+
135
+ expect(initCount).toBe(1)
136
+ expect(runCount).toBe(4)
137
+ end)
138
+ end)
139
+
140
+ describe("Error Cases", function()
141
+ test("invalid return type", function()
142
+ expect.assertions(4)
143
+
144
+ local attemptCount = 0
145
+
146
+ local function brokenSystem()
147
+ attemptCount += 1
148
+ return "invalid"
149
+ end
150
+
151
+ local myScheduler = Scheduler.new()
152
+ local myPhase = Phase.new("myPhase")
153
+
154
+ myScheduler:insert(myPhase):addSystem(brokenSystem, myPhase)
155
+
156
+ myScheduler:runPhase(myPhase)
157
+ expect(myScheduler._systemInfo[brokenSystem].initialized).toBe(true)
158
+ expect(attemptCount).toBe(1)
159
+
160
+ myScheduler:runPhase(myPhase)
161
+ expect(myScheduler._systemInfo[brokenSystem].initialized).toBe(true)
162
+ expect(attemptCount).toBe(2)
163
+ end)
164
+
165
+ test("init with run condition", function()
166
+ expect.assertions(5)
167
+
168
+ local initCount = 0
169
+ local runCount = 0
170
+ local allowRun = false
171
+
172
+ local function conditionalInit()
173
+ initCount += 1
174
+ return function()
175
+ runCount += 1
176
+ end
177
+ end
178
+
179
+ local myScheduler = Scheduler.new()
180
+ local myPhase = Phase.new("myPhase")
181
+
182
+ myScheduler
183
+ :insert(myPhase)
184
+ :addSystem(conditionalInit, myPhase)
185
+ :addRunCondition(conditionalInit, function()
186
+ return allowRun
187
+ end)
188
+
189
+ myScheduler:runPhase(myPhase)
190
+ expect(initCount).toBe(0)
191
+ expect(runCount).toBe(0)
192
+
193
+ allowRun = true
194
+
195
+ myScheduler:runPhase(myPhase)
196
+ expect(initCount).toBe(1)
197
+ expect(runCount).toBe(1)
198
+
199
+ myScheduler:runPhase(myPhase)
200
+ expect(runCount).toBe(2)
201
+ end)
202
+
203
+ test("nested initializer should not run inner", function()
204
+ expect.assertions(3)
205
+
206
+ local initCount = 0
207
+ local runtimeReturnCount = 0
208
+ local innerRunCount = 0
209
+
210
+ local function nestedInit()
211
+ initCount += 1
212
+ return function()
213
+ runtimeReturnCount += 1
214
+ return function()
215
+ innerRunCount += 1
216
+ end
217
+ end
218
+ end
219
+
220
+ local myScheduler = Scheduler.new()
221
+ local myPhase = Phase.new("myPhase")
222
+
223
+ myScheduler:insert(myPhase):addSystem(nestedInit, myPhase)
224
+
225
+ myScheduler:runPhase(myPhase)
226
+ myScheduler:runPhase(myPhase)
227
+ myScheduler:runPhase(myPhase)
228
+
229
+ expect(initCount).toBe(1)
230
+ expect(runtimeReturnCount).toBe(3)
231
+ expect(innerRunCount).toBe(0)
232
+ end)
233
+
234
+ test("nil with second value", function()
235
+ expect.assertions(1)
236
+
237
+ local function invalidNilSystem()
238
+ return nil, "oops"
239
+ end
240
+
241
+ local myScheduler = Scheduler.new()
242
+ local myPhase = Phase.new("myPhase")
243
+
244
+ myScheduler:insert(myPhase):addSystem(invalidNilSystem, myPhase)
245
+
246
+ myScheduler:runPhase(myPhase)
247
+ expect(myScheduler._systemInfo[invalidNilSystem].initialized).toBe(
248
+ true
249
+ )
250
+ end)
251
+
252
+ test("same system in multiple phases", function()
253
+ expect.assertions(2)
254
+
255
+ local initCount = 0
256
+
257
+ local function sharedSystem()
258
+ initCount += 1
259
+ return function() end
260
+ end
261
+
262
+ local myScheduler = Scheduler.new()
263
+ local phase1 = Phase.new("Phase1")
264
+ local phase2 = Phase.new("Phase2")
265
+
266
+ myScheduler
267
+ :insert(phase1)
268
+ :insert(phase2)
269
+ :addSystem(sharedSystem, phase1)
270
+ :addSystem(sharedSystem, phase2)
271
+
272
+ myScheduler:runPhase(phase1)
273
+ expect(initCount).toBe(1)
274
+
275
+ myScheduler:runPhase(phase2)
276
+ expect(initCount).toBe(1)
277
+ end)
278
+
279
+ test("table with 2 functions", function()
280
+ expect.assertions(4)
281
+
282
+ local initCount = 0
283
+ local runCount = 0
284
+ local cleanupCount = 0
285
+
286
+ local function tableStyleInit()
287
+ initCount += 1
288
+ return {
289
+ system = function()
290
+ runCount += 1
291
+ end,
292
+ cleanup = function()
293
+ cleanupCount += 1
294
+ end,
295
+ }
296
+ end
297
+
298
+ local myScheduler = Scheduler.new()
299
+ local myPhase = Phase.new("myPhase")
300
+
301
+ myScheduler:insert(myPhase):addSystem(tableStyleInit, myPhase)
302
+
303
+ myScheduler:runPhase(myPhase)
304
+ expect(initCount).toBe(1)
305
+ expect(runCount).toBe(1)
306
+
307
+ myScheduler:runPhase(myPhase)
308
+ expect(runCount).toBe(2)
309
+
310
+ myScheduler:removeSystem(tableStyleInit)
311
+ expect(cleanupCount).toBe(1)
312
+ end)
313
+
314
+ test("table with 1 function", function()
315
+ expect.assertions(3)
316
+
317
+ local initCount = 0
318
+ local runCount = 0
319
+
320
+ local function singleTableInit()
321
+ initCount += 1
322
+ return {
323
+ system = function()
324
+ runCount += 1
325
+ end,
326
+ }
327
+ end
328
+
329
+ local myScheduler = Scheduler.new()
330
+ local myPhase = Phase.new("myPhase")
331
+
332
+ myScheduler:insert(myPhase):addSystem(singleTableInit, myPhase)
333
+
334
+ myScheduler:runPhase(myPhase)
335
+ expect(initCount).toBe(1)
336
+ expect(runCount).toBe(1)
337
+
338
+ myScheduler:runPhase(myPhase)
339
+ expect(runCount).toBe(2)
340
+ end)
341
+
342
+ test("table with non-functions", function()
343
+ expect.assertions(1)
344
+
345
+ local function invalidTableSystem()
346
+ return { system = "not a function", cleanup = 123 }
347
+ end
348
+
349
+ local myScheduler = Scheduler.new()
350
+ local myPhase = Phase.new("myPhase")
351
+
352
+ myScheduler:insert(myPhase):addSystem(invalidTableSystem, myPhase)
353
+
354
+ myScheduler:runPhase(myPhase)
355
+ expect(myScheduler._systemInfo[invalidTableSystem].initialized).toBe(
356
+ true
357
+ )
358
+ end)
359
+
360
+ test("table with cleanup only (startup system)", function()
361
+ expect.assertions(5)
362
+
363
+ local startupRunCount = 0
364
+ local cleanupRan = false
365
+
366
+ local function startupSystem()
367
+ startupRunCount += 1
368
+ -- Returns cleanup only - no runtime system
369
+ return {
370
+ cleanup = function()
371
+ cleanupRan = true
372
+ end,
373
+ }
374
+ end
375
+
376
+ local myScheduler = Scheduler.new()
377
+ local myPhase = Phase.new("myPhase")
378
+
379
+ myScheduler:insert(myPhase):addSystem(startupSystem, myPhase)
380
+
381
+ -- First run: startup logic executes
382
+ myScheduler:runPhase(myPhase)
383
+ expect(startupRunCount).toBe(1)
384
+ expect(myScheduler._systemInfo[startupSystem].initialized).toBe(
385
+ true
386
+ )
387
+
388
+ -- Second run: original function runs again (startup-only pattern)
389
+ myScheduler:runPhase(myPhase)
390
+ expect(startupRunCount).toBe(2)
391
+
392
+ -- Cleanup on removal
393
+ expect(cleanupRan).toBe(false)
394
+ myScheduler:removeSystem(startupSystem)
395
+ expect(cleanupRan).toBe(true)
396
+ end)
397
+
398
+ test("table with neither system nor cleanup", function()
399
+ expect.assertions(1)
400
+
401
+ local function invalidSystem()
402
+ return { foo = "bar" } -- No system or cleanup fields
403
+ end
404
+
405
+ local myScheduler = Scheduler.new()
406
+ local myPhase = Phase.new("myPhase")
407
+
408
+ myScheduler:insert(myPhase):addSystem(invalidSystem, myPhase)
409
+
410
+ myScheduler:runPhase(myPhase)
411
+ expect(myScheduler._systemInfo[invalidSystem].initialized).toBe(
412
+ true
413
+ )
414
+ end)
415
+ end)
416
+
417
+ describe("Cleanup", function()
418
+ test("cleanup once", function()
419
+ expect.assertions(1)
420
+
421
+ local cleanupCount = 0
422
+
423
+ local function systemWithCleanup()
424
+ return function() end, function()
425
+ cleanupCount += 1
426
+ end
427
+ end
428
+
429
+ local myScheduler = Scheduler.new()
430
+ local myPhase = Phase.new("myPhase")
431
+
432
+ myScheduler:insert(myPhase):addSystem(systemWithCleanup, myPhase)
433
+
434
+ myScheduler:runPhase(myPhase)
435
+ myScheduler:removeSystem(systemWithCleanup)
436
+
437
+ expect(cleanupCount).toBe(1)
438
+ end)
439
+
440
+ test("no cleanup if uninitialized", function()
441
+ expect.assertions(1)
442
+
443
+ local cleanupCount = 0
444
+ local allowRun = false
445
+
446
+ local function conditionalSystem()
447
+ return function() end, function()
448
+ cleanupCount += 1
449
+ end
450
+ end
451
+
452
+ local myScheduler = Scheduler.new()
453
+ local myPhase = Phase.new("myPhase")
454
+
455
+ myScheduler
456
+ :insert(myPhase)
457
+ :addSystem(conditionalSystem, myPhase)
458
+ :addRunCondition(conditionalSystem, function()
459
+ return allowRun
460
+ end)
461
+
462
+ myScheduler:runPhase(myPhase)
463
+
464
+ myScheduler:removeSystem(conditionalSystem)
465
+ expect(cleanupCount).toBe(0)
466
+ end)
467
+
468
+ test("cleanup failure", function()
469
+ expect.assertions(2)
470
+
471
+ local cleanupRan = false
472
+
473
+ local function brokenCleanupSystem()
474
+ return function() end, function()
475
+ cleanupRan = true
476
+ error("Cleanup failed!")
477
+ end
478
+ end
479
+
480
+ local myScheduler = Scheduler.new()
481
+ local myPhase = Phase.new("myPhase")
482
+
483
+ myScheduler:insert(myPhase):addSystem(brokenCleanupSystem, myPhase)
484
+
485
+ myScheduler:runPhase(myPhase)
486
+
487
+ myScheduler:removeSystem(brokenCleanupSystem)
488
+
489
+ expect(cleanupRan).toBe(true)
490
+ expect(myScheduler._systemInfo[brokenCleanupSystem]).toBeNil()
491
+ end)
492
+
493
+ test("cleanup on replaceSystem", function()
494
+ expect.assertions(3)
495
+
496
+ local cleanupCount = 0
497
+ local newSystemRan = false
498
+
499
+ local function oldSystem()
500
+ return function() end, function()
501
+ cleanupCount += 1
502
+ end
503
+ end
504
+
505
+ local function newSystem()
506
+ newSystemRan = true
507
+ end
508
+
509
+ local myScheduler = Scheduler.new()
510
+ local myPhase = Phase.new("myPhase")
511
+
512
+ myScheduler:insert(myPhase):addSystem(oldSystem, myPhase)
513
+
514
+ myScheduler:runPhase(myPhase)
515
+ expect(cleanupCount).toBe(0)
516
+
517
+ myScheduler:replaceSystem(oldSystem, newSystem)
518
+ expect(cleanupCount).toBe(1)
519
+
520
+ myScheduler:runPhase(myPhase)
521
+ expect(newSystemRan).toBe(true)
522
+ end)
523
+
524
+ test("cleanup receives custom args on removeSystem", function()
525
+ expect.assertions(1)
526
+
527
+ local capturedArgs = nil
528
+
529
+ local function systemWithArgsInCleanup()
530
+ return function() end, function(...)
531
+ capturedArgs = { ... }
532
+ end
533
+ end
534
+
535
+ local myScheduler = Scheduler.new(1, 2, 3)
536
+ local myPhase = Phase.new("myPhase")
537
+
538
+ myScheduler
539
+ :insert(myPhase)
540
+ :addSystem(systemWithArgsInCleanup, myPhase)
541
+
542
+ myScheduler:runPhase(myPhase) -- Initialize
543
+ myScheduler:removeSystem(systemWithArgsInCleanup)
544
+
545
+ expect(capturedArgs).toEqual({ 1, 2, 3 })
546
+ end)
547
+
548
+ test("cleanup receives custom args on replaceSystem", function()
549
+ expect.assertions(1)
550
+
551
+ local capturedArgs = nil
552
+
553
+ local function oldSystem()
554
+ return function() end, function(...)
555
+ capturedArgs = { ... }
556
+ end
557
+ end
558
+
559
+ local function newSystem() end
560
+
561
+ local myScheduler = Scheduler.new(1, 2, 3)
562
+ local myPhase = Phase.new("myPhase")
563
+
564
+ myScheduler:insert(myPhase):addSystem(oldSystem, myPhase)
565
+
566
+ myScheduler:runPhase(myPhase) -- Initialize
567
+ myScheduler:replaceSystem(oldSystem, newSystem)
568
+
569
+ expect(capturedArgs).toEqual({ 1, 2, 3 })
570
+ end)
571
+ end)
572
+
573
+ describe("Integration", function()
574
+ test("with ordering", function()
575
+ expect.assertions(6)
576
+
577
+ local order = {}
578
+
579
+ local function systemA()
580
+ table.insert(order, "A-init")
581
+ return function()
582
+ table.insert(order, "A-run")
583
+ end
584
+ end
585
+
586
+ local function systemB()
587
+ table.insert(order, "B-init")
588
+ return function()
589
+ table.insert(order, "B-run")
590
+ end
591
+ end
592
+
593
+ local myScheduler = Scheduler.new()
594
+ local myPhase = Phase.new("myPhase")
595
+
596
+ myScheduler
597
+ :insert(myPhase)
598
+ :addSystem(systemA, myPhase)
599
+ :addSystem(systemB, myPhase)
600
+
601
+ myScheduler:runPhase(myPhase)
602
+ expect(order[1]).toBe("A-init")
603
+ expect(order[2]).toBe("A-run")
604
+ expect(order[3]).toBe("B-init")
605
+ expect(order[4]).toBe("B-run")
606
+
607
+ table.clear(order)
608
+
609
+ myScheduler:runPhase(myPhase)
610
+ expect(order[1]).toBe("A-run")
611
+ expect(order[2]).toBe("B-run")
612
+ end)
613
+
614
+ test("with dependency graph", function()
615
+ expect.assertions(6)
616
+
617
+ local order = {}
618
+
619
+ local function systemA()
620
+ table.insert(order, "A-init")
621
+ return function()
622
+ table.insert(order, "A-run")
623
+ end
624
+ end
625
+
626
+ local function systemB()
627
+ table.insert(order, "B-init")
628
+ return function()
629
+ table.insert(order, "B-run")
630
+ end
631
+ end
632
+
633
+ local myScheduler = Scheduler.new()
634
+ local phaseA = Phase.new("PhaseA")
635
+ local phaseB = Phase.new("PhaseB")
636
+
637
+ myScheduler
638
+ :insert(phaseA)
639
+ :insertAfter(phaseB, phaseA)
640
+ :addSystem(systemA, phaseA)
641
+ :addSystem(systemB, phaseB)
642
+
643
+ myScheduler:runPhase(phaseA)
644
+ myScheduler:runPhase(phaseB)
645
+
646
+ expect(order[1]).toBe("A-init")
647
+ expect(order[2]).toBe("A-run")
648
+ expect(order[3]).toBe("B-init")
649
+ expect(order[4]).toBe("B-run")
650
+
651
+ table.clear(order)
652
+
653
+ myScheduler:runPhase(phaseA)
654
+ myScheduler:runPhase(phaseB)
655
+
656
+ expect(order[1]).toBe("A-run")
657
+ expect(order[2]).toBe("B-run")
658
+ end)
659
+ end)
660
+ end)