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