@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 +10 -0
- package/package.json +12 -12
- package/src/Shared/ObservableCountingMap.lua +11 -4
- package/src/Shared/SortedList/ObservableSortedList.spec.lua +0 -128
- package/src/Shared/SortedList/SortedNode.spec.lua +0 -17
- package/src/Shared/SortedList/UnifiedChangedSpanTracker.spec.lua +0 -28
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.
|
|
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.
|
|
33
|
+
"@quenty/baseobject": "10.15.0",
|
|
34
34
|
"@quenty/binarysearch": "2.4.0",
|
|
35
|
-
"@quenty/brio": "14.
|
|
35
|
+
"@quenty/brio": "14.33.0",
|
|
36
36
|
"@quenty/ducktype": "5.11.0",
|
|
37
|
-
"@quenty/instanceutils": "13.
|
|
37
|
+
"@quenty/instanceutils": "13.33.0",
|
|
38
38
|
"@quenty/loader": "10.11.0",
|
|
39
|
-
"@quenty/maid": "3.
|
|
40
|
-
"@quenty/nevermore-test-runner": "1.
|
|
41
|
-
"@quenty/promise": "10.
|
|
42
|
-
"@quenty/rx": "13.
|
|
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.
|
|
44
|
+
"@quenty/steputils": "3.7.0",
|
|
45
45
|
"@quenty/symbol": "3.5.2",
|
|
46
46
|
"@quenty/table": "3.9.2",
|
|
47
|
-
"@quenty/valueobject": "13.
|
|
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.
|
|
51
|
+
"@quenty/blend": "12.40.1"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
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
|
-
|
|
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
|
-
|
|
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)
|