@quenty/binder 14.39.0 → 14.41.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 +13 -13
- package/src/Shared/Binder.Loading.spec.lua +54 -0
- package/src/Shared/Binder.ObserveBrio.spec.lua +209 -0
- package/src/Shared/Binder.spec.lua +586 -0
- package/src/Shared/BinderGroup.spec.lua +151 -0
- package/src/Shared/BinderGroupProvider.spec.lua +117 -0
- package/src/Shared/BinderTestUtils.lua +163 -0
- package/src/Shared/BinderUtils.spec.lua +338 -0
- package/src/Shared/Collection/BoundChildCollection.spec.lua +203 -0
- package/src/Shared/Promise/promiseBoundClass.spec.lua +129 -0
- package/src/Shared/Trackers/BoundAncestorTracker.spec.lua +167 -0
- package/src/Shared/Trackers/BoundParentTracker.spec.lua +159 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class BinderUtils.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 BinderUtils = require("BinderUtils")
|
|
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 = "BinderUtilsSpecClass"
|
|
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 = "BinderUtilsSpecContainer"
|
|
38
|
+
container.Parent = workspace
|
|
39
|
+
|
|
40
|
+
local instances = {}
|
|
41
|
+
local pendingBinders: { any } = {}
|
|
42
|
+
local tagCounter = 0
|
|
43
|
+
local booted = false
|
|
44
|
+
|
|
45
|
+
local function newBinder(): Binder.Binder<any>
|
|
46
|
+
assert(not booted, "Cannot add a binder after boot()")
|
|
47
|
+
tagCounter += 1
|
|
48
|
+
local binder = Binder.new(string.format("BinderUtilsSpecTag_%d_%d", suffix, tagCounter), makeClass() :: any)
|
|
49
|
+
table.insert(pendingBinders, binder)
|
|
50
|
+
return binder
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
local function newInstance(parent: Instance?, className: string?): Instance
|
|
54
|
+
local inst = Instance.new(className or "Folder")
|
|
55
|
+
inst.Parent = parent or container
|
|
56
|
+
table.insert(instances, inst)
|
|
57
|
+
return inst
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
local function boot()
|
|
61
|
+
assert(not booted, "Already booted")
|
|
62
|
+
booted = true
|
|
63
|
+
|
|
64
|
+
local binders = pendingBinders
|
|
65
|
+
local provider = BinderProvider.new(string.format("BinderUtilsSpecProvider_%d", suffix), function(self)
|
|
66
|
+
for _, binder in binders do
|
|
67
|
+
self:Add(binder)
|
|
68
|
+
end
|
|
69
|
+
end)
|
|
70
|
+
serviceBag:GetService(provider)
|
|
71
|
+
serviceBag:Init()
|
|
72
|
+
serviceBag:Start()
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
container = container,
|
|
77
|
+
newBinder = newBinder,
|
|
78
|
+
newInstance = newInstance,
|
|
79
|
+
boot = boot,
|
|
80
|
+
destroy = function()
|
|
81
|
+
serviceBag:Destroy()
|
|
82
|
+
for _, inst in instances do
|
|
83
|
+
pcall(function()
|
|
84
|
+
inst:Destroy()
|
|
85
|
+
end)
|
|
86
|
+
end
|
|
87
|
+
container:Destroy()
|
|
88
|
+
end,
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe("BinderUtils.findFirstAncestor()", function()
|
|
93
|
+
it("returns the nearest bound ancestor, skipping the child itself", function()
|
|
94
|
+
local controller = setup()
|
|
95
|
+
|
|
96
|
+
local grandparent = controller.newInstance()
|
|
97
|
+
local parent = controller.newInstance(grandparent)
|
|
98
|
+
local child = controller.newInstance(parent)
|
|
99
|
+
|
|
100
|
+
local binder = controller.newBinder()
|
|
101
|
+
binder:Tag(grandparent)
|
|
102
|
+
binder:Tag(child) -- child is bound too, but must be skipped
|
|
103
|
+
controller.boot()
|
|
104
|
+
|
|
105
|
+
expect(BinderUtils.findFirstAncestor(binder, child)).toEqual(binder:Get(grandparent))
|
|
106
|
+
|
|
107
|
+
controller.destroy()
|
|
108
|
+
end)
|
|
109
|
+
|
|
110
|
+
it("returns nil when no ancestor is bound", function()
|
|
111
|
+
local controller = setup()
|
|
112
|
+
|
|
113
|
+
local parent = controller.newInstance()
|
|
114
|
+
local child = controller.newInstance(parent)
|
|
115
|
+
local binder = controller.newBinder()
|
|
116
|
+
controller.boot()
|
|
117
|
+
|
|
118
|
+
expect(BinderUtils.findFirstAncestor(binder, child)).toBeNil()
|
|
119
|
+
|
|
120
|
+
controller.destroy()
|
|
121
|
+
end)
|
|
122
|
+
|
|
123
|
+
it("throws for a non-instance child", function()
|
|
124
|
+
local controller = setup()
|
|
125
|
+
|
|
126
|
+
local binder = controller.newBinder()
|
|
127
|
+
controller.boot()
|
|
128
|
+
expect(function()
|
|
129
|
+
BinderUtils.findFirstAncestor(binder, 5 :: any)
|
|
130
|
+
end).toThrow()
|
|
131
|
+
|
|
132
|
+
controller.destroy()
|
|
133
|
+
end)
|
|
134
|
+
end)
|
|
135
|
+
|
|
136
|
+
describe("BinderUtils.findFirstChild()", function()
|
|
137
|
+
it("returns the first bound child", function()
|
|
138
|
+
local controller = setup()
|
|
139
|
+
|
|
140
|
+
local parent = controller.newInstance()
|
|
141
|
+
local unboundChild = controller.newInstance(parent)
|
|
142
|
+
local boundChild = controller.newInstance(parent)
|
|
143
|
+
|
|
144
|
+
local binder = controller.newBinder()
|
|
145
|
+
binder:Tag(boundChild)
|
|
146
|
+
controller.boot()
|
|
147
|
+
|
|
148
|
+
expect(BinderUtils.findFirstChild(binder, parent)).toEqual(binder:Get(boundChild))
|
|
149
|
+
expect(binder:Get(unboundChild)).toBeNil()
|
|
150
|
+
|
|
151
|
+
controller.destroy()
|
|
152
|
+
end)
|
|
153
|
+
|
|
154
|
+
it("returns nil when no child is bound", function()
|
|
155
|
+
local controller = setup()
|
|
156
|
+
|
|
157
|
+
local parent = controller.newInstance()
|
|
158
|
+
controller.newInstance(parent)
|
|
159
|
+
local binder = controller.newBinder()
|
|
160
|
+
controller.boot()
|
|
161
|
+
|
|
162
|
+
expect(BinderUtils.findFirstChild(binder, parent)).toBeNil()
|
|
163
|
+
|
|
164
|
+
controller.destroy()
|
|
165
|
+
end)
|
|
166
|
+
end)
|
|
167
|
+
|
|
168
|
+
describe("BinderUtils.getChildren()", function()
|
|
169
|
+
it("returns every bound child", function()
|
|
170
|
+
local controller = setup()
|
|
171
|
+
|
|
172
|
+
local parent = controller.newInstance()
|
|
173
|
+
local childA = controller.newInstance(parent)
|
|
174
|
+
local childB = controller.newInstance(parent)
|
|
175
|
+
controller.newInstance(parent) -- unbound
|
|
176
|
+
|
|
177
|
+
local binder = controller.newBinder()
|
|
178
|
+
binder:Tag(childA)
|
|
179
|
+
binder:Tag(childB)
|
|
180
|
+
controller.boot()
|
|
181
|
+
|
|
182
|
+
expect(#BinderUtils.getChildren(binder, parent)).toEqual(2)
|
|
183
|
+
|
|
184
|
+
controller.destroy()
|
|
185
|
+
end)
|
|
186
|
+
|
|
187
|
+
it("does not include bound descendants deeper than one level", function()
|
|
188
|
+
local controller = setup()
|
|
189
|
+
|
|
190
|
+
local parent = controller.newInstance()
|
|
191
|
+
local child = controller.newInstance(parent)
|
|
192
|
+
local grandchild = controller.newInstance(child)
|
|
193
|
+
|
|
194
|
+
local binder = controller.newBinder()
|
|
195
|
+
binder:Tag(grandchild)
|
|
196
|
+
controller.boot()
|
|
197
|
+
|
|
198
|
+
expect(#BinderUtils.getChildren(binder, parent)).toEqual(0)
|
|
199
|
+
|
|
200
|
+
controller.destroy()
|
|
201
|
+
end)
|
|
202
|
+
end)
|
|
203
|
+
|
|
204
|
+
describe("BinderUtils.getDescendants()", function()
|
|
205
|
+
it("returns bound instances at any depth", function()
|
|
206
|
+
local controller = setup()
|
|
207
|
+
|
|
208
|
+
local parent = controller.newInstance()
|
|
209
|
+
local child = controller.newInstance(parent)
|
|
210
|
+
local grandchild = controller.newInstance(child)
|
|
211
|
+
|
|
212
|
+
local binder = controller.newBinder()
|
|
213
|
+
binder:Tag(child)
|
|
214
|
+
binder:Tag(grandchild)
|
|
215
|
+
controller.boot()
|
|
216
|
+
|
|
217
|
+
expect(#BinderUtils.getDescendants(binder, parent)).toEqual(2)
|
|
218
|
+
|
|
219
|
+
controller.destroy()
|
|
220
|
+
end)
|
|
221
|
+
end)
|
|
222
|
+
|
|
223
|
+
describe("BinderUtils.mapBinderListToTable()", function()
|
|
224
|
+
it("keys binders by their tag", function()
|
|
225
|
+
local controller = setup()
|
|
226
|
+
|
|
227
|
+
local binderA = controller.newBinder()
|
|
228
|
+
local binderB = controller.newBinder()
|
|
229
|
+
controller.boot()
|
|
230
|
+
|
|
231
|
+
local map = BinderUtils.mapBinderListToTable({ binderA, binderB })
|
|
232
|
+
expect(map[binderA:GetTag()]).toEqual(binderA)
|
|
233
|
+
expect(map[binderB:GetTag()]).toEqual(binderB)
|
|
234
|
+
|
|
235
|
+
controller.destroy()
|
|
236
|
+
end)
|
|
237
|
+
end)
|
|
238
|
+
|
|
239
|
+
describe("BinderUtils.getMappedFromList()", function()
|
|
240
|
+
it("resolves bound values across an instance list by tag", function()
|
|
241
|
+
local controller = setup()
|
|
242
|
+
|
|
243
|
+
local instA = controller.newInstance()
|
|
244
|
+
local instB = controller.newInstance()
|
|
245
|
+
local binderA = controller.newBinder()
|
|
246
|
+
local binderB = controller.newBinder()
|
|
247
|
+
binderA:Tag(instA)
|
|
248
|
+
binderB:Tag(instB)
|
|
249
|
+
controller.boot()
|
|
250
|
+
|
|
251
|
+
local tagsMap = BinderUtils.mapBinderListToTable({ binderA, binderB })
|
|
252
|
+
local objects = BinderUtils.getMappedFromList(tagsMap, { instA, instB, controller.newInstance() })
|
|
253
|
+
|
|
254
|
+
expect(#objects).toEqual(2)
|
|
255
|
+
|
|
256
|
+
controller.destroy()
|
|
257
|
+
end)
|
|
258
|
+
end)
|
|
259
|
+
|
|
260
|
+
describe("BinderUtils.getChildrenOfBinders()", function()
|
|
261
|
+
it("returns children bound by any binder in the list", function()
|
|
262
|
+
local controller = setup()
|
|
263
|
+
|
|
264
|
+
local parent = controller.newInstance()
|
|
265
|
+
local childA = controller.newInstance(parent)
|
|
266
|
+
local childB = controller.newInstance(parent)
|
|
267
|
+
|
|
268
|
+
local binderA = controller.newBinder()
|
|
269
|
+
local binderB = controller.newBinder()
|
|
270
|
+
binderA:Tag(childA)
|
|
271
|
+
binderB:Tag(childB)
|
|
272
|
+
controller.boot()
|
|
273
|
+
|
|
274
|
+
expect(#BinderUtils.getChildrenOfBinders({ binderA, binderB }, parent)).toEqual(2)
|
|
275
|
+
|
|
276
|
+
controller.destroy()
|
|
277
|
+
end)
|
|
278
|
+
end)
|
|
279
|
+
|
|
280
|
+
describe("BinderUtils.getLinkedChildren()", function()
|
|
281
|
+
it("resolves bound targets of matching ObjectValue links", function()
|
|
282
|
+
local controller = setup()
|
|
283
|
+
|
|
284
|
+
local parent = controller.newInstance()
|
|
285
|
+
local target = controller.newInstance()
|
|
286
|
+
local binder = controller.newBinder()
|
|
287
|
+
binder:Tag(target)
|
|
288
|
+
|
|
289
|
+
local link = controller.newInstance(parent, "ObjectValue") :: ObjectValue
|
|
290
|
+
link.Name = "Link"
|
|
291
|
+
link.Value = target
|
|
292
|
+
controller.boot()
|
|
293
|
+
|
|
294
|
+
local objects = BinderUtils.getLinkedChildren(binder, "Link", parent)
|
|
295
|
+
expect(#objects).toEqual(1)
|
|
296
|
+
expect(objects[1]).toEqual(binder:Get(target))
|
|
297
|
+
|
|
298
|
+
controller.destroy()
|
|
299
|
+
end)
|
|
300
|
+
|
|
301
|
+
it("ignores links whose name does not match", function()
|
|
302
|
+
local controller = setup()
|
|
303
|
+
|
|
304
|
+
local parent = controller.newInstance()
|
|
305
|
+
local target = controller.newInstance()
|
|
306
|
+
local binder = controller.newBinder()
|
|
307
|
+
binder:Tag(target)
|
|
308
|
+
|
|
309
|
+
local link = controller.newInstance(parent, "ObjectValue") :: ObjectValue
|
|
310
|
+
link.Name = "OtherLink"
|
|
311
|
+
link.Value = target
|
|
312
|
+
controller.boot()
|
|
313
|
+
|
|
314
|
+
expect(#BinderUtils.getLinkedChildren(binder, "Link", parent)).toEqual(0)
|
|
315
|
+
|
|
316
|
+
controller.destroy()
|
|
317
|
+
end)
|
|
318
|
+
|
|
319
|
+
it("deduplicates when two links point to the same bound target", function()
|
|
320
|
+
local controller = setup()
|
|
321
|
+
|
|
322
|
+
local parent = controller.newInstance()
|
|
323
|
+
local target = controller.newInstance()
|
|
324
|
+
local binder = controller.newBinder()
|
|
325
|
+
binder:Tag(target)
|
|
326
|
+
|
|
327
|
+
for _ = 1, 2 do
|
|
328
|
+
local link = controller.newInstance(parent, "ObjectValue") :: ObjectValue
|
|
329
|
+
link.Name = "Link"
|
|
330
|
+
link.Value = target
|
|
331
|
+
end
|
|
332
|
+
controller.boot()
|
|
333
|
+
|
|
334
|
+
expect(#BinderUtils.getLinkedChildren(binder, "Link", parent)).toEqual(1)
|
|
335
|
+
|
|
336
|
+
controller.destroy()
|
|
337
|
+
end)
|
|
338
|
+
end)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class BoundChildCollection.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 BoundChildCollection = require("BoundChildCollection")
|
|
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 = "BoundChildCollectionSpecClass"
|
|
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 = "BoundChildCollectionSpecContainer"
|
|
38
|
+
container.Parent = workspace
|
|
39
|
+
|
|
40
|
+
local instances = {}
|
|
41
|
+
local cleanups = {}
|
|
42
|
+
local booted = false
|
|
43
|
+
|
|
44
|
+
local binder = Binder.new(string.format("BoundChildCollectionSpecTag_%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("BoundChildCollectionSpecProvider_%d", suffix), function(self)
|
|
63
|
+
self:Add(binder)
|
|
64
|
+
end)
|
|
65
|
+
serviceBag:GetService(provider)
|
|
66
|
+
serviceBag:Init()
|
|
67
|
+
serviceBag:Start()
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
container = container,
|
|
72
|
+
binder = binder,
|
|
73
|
+
newInstance = newInstance,
|
|
74
|
+
track = track,
|
|
75
|
+
boot = boot,
|
|
76
|
+
destroy = function()
|
|
77
|
+
for _, item in cleanups do
|
|
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()
|
|
89
|
+
end,
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe("BoundChildCollection construction", function()
|
|
94
|
+
it("counts children bound before construction without firing ClassAdded", function()
|
|
95
|
+
local controller = setup()
|
|
96
|
+
|
|
97
|
+
local parent = controller.newInstance()
|
|
98
|
+
local childA = controller.newInstance(parent)
|
|
99
|
+
local childB = controller.newInstance(parent)
|
|
100
|
+
controller.newInstance(parent) -- unbound child
|
|
101
|
+
|
|
102
|
+
controller.binder:Tag(childA)
|
|
103
|
+
controller.binder:Tag(childB)
|
|
104
|
+
controller.boot()
|
|
105
|
+
|
|
106
|
+
local fired = 0
|
|
107
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
108
|
+
collection.ClassAdded:Connect(function()
|
|
109
|
+
fired += 1
|
|
110
|
+
end)
|
|
111
|
+
|
|
112
|
+
expect(collection:GetSize()).toEqual(2)
|
|
113
|
+
expect(fired).toEqual(0)
|
|
114
|
+
expect(#collection:GetClasses()).toEqual(2)
|
|
115
|
+
expect(collection:HasClass(controller.binder:Get(childA))).toEqual(true)
|
|
116
|
+
|
|
117
|
+
controller.destroy()
|
|
118
|
+
end)
|
|
119
|
+
end)
|
|
120
|
+
|
|
121
|
+
describe("BoundChildCollection dynamic updates", function()
|
|
122
|
+
it("fires ClassAdded when a bound child is reparented in", function()
|
|
123
|
+
local controller = setup()
|
|
124
|
+
|
|
125
|
+
local parent = controller.newInstance()
|
|
126
|
+
controller.boot()
|
|
127
|
+
|
|
128
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
129
|
+
|
|
130
|
+
local child = controller.newInstance()
|
|
131
|
+
controller.binder:Tag(child)
|
|
132
|
+
local ok, class = controller.binder:Promise(child):Yield()
|
|
133
|
+
assert(ok, "child never bound")
|
|
134
|
+
|
|
135
|
+
local addedClass
|
|
136
|
+
local conn = collection.ClassAdded:Connect(function(c)
|
|
137
|
+
addedClass = c
|
|
138
|
+
end)
|
|
139
|
+
|
|
140
|
+
child.Parent = parent
|
|
141
|
+
if collection:GetSize() == 0 then
|
|
142
|
+
collection.ClassAdded:Wait()
|
|
143
|
+
end
|
|
144
|
+
conn:Disconnect()
|
|
145
|
+
|
|
146
|
+
expect(addedClass).toEqual(class)
|
|
147
|
+
expect(collection:GetSize()).toEqual(1)
|
|
148
|
+
|
|
149
|
+
controller.destroy()
|
|
150
|
+
end)
|
|
151
|
+
|
|
152
|
+
it("fires ClassRemoved when a tracked child is reparented out", function()
|
|
153
|
+
local controller = setup()
|
|
154
|
+
|
|
155
|
+
local parent = controller.newInstance()
|
|
156
|
+
local child = controller.newInstance(parent)
|
|
157
|
+
controller.binder:Tag(child)
|
|
158
|
+
controller.boot()
|
|
159
|
+
|
|
160
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
161
|
+
expect(collection:GetSize()).toEqual(1)
|
|
162
|
+
|
|
163
|
+
local removedClass
|
|
164
|
+
local conn = collection.ClassRemoved:Connect(function(c)
|
|
165
|
+
removedClass = c
|
|
166
|
+
end)
|
|
167
|
+
|
|
168
|
+
local class = controller.binder:Get(child)
|
|
169
|
+
child.Parent = controller.container
|
|
170
|
+
if collection:GetSize() == 1 then
|
|
171
|
+
collection.ClassRemoved:Wait()
|
|
172
|
+
end
|
|
173
|
+
conn:Disconnect()
|
|
174
|
+
|
|
175
|
+
expect(removedClass).toEqual(class)
|
|
176
|
+
expect(collection:GetSize()).toEqual(0)
|
|
177
|
+
|
|
178
|
+
controller.destroy()
|
|
179
|
+
end)
|
|
180
|
+
|
|
181
|
+
it("fires ClassRemoved when a tracked child is unbound", function()
|
|
182
|
+
local controller = setup()
|
|
183
|
+
|
|
184
|
+
local parent = controller.newInstance()
|
|
185
|
+
local child = controller.newInstance(parent)
|
|
186
|
+
controller.binder:Tag(child)
|
|
187
|
+
controller.boot()
|
|
188
|
+
|
|
189
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
190
|
+
expect(collection:GetSize()).toEqual(1)
|
|
191
|
+
|
|
192
|
+
local conn = collection.ClassRemoved:Connect(function() end)
|
|
193
|
+
controller.binder:Untag(child)
|
|
194
|
+
if collection:GetSize() == 1 then
|
|
195
|
+
collection.ClassRemoved:Wait()
|
|
196
|
+
end
|
|
197
|
+
conn:Disconnect()
|
|
198
|
+
|
|
199
|
+
expect(collection:GetSize()).toEqual(0)
|
|
200
|
+
|
|
201
|
+
controller.destroy()
|
|
202
|
+
end)
|
|
203
|
+
end)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class promiseBoundClass.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 Jest = require("Jest")
|
|
11
|
+
local ServiceBag = require("ServiceBag")
|
|
12
|
+
local promiseBoundClass = require("promiseBoundClass")
|
|
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 = "PromiseBoundClassSpecClass"
|
|
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 = "PromiseBoundClassSpecContainer"
|
|
38
|
+
container.Parent = workspace
|
|
39
|
+
|
|
40
|
+
local instances = {}
|
|
41
|
+
local booted = false
|
|
42
|
+
|
|
43
|
+
local binder = Binder.new(string.format("PromiseBoundClassSpecTag_%d", suffix), makeClass() :: any)
|
|
44
|
+
|
|
45
|
+
local function newInstance(): Instance
|
|
46
|
+
local inst = Instance.new("Folder")
|
|
47
|
+
inst.Parent = container
|
|
48
|
+
table.insert(instances, inst)
|
|
49
|
+
return inst
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
local function boot()
|
|
53
|
+
assert(not booted, "Already booted")
|
|
54
|
+
booted = true
|
|
55
|
+
|
|
56
|
+
local provider = BinderProvider.new(string.format("PromiseBoundClassSpecProvider_%d", suffix), function(self)
|
|
57
|
+
self:Add(binder)
|
|
58
|
+
end)
|
|
59
|
+
serviceBag:GetService(provider)
|
|
60
|
+
serviceBag:Init()
|
|
61
|
+
serviceBag:Start()
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
binder = binder,
|
|
66
|
+
newInstance = newInstance,
|
|
67
|
+
boot = boot,
|
|
68
|
+
destroy = 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
|
+
end
|
|
79
|
+
|
|
80
|
+
describe("promiseBoundClass()", function()
|
|
81
|
+
it("resolves with the bound class", function()
|
|
82
|
+
local controller = setup()
|
|
83
|
+
|
|
84
|
+
local inst = controller.newInstance()
|
|
85
|
+
controller.binder:Tag(inst)
|
|
86
|
+
controller.boot()
|
|
87
|
+
|
|
88
|
+
local ok, class = promiseBoundClass(controller.binder, inst):Yield()
|
|
89
|
+
assert(ok, "Never bound")
|
|
90
|
+
expect(class).toEqual(controller.binder:Get(inst))
|
|
91
|
+
|
|
92
|
+
controller.destroy()
|
|
93
|
+
end)
|
|
94
|
+
|
|
95
|
+
it("resolves once an instance is bound after start", function()
|
|
96
|
+
local controller = setup()
|
|
97
|
+
|
|
98
|
+
controller.boot()
|
|
99
|
+
local inst = controller.newInstance()
|
|
100
|
+
controller.binder:Tag(inst)
|
|
101
|
+
|
|
102
|
+
local ok = promiseBoundClass(controller.binder, inst):Yield()
|
|
103
|
+
expect(ok).toEqual(true)
|
|
104
|
+
|
|
105
|
+
controller.destroy()
|
|
106
|
+
end)
|
|
107
|
+
|
|
108
|
+
it("throws when the binder is not a binder", function()
|
|
109
|
+
local controller = setup()
|
|
110
|
+
controller.boot()
|
|
111
|
+
|
|
112
|
+
expect(function()
|
|
113
|
+
promiseBoundClass({} :: any, controller.newInstance())
|
|
114
|
+
end).toThrow()
|
|
115
|
+
|
|
116
|
+
controller.destroy()
|
|
117
|
+
end)
|
|
118
|
+
|
|
119
|
+
it("throws when the instance is not an Instance", function()
|
|
120
|
+
local controller = setup()
|
|
121
|
+
controller.boot()
|
|
122
|
+
|
|
123
|
+
expect(function()
|
|
124
|
+
promiseBoundClass(controller.binder, 5 :: any)
|
|
125
|
+
end).toThrow()
|
|
126
|
+
|
|
127
|
+
controller.destroy()
|
|
128
|
+
end)
|
|
129
|
+
end)
|