@quenty/observablecollection 12.41.0 → 12.42.1

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 CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [12.42.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.42.0...@quenty/observablecollection@12.42.1) (2026-07-23)
7
+
8
+ **Note:** Version bump only for package @quenty/observablecollection
9
+
10
+ # [12.42.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.41.0...@quenty/observablecollection@12.42.0) (2026-07-23)
11
+
12
+ ### Features
13
+
14
+ - Add baseline player-mock and support across Nevermore for mocked players. ([567d121](https://github.com/Quenty/NevermoreEngine/commit/567d121ffc014b42391554088189a1a6296dda83))
15
+
6
16
  # [12.41.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/observablecollection@12.40.0...@quenty/observablecollection@12.41.0) (2026-07-18)
7
17
 
8
18
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/observablecollection",
3
- "version": "12.41.0",
3
+ "version": "12.42.1",
4
4
  "description": "A set of observable collections, such as sets, maps, sorted lists, and more.",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -30,28 +30,28 @@
30
30
  "Quenty"
31
31
  ],
32
32
  "dependencies": {
33
- "@quenty/baseobject": "10.14.0",
33
+ "@quenty/baseobject": "10.15.0",
34
34
  "@quenty/binarysearch": "2.4.0",
35
- "@quenty/brio": "14.32.0",
35
+ "@quenty/brio": "14.33.0",
36
36
  "@quenty/ducktype": "5.11.0",
37
- "@quenty/instanceutils": "13.32.0",
37
+ "@quenty/instanceutils": "13.33.0",
38
38
  "@quenty/loader": "10.11.0",
39
- "@quenty/maid": "3.10.0",
40
- "@quenty/nevermore-test-runner": "1.4.0",
41
- "@quenty/promise": "10.20.0",
42
- "@quenty/rx": "13.30.0",
39
+ "@quenty/maid": "3.11.0",
40
+ "@quenty/nevermore-test-runner": "1.5.0",
41
+ "@quenty/promise": "10.21.0",
42
+ "@quenty/rx": "13.31.0",
43
43
  "@quenty/signal": "7.13.1",
44
- "@quenty/steputils": "3.6.3",
44
+ "@quenty/steputils": "3.7.0",
45
45
  "@quenty/symbol": "3.5.2",
46
46
  "@quenty/table": "3.9.2",
47
- "@quenty/valueobject": "13.33.0",
47
+ "@quenty/valueobject": "13.34.0",
48
48
  "@quentystudios/jest-lua": "3.10.0-quenty.2"
49
49
  },
50
50
  "devDependencies": {
51
- "@quenty/blend": "12.39.0"
51
+ "@quenty/blend": "12.40.1"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "gitHead": "647acfad93dca41c38f59cda247d2bfc0fff36d4"
56
+ "gitHead": "5ea0583dedebba6492f9ac6d719d32b45c5543fa"
57
57
  }
@@ -327,8 +327,12 @@ function ObservableCountingMap.Add<T>(self: ObservableCountingMap<T>, key: T, am
327
327
  -- Remove item
328
328
  self._map[key] = nil
329
329
 
330
- -- Fire events
331
- self._totalKeyCountValue.Value = self._totalKeyCountValue.Value - 1
330
+ -- Fire events. The backing ValueObject may already be destroyed when a stored removal
331
+ -- callback fires during this map's own teardown, in which case its Value reads nil.
332
+ local totalKeyCount = self._totalKeyCountValue.Value
333
+ if totalKeyCount ~= nil then
334
+ self._totalKeyCountValue.Value = totalKeyCount - 1
335
+ end
332
336
 
333
337
  if self.Destroy then
334
338
  self.KeyRemoved:Fire(key)
@@ -348,8 +352,11 @@ function ObservableCountingMap.Add<T>(self: ObservableCountingMap<T>, key: T, am
348
352
  -- Add item
349
353
  self._map[key] = change
350
354
 
351
- -- Fire events
352
- self._totalKeyCountValue.Value = self._totalKeyCountValue.Value + 1
355
+ -- Fire events (see the removal branch for why Value may read nil here)
356
+ local totalKeyCount = self._totalKeyCountValue.Value
357
+ if totalKeyCount ~= nil then
358
+ self._totalKeyCountValue.Value = totalKeyCount + 1
359
+ end
353
360
 
354
361
  if self.Destroy then
355
362
  self.KeyAdded:Fire(key)
@@ -120,7 +120,6 @@ describe("ObservableSortedList", function()
120
120
 
121
121
  expect(list:GetList()).toEqual({ "a", "b", "c" })
122
122
 
123
- -- Move A to the end
124
123
  sortValueA.Value = 10
125
124
 
126
125
  expect(list:GetList()).toEqual({ "b", "c", "a" })
@@ -157,7 +156,6 @@ describe("ObservableSortedList", function()
157
156
  local maid = Maid.new()
158
157
  local list = maid:Add(ObservableSortedListTestUtils.fromList({ "a", "b" }))
159
158
 
160
- -- GetCount reads from _countValue which updates via deferred events
161
159
  list:_testForceFireEvents()
162
160
 
163
161
  expect(list:GetCount()).toEqual(2)
@@ -234,7 +232,6 @@ describe("ObservableSortedList", function()
234
232
  expect(#seenIndices).toEqual(1)
235
233
  expect(seenIndices[1]).toEqual(3)
236
234
 
237
- -- Remove B (index 2). C should shift from 3 to 2
238
235
  removeB()
239
236
  list:_testForceFireEvents()
240
237
 
@@ -257,20 +254,17 @@ describe("ObservableSortedList", function()
257
254
 
258
255
  expect(list:GetCount()).toEqual(5)
259
256
 
260
- -- Observe index of the last element (E, currently at index 5)
261
257
  local seenIndicesForE, sub = ObservableSortedListTestUtils.collectValues(list:ObserveIndex(5))
262
258
 
263
259
  expect(#seenIndicesForE).toEqual(1)
264
260
  expect(seenIndicesForE[1]).toEqual(5)
265
261
 
266
- -- Remove B (index 2). This should shift C->2, D->3, E->4
267
262
  removeB()
268
263
  list:_testForceFireEvents()
269
264
 
270
265
  expect(list:GetCount()).toEqual(4)
271
266
  expect(list:GetList()).toEqual({ "a", "c", "d", "e" })
272
267
 
273
- -- E should have been notified that its index changed from 5 to 4
274
268
  expect(#seenIndicesForE).toEqual(2)
275
269
  expect(seenIndicesForE[2]).toEqual(4)
276
270
 
@@ -293,7 +287,6 @@ describe("ObservableSortedList", function()
293
287
  expect(#seenItems).toEqual(1)
294
288
  expect(seenItems[1]).toEqual("b")
295
289
 
296
- -- Remove B. Now C should be at index 2
297
290
  removeB()
298
291
  list:_testForceFireEvents()
299
292
 
@@ -478,7 +471,6 @@ describe("ObservableSortedList", function()
478
471
  local list = maid:Add(ObservableSortedList.new())
479
472
  list:Add("a", 1)
480
473
 
481
- -- Use Get to verify synchronously, since GetCount requires deferred events
482
474
  expect(list:Get(1)).toEqual("a")
483
475
 
484
476
  maid:Destroy()
@@ -603,7 +595,6 @@ describe("ObservableSortedList", function()
603
595
  expect(#seenItems).toEqual(1)
604
596
  expect(seenItems[1]).toEqual("b")
605
597
 
606
- -- Remove A. B moves to index 1, index 2 is now empty
607
598
  removeA()
608
599
  list:_testForceFireEvents()
609
600
 
@@ -632,17 +623,14 @@ describe("ObservableSortedList", function()
632
623
  fireCount += 1
633
624
  end)
634
625
 
635
- -- Initial fire on subscribe
636
626
  expect(fireCount).toEqual(1)
637
627
 
638
- -- Change all three sort values before deferring
639
628
  sortValueA.Value = 30
640
629
  sortValueB.Value = 20
641
630
  sortValueC.Value = 10
642
631
 
643
632
  list:_testForceFireEvents()
644
633
 
645
- -- Should have batched into a single additional fire
646
634
  expect(fireCount).toEqual(2)
647
635
  expect(list:GetList()).toEqual({ "c", "b", "a" })
648
636
 
@@ -665,7 +653,6 @@ describe("ObservableSortedList", function()
665
653
 
666
654
  expect(completed).toEqual(false)
667
655
 
668
- -- Remove A (the tracked node at index 1)
669
656
  removeA()
670
657
  list:_testForceFireEvents()
671
658
 
@@ -681,7 +668,6 @@ describe("ObservableSortedList", function()
681
668
 
682
669
  local seenBrios, sub = ObservableSortedListTestUtils.collectValues(list:ObserveItemsBrio())
683
670
 
684
- -- Should already have brios without deferWait
685
671
  expect(#seenBrios).toEqual(2)
686
672
 
687
673
  sub:Destroy()
@@ -726,7 +712,6 @@ describe("ObservableSortedList", function()
726
712
 
727
713
  expect(fireCount).toEqual(1)
728
714
 
729
- -- Set to same value - should be a no-op
730
715
  sortValue.Value = 2
731
716
  list:_testForceFireEvents()
732
717
 
@@ -749,17 +734,14 @@ describe("ObservableSortedList", function()
749
734
 
750
735
  expect(list:GetList()).toEqual({ "a", "b", "c" })
751
736
 
752
- -- Change multiple times before defer fires
753
737
  sortValue.Value = 10 -- a goes to end
754
738
  sortValue.Value = 0 -- a goes back to start
755
739
  sortValue.Value = 5 -- a ends up in the middle
756
740
 
757
- -- Tree should reflect the final state synchronously
758
741
  expect(list:GetList()).toEqual({ "b", "c", "a" })
759
742
 
760
743
  list:_testForceFireEvents()
761
744
 
762
- -- Should still be correct after events fire
763
745
  expect(list:GetList()).toEqual({ "b", "c", "a" })
764
746
 
765
747
  maid:Destroy()
@@ -781,15 +763,12 @@ describe("ObservableSortedList", function()
781
763
  end)
782
764
  expect(fireCount).toEqual(1)
783
765
 
784
- -- Oscillate: move to end, then back to start
785
766
  sortValue.Value = 10
786
767
  sortValue.Value = 1
787
768
 
788
769
  expect(list:GetList()).toEqual({ "a", "b", "c" })
789
770
  list:_testForceFireEvents()
790
771
 
791
- -- Events may or may not fire depending on span tracker,
792
- -- but the list should be correct
793
772
  expect(list:GetList()).toEqual({ "a", "b", "c" })
794
773
 
795
774
  sub:Destroy()
@@ -812,7 +791,6 @@ describe("ObservableSortedList", function()
812
791
 
813
792
  expect(list:GetList()).toEqual({ "a", "b", "c", "d" })
814
793
 
815
- -- Reverse all sort values simultaneously
816
794
  sortA.Value = 4
817
795
  sortB.Value = 3
818
796
  sortC.Value = 2
@@ -840,13 +818,10 @@ describe("ObservableSortedList", function()
840
818
 
841
819
  expect(list:GetList()).toEqual({ "a", "b", "c" })
842
820
 
843
- -- Move all to the same sort value
844
821
  sortA.Value = 5
845
822
  sortB.Value = 5
846
823
  sortC.Value = 5
847
824
 
848
- -- All have equal sort values - order may vary based on tree rotation,
849
- -- but should be deterministic
850
825
  local result = list:GetList()
851
826
  expect(#result).toEqual(3)
852
827
  expect(list:Contains("a")).toEqual(true)
@@ -869,11 +844,9 @@ describe("ObservableSortedList", function()
869
844
  local seenIndices, sub = ObservableSortedListTestUtils.collectValues(list:ObserveIndex(2))
870
845
  expect(seenIndices[1]).toEqual(2)
871
846
 
872
- -- Change from 5 to 7 - still between 1 and 10, no movement needed
873
847
  sortB.Value = 7
874
848
  list:_testForceFireEvents()
875
849
 
876
- -- Index should not have changed
877
850
  expect(#seenIndices).toEqual(1)
878
851
  expect(list:GetList()).toEqual({ "a", "b", "c" })
879
852
 
@@ -892,11 +865,9 @@ describe("ObservableSortedList", function()
892
865
  list:Add("b", sortValue:Observe())
893
866
  list:Add("c", 3)
894
867
 
895
- -- B should not be in the tree since its sort value is nil
896
868
  expect(list:GetList()).toEqual({ "a", "c" })
897
869
  expect(list:Contains("b")).toEqual(false)
898
870
 
899
- -- Now emit a real value - B should appear
900
871
  sortValue.Value = 2
901
872
  expect(list:GetList()).toEqual({ "a", "b", "c" })
902
873
  expect(list:Contains("b")).toEqual(true)
@@ -915,12 +886,10 @@ describe("ObservableSortedList", function()
915
886
 
916
887
  expect(list:GetList()).toEqual({ "a", "b", "c" })
917
888
 
918
- -- Emit nil - B should be removed from tree
919
889
  sortValue.Value = nil :: any
920
890
  expect(list:GetList()).toEqual({ "a", "c" })
921
891
  expect(list:Contains("b")).toEqual(false)
922
892
 
923
- -- Re-emit a value - B should come back
924
893
  sortValue.Value = 2
925
894
  expect(list:GetList()).toEqual({ "a", "b", "c" })
926
895
 
@@ -941,7 +910,6 @@ describe("ObservableSortedList", function()
941
910
  expect(seenBrios[1]:IsDead()).toEqual(false)
942
911
  expect(seenBrios[2]:IsDead()).toEqual(false)
943
912
 
944
- -- Emit nil - B's brio should die
945
913
  sortValue.Value = nil :: any
946
914
  list:_testForceFireEvents()
947
915
 
@@ -960,13 +928,11 @@ describe("ObservableSortedList", function()
960
928
  list:Add("b", Rx.EMPTY :: any)
961
929
  list:Add("c", 3)
962
930
 
963
- -- B should never enter the tree
964
931
  expect(list:GetList()).toEqual({ "a", "c" })
965
932
  expect(list:Contains("b")).toEqual(false)
966
933
 
967
934
  list:_testForceFireEvents()
968
935
 
969
- -- Still not there after events fire
970
936
  expect(list:GetList()).toEqual({ "a", "c" })
971
937
 
972
938
  maid:Destroy()
@@ -983,15 +949,12 @@ describe("ObservableSortedList", function()
983
949
 
984
950
  expect(list:GetList()).toEqual({ "a", "c" })
985
951
 
986
- -- First appearance
987
952
  sortValue.Value = 2
988
953
  expect(list:GetList()).toEqual({ "a", "b", "c" })
989
954
 
990
- -- Disappear
991
955
  sortValue.Value = nil :: any
992
956
  expect(list:GetList()).toEqual({ "a", "c" })
993
957
 
994
- -- Reappear at different position
995
958
  sortValue.Value = 4
996
959
  expect(list:GetList()).toEqual({ "a", "c", "b" })
997
960
 
@@ -1010,7 +973,6 @@ describe("ObservableSortedList", function()
1010
973
 
1011
974
  expect(list:GetList()).toEqual({ "a", "b", "c" })
1012
975
 
1013
- -- Go nil then come back in the same frame
1014
976
  sortValue.Value = nil :: any
1015
977
  sortValue.Value = 2
1016
978
 
@@ -1039,13 +1001,11 @@ describe("ObservableSortedList", function()
1039
1001
  removeCount += 1
1040
1002
  end))
1041
1003
 
1042
- -- Add and remove before events fire
1043
1004
  local remove = list:Add("b", 2)
1044
1005
  remove()
1045
1006
 
1046
1007
  list:_testForceFireEvents()
1047
1008
 
1048
- -- Should have cancelled out - no add or remove events
1049
1009
  expect(addCount).toEqual(0)
1050
1010
  expect(removeCount).toEqual(0)
1051
1011
  expect(list:GetList()).toEqual({ "a" })
@@ -1064,12 +1024,10 @@ describe("ObservableSortedList", function()
1064
1024
 
1065
1025
  expect(list:GetList()).toEqual({ "a", "b", "c" })
1066
1026
 
1067
- -- Remove while sort value is "live" - should cleanly unsubscribe
1068
1027
  remove()
1069
1028
 
1070
1029
  expect(list:GetList()).toEqual({ "a", "c" })
1071
1030
 
1072
- -- Sort value change after removal should have no effect
1073
1031
  sortValue.Value = 10
1074
1032
  expect(list:GetList()).toEqual({ "a", "c" })
1075
1033
 
@@ -1084,12 +1042,10 @@ describe("ObservableSortedList", function()
1084
1042
 
1085
1043
  list:Add("a", 1)
1086
1044
  list:Add("b", 2)
1087
- -- Don't wait - add more before events fire
1088
1045
  list:Add("c", 3)
1089
1046
 
1090
1047
  list:_testForceFireEvents()
1091
1048
 
1092
- -- All three should batch into one count update
1093
1049
  expect(counts[1]).toEqual(0)
1094
1050
  expect(counts[2]).toEqual(3)
1095
1051
  expect(#counts).toEqual(2)
@@ -1114,7 +1070,6 @@ describe("ObservableSortedList", function()
1114
1070
 
1115
1071
  list:_testForceFireEvents()
1116
1072
 
1117
- -- Net result: b, c, d (3 items)
1118
1073
  expect(list:GetList()).toEqual({ "b", "c", "d" })
1119
1074
  expect(counts[2]).toEqual(3)
1120
1075
 
@@ -1132,7 +1087,6 @@ describe("ObservableSortedList", function()
1132
1087
 
1133
1088
  expect(list:GetList()).toEqual({ "a", "b" })
1134
1089
 
1135
- -- Set nil (removes from tree) then also call cleanup
1136
1090
  sortValue.Value = nil :: any
1137
1091
  remove()
1138
1092
 
@@ -1204,7 +1158,6 @@ describe("ObservableSortedList", function()
1204
1158
  local indexChanges, sub = ObservableSortedListTestUtils.collectValues(list:ObserveIndex(2))
1205
1159
  expect(#indexChanges).toEqual(1)
1206
1160
 
1207
- -- Change value but stay in same position (between 1 and 10)
1208
1161
  sortB.Value = 8
1209
1162
  list:_testForceFireEvents()
1210
1163
 
@@ -1228,7 +1181,6 @@ describe("ObservableSortedList", function()
1228
1181
  local seenAtIndex1, sub = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(1))
1229
1182
  expect(seenAtIndex1[1]).toEqual("a")
1230
1183
 
1231
- -- Move A to the end
1232
1184
  sortA.Value = 10
1233
1185
  list:_testForceFireEvents()
1234
1186
 
@@ -1254,17 +1206,14 @@ describe("ObservableSortedList", function()
1254
1206
 
1255
1207
  expect(#seenBrios).toEqual(2)
1256
1208
 
1257
- -- Remove via nil
1258
1209
  sortValue.Value = nil :: any
1259
1210
  list:_testForceFireEvents()
1260
1211
 
1261
1212
  expect(seenBrios[2]:IsDead()).toEqual(true)
1262
1213
 
1263
- -- Re-add via value
1264
1214
  sortValue.Value = 2
1265
1215
  list:_testForceFireEvents()
1266
1216
 
1267
- -- Should have gotten a new brio
1268
1217
  expect(#seenBrios).toEqual(3)
1269
1218
  expect(seenBrios[3]:IsDead()).toEqual(false)
1270
1219
 
@@ -1281,7 +1230,6 @@ describe("ObservableSortedList", function()
1281
1230
  maid:Destroy()
1282
1231
  end))
1283
1232
 
1284
- -- Should not error when the list is destroyed during event firing
1285
1233
  list:Add("a", 1)
1286
1234
  list:_testForceFireEvents()
1287
1235
 
@@ -1316,7 +1264,6 @@ describe("ObservableSortedList", function()
1316
1264
 
1317
1265
  expect(list:GetList()).toEqual({ "a", "b", "c", "d", "e" })
1318
1266
 
1319
- -- Move C to the end - a, b, d, e should keep their relative order
1320
1267
  sortC.Value = 10
1321
1268
 
1322
1269
  expect(list:GetList()).toEqual({ "a", "b", "d", "e", "c" })
@@ -1340,7 +1287,6 @@ describe("ObservableSortedList", function()
1340
1287
 
1341
1288
  expect(list:GetList()).toEqual({ "a", "b", "c", "d", "e" })
1342
1289
 
1343
- -- Swap B and D's positions
1344
1290
  sortB.Value = 4
1345
1291
  sortD.Value = 2
1346
1292
 
@@ -1363,15 +1309,12 @@ describe("ObservableSortedList", function()
1363
1309
  list:Add("d", 4)
1364
1310
  local removeE = list:Add("e", 5)
1365
1311
 
1366
- -- Remove last
1367
1312
  removeE()
1368
1313
  expect(list:GetList()).toEqual({ "a", "b", "c", "d" })
1369
1314
 
1370
- -- Remove first
1371
1315
  removeA()
1372
1316
  expect(list:GetList()).toEqual({ "b", "c", "d" })
1373
1317
 
1374
- -- Remove middle
1375
1318
  removeC()
1376
1319
  expect(list:GetList()).toEqual({ "b", "d" })
1377
1320
 
@@ -1398,7 +1341,6 @@ describe("ObservableSortedList", function()
1398
1341
  expect(#fires1).toEqual(1)
1399
1342
  expect(#fires5).toEqual(1)
1400
1343
 
1401
- -- Move B past C and D: changed span [2, 4], indices 1, 5 unaffected
1402
1344
  sortB.Value = 4.5
1403
1345
  list:_testForceFireEvents()
1404
1346
 
@@ -1433,7 +1375,6 @@ describe("ObservableSortedList", function()
1433
1375
  expect(#firesA).toEqual(1)
1434
1376
  expect(#firesE).toEqual(1)
1435
1377
 
1436
- -- Move B past C and D: only span [2, 4] changed
1437
1378
  sortB.Value = 4.5
1438
1379
  list:_testForceFireEvents()
1439
1380
 
@@ -1530,7 +1471,6 @@ describe("ObservableSortedList", function()
1530
1471
  expect(#fires1).toEqual(1)
1531
1472
  expect(#fires2).toEqual(1)
1532
1473
 
1533
- -- Remove c from middle (index 3). Indices 1, 2 unaffected.
1534
1474
  removeC()
1535
1475
  list:_testForceFireEvents()
1536
1476
 
@@ -1554,7 +1494,6 @@ describe("ObservableSortedList", function()
1554
1494
  list:Add("e", 5)
1555
1495
  list:_testForceFireEvents()
1556
1496
 
1557
- -- Index 4 (currently "d") will shift to index 3 after c is removed
1558
1497
  local fires4, sub4 = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(4))
1559
1498
  expect(#fires4).toEqual(1)
1560
1499
  expect(fires4[1]).toEqual("d")
@@ -1563,7 +1502,6 @@ describe("ObservableSortedList", function()
1563
1502
  list:_testForceFireEvents()
1564
1503
 
1565
1504
  expect(list:GetList()).toEqual({ "a", "b", "d", "e" })
1566
- -- Index 4 should have fired — now contains "e" instead of "d"
1567
1505
  expect(#fires4).toEqual(2)
1568
1506
  expect(fires4[2]).toEqual("e")
1569
1507
 
@@ -1595,7 +1533,6 @@ describe("ObservableSortedList", function()
1595
1533
  removeC()
1596
1534
  list:_testForceFireEvents()
1597
1535
 
1598
- -- D shifted 4->3, E shifted 5->4
1599
1536
  expect(#firesD).toEqual(2)
1600
1537
  expect(firesD[2]).toEqual(3)
1601
1538
  expect(#firesE).toEqual(2)
@@ -1625,14 +1562,12 @@ describe("ObservableSortedList", function()
1625
1562
  expect(#fires4).toEqual(1)
1626
1563
  expect(#fires5).toEqual(1)
1627
1564
 
1628
- -- Remove b at index 2, add f at index 2. Count unchanged, only index 2 content changed.
1629
1565
  removeB()
1630
1566
  list:Add("f", 2)
1631
1567
  list:_testForceFireEvents()
1632
1568
 
1633
1569
  expect(list:GetList()).toEqual({ "a", "f", "c", "d", "e" })
1634
1570
 
1635
- -- Indices 3-5 unchanged — should not fire
1636
1571
  expect(#fires3).toEqual(1)
1637
1572
  expect(#fires4).toEqual(1)
1638
1573
  expect(#fires5).toEqual(1)
@@ -1667,7 +1602,6 @@ describe("ObservableSortedList", function()
1667
1602
  expect(#firesD).toEqual(1)
1668
1603
  expect(#firesE).toEqual(1)
1669
1604
 
1670
- -- Replace b with f at same index. C, D, E positions unchanged.
1671
1605
  removeB()
1672
1606
  list:Add("f", 2)
1673
1607
  list:_testForceFireEvents()
@@ -1694,20 +1628,16 @@ describe("ObservableSortedList", function()
1694
1628
  list:Add("e", 5)
1695
1629
  list:_testForceFireEvents()
1696
1630
 
1697
- -- Observe index 3 (currently "c")
1698
1631
  local fires3, sub3 = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(3))
1699
1632
  expect(#fires3).toEqual(1)
1700
1633
  expect(fires3[1]).toEqual("c")
1701
1634
 
1702
- -- Remove b (index 2), add f at index 4. Count unchanged, but index 3 shifts: c was at 3, now d is at 3
1703
1635
  removeB()
1704
1636
  list:Add("f", 4.5)
1705
1637
  list:_testForceFireEvents()
1706
1638
 
1707
- -- List is now: a, c, d, f, e
1708
1639
  expect(list:GetList()).toEqual({ "a", "c", "d", "f", "e" })
1709
1640
 
1710
- -- Index 3 changed from "c" to "d" — must fire
1711
1641
  expect(#fires3).toEqual(2)
1712
1642
  expect(fires3[2]).toEqual("d")
1713
1643
 
@@ -1736,15 +1666,12 @@ describe("ObservableSortedList", function()
1736
1666
  expect(firesC[1]).toEqual(3)
1737
1667
  expect(firesD[1]).toEqual(4)
1738
1668
 
1739
- -- Remove b (index 2), add f at index 4.5. Count unchanged, but c shifts 3->2, d shifts 4->3
1740
1669
  removeB()
1741
1670
  list:Add("f", 4.5)
1742
1671
  list:_testForceFireEvents()
1743
1672
 
1744
- -- List is now: a, c, d, f, e
1745
1673
  expect(list:GetList()).toEqual({ "a", "c", "d", "f", "e" })
1746
1674
 
1747
- -- C shifted 3->2, D shifted 4->3
1748
1675
  expect(#firesC).toEqual(2)
1749
1676
  expect(firesC[2]).toEqual(2)
1750
1677
  expect(#firesD).toEqual(2)
@@ -1769,25 +1696,20 @@ describe("ObservableSortedList", function()
1769
1696
  local fires3, sub3 = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(3))
1770
1697
  expect(fires3[1]).toEqual("c")
1771
1698
 
1772
- -- Remove b (index 2) and d (index 4), add f (1.5) and g (4.5). Count unchanged.
1773
1699
  removeB()
1774
1700
  removeD()
1775
1701
  list:Add("f", 1.5)
1776
1702
  list:Add("g", 4.5)
1777
1703
  list:_testForceFireEvents()
1778
1704
 
1779
- -- List: a, f, c, g, e
1780
1705
  expect(list:GetList()).toEqual({ "a", "f", "c", "g", "e" })
1781
1706
 
1782
- -- Index 3 still has "c" — should not fire extra
1783
1707
  expect(#fires3).toEqual(1)
1784
1708
 
1785
1709
  sub3:Destroy()
1786
1710
  maid:Destroy()
1787
1711
  end)
1788
1712
 
1789
- -- Should-fire tests: verify events ARE emitted when content/index actually changes
1790
-
1791
1713
  it("should fire ObserveAtIndex for ALL shifted indices when removing from beginning", function()
1792
1714
  local maid = Maid.new()
1793
1715
  local list = maid:Add(ObservableSortedList.new())
@@ -1809,7 +1731,6 @@ describe("ObservableSortedList", function()
1809
1731
  removeA()
1810
1732
  list:_testForceFireEvents()
1811
1733
 
1812
- -- Every index shifted: a removed, b->1, c->2, d->3
1813
1734
  expect(list:GetList()).toEqual({ "b", "c", "d" })
1814
1735
  expect(#fires1).toEqual(2)
1815
1736
  expect(fires1[2]).toEqual("b")
@@ -1847,7 +1768,6 @@ describe("ObservableSortedList", function()
1847
1768
  removeC()
1848
1769
  list:_testForceFireEvents()
1849
1770
 
1850
- -- List: a, d, e — indices 2-4 all changed
1851
1771
  expect(list:GetList()).toEqual({ "a", "d", "e" })
1852
1772
  expect(#fires2).toEqual(2)
1853
1773
  expect(fires2[2]).toEqual("d")
@@ -1882,7 +1802,6 @@ describe("ObservableSortedList", function()
1882
1802
  list:Add("a", 1)
1883
1803
  list:_testForceFireEvents()
1884
1804
 
1885
- -- List: a, b, c, d — everything shifted right
1886
1805
  expect(list:GetList()).toEqual({ "a", "b", "c", "d" })
1887
1806
  expect(#fires1).toEqual(2)
1888
1807
  expect(fires1[2]).toEqual("a")
@@ -1921,7 +1840,6 @@ describe("ObservableSortedList", function()
1921
1840
  expect(firesC[1]).toEqual(3)
1922
1841
  expect(firesD[1]).toEqual(4)
1923
1842
 
1924
- -- Move A from index 1 to index 4 — all others shift left
1925
1843
  sortA.Value = 10
1926
1844
  list:_testForceFireEvents()
1927
1845
 
@@ -1939,8 +1857,6 @@ describe("ObservableSortedList", function()
1939
1857
  maid:Destroy()
1940
1858
  end)
1941
1859
 
1942
- -- Should-NOT-fire tests: verify events are NOT emitted when nothing changed at the observed position
1943
-
1944
1860
  it("should not fire ObserveAtIndex when remove+add cancel out at the same index", function()
1945
1861
  local maid = Maid.new()
1946
1862
  local list = maid:Add(ObservableSortedList.new())
@@ -1959,7 +1875,6 @@ describe("ObservableSortedList", function()
1959
1875
  expect(fires3[1]).toEqual("c")
1960
1876
  expect(fires4[1]).toEqual("d")
1961
1877
 
1962
- -- Remove b (index 2), add f at same sort position. c, d stay put.
1963
1878
  removeB()
1964
1879
  list:Add("f", 2)
1965
1880
  list:_testForceFireEvents()
@@ -1995,7 +1910,6 @@ describe("ObservableSortedList", function()
1995
1910
  expect(fires2[1]).toEqual("b")
1996
1911
  expect(fires3[1]).toEqual("c")
1997
1912
 
1998
- -- Move D past E: only indices 4 and 5 change
1999
1913
  sortD.Value = 6
2000
1914
  list:_testForceFireEvents()
2001
1915
 
@@ -2033,14 +1947,12 @@ describe("ObservableSortedList", function()
2033
1947
  expect(firesA[1]).toEqual(1)
2034
1948
  expect(firesE[1]).toEqual(5)
2035
1949
 
2036
- -- Remove b and c (indices 2-3), add f(2) and g(3). Count stays at 5.
2037
1950
  removeB()
2038
1951
  removeC()
2039
1952
  list:Add("f", 2)
2040
1953
  list:Add("g", 3)
2041
1954
  list:_testForceFireEvents()
2042
1955
 
2043
- -- List: a, f, g, d, e — A at 1 and E at 5, unchanged
2044
1956
  expect(list:GetList()).toEqual({ "a", "f", "g", "d", "e" })
2045
1957
  expect(#firesA).toEqual(1)
2046
1958
  expect(#firesE).toEqual(1)
@@ -2072,15 +1984,12 @@ describe("ObservableSortedList", function()
2072
1984
  expect(fires3[1]).toEqual("c")
2073
1985
  expect(fires4[1]).toEqual("d")
2074
1986
 
2075
- -- Remove first and last: a(1) and e(5). Add f(0.5) at beginning and g(5.5) at end.
2076
- -- Net effect: f replaces a's slot, g replaces e's slot. Middle is untouched.
2077
1987
  removeA()
2078
1988
  removeE()
2079
1989
  list:Add("f", 0.5)
2080
1990
  list:Add("g", 5.5)
2081
1991
  list:_testForceFireEvents()
2082
1992
 
2083
- -- List: f, b, c, d, g — indices 2, 3, 4 unchanged
2084
1993
  expect(list:GetList()).toEqual({ "f", "b", "c", "d", "g" })
2085
1994
  expect(#fires2).toEqual(1)
2086
1995
  expect(#fires3).toEqual(1)
@@ -2114,13 +2023,10 @@ describe("ObservableSortedList", function()
2114
2023
  expect(fires3[1]).toEqual("c")
2115
2024
  expect(fires4[1]).toEqual("d")
2116
2025
 
2117
- -- Swap a and b (sort 1<->2), swap d and e (sort 4<->5). Index 3 unchanged.
2118
2026
  sortA.Value = 2.5
2119
2027
  sortE.Value = 3.5
2120
2028
  list:_testForceFireEvents()
2121
2029
 
2122
- -- List: b, a, e, c, d -- wait, let me reconsider
2123
- -- Actually a at 2.5, e at 3.5: b(2), a(2.5), c(3), e(3.5), d(4)
2124
2030
  expect(list:GetList()).toEqual({ "b", "a", "c", "e", "d" })
2125
2031
  expect(#fires3).toEqual(1)
2126
2032
 
@@ -2149,15 +2055,12 @@ describe("ObservableSortedList", function()
2149
2055
  local firesC, subC = ObservableSortedListTestUtils.collectValues(list:ObserveIndexByKey(nodeC))
2150
2056
  expect(firesC[1]).toEqual(3)
2151
2057
 
2152
- -- Remove a (before c), remove e (after c). Add f(0.5) before c, add g(5.5) after c.
2153
- -- C stays at index 3.
2154
2058
  removeA()
2155
2059
  removeE()
2156
2060
  list:Add("f", 0.5)
2157
2061
  list:Add("g", 5.5)
2158
2062
  list:_testForceFireEvents()
2159
2063
 
2160
- -- List: f, b, c, d, g — c is still at index 3
2161
2064
  expect(list:GetList()).toEqual({ "f", "b", "c", "d", "g" })
2162
2065
  expect(#firesC).toEqual(1)
2163
2066
 
@@ -2168,14 +2071,7 @@ describe("ObservableSortedList", function()
2168
2071
  end)
2169
2072
 
2170
2073
  describe("coordinate system correctness", function()
2171
- -- These tests reproduce scenarios where intermediate tree mutations cause
2172
- -- span indices to be recorded in different coordinate systems, potentially
2173
- -- leading to under-firing (missed notifications).
2174
-
2175
2074
  it("should fire ObserveAtIndex for tail index when two removes collapse recorded indices", function()
2176
- -- Removing d then e records removed index 4 twice (from different tree states),
2177
- -- losing the information that e was originally at index 5. The effective span
2178
- -- must still cover index 5.
2179
2075
  local maid = Maid.new()
2180
2076
  local list = maid:Add(ObservableSortedList.new())
2181
2077
 
@@ -2189,18 +2085,14 @@ describe("ObservableSortedList", function()
2189
2085
  local fires5, sub5 = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(5))
2190
2086
  expect(fires5[1]).toEqual("e")
2191
2087
 
2192
- -- Remove d (index 4 in 5-element tree), then e (index 4 in 4-element tree).
2193
- -- Add f(1.5) and g(2.5). Count stays at 5.
2194
2088
  removeD()
2195
2089
  removeE()
2196
2090
  list:Add("f", 1.5)
2197
2091
  list:Add("g", 2.5)
2198
2092
  list:_testForceFireEvents()
2199
2093
 
2200
- -- List: a, f, b, g, c — index 5 changed from "e" to "c"
2201
2094
  expect(list:GetList()).toEqual({ "a", "f", "b", "g", "c" })
2202
2095
 
2203
- -- Index 5 MUST fire — content changed from "e" to "c"
2204
2096
  expect(#fires5).toEqual(2)
2205
2097
  expect(fires5[2]).toEqual("c")
2206
2098
 
@@ -2209,8 +2101,6 @@ describe("ObservableSortedList", function()
2209
2101
  end)
2210
2102
 
2211
2103
  it("should fire ObserveIndexByKey for node shifted beyond recorded span", function()
2212
- -- When two removes from the end record the same intermediate index,
2213
- -- the span may not extend far enough to cover nodes that shifted.
2214
2104
  local maid = Maid.new()
2215
2105
  local list = maid:Add(ObservableSortedList.new())
2216
2106
 
@@ -2228,15 +2118,12 @@ describe("ObservableSortedList", function()
2228
2118
  local firesF, subF = ObservableSortedListTestUtils.collectValues(list:ObserveIndexByKey(nodeF))
2229
2119
  expect(firesF[1]).toEqual(6)
2230
2120
 
2231
- -- Remove d (index 4 in 6-element tree), then e (index 4 in 5-element tree).
2232
- -- Count goes from 6 to 4. F shifts from index 6 to index 4.
2233
2121
  removeD()
2234
2122
  removeE()
2235
2123
  list:_testForceFireEvents()
2236
2124
 
2237
2125
  expect(list:GetList()).toEqual({ "a", "b", "c", "f" })
2238
2126
 
2239
- -- F MUST be notified: its index changed from 6 to 4
2240
2127
  expect(#firesF).toEqual(2)
2241
2128
  expect(firesF[2]).toEqual(4)
2242
2129
 
@@ -2245,8 +2132,6 @@ describe("ObservableSortedList", function()
2245
2132
  end)
2246
2133
 
2247
2134
  it("should fire ObserveAtIndex for all affected indices when three consecutive items are removed", function()
2248
- -- Removing items 3, 4, 5 sequentially records indices 3, 3, 3 in intermediate
2249
- -- tree states, but indices 4 and 5 also changed content.
2250
2135
  local maid = Maid.new()
2251
2136
  local list = maid:Add(ObservableSortedList.new())
2252
2137
 
@@ -2265,20 +2150,16 @@ describe("ObservableSortedList", function()
2265
2150
  expect(fires4[1]).toEqual("d")
2266
2151
  expect(fires5[1]).toEqual("e")
2267
2152
 
2268
- -- Remove c, d, e. Each removal records index 3 in the shrinking tree.
2269
2153
  removeC()
2270
2154
  removeD()
2271
2155
  removeE()
2272
2156
  list:_testForceFireEvents()
2273
2157
 
2274
- -- List: a, b, f, g — indices 4 and 5 changed
2275
2158
  expect(list:GetList()).toEqual({ "a", "b", "f", "g" })
2276
2159
 
2277
- -- Index 4 changed from "d" to "g"
2278
2160
  expect(#fires4).toEqual(2)
2279
2161
  expect(fires4[2]).toEqual("g")
2280
2162
 
2281
- -- Index 5 went out of bounds — must fire nil
2282
2163
  expect(#fires5).toEqual(2)
2283
2164
  expect(fires5[2]).toEqual(NIL_VALUE)
2284
2165
 
@@ -2288,8 +2169,6 @@ describe("ObservableSortedList", function()
2288
2169
  end)
2289
2170
 
2290
2171
  it("should fire ObserveAtIndex when adding at beginning shifts tail beyond recorded add index", function()
2291
- -- Adding three items at sort values 0.1, 0.2, 0.3 records add indices 1, 2, 3
2292
- -- in intermediate trees. But the original item at index 3 shifts to index 6.
2293
2172
  local maid = Maid.new()
2294
2173
  local list = maid:Add(ObservableSortedList.new())
2295
2174
 
@@ -2301,16 +2180,13 @@ describe("ObservableSortedList", function()
2301
2180
  local fires3, sub3 = ObservableSortedListTestUtils.collectValues(list:ObserveAtIndex(3))
2302
2181
  expect(fires3[1]).toEqual("c")
2303
2182
 
2304
- -- Add three items before everything. Each records a low add index.
2305
2183
  list:Add("x", 0.1)
2306
2184
  list:Add("y", 0.2)
2307
2185
  list:Add("z", 0.3)
2308
2186
  list:_testForceFireEvents()
2309
2187
 
2310
- -- List: x, y, z, a, b, c — index 3 changed from "c" to "z"
2311
2188
  expect(list:GetList()).toEqual({ "x", "y", "z", "a", "b", "c" })
2312
2189
 
2313
- -- Index 3 MUST fire — content changed from "c" to "z"
2314
2190
  expect(#fires3).toEqual(2)
2315
2191
  expect(fires3[2]).toEqual("z")
2316
2192
 
@@ -2319,8 +2195,6 @@ describe("ObservableSortedList", function()
2319
2195
  end)
2320
2196
 
2321
2197
  it("should fire ObserveIndexByKey when multiple removes from same position shift a distant node", function()
2322
- -- Removing 3 items at position 1 records index 1 three times.
2323
- -- A node originally at index 5 should shift to index 2.
2324
2198
  local maid = Maid.new()
2325
2199
  local list = maid:Add(ObservableSortedList.new())
2326
2200
 
@@ -2337,7 +2211,6 @@ describe("ObservableSortedList", function()
2337
2211
  local firesE, subE = ObservableSortedListTestUtils.collectValues(list:ObserveIndexByKey(nodeE))
2338
2212
  expect(firesE[1]).toEqual(5)
2339
2213
 
2340
- -- Remove a, b, c. Each records removal at index 1 in the shrinking tree.
2341
2214
  removeA()
2342
2215
  removeB()
2343
2216
  removeC()
@@ -2345,7 +2218,6 @@ describe("ObservableSortedList", function()
2345
2218
 
2346
2219
  expect(list:GetList()).toEqual({ "d", "e" })
2347
2220
 
2348
- -- E shifted from 5 to 2 — must be notified
2349
2221
  expect(#firesE).toEqual(2)
2350
2222
  expect(firesE[2]).toEqual(2)
2351
2223
 
@@ -112,7 +112,6 @@ describe("SortedNode", function()
112
112
  second.value = 1
113
113
  root = root:InsertNode(second)
114
114
 
115
- -- Both should be present
116
115
  expect(root.descendantCount).toEqual(2)
117
116
 
118
117
  local result = {}
@@ -276,7 +275,6 @@ describe("SortedNode", function()
276
275
  describe("NeedsToMove", function()
277
276
  it("should return false when value stays valid relative to neighbors", function()
278
277
  local root, nodes = buildTree({ 1, 3, 5 })
279
- -- nodes[2] = "c" (value=3), between 1 and 5
280
278
 
281
279
  expect(nodes[2]:NeedsToMove(root, 2)).toEqual(false)
282
280
  expect(nodes[2]:NeedsToMove(root, 4)).toEqual(false)
@@ -285,7 +283,6 @@ describe("SortedNode", function()
285
283
  it("should return true when value violates parent constraint", function()
286
284
  local root, nodes = buildTree({ 1, 3, 5 })
287
285
 
288
- -- Try to move "a" (value=1, leftmost) past its parent
289
286
  expect(nodes[1]:NeedsToMove(root, 100)).toEqual(true)
290
287
  end)
291
288
 
@@ -371,7 +368,6 @@ describe("SortedNode", function()
371
368
  table.insert(result, node.data)
372
369
  end
373
370
 
374
- -- -2 means up to second-to-last (index 4)
375
371
  expect(result).toEqual({ "a", "b", "c", "d" })
376
372
  end)
377
373
 
@@ -410,7 +406,6 @@ describe("SortedNode", function()
410
406
  it("should maintain sorted order through a sequence of inserts and removes", function()
411
407
  local root, nodes = buildTree({ 5, 2, 8, 1, 3, 7, 9 })
412
408
 
413
- -- Remove some internal nodes
414
409
  root = root:RemoveNode(nodes[3]) -- value=8
415
410
  root = root:RemoveNode(nodes[4]) -- value=1
416
411
 
@@ -423,25 +418,18 @@ describe("SortedNode", function()
423
418
  end)
424
419
 
425
420
  it("should maintain sorted order when swapping two nodes' values via remove-reinsert", function()
426
- -- Simulates what ObservableSortedList does when two sort values swap:
427
- -- Tree: a(1), b(2), c(3), d(4), e(5)
428
- -- Swap B(2→4) and D(4→2)
429
- -- Expected result: a(1), d(2), c(3), b(4), e(5)
430
421
  local root, nodes = buildTree({ 1, 2, 3, 4, 5 })
431
422
  local nodeB = nodes[2] -- data="b", value=2
432
423
  local nodeD = nodes[4] -- data="d", value=4
433
424
 
434
- -- Step 1: Move B from 2 to 4 (remove, change value, reinsert)
435
425
  root = root:RemoveNode(nodeB)
436
426
  nodeB.value = 4
437
427
  root = root:InsertNode(nodeB)
438
428
 
439
- -- Step 2: Move D from 4 to 2 (remove, change value, reinsert)
440
429
  root = root:RemoveNode(nodeD)
441
430
  nodeD.value = 2
442
431
  root = root:InsertNode(nodeD)
443
432
 
444
- -- Should be sorted: a(1), d(2), c(3), b(4), e(5)
445
433
  local result = {}
446
434
  for _, data in root:IterateData() do
447
435
  table.insert(result, data)
@@ -451,13 +439,10 @@ describe("SortedNode", function()
451
439
  end)
452
440
 
453
441
  it("should maintain sorted order when swapping via NeedsToMove gated remove-reinsert", function()
454
- -- Same as above but uses NeedsToMove to gate whether to remove-reinsert,
455
- -- matching the exact logic of ObservableSortedList._assignSortValue
456
442
  local root, nodes = buildTree({ 1, 2, 3, 4, 5 })
457
443
  local nodeB = nodes[2] -- data="b", value=2
458
444
  local nodeD = nodes[4] -- data="d", value=4
459
445
 
460
- -- Step 1: Move B from 2 to 4
461
446
  if nodeB:NeedsToMove(root, 4) then
462
447
  root = root:RemoveNode(nodeB)
463
448
  nodeB.value = 4
@@ -466,7 +451,6 @@ describe("SortedNode", function()
466
451
  nodeB.value = 4
467
452
  end
468
453
 
469
- -- Step 2: Move D from 4 to 2
470
454
  if nodeD:NeedsToMove(root, 2) then
471
455
  root = root:RemoveNode(nodeD)
472
456
  nodeD.value = 2
@@ -475,7 +459,6 @@ describe("SortedNode", function()
475
459
  nodeD.value = 2
476
460
  end
477
461
 
478
- -- Should be sorted: a(1), d(2), c(3), b(4), e(5)
479
462
  local result = {}
480
463
  for _, data in root:IterateData() do
481
464
  table.insert(result, data)
@@ -227,7 +227,6 @@ describe("UnifiedChangedSpanTracker", function()
227
227
 
228
228
  local result = tracker:ComputeEffectiveSpans(5, 5)
229
229
 
230
- -- Index 3 stays as item c, so it's not dirty
231
230
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 1)).toEqual(false)
232
231
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 2)).toEqual(true)
233
232
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 3)).toEqual(false)
@@ -296,7 +295,6 @@ describe("UnifiedChangedSpanTracker", function()
296
295
  local result1 = tracker:ComputeEffectiveSpans(3, 4)
297
296
  expect(#result1 > 0).toEqual(true)
298
297
 
299
- -- Second call should return nothing since state was cleared
300
298
  local result2 = tracker:ComputeEffectiveSpans(4, 4)
301
299
  expect(#result2).toEqual(0)
302
300
  end)
@@ -306,10 +304,6 @@ describe("UnifiedChangedSpanTracker", function()
306
304
  it(
307
305
  "should cover index 5 when two end-removes collapse to same recorded index and count is unchanged",
308
306
  function()
309
- -- List [a,b,c,d,e] (5 items)
310
- -- Remove d at index 4 → [a,b,c,e], remove e at index 4 → [a,b,c]
311
- -- Add f at index 2 → [a,f,b,c], add g at index 4 → [a,f,b,g,c]
312
- -- Count stays 5. Both removes record index 4.
313
307
  local tracker = UnifiedChangedSpanTracker.new()
314
308
  tracker:LogRemove(4)
315
309
  tracker:LogRemove(4)
@@ -318,7 +312,6 @@ describe("UnifiedChangedSpanTracker", function()
318
312
 
319
313
  local result = tracker:ComputeEffectiveSpans(5, 5)
320
314
 
321
- -- Index 5 changed content (was "e", now "c"). Must be covered.
322
315
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 1)).toEqual(false)
323
316
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 2)).toEqual(true)
324
317
  expect(UnifiedChangedSpanTracker.isIndexInSpan(result, 3)).toEqual(true)
@@ -328,10 +321,6 @@ describe("UnifiedChangedSpanTracker", function()
328
321
  )
329
322
 
330
323
  it("should only cover edges when removing first and last and replacing at edges", function()
331
- -- List [a,b,c,d,e] (5 items)
332
- -- Remove a at 1 → [b,c,d,e], remove e at 4 → [b,c,d]
333
- -- Add f at 1 → [f,b,c,d], add g at 5 → [f,b,c,d,g]
334
- -- Count stays 5. Middle indices 2,3,4 are unchanged.
335
324
  local tracker = UnifiedChangedSpanTracker.new()
336
325
  tracker:LogRemove(1)
337
326
  tracker:LogRemove(4)
@@ -348,7 +337,6 @@ describe("UnifiedChangedSpanTracker", function()
348
337
  end)
349
338
 
350
339
  it("should produce two separate spans for non-overlapping moves when count is unchanged", function()
351
- -- Item at 2 moves to 3, item at 8 moves to 9. Count stays 10.
352
340
  local tracker = UnifiedChangedSpanTracker.new()
353
341
  tracker:LogMove(2, 3)
354
342
  tracker:LogMove(8, 9)
@@ -366,10 +354,6 @@ describe("UnifiedChangedSpanTracker", function()
366
354
  end)
367
355
 
368
356
  it("should not dirty index 3 when removing two and adding two leaves c at index 3", function()
369
- -- List [a,b,c,d,e] (5 items)
370
- -- Remove b at index 2 → [a,c,d,e], remove d at index 3 → [a,c,e]
371
- -- Add f at index 2 (sort 1.5) → [a,f,c,e], add g at index 4 (sort 4.5) → [a,f,c,g,e]
372
- -- Index 3 still has c.
373
357
  local tracker = UnifiedChangedSpanTracker.new()
374
358
  tracker:LogRemove(2)
375
359
  tracker:LogRemove(3)
@@ -386,10 +370,6 @@ describe("UnifiedChangedSpanTracker", function()
386
370
  end)
387
371
 
388
372
  it("should not dirty middle indices when removing first and last and adding at edges", function()
389
- -- List [a,b,c,d,e] (5 items)
390
- -- Remove a at index 1 → [b,c,d,e], remove e at index 4 → [b,c,d]
391
- -- Add f at index 1 (sort 0.5) → [f,b,c,d], add g at index 5 (sort 5.5) → [f,b,c,d,g]
392
- -- Indices 2,3,4 unchanged.
393
373
  local tracker = UnifiedChangedSpanTracker.new()
394
374
  tracker:LogRemove(1)
395
375
  tracker:LogRemove(4)
@@ -406,10 +386,6 @@ describe("UnifiedChangedSpanTracker", function()
406
386
  end)
407
387
 
408
388
  it("should not dirty index 3 when two opposite-end moves leave middle unchanged", function()
409
- -- List [a,b,c,d,e] (5 items)
410
- -- a moves from index 1 → index 2 (sort 1 → 2.5): remove at 1, insert at 2
411
- -- e moves from index 5 → index 4 (sort 5 → 3.5): remove at 5, insert at 4
412
- -- List becomes [b,a,c,e,d]. Index 3 still has c.
413
389
  local tracker = UnifiedChangedSpanTracker.new()
414
390
  tracker:LogMove(1, 2)
415
391
  tracker:LogMove(5, 4)
@@ -424,10 +400,6 @@ describe("UnifiedChangedSpanTracker", function()
424
400
  end)
425
401
 
426
402
  it("should dirty index 5 when two removes from end collapse and adds shift content", function()
427
- -- List [a,b,c,d,e] (5 items)
428
- -- Remove d at index 4 → [a,b,c,e], remove e at index 4 → [a,b,c]
429
- -- Add f at index 2 (sort 1.5) → [a,f,b,c], add g at index 3 (sort 2.5) → [a,f,g,b,c]
430
- -- Index 5 changed from "e" to "c".
431
403
  local tracker = UnifiedChangedSpanTracker.new()
432
404
  tracker:LogRemove(4)
433
405
  tracker:LogRemove(4)