@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,586 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class Binder.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Binder = require("Binder")
|
|
9
|
+
local BinderTestUtils = require("BinderTestUtils")
|
|
10
|
+
local Jest = require("Jest")
|
|
11
|
+
|
|
12
|
+
local describe = Jest.Globals.describe
|
|
13
|
+
local expect = Jest.Globals.expect
|
|
14
|
+
local it = Jest.Globals.it
|
|
15
|
+
|
|
16
|
+
local setup = BinderTestUtils.setup
|
|
17
|
+
local makeTrackingClass = BinderTestUtils.makeTrackingClass
|
|
18
|
+
local awaitUnbound = BinderTestUtils.awaitUnbound
|
|
19
|
+
local noopConstructor = BinderTestUtils.noopConstructor
|
|
20
|
+
|
|
21
|
+
describe("Binder.new()", function()
|
|
22
|
+
it("constructs a binder", function()
|
|
23
|
+
local binder = Binder.new("BinderNewSpecTag", noopConstructor)
|
|
24
|
+
expect(Binder.isBinder(binder)).toEqual(true)
|
|
25
|
+
end)
|
|
26
|
+
|
|
27
|
+
it("derives the ServiceName from the tag", function()
|
|
28
|
+
local binder = Binder.new("BinderNameSpecTag", noopConstructor)
|
|
29
|
+
expect(binder.ServiceName).toEqual("BinderNameSpecTagBinder")
|
|
30
|
+
end)
|
|
31
|
+
|
|
32
|
+
it("throws on a non-string tag name", function()
|
|
33
|
+
expect(function()
|
|
34
|
+
Binder.new(123 :: any, noopConstructor)
|
|
35
|
+
end).toThrow()
|
|
36
|
+
end)
|
|
37
|
+
|
|
38
|
+
it("throws when constructing a binder of a binder", function()
|
|
39
|
+
local inner = Binder.new("BinderOfBinderInner", noopConstructor)
|
|
40
|
+
expect(function()
|
|
41
|
+
Binder.new("BinderOfBinderOuter", inner :: any)
|
|
42
|
+
end).toThrow()
|
|
43
|
+
end)
|
|
44
|
+
|
|
45
|
+
it("captures variadic constructor args", function()
|
|
46
|
+
local controller = setup()
|
|
47
|
+
|
|
48
|
+
local binder = controller.addBinder(function(_inst, a, b)
|
|
49
|
+
return { a = a, b = b }
|
|
50
|
+
end, "a", "b")
|
|
51
|
+
controller.boot()
|
|
52
|
+
|
|
53
|
+
local inst = controller.newInstance()
|
|
54
|
+
binder:Tag(inst)
|
|
55
|
+
|
|
56
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
57
|
+
assert(ok, "Never bound")
|
|
58
|
+
expect(class.a).toEqual("a")
|
|
59
|
+
expect(class.b).toEqual("b")
|
|
60
|
+
|
|
61
|
+
controller.destroy()
|
|
62
|
+
end)
|
|
63
|
+
end)
|
|
64
|
+
|
|
65
|
+
describe("Binder.isBinder()", function()
|
|
66
|
+
it("returns false for non-tables", function()
|
|
67
|
+
expect(Binder.isBinder(nil)).toEqual(false)
|
|
68
|
+
expect(Binder.isBinder(5)).toEqual(false)
|
|
69
|
+
expect(Binder.isBinder("binder")).toEqual(false)
|
|
70
|
+
end)
|
|
71
|
+
|
|
72
|
+
it("returns false for tables missing the interface", function()
|
|
73
|
+
expect(Binder.isBinder({ Start = function() end })).toEqual(false)
|
|
74
|
+
end)
|
|
75
|
+
|
|
76
|
+
it("returns true for a real binder", function()
|
|
77
|
+
local binder = Binder.new("BinderIsBinderSpecTag", noopConstructor)
|
|
78
|
+
expect(Binder.isBinder(binder)).toEqual(true)
|
|
79
|
+
end)
|
|
80
|
+
end)
|
|
81
|
+
|
|
82
|
+
describe("Binder metadata", function()
|
|
83
|
+
it("returns the tag from GetTag()", function()
|
|
84
|
+
local binder = Binder.new("BinderGetTagSpecTag", noopConstructor)
|
|
85
|
+
expect(binder:GetTag()).toEqual("BinderGetTagSpecTag")
|
|
86
|
+
end)
|
|
87
|
+
|
|
88
|
+
it("returns the constructor from GetConstructor()", function()
|
|
89
|
+
local constructor = noopConstructor
|
|
90
|
+
local binder = Binder.new("BinderGetConstructorSpecTag", constructor)
|
|
91
|
+
expect(binder:GetConstructor()).toEqual(constructor)
|
|
92
|
+
end)
|
|
93
|
+
end)
|
|
94
|
+
|
|
95
|
+
describe("Binder constructor variants", function()
|
|
96
|
+
it("supports a plain function constructor", function()
|
|
97
|
+
local controller = setup()
|
|
98
|
+
|
|
99
|
+
local binder = controller.addBinder(function(inst)
|
|
100
|
+
return { instance = inst, kind = "function" }
|
|
101
|
+
end)
|
|
102
|
+
controller.boot()
|
|
103
|
+
|
|
104
|
+
local inst = controller.newInstance()
|
|
105
|
+
binder:Tag(inst)
|
|
106
|
+
|
|
107
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
108
|
+
assert(ok, "Never bound")
|
|
109
|
+
expect(class.kind).toEqual("function")
|
|
110
|
+
expect(class.instance).toEqual(inst)
|
|
111
|
+
|
|
112
|
+
controller.destroy()
|
|
113
|
+
end)
|
|
114
|
+
|
|
115
|
+
it("supports a class table with .new", function()
|
|
116
|
+
local controller = setup()
|
|
117
|
+
|
|
118
|
+
local Class = makeTrackingClass()
|
|
119
|
+
local binder = controller.addBinder(Class)
|
|
120
|
+
controller.boot()
|
|
121
|
+
|
|
122
|
+
local inst = controller.newInstance()
|
|
123
|
+
binder:Tag(inst)
|
|
124
|
+
|
|
125
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
126
|
+
assert(ok, "Never bound")
|
|
127
|
+
expect(class.instance).toEqual(inst)
|
|
128
|
+
|
|
129
|
+
controller.destroy()
|
|
130
|
+
end)
|
|
131
|
+
|
|
132
|
+
it("supports a provider table with :Create", function()
|
|
133
|
+
local controller = setup()
|
|
134
|
+
|
|
135
|
+
local provider = {}
|
|
136
|
+
function provider.Create(_self, inst)
|
|
137
|
+
return { instance = inst, kind = "provider" }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
local binder = controller.addBinder(provider :: any)
|
|
141
|
+
controller.boot()
|
|
142
|
+
|
|
143
|
+
local inst = controller.newInstance()
|
|
144
|
+
binder:Tag(inst)
|
|
145
|
+
|
|
146
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
147
|
+
assert(ok, "Never bound")
|
|
148
|
+
expect(class.kind).toEqual("provider")
|
|
149
|
+
|
|
150
|
+
controller.destroy()
|
|
151
|
+
end)
|
|
152
|
+
end)
|
|
153
|
+
|
|
154
|
+
describe("Binder lifecycle guards", function()
|
|
155
|
+
it("Init is idempotent", function()
|
|
156
|
+
local binder = Binder.new("BinderInitIdempotentSpecTag", noopConstructor)
|
|
157
|
+
binder:Init()
|
|
158
|
+
expect(function()
|
|
159
|
+
binder:Init()
|
|
160
|
+
end).never.toThrow()
|
|
161
|
+
binder:Destroy()
|
|
162
|
+
end)
|
|
163
|
+
|
|
164
|
+
it("Start is idempotent", function()
|
|
165
|
+
local binder = Binder.new("BinderStartIdempotentSpecTag", noopConstructor)
|
|
166
|
+
binder:Start()
|
|
167
|
+
expect(function()
|
|
168
|
+
binder:Start()
|
|
169
|
+
end).never.toThrow()
|
|
170
|
+
binder:Destroy()
|
|
171
|
+
end)
|
|
172
|
+
|
|
173
|
+
it("Start initializes implicitly", function()
|
|
174
|
+
local binder = Binder.new("BinderStartInitSpecTag", noopConstructor)
|
|
175
|
+
binder:Start()
|
|
176
|
+
expect(binder:GetAll()).toEqual({})
|
|
177
|
+
binder:Destroy()
|
|
178
|
+
end)
|
|
179
|
+
end)
|
|
180
|
+
|
|
181
|
+
describe("Binder binding via ServiceBag", function()
|
|
182
|
+
it("binds instances tagged before start", function()
|
|
183
|
+
local controller = setup()
|
|
184
|
+
|
|
185
|
+
local Class = makeTrackingClass()
|
|
186
|
+
local binder = controller.addBinder(Class)
|
|
187
|
+
|
|
188
|
+
local inst = controller.newInstance()
|
|
189
|
+
binder:Tag(inst)
|
|
190
|
+
|
|
191
|
+
controller.boot()
|
|
192
|
+
|
|
193
|
+
expect(binder:Get(inst)).never.toBeNil()
|
|
194
|
+
|
|
195
|
+
controller.destroy()
|
|
196
|
+
end)
|
|
197
|
+
|
|
198
|
+
it("binds instances tagged after start", function()
|
|
199
|
+
local controller = setup()
|
|
200
|
+
|
|
201
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
202
|
+
controller.boot()
|
|
203
|
+
|
|
204
|
+
local inst = controller.newInstance()
|
|
205
|
+
binder:Tag(inst)
|
|
206
|
+
|
|
207
|
+
local ok = binder:Promise(inst):Yield()
|
|
208
|
+
expect(ok).toEqual(true)
|
|
209
|
+
|
|
210
|
+
controller.destroy()
|
|
211
|
+
end)
|
|
212
|
+
end)
|
|
213
|
+
|
|
214
|
+
describe("Binder:Get()", function()
|
|
215
|
+
it("returns nil for an unbound instance", function()
|
|
216
|
+
local controller = setup()
|
|
217
|
+
|
|
218
|
+
local binder = controller.addBinder(function() end)
|
|
219
|
+
controller.boot()
|
|
220
|
+
|
|
221
|
+
expect(binder:Get(controller.newInstance())).toBeNil()
|
|
222
|
+
|
|
223
|
+
controller.destroy()
|
|
224
|
+
end)
|
|
225
|
+
|
|
226
|
+
it("throws when passed a non-instance", function()
|
|
227
|
+
local controller = setup()
|
|
228
|
+
|
|
229
|
+
local binder = controller.addBinder(function() end)
|
|
230
|
+
controller.boot()
|
|
231
|
+
|
|
232
|
+
expect(function()
|
|
233
|
+
binder:Get(5 :: any)
|
|
234
|
+
end).toThrow()
|
|
235
|
+
|
|
236
|
+
controller.destroy()
|
|
237
|
+
end)
|
|
238
|
+
end)
|
|
239
|
+
|
|
240
|
+
describe("Binder tagging", function()
|
|
241
|
+
it("Tag/HasTag/Untag manage the collection service tag", function()
|
|
242
|
+
local controller = setup()
|
|
243
|
+
|
|
244
|
+
local binder = controller.addBinder(function() end)
|
|
245
|
+
controller.boot()
|
|
246
|
+
|
|
247
|
+
local inst = controller.newInstance()
|
|
248
|
+
expect(binder:HasTag(inst)).toEqual(false)
|
|
249
|
+
binder:Tag(inst)
|
|
250
|
+
expect(binder:HasTag(inst)).toEqual(true)
|
|
251
|
+
binder:Untag(inst)
|
|
252
|
+
expect(binder:HasTag(inst)).toEqual(false)
|
|
253
|
+
|
|
254
|
+
controller.destroy()
|
|
255
|
+
end)
|
|
256
|
+
|
|
257
|
+
it("Bind tags and Unbind untags on the server", function()
|
|
258
|
+
local controller = setup()
|
|
259
|
+
|
|
260
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
261
|
+
controller.boot()
|
|
262
|
+
|
|
263
|
+
local inst = controller.newInstance()
|
|
264
|
+
binder:Bind(inst)
|
|
265
|
+
expect(binder:HasTag(inst)).toEqual(true)
|
|
266
|
+
|
|
267
|
+
local ok = binder:Promise(inst):Yield()
|
|
268
|
+
expect(ok).toEqual(true)
|
|
269
|
+
|
|
270
|
+
binder:Unbind(inst)
|
|
271
|
+
expect(binder:HasTag(inst)).toEqual(false)
|
|
272
|
+
|
|
273
|
+
controller.destroy()
|
|
274
|
+
end)
|
|
275
|
+
end)
|
|
276
|
+
|
|
277
|
+
describe("Binder:Create()", function()
|
|
278
|
+
it("creates a tagged, non-archivable instance named after the tag", function()
|
|
279
|
+
local binder = Binder.new("BinderCreateSpecTag", noopConstructor)
|
|
280
|
+
|
|
281
|
+
-- Cast: Create's declared signature marks className required, but it defaults when omitted.
|
|
282
|
+
local inst = (binder :: any):Create()
|
|
283
|
+
expect(inst.Name).toEqual("BinderCreateSpecTag")
|
|
284
|
+
expect(inst.Archivable).toEqual(false)
|
|
285
|
+
expect(binder:HasTag(inst)).toEqual(true)
|
|
286
|
+
expect(inst:IsA("Folder")).toEqual(true)
|
|
287
|
+
|
|
288
|
+
inst:Destroy()
|
|
289
|
+
end)
|
|
290
|
+
|
|
291
|
+
it("honors an explicit class name", function()
|
|
292
|
+
local binder = Binder.new("BinderCreateNamedSpecTag", noopConstructor)
|
|
293
|
+
local inst = binder:Create("BoolValue")
|
|
294
|
+
expect(inst:IsA("BoolValue")).toEqual(true)
|
|
295
|
+
|
|
296
|
+
inst:Destroy()
|
|
297
|
+
end)
|
|
298
|
+
|
|
299
|
+
it("throws on a non-string class name", function()
|
|
300
|
+
local binder = Binder.new("BinderCreateBadSpecTag", noopConstructor)
|
|
301
|
+
expect(function()
|
|
302
|
+
binder:Create(5 :: any)
|
|
303
|
+
end).toThrow()
|
|
304
|
+
end)
|
|
305
|
+
end)
|
|
306
|
+
|
|
307
|
+
describe("Binder:GetAll() / GetAllSet()", function()
|
|
308
|
+
it("tracks every bound class", function()
|
|
309
|
+
local controller = setup()
|
|
310
|
+
|
|
311
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
312
|
+
controller.boot()
|
|
313
|
+
|
|
314
|
+
local instA = controller.newInstance()
|
|
315
|
+
local instB = controller.newInstance()
|
|
316
|
+
binder:Tag(instA)
|
|
317
|
+
binder:Tag(instB)
|
|
318
|
+
|
|
319
|
+
assert((binder:Promise(instA):Yield()), "A never bound")
|
|
320
|
+
assert((binder:Promise(instB):Yield()), "B never bound")
|
|
321
|
+
|
|
322
|
+
expect(#binder:GetAll()).toEqual(2)
|
|
323
|
+
|
|
324
|
+
local set = binder:GetAllSet()
|
|
325
|
+
expect(set[binder:Get(instA)]).toEqual(true)
|
|
326
|
+
expect(set[binder:Get(instB)]).toEqual(true)
|
|
327
|
+
|
|
328
|
+
controller.destroy()
|
|
329
|
+
end)
|
|
330
|
+
end)
|
|
331
|
+
|
|
332
|
+
describe("Binder signals", function()
|
|
333
|
+
it("memoizes the added/removing/removed signals", function()
|
|
334
|
+
local controller = setup()
|
|
335
|
+
|
|
336
|
+
local binder = controller.addBinder(function() end)
|
|
337
|
+
controller.boot()
|
|
338
|
+
|
|
339
|
+
expect(binder:GetClassAddedSignal()).toEqual(binder:GetClassAddedSignal())
|
|
340
|
+
expect(binder:GetClassRemovingSignal()).toEqual(binder:GetClassRemovingSignal())
|
|
341
|
+
expect(binder:GetClassRemovedSignal()).toEqual(binder:GetClassRemovedSignal())
|
|
342
|
+
|
|
343
|
+
controller.destroy()
|
|
344
|
+
end)
|
|
345
|
+
|
|
346
|
+
it("fires the added signal with the class and instance", function()
|
|
347
|
+
local controller = setup()
|
|
348
|
+
|
|
349
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
350
|
+
controller.boot()
|
|
351
|
+
|
|
352
|
+
local firedClass, firedInst
|
|
353
|
+
binder:GetClassAddedSignal():Connect(function(class, inst)
|
|
354
|
+
firedClass = class
|
|
355
|
+
firedInst = inst
|
|
356
|
+
end)
|
|
357
|
+
|
|
358
|
+
local inst = controller.newInstance()
|
|
359
|
+
binder:Tag(inst)
|
|
360
|
+
assert((binder:Promise(inst):Yield()), "Never bound")
|
|
361
|
+
|
|
362
|
+
expect(firedInst).toEqual(inst)
|
|
363
|
+
expect(firedClass).toEqual(binder:Get(inst))
|
|
364
|
+
|
|
365
|
+
controller.destroy()
|
|
366
|
+
end)
|
|
367
|
+
|
|
368
|
+
it("fires removing then removed on unbind", function()
|
|
369
|
+
local controller = setup()
|
|
370
|
+
|
|
371
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
372
|
+
controller.boot()
|
|
373
|
+
|
|
374
|
+
local order = {}
|
|
375
|
+
binder:GetClassRemovingSignal():Connect(function()
|
|
376
|
+
table.insert(order, "removing")
|
|
377
|
+
end)
|
|
378
|
+
binder:GetClassRemovedSignal():Connect(function()
|
|
379
|
+
table.insert(order, "removed")
|
|
380
|
+
end)
|
|
381
|
+
|
|
382
|
+
local inst = controller.newInstance()
|
|
383
|
+
binder:Tag(inst)
|
|
384
|
+
assert((binder:Promise(inst):Yield()), "Never bound")
|
|
385
|
+
|
|
386
|
+
binder:Untag(inst)
|
|
387
|
+
awaitUnbound(binder, inst)
|
|
388
|
+
|
|
389
|
+
expect(order[1]).toEqual("removing")
|
|
390
|
+
expect(order[2]).toEqual("removed")
|
|
391
|
+
|
|
392
|
+
controller.destroy()
|
|
393
|
+
end)
|
|
394
|
+
|
|
395
|
+
it("destroys the bound class when it is removed", function()
|
|
396
|
+
local controller = setup()
|
|
397
|
+
|
|
398
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
399
|
+
controller.boot()
|
|
400
|
+
|
|
401
|
+
local inst = controller.newInstance()
|
|
402
|
+
binder:Tag(inst)
|
|
403
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
404
|
+
assert(ok, "Never bound")
|
|
405
|
+
|
|
406
|
+
binder:Untag(inst)
|
|
407
|
+
awaitUnbound(binder, inst)
|
|
408
|
+
|
|
409
|
+
expect(class.destroyed).toEqual(true)
|
|
410
|
+
|
|
411
|
+
controller.destroy()
|
|
412
|
+
end)
|
|
413
|
+
end)
|
|
414
|
+
|
|
415
|
+
describe("Binder:ObserveInstance()", function()
|
|
416
|
+
it("fires with the class on bind and nil on unbind", function()
|
|
417
|
+
local controller = setup()
|
|
418
|
+
|
|
419
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
420
|
+
controller.boot()
|
|
421
|
+
|
|
422
|
+
local inst = controller.newInstance()
|
|
423
|
+
|
|
424
|
+
-- Wrap each emission so a nil value is still recorded (table.insert(t, nil) is a no-op).
|
|
425
|
+
local emissions = {}
|
|
426
|
+
local cleanup = binder:ObserveInstance(inst, function(class)
|
|
427
|
+
table.insert(emissions, { value = class })
|
|
428
|
+
end)
|
|
429
|
+
|
|
430
|
+
binder:Tag(inst)
|
|
431
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
432
|
+
assert(ok, "Never bound")
|
|
433
|
+
|
|
434
|
+
binder:Untag(inst)
|
|
435
|
+
awaitUnbound(binder, inst)
|
|
436
|
+
|
|
437
|
+
expect(#emissions).toEqual(2)
|
|
438
|
+
expect(emissions[1].value).toEqual(class)
|
|
439
|
+
expect(emissions[2].value).toBeNil()
|
|
440
|
+
|
|
441
|
+
cleanup()
|
|
442
|
+
|
|
443
|
+
controller.destroy()
|
|
444
|
+
end)
|
|
445
|
+
|
|
446
|
+
it("returns a cleanup that stops future callbacks", function()
|
|
447
|
+
local controller = setup()
|
|
448
|
+
|
|
449
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
450
|
+
controller.boot()
|
|
451
|
+
|
|
452
|
+
local inst = controller.newInstance()
|
|
453
|
+
|
|
454
|
+
local count = 0
|
|
455
|
+
local cleanup = binder:ObserveInstance(inst, function()
|
|
456
|
+
count += 1
|
|
457
|
+
end)
|
|
458
|
+
cleanup()
|
|
459
|
+
|
|
460
|
+
binder:Tag(inst)
|
|
461
|
+
assert((binder:Promise(inst):Yield()), "Never bound")
|
|
462
|
+
|
|
463
|
+
expect(count).toEqual(0)
|
|
464
|
+
|
|
465
|
+
controller.destroy()
|
|
466
|
+
end)
|
|
467
|
+
|
|
468
|
+
it("throws for a non-instance or non-function", function()
|
|
469
|
+
local controller = setup()
|
|
470
|
+
|
|
471
|
+
local binder = controller.addBinder(function() end)
|
|
472
|
+
controller.boot()
|
|
473
|
+
|
|
474
|
+
expect(function()
|
|
475
|
+
binder:ObserveInstance(5 :: any, function() end)
|
|
476
|
+
end).toThrow()
|
|
477
|
+
expect(function()
|
|
478
|
+
binder:ObserveInstance(controller.newInstance(), 5 :: any)
|
|
479
|
+
end).toThrow()
|
|
480
|
+
|
|
481
|
+
controller.destroy()
|
|
482
|
+
end)
|
|
483
|
+
end)
|
|
484
|
+
|
|
485
|
+
describe("Binder:Observe()", function()
|
|
486
|
+
it("emits the current value on subscribe and updates on bind", function()
|
|
487
|
+
local controller = setup()
|
|
488
|
+
|
|
489
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
490
|
+
controller.boot()
|
|
491
|
+
|
|
492
|
+
local inst = controller.newInstance()
|
|
493
|
+
|
|
494
|
+
-- Wrap each emission so the initial nil value is still recorded.
|
|
495
|
+
local emissions = {}
|
|
496
|
+
local sub = binder:Observe(inst):Subscribe(function(class)
|
|
497
|
+
table.insert(emissions, { value = class })
|
|
498
|
+
end)
|
|
499
|
+
|
|
500
|
+
expect(#emissions).toEqual(1)
|
|
501
|
+
expect(emissions[1].value).toBeNil()
|
|
502
|
+
|
|
503
|
+
binder:Tag(inst)
|
|
504
|
+
assert((binder:Promise(inst):Yield()), "Never bound")
|
|
505
|
+
|
|
506
|
+
expect(#emissions).toEqual(2)
|
|
507
|
+
expect(emissions[2].value).toEqual(binder:Get(inst))
|
|
508
|
+
|
|
509
|
+
sub:Destroy()
|
|
510
|
+
controller.destroy()
|
|
511
|
+
end)
|
|
512
|
+
end)
|
|
513
|
+
|
|
514
|
+
describe("Binder:Promise()", function()
|
|
515
|
+
it("resolves immediately when already bound", function()
|
|
516
|
+
local controller = setup()
|
|
517
|
+
|
|
518
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
519
|
+
|
|
520
|
+
local inst = controller.newInstance()
|
|
521
|
+
binder:Tag(inst)
|
|
522
|
+
controller.boot()
|
|
523
|
+
|
|
524
|
+
local promise = binder:Promise(inst)
|
|
525
|
+
expect(promise:IsFulfilled()).toEqual(true)
|
|
526
|
+
|
|
527
|
+
controller.destroy()
|
|
528
|
+
end)
|
|
529
|
+
|
|
530
|
+
it("throws when passed a non-instance", function()
|
|
531
|
+
local controller = setup()
|
|
532
|
+
|
|
533
|
+
local binder = controller.addBinder(function() end)
|
|
534
|
+
controller.boot()
|
|
535
|
+
|
|
536
|
+
expect(function()
|
|
537
|
+
binder:Promise(5 :: any)
|
|
538
|
+
end).toThrow()
|
|
539
|
+
|
|
540
|
+
controller.destroy()
|
|
541
|
+
end)
|
|
542
|
+
end)
|
|
543
|
+
|
|
544
|
+
describe("Binder:_add() dedupe", function()
|
|
545
|
+
it("does not rebind an already-bound instance", function()
|
|
546
|
+
local controller = setup()
|
|
547
|
+
|
|
548
|
+
local constructed = 0
|
|
549
|
+
local binder = controller.addBinder(function(inst)
|
|
550
|
+
constructed += 1
|
|
551
|
+
return { instance = inst }
|
|
552
|
+
end)
|
|
553
|
+
controller.boot()
|
|
554
|
+
|
|
555
|
+
local inst = controller.newInstance()
|
|
556
|
+
binder:Tag(inst)
|
|
557
|
+
assert((binder:Promise(inst):Yield()), "Never bound")
|
|
558
|
+
|
|
559
|
+
binder:Tag(inst)
|
|
560
|
+
task.wait()
|
|
561
|
+
|
|
562
|
+
expect(constructed).toEqual(1)
|
|
563
|
+
|
|
564
|
+
controller.destroy()
|
|
565
|
+
end)
|
|
566
|
+
end)
|
|
567
|
+
|
|
568
|
+
describe("Binder teardown", function()
|
|
569
|
+
it("removes and destroys all bound classes when the service bag is destroyed", function()
|
|
570
|
+
local controller = setup()
|
|
571
|
+
|
|
572
|
+
local binder = controller.addBinder(makeTrackingClass())
|
|
573
|
+
controller.boot()
|
|
574
|
+
|
|
575
|
+
local inst = controller.newInstance()
|
|
576
|
+
binder:Tag(inst)
|
|
577
|
+
local ok, class = binder:Promise(inst):Yield()
|
|
578
|
+
assert(ok, "Never bound")
|
|
579
|
+
|
|
580
|
+
controller.destroyServiceBag()
|
|
581
|
+
|
|
582
|
+
expect(class.destroyed).toEqual(true)
|
|
583
|
+
|
|
584
|
+
controller.destroy()
|
|
585
|
+
end)
|
|
586
|
+
end)
|