@quenty/binder 14.44.0 → 14.46.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 +10 -0
- package/package.json +6 -6
- package/src/Shared/Binder.Loading.spec.lua +1 -1
- package/src/Shared/Binder.ObserveBrio.spec.lua +7 -7
- package/src/Shared/Binder.spec.lua +23 -23
- package/src/Shared/BinderGroup.spec.lua +25 -13
- package/src/Shared/BinderTestUtils.lua +24 -12
- package/src/Shared/BinderUtils.spec.lua +36 -24
- package/src/Shared/Collection/BoundChildCollection.lua +26 -21
- package/src/Shared/Collection/BoundChildCollection.spec.lua +122 -20
- package/src/Shared/Promise/promiseBoundClass.spec.lua +26 -14
- package/src/Shared/Trackers/BoundAncestorTracker.lua +0 -83
- package/src/Shared/Trackers/BoundAncestorTracker.spec.lua +0 -167
- package/src/Shared/Trackers/BoundParentTracker.lua +0 -64
- package/src/Shared/Trackers/BoundParentTracker.spec.lua +0 -159
|
@@ -21,6 +21,7 @@ export type BoundChildCollection<T> =
|
|
|
21
21
|
_binder: Binder.Binder<T>,
|
|
22
22
|
_parent: Instance,
|
|
23
23
|
_classes: Set.Set<T>,
|
|
24
|
+
_childToClass: { [Instance]: T },
|
|
24
25
|
ClassAdded: Signal.Signal<T>,
|
|
25
26
|
ClassRemoved: Signal.Signal<T>,
|
|
26
27
|
_size: number,
|
|
@@ -56,15 +57,9 @@ function BoundChildCollection.new<T>(binder: Binder.Binder<T>, parent: Instance)
|
|
|
56
57
|
self.ClassRemoved = self._maid:Add(Signal.new() :: any) -- :Fire(class)
|
|
57
58
|
|
|
58
59
|
self._classes = {} -- [class] = true
|
|
60
|
+
self._childToClass = {}
|
|
59
61
|
self._size = 0
|
|
60
62
|
|
|
61
|
-
self._maid:GiveTask(self._binder:GetClassAddedSignal():Connect(function(...)
|
|
62
|
-
self:_handleNewClassBound(...)
|
|
63
|
-
end))
|
|
64
|
-
self._maid:GiveTask(self._binder:GetClassRemovingSignal():Connect(function(class)
|
|
65
|
-
self:_removeClass(class)
|
|
66
|
-
end))
|
|
67
|
-
|
|
68
63
|
self:_startTracking()
|
|
69
64
|
|
|
70
65
|
return self
|
|
@@ -128,29 +123,39 @@ function BoundChildCollection._startTracking<T>(self: BoundChildCollection<T>)
|
|
|
128
123
|
end
|
|
129
124
|
|
|
130
125
|
function BoundChildCollection._addChild<T>(self: BoundChildCollection<T>, inst: Instance, doNotFire: boolean?): ()
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
end
|
|
126
|
+
self._maid[inst] = self._binder:ObserveInstance(inst, function(class)
|
|
127
|
+
self:_setChildClass(inst, class)
|
|
128
|
+
end)
|
|
135
129
|
|
|
136
|
-
self:
|
|
130
|
+
self:_setChildClass(inst, self._binder:Get(inst), doNotFire)
|
|
137
131
|
end
|
|
138
132
|
|
|
139
|
-
function BoundChildCollection.
|
|
140
|
-
|
|
141
|
-
return
|
|
142
|
-
end
|
|
133
|
+
function BoundChildCollection._removeChild<T>(self: BoundChildCollection<T>, inst: Instance): ()
|
|
134
|
+
self._maid[inst] = nil
|
|
143
135
|
|
|
144
|
-
self:
|
|
136
|
+
self:_setChildClass(inst, nil)
|
|
145
137
|
end
|
|
146
138
|
|
|
147
|
-
function BoundChildCollection.
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
function BoundChildCollection._setChildClass<T>(
|
|
140
|
+
self: BoundChildCollection<T>,
|
|
141
|
+
inst: Instance,
|
|
142
|
+
class: T?,
|
|
143
|
+
doNotFire: boolean?
|
|
144
|
+
): ()
|
|
145
|
+
local existing = self._childToClass[inst]
|
|
146
|
+
if existing == class then
|
|
150
147
|
return
|
|
151
148
|
end
|
|
152
149
|
|
|
153
|
-
|
|
150
|
+
if existing ~= nil then
|
|
151
|
+
self._childToClass[inst] = nil
|
|
152
|
+
self:_removeClass(existing)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if class ~= nil then
|
|
156
|
+
self._childToClass[inst] = class
|
|
157
|
+
self:_addClass(class, doNotFire)
|
|
158
|
+
end
|
|
154
159
|
end
|
|
155
160
|
|
|
156
161
|
function BoundChildCollection._addClass<T>(self: BoundChildCollection<T>, class: T, doNotFire: boolean?): ()
|
|
@@ -9,6 +9,8 @@ local Binder = require("Binder")
|
|
|
9
9
|
local BinderProvider = require("BinderProvider")
|
|
10
10
|
local BoundChildCollection = require("BoundChildCollection")
|
|
11
11
|
local Jest = require("Jest")
|
|
12
|
+
local JestUtils = require("JestUtils")
|
|
13
|
+
local Maid = require("Maid")
|
|
12
14
|
local ServiceBag = require("ServiceBag")
|
|
13
15
|
|
|
14
16
|
local describe = Jest.Globals.describe
|
|
@@ -29,6 +31,8 @@ local function makeClass()
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
local function setup()
|
|
34
|
+
local maid = Maid.new()
|
|
35
|
+
|
|
32
36
|
specCounter += 1
|
|
33
37
|
local suffix = specCounter
|
|
34
38
|
|
|
@@ -37,8 +41,8 @@ local function setup()
|
|
|
37
41
|
container.Name = "BoundChildCollectionSpecContainer"
|
|
38
42
|
container.Parent = workspace
|
|
39
43
|
|
|
40
|
-
local instances = {}
|
|
41
|
-
local cleanups = {}
|
|
44
|
+
local instances: { Instance } = {}
|
|
45
|
+
local cleanups: { any } = {}
|
|
42
46
|
local booted = false
|
|
43
47
|
|
|
44
48
|
local binder = Binder.new(string.format("BoundChildCollectionSpecTag_%d", suffix), makeClass() :: any)
|
|
@@ -67,27 +71,35 @@ local function setup()
|
|
|
67
71
|
serviceBag:Start()
|
|
68
72
|
end
|
|
69
73
|
|
|
70
|
-
|
|
74
|
+
maid:GiveTask(function()
|
|
75
|
+
for _, item in cleanups do
|
|
76
|
+
pcall(function()
|
|
77
|
+
item:Destroy()
|
|
78
|
+
end)
|
|
79
|
+
end
|
|
80
|
+
serviceBag:Destroy()
|
|
81
|
+
for _, inst in instances do
|
|
82
|
+
pcall(function()
|
|
83
|
+
inst:Destroy()
|
|
84
|
+
end)
|
|
85
|
+
end
|
|
86
|
+
container:Destroy()
|
|
87
|
+
end)
|
|
88
|
+
|
|
89
|
+
local controller = {
|
|
71
90
|
container = container,
|
|
72
91
|
binder = binder,
|
|
73
92
|
newInstance = newInstance,
|
|
74
93
|
track = track,
|
|
75
94
|
boot = boot,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
pcall(function()
|
|
79
|
-
item:Destroy()
|
|
80
|
-
end)
|
|
81
|
-
end
|
|
82
|
-
serviceBag:Destroy()
|
|
83
|
-
for _, inst in instances do
|
|
84
|
-
pcall(function()
|
|
85
|
-
inst:Destroy()
|
|
86
|
-
end)
|
|
87
|
-
end
|
|
88
|
-
container:Destroy()
|
|
95
|
+
Destroy = function(_self)
|
|
96
|
+
maid:DoCleaning()
|
|
89
97
|
end,
|
|
90
98
|
}
|
|
99
|
+
|
|
100
|
+
maid:GiveTask(JestUtils.afterThis(controller))
|
|
101
|
+
|
|
102
|
+
return controller
|
|
91
103
|
end
|
|
92
104
|
|
|
93
105
|
describe("BoundChildCollection construction", function()
|
|
@@ -114,7 +126,7 @@ describe("BoundChildCollection construction", function()
|
|
|
114
126
|
expect(#collection:GetClasses()).toEqual(2)
|
|
115
127
|
expect(collection:HasClass(controller.binder:Get(childA))).toEqual(true)
|
|
116
128
|
|
|
117
|
-
controller
|
|
129
|
+
controller:Destroy()
|
|
118
130
|
end)
|
|
119
131
|
end)
|
|
120
132
|
|
|
@@ -146,7 +158,97 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
146
158
|
expect(addedClass).toEqual(class)
|
|
147
159
|
expect(collection:GetSize()).toEqual(1)
|
|
148
160
|
|
|
149
|
-
controller
|
|
161
|
+
controller:Destroy()
|
|
162
|
+
end)
|
|
163
|
+
|
|
164
|
+
it("fires ClassAdded when an existing child binds after construction", function()
|
|
165
|
+
local controller = setup()
|
|
166
|
+
|
|
167
|
+
local parent = controller.newInstance()
|
|
168
|
+
local child = controller.newInstance(parent)
|
|
169
|
+
controller.boot()
|
|
170
|
+
|
|
171
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
172
|
+
expect(collection:GetSize()).toEqual(0)
|
|
173
|
+
|
|
174
|
+
local added = {}
|
|
175
|
+
collection.ClassAdded:Connect(function(class)
|
|
176
|
+
table.insert(added, class)
|
|
177
|
+
end)
|
|
178
|
+
|
|
179
|
+
controller.binder:Tag(child)
|
|
180
|
+
|
|
181
|
+
expect(collection:GetSize()).toEqual(1)
|
|
182
|
+
expect(#added).toEqual(1)
|
|
183
|
+
expect(added[1]).toEqual(controller.binder:Get(child))
|
|
184
|
+
|
|
185
|
+
controller:Destroy()
|
|
186
|
+
end)
|
|
187
|
+
|
|
188
|
+
it("ignores a bind on an instance that is not a child of the parent", function()
|
|
189
|
+
local controller = setup()
|
|
190
|
+
|
|
191
|
+
local parent = controller.newInstance()
|
|
192
|
+
local outsider = controller.newInstance()
|
|
193
|
+
local grandchild = controller.newInstance(controller.newInstance(parent))
|
|
194
|
+
controller.boot()
|
|
195
|
+
|
|
196
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
197
|
+
|
|
198
|
+
controller.binder:Tag(outsider)
|
|
199
|
+
controller.binder:Tag(grandchild)
|
|
200
|
+
|
|
201
|
+
expect(collection:GetSize()).toEqual(0)
|
|
202
|
+
|
|
203
|
+
controller:Destroy()
|
|
204
|
+
end)
|
|
205
|
+
|
|
206
|
+
it("stops following a child that was reparented out", function()
|
|
207
|
+
local controller = setup()
|
|
208
|
+
|
|
209
|
+
local parent = controller.newInstance()
|
|
210
|
+
local child = controller.newInstance(parent)
|
|
211
|
+
controller.binder:Tag(child)
|
|
212
|
+
controller.boot()
|
|
213
|
+
|
|
214
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
215
|
+
expect(collection:GetSize()).toEqual(1)
|
|
216
|
+
|
|
217
|
+
child.Parent = controller.container
|
|
218
|
+
expect(collection:GetSize()).toEqual(0)
|
|
219
|
+
|
|
220
|
+
local removed = 0
|
|
221
|
+
collection.ClassRemoved:Connect(function()
|
|
222
|
+
removed += 1
|
|
223
|
+
end)
|
|
224
|
+
|
|
225
|
+
controller.binder:Untag(child)
|
|
226
|
+
|
|
227
|
+
expect(collection:GetSize()).toEqual(0)
|
|
228
|
+
expect(removed).toEqual(0)
|
|
229
|
+
|
|
230
|
+
controller:Destroy()
|
|
231
|
+
end)
|
|
232
|
+
|
|
233
|
+
it("re-adds a child that leaves and comes back", function()
|
|
234
|
+
local controller = setup()
|
|
235
|
+
|
|
236
|
+
local parent = controller.newInstance()
|
|
237
|
+
local child = controller.newInstance(parent)
|
|
238
|
+
controller.binder:Tag(child)
|
|
239
|
+
controller.boot()
|
|
240
|
+
|
|
241
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
242
|
+
expect(collection:GetSize()).toEqual(1)
|
|
243
|
+
|
|
244
|
+
child.Parent = controller.container
|
|
245
|
+
expect(collection:GetSize()).toEqual(0)
|
|
246
|
+
|
|
247
|
+
child.Parent = parent
|
|
248
|
+
expect(collection:GetSize()).toEqual(1)
|
|
249
|
+
expect(collection:HasClass(controller.binder:Get(child))).toEqual(true)
|
|
250
|
+
|
|
251
|
+
controller:Destroy()
|
|
150
252
|
end)
|
|
151
253
|
|
|
152
254
|
it("fires ClassRemoved when a tracked child is reparented out", function()
|
|
@@ -175,7 +277,7 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
175
277
|
expect(removedClass).toEqual(class)
|
|
176
278
|
expect(collection:GetSize()).toEqual(0)
|
|
177
279
|
|
|
178
|
-
controller
|
|
280
|
+
controller:Destroy()
|
|
179
281
|
end)
|
|
180
282
|
|
|
181
283
|
it("fires ClassRemoved when a tracked child is unbound", function()
|
|
@@ -198,6 +300,6 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
198
300
|
|
|
199
301
|
expect(collection:GetSize()).toEqual(0)
|
|
200
302
|
|
|
201
|
-
controller
|
|
303
|
+
controller:Destroy()
|
|
202
304
|
end)
|
|
203
305
|
end)
|
|
@@ -8,6 +8,8 @@ local require = require(script.Parent.loader).load(script)
|
|
|
8
8
|
local Binder = require("Binder")
|
|
9
9
|
local BinderProvider = require("BinderProvider")
|
|
10
10
|
local Jest = require("Jest")
|
|
11
|
+
local JestUtils = require("JestUtils")
|
|
12
|
+
local Maid = require("Maid")
|
|
11
13
|
local ServiceBag = require("ServiceBag")
|
|
12
14
|
local promiseBoundClass = require("promiseBoundClass")
|
|
13
15
|
|
|
@@ -29,6 +31,8 @@ local function makeClass()
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
local function setup()
|
|
34
|
+
local maid = Maid.new()
|
|
35
|
+
|
|
32
36
|
specCounter += 1
|
|
33
37
|
local suffix = specCounter
|
|
34
38
|
|
|
@@ -37,7 +41,7 @@ local function setup()
|
|
|
37
41
|
container.Name = "PromiseBoundClassSpecContainer"
|
|
38
42
|
container.Parent = workspace
|
|
39
43
|
|
|
40
|
-
local instances = {}
|
|
44
|
+
local instances: { Instance } = {}
|
|
41
45
|
local booted = false
|
|
42
46
|
|
|
43
47
|
local binder = Binder.new(string.format("PromiseBoundClassSpecTag_%d", suffix), makeClass() :: any)
|
|
@@ -61,20 +65,28 @@ local function setup()
|
|
|
61
65
|
serviceBag:Start()
|
|
62
66
|
end
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
maid:GiveTask(function()
|
|
69
|
+
serviceBag:Destroy()
|
|
70
|
+
for _, inst in instances do
|
|
71
|
+
pcall(function()
|
|
72
|
+
inst:Destroy()
|
|
73
|
+
end)
|
|
74
|
+
end
|
|
75
|
+
container:Destroy()
|
|
76
|
+
end)
|
|
77
|
+
|
|
78
|
+
local controller = {
|
|
65
79
|
binder = binder,
|
|
66
80
|
newInstance = newInstance,
|
|
67
81
|
boot = boot,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
for _, inst in instances do
|
|
71
|
-
pcall(function()
|
|
72
|
-
inst:Destroy()
|
|
73
|
-
end)
|
|
74
|
-
end
|
|
75
|
-
container:Destroy()
|
|
82
|
+
Destroy = function(_self)
|
|
83
|
+
maid:DoCleaning()
|
|
76
84
|
end,
|
|
77
85
|
}
|
|
86
|
+
|
|
87
|
+
maid:GiveTask(JestUtils.afterThis(controller))
|
|
88
|
+
|
|
89
|
+
return controller
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
describe("promiseBoundClass()", function()
|
|
@@ -89,7 +101,7 @@ describe("promiseBoundClass()", function()
|
|
|
89
101
|
assert(ok, "Never bound")
|
|
90
102
|
expect(class).toEqual(controller.binder:Get(inst))
|
|
91
103
|
|
|
92
|
-
controller
|
|
104
|
+
controller:Destroy()
|
|
93
105
|
end)
|
|
94
106
|
|
|
95
107
|
it("resolves once an instance is bound after start", function()
|
|
@@ -102,7 +114,7 @@ describe("promiseBoundClass()", function()
|
|
|
102
114
|
local ok = promiseBoundClass(controller.binder, inst):Yield()
|
|
103
115
|
expect(ok).toEqual(true)
|
|
104
116
|
|
|
105
|
-
controller
|
|
117
|
+
controller:Destroy()
|
|
106
118
|
end)
|
|
107
119
|
|
|
108
120
|
it("throws when the binder is not a binder", function()
|
|
@@ -113,7 +125,7 @@ describe("promiseBoundClass()", function()
|
|
|
113
125
|
promiseBoundClass({} :: any, controller.newInstance())
|
|
114
126
|
end).toThrow()
|
|
115
127
|
|
|
116
|
-
controller
|
|
128
|
+
controller:Destroy()
|
|
117
129
|
end)
|
|
118
130
|
|
|
119
131
|
it("throws when the instance is not an Instance", function()
|
|
@@ -124,6 +136,6 @@ describe("promiseBoundClass()", function()
|
|
|
124
136
|
promiseBoundClass(controller.binder, 5 :: any)
|
|
125
137
|
end).toThrow()
|
|
126
138
|
|
|
127
|
-
controller
|
|
139
|
+
controller:Destroy()
|
|
128
140
|
end)
|
|
129
141
|
end)
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--[=[
|
|
3
|
-
Tracks a parent bound to a specific binder
|
|
4
|
-
@class BoundAncestorTracker
|
|
5
|
-
]=]
|
|
6
|
-
|
|
7
|
-
local require = require(script.Parent.loader).load(script)
|
|
8
|
-
|
|
9
|
-
local BaseObject = require("BaseObject")
|
|
10
|
-
local Binder = require("Binder")
|
|
11
|
-
local BinderUtils = require("BinderUtils")
|
|
12
|
-
local ValueObject = require("ValueObject")
|
|
13
|
-
|
|
14
|
-
local BoundAncestorTracker = setmetatable({}, BaseObject)
|
|
15
|
-
BoundAncestorTracker.ClassName = "BoundAncestorTracker"
|
|
16
|
-
BoundAncestorTracker.__index = BoundAncestorTracker
|
|
17
|
-
|
|
18
|
-
export type BoundAncestorTracker<T> =
|
|
19
|
-
typeof(setmetatable(
|
|
20
|
-
{} :: {
|
|
21
|
-
_child: Instance,
|
|
22
|
-
_binder: Binder.Binder<T>,
|
|
23
|
-
Class: ValueObject.ValueObject<T?>,
|
|
24
|
-
},
|
|
25
|
-
{} :: typeof({ __index = BoundAncestorTracker })
|
|
26
|
-
))
|
|
27
|
-
& BaseObject.BaseObject
|
|
28
|
-
|
|
29
|
-
--[=[
|
|
30
|
-
Constructs a new BoundAncestorTracker
|
|
31
|
-
|
|
32
|
-
@param binder Binder<T>
|
|
33
|
-
@param child Instance
|
|
34
|
-
@return BoundAncestorTracker
|
|
35
|
-
]=]
|
|
36
|
-
function BoundAncestorTracker.new<T>(binder: Binder.Binder<T>, child: Instance): BoundAncestorTracker<T>
|
|
37
|
-
local self: BoundAncestorTracker<T> = setmetatable(BaseObject.new() :: any, BoundAncestorTracker)
|
|
38
|
-
|
|
39
|
-
self._child = child or error("No child")
|
|
40
|
-
self._binder = binder or error("No binder")
|
|
41
|
-
|
|
42
|
-
--[=[
|
|
43
|
-
@prop Class ValueObject<T>
|
|
44
|
-
@readonly
|
|
45
|
-
@within BoundAncestorTracker
|
|
46
|
-
Bound value
|
|
47
|
-
]=]
|
|
48
|
-
self.Class = ValueObject.new() :: ValueObject.ValueObject<T?>
|
|
49
|
-
self._maid:GiveTask(self.Class)
|
|
50
|
-
|
|
51
|
-
-- Handle instance removing
|
|
52
|
-
self._maid:GiveTask(self._binder:GetClassRemovingSignal():Connect(function(class)
|
|
53
|
-
if class == self.Class.Value then
|
|
54
|
-
self.Class.Value = nil
|
|
55
|
-
end
|
|
56
|
-
end))
|
|
57
|
-
|
|
58
|
-
self._maid:GiveTask(self._binder:GetClassAddedSignal():Connect(function(_, instance)
|
|
59
|
-
if self._child:IsDescendantOf(instance) then
|
|
60
|
-
self:_update()
|
|
61
|
-
end
|
|
62
|
-
end))
|
|
63
|
-
|
|
64
|
-
-- Perform update
|
|
65
|
-
self._maid:GiveTask(self._child.AncestryChanged:Connect(function()
|
|
66
|
-
self:_update()
|
|
67
|
-
end))
|
|
68
|
-
self:_update()
|
|
69
|
-
|
|
70
|
-
return self
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
function BoundAncestorTracker._update<T>(self: BoundAncestorTracker<T>): ()
|
|
74
|
-
local parent = self._child.Parent
|
|
75
|
-
if not parent then
|
|
76
|
-
self.Class.Value = nil
|
|
77
|
-
return
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
self.Class.Value = BinderUtils.findFirstAncestor(self._binder, parent)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
return BoundAncestorTracker
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--[[
|
|
3
|
-
@class BoundAncestorTracker.spec.lua
|
|
4
|
-
]]
|
|
5
|
-
|
|
6
|
-
local require = require(script.Parent.loader).load(script)
|
|
7
|
-
|
|
8
|
-
local Binder = require("Binder")
|
|
9
|
-
local BinderProvider = require("BinderProvider")
|
|
10
|
-
local BoundAncestorTracker = require("BoundAncestorTracker")
|
|
11
|
-
local Jest = require("Jest")
|
|
12
|
-
local ServiceBag = require("ServiceBag")
|
|
13
|
-
|
|
14
|
-
local describe = Jest.Globals.describe
|
|
15
|
-
local expect = Jest.Globals.expect
|
|
16
|
-
local it = Jest.Globals.it
|
|
17
|
-
|
|
18
|
-
local specCounter = 0
|
|
19
|
-
|
|
20
|
-
local function makeClass()
|
|
21
|
-
local Class = {}
|
|
22
|
-
Class.__index = Class
|
|
23
|
-
Class.ClassName = "BoundAncestorTrackerSpecClass"
|
|
24
|
-
function Class.new(inst)
|
|
25
|
-
return setmetatable({ instance = inst }, Class)
|
|
26
|
-
end
|
|
27
|
-
function Class:Destroy() end
|
|
28
|
-
return Class
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
local function setup()
|
|
32
|
-
specCounter += 1
|
|
33
|
-
local suffix = specCounter
|
|
34
|
-
|
|
35
|
-
local serviceBag = ServiceBag.new()
|
|
36
|
-
local container = Instance.new("Folder")
|
|
37
|
-
container.Name = "BoundAncestorTrackerSpecContainer"
|
|
38
|
-
container.Parent = workspace
|
|
39
|
-
|
|
40
|
-
local instances = {}
|
|
41
|
-
local cleanups = {}
|
|
42
|
-
local booted = false
|
|
43
|
-
|
|
44
|
-
local binder = Binder.new(string.format("BoundAncestorTrackerSpecTag_%d", suffix), makeClass() :: any)
|
|
45
|
-
|
|
46
|
-
local function newInstance(parent: Instance?): Instance
|
|
47
|
-
local inst = Instance.new("Folder")
|
|
48
|
-
inst.Parent = parent or container
|
|
49
|
-
table.insert(instances, inst)
|
|
50
|
-
return inst
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
local function track(item: any): any
|
|
54
|
-
table.insert(cleanups, item)
|
|
55
|
-
return item
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
local function boot()
|
|
59
|
-
assert(not booted, "Already booted")
|
|
60
|
-
booted = true
|
|
61
|
-
|
|
62
|
-
local provider = BinderProvider.new(string.format("BoundAncestorTrackerSpecProvider_%d", suffix), function(self)
|
|
63
|
-
self:Add(binder)
|
|
64
|
-
end)
|
|
65
|
-
serviceBag:GetService(provider)
|
|
66
|
-
serviceBag:Init()
|
|
67
|
-
serviceBag:Start()
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
local function awaitChange(tracker, previous)
|
|
71
|
-
if tracker.Class.Value == previous then
|
|
72
|
-
tracker.Class.Changed:Wait()
|
|
73
|
-
end
|
|
74
|
-
return tracker.Class.Value
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
container = container,
|
|
79
|
-
binder = binder,
|
|
80
|
-
newInstance = newInstance,
|
|
81
|
-
track = track,
|
|
82
|
-
boot = boot,
|
|
83
|
-
awaitChange = awaitChange,
|
|
84
|
-
destroy = function()
|
|
85
|
-
for _, item in cleanups do
|
|
86
|
-
pcall(function()
|
|
87
|
-
item:Destroy()
|
|
88
|
-
end)
|
|
89
|
-
end
|
|
90
|
-
serviceBag:Destroy()
|
|
91
|
-
for _, inst in instances do
|
|
92
|
-
pcall(function()
|
|
93
|
-
inst:Destroy()
|
|
94
|
-
end)
|
|
95
|
-
end
|
|
96
|
-
container:Destroy()
|
|
97
|
-
end,
|
|
98
|
-
}
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
describe("BoundAncestorTracker tracking", function()
|
|
102
|
-
it("exposes the nearest bound ancestor's class", function()
|
|
103
|
-
local controller = setup()
|
|
104
|
-
|
|
105
|
-
local grandparent = controller.newInstance()
|
|
106
|
-
local parent = controller.newInstance(grandparent)
|
|
107
|
-
local child = controller.newInstance(parent)
|
|
108
|
-
controller.binder:Tag(grandparent)
|
|
109
|
-
controller.boot()
|
|
110
|
-
|
|
111
|
-
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
112
|
-
expect(tracker.Class.Value).toEqual(controller.binder:Get(grandparent))
|
|
113
|
-
|
|
114
|
-
controller.destroy()
|
|
115
|
-
end)
|
|
116
|
-
|
|
117
|
-
it("has no value when no ancestor is bound", function()
|
|
118
|
-
local controller = setup()
|
|
119
|
-
|
|
120
|
-
local parent = controller.newInstance()
|
|
121
|
-
local child = controller.newInstance(parent)
|
|
122
|
-
controller.boot()
|
|
123
|
-
|
|
124
|
-
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
125
|
-
expect(tracker.Class.Value).toBeNil()
|
|
126
|
-
|
|
127
|
-
controller.destroy()
|
|
128
|
-
end)
|
|
129
|
-
|
|
130
|
-
it("updates when an ancestor becomes bound", function()
|
|
131
|
-
local controller = setup()
|
|
132
|
-
|
|
133
|
-
-- The tracker resolves ancestors above the child's direct parent, so bind the grandparent.
|
|
134
|
-
local ancestor = controller.newInstance()
|
|
135
|
-
local parent = controller.newInstance(ancestor)
|
|
136
|
-
local child = controller.newInstance(parent)
|
|
137
|
-
controller.boot()
|
|
138
|
-
|
|
139
|
-
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
140
|
-
expect(tracker.Class.Value).toBeNil()
|
|
141
|
-
|
|
142
|
-
controller.binder:Tag(ancestor)
|
|
143
|
-
local value = controller.awaitChange(tracker, nil)
|
|
144
|
-
expect(value).toEqual(controller.binder:Get(ancestor))
|
|
145
|
-
|
|
146
|
-
controller.destroy()
|
|
147
|
-
end)
|
|
148
|
-
|
|
149
|
-
it("clears the value when the child leaves the bound ancestry", function()
|
|
150
|
-
local controller = setup()
|
|
151
|
-
|
|
152
|
-
local ancestor = controller.newInstance()
|
|
153
|
-
local parent = controller.newInstance(ancestor)
|
|
154
|
-
local child = controller.newInstance(parent)
|
|
155
|
-
controller.binder:Tag(ancestor)
|
|
156
|
-
controller.boot()
|
|
157
|
-
|
|
158
|
-
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
159
|
-
local class = controller.binder:Get(ancestor)
|
|
160
|
-
expect(tracker.Class.Value).toEqual(class)
|
|
161
|
-
|
|
162
|
-
child.Parent = controller.container
|
|
163
|
-
expect(controller.awaitChange(tracker, class)).toBeNil()
|
|
164
|
-
|
|
165
|
-
controller.destroy()
|
|
166
|
-
end)
|
|
167
|
-
end)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--[=[
|
|
3
|
-
Tracks a parent bound to a specific binder
|
|
4
|
-
@class BoundParentTracker
|
|
5
|
-
]=]
|
|
6
|
-
|
|
7
|
-
local require = require(script.Parent.loader).load(script)
|
|
8
|
-
|
|
9
|
-
local BaseObject = require("BaseObject")
|
|
10
|
-
local Binder = require("Binder")
|
|
11
|
-
local ValueObject = require("ValueObject")
|
|
12
|
-
|
|
13
|
-
local BoundParentTracker = setmetatable({}, BaseObject)
|
|
14
|
-
BoundParentTracker.ClassName = "BoundParentTracker"
|
|
15
|
-
BoundParentTracker.__index = BoundParentTracker
|
|
16
|
-
|
|
17
|
-
export type BoundParentTracker<T> =
|
|
18
|
-
typeof(setmetatable(
|
|
19
|
-
{} :: {
|
|
20
|
-
_child: Instance,
|
|
21
|
-
_binder: Binder.Binder<T>,
|
|
22
|
-
Class: ValueObject.ValueObject<T?>,
|
|
23
|
-
},
|
|
24
|
-
{} :: typeof({ __index = BoundParentTracker })
|
|
25
|
-
))
|
|
26
|
-
& BaseObject.BaseObject
|
|
27
|
-
|
|
28
|
-
function BoundParentTracker.new<T>(binder: Binder.Binder<T>, child: Instance): BoundParentTracker<T>
|
|
29
|
-
local self: BoundParentTracker<T> = setmetatable(BaseObject.new() :: any, BoundParentTracker)
|
|
30
|
-
|
|
31
|
-
self._child = child or error("No child")
|
|
32
|
-
self._binder = binder or error("No binder")
|
|
33
|
-
|
|
34
|
-
-- Bound value
|
|
35
|
-
self.Class = ValueObject.new(nil :: T?)
|
|
36
|
-
self._maid:GiveTask(self.Class)
|
|
37
|
-
|
|
38
|
-
-- Handle instance removing
|
|
39
|
-
self._maid:GiveTask(self._binder:GetClassRemovingSignal():Connect(function(class)
|
|
40
|
-
if class == self.Class.Value then
|
|
41
|
-
self.Class.Value = nil
|
|
42
|
-
end
|
|
43
|
-
end))
|
|
44
|
-
|
|
45
|
-
-- Perform update
|
|
46
|
-
self._maid:GiveTask(self._child:GetPropertyChangedSignal("Parent"):Connect(function()
|
|
47
|
-
self:_update()
|
|
48
|
-
end))
|
|
49
|
-
self:_update()
|
|
50
|
-
|
|
51
|
-
return self
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
function BoundParentTracker._update<T>(self: BoundParentTracker<T>): ()
|
|
55
|
-
local parent = self._child.Parent
|
|
56
|
-
if not parent then
|
|
57
|
-
self.Class.Value = nil
|
|
58
|
-
return
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
self.Class.Value = self._binder:Get(parent)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
return BoundParentTracker
|