@quenty/binder 14.40.0 → 14.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 +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 +120 -255
- package/src/Shared/BinderGroup.spec.lua +19 -24
- package/src/Shared/BinderTestUtils.lua +163 -0
- package/src/Shared/BinderUtils.spec.lua +94 -103
- package/src/Shared/Collection/BoundChildCollection.spec.lua +36 -45
- package/src/Shared/Promise/promiseBoundClass.spec.lua +21 -27
- package/src/Shared/Trackers/BoundAncestorTracker.spec.lua +36 -44
- package/src/Shared/Trackers/BoundParentTracker.spec.lua +28 -36
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--[[
|
|
3
|
-
Coverage for BoundChildCollection, which tracks the bound children of a parent instance.
|
|
4
|
-
|
|
5
|
-
The binder is booted through a ServiceBag (as production code does); the collection itself is a
|
|
6
|
-
plain BaseObject and is constructed directly. Everything lives under a workspace container so
|
|
7
|
-
CollectionService and ChildAdded/ChildRemoved signals fire. Children tagged before the service
|
|
8
|
-
bag starts bind synchronously; changes made after construction are awaited event-driven via the
|
|
9
|
-
collection's own ClassAdded/ClassRemoved signals rather than a fixed sleep.
|
|
10
|
-
|
|
11
3
|
@class BoundChildCollection.spec.lua
|
|
12
4
|
]]
|
|
13
5
|
|
|
@@ -100,19 +92,19 @@ end
|
|
|
100
92
|
|
|
101
93
|
describe("BoundChildCollection construction", function()
|
|
102
94
|
it("counts children bound before construction without firing ClassAdded", function()
|
|
103
|
-
local
|
|
95
|
+
local controller = setup()
|
|
104
96
|
|
|
105
|
-
local parent =
|
|
106
|
-
local childA =
|
|
107
|
-
local childB =
|
|
108
|
-
|
|
97
|
+
local parent = controller.newInstance()
|
|
98
|
+
local childA = controller.newInstance(parent)
|
|
99
|
+
local childB = controller.newInstance(parent)
|
|
100
|
+
controller.newInstance(parent) -- unbound child
|
|
109
101
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
controller.binder:Tag(childA)
|
|
103
|
+
controller.binder:Tag(childB)
|
|
104
|
+
controller.boot()
|
|
113
105
|
|
|
114
106
|
local fired = 0
|
|
115
|
-
local collection =
|
|
107
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
116
108
|
collection.ClassAdded:Connect(function()
|
|
117
109
|
fired += 1
|
|
118
110
|
end)
|
|
@@ -120,25 +112,24 @@ describe("BoundChildCollection construction", function()
|
|
|
120
112
|
expect(collection:GetSize()).toEqual(2)
|
|
121
113
|
expect(fired).toEqual(0)
|
|
122
114
|
expect(#collection:GetClasses()).toEqual(2)
|
|
123
|
-
expect(collection:HasClass(
|
|
115
|
+
expect(collection:HasClass(controller.binder:Get(childA))).toEqual(true)
|
|
124
116
|
|
|
125
|
-
|
|
117
|
+
controller.destroy()
|
|
126
118
|
end)
|
|
127
119
|
end)
|
|
128
120
|
|
|
129
121
|
describe("BoundChildCollection dynamic updates", function()
|
|
130
122
|
it("fires ClassAdded when a bound child is reparented in", function()
|
|
131
|
-
local
|
|
123
|
+
local controller = setup()
|
|
132
124
|
|
|
133
|
-
local parent =
|
|
134
|
-
|
|
125
|
+
local parent = controller.newInstance()
|
|
126
|
+
controller.boot()
|
|
135
127
|
|
|
136
|
-
local collection =
|
|
128
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
137
129
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
local ok, class = env.binder:Promise(child):Yield()
|
|
130
|
+
local child = controller.newInstance()
|
|
131
|
+
controller.binder:Tag(child)
|
|
132
|
+
local ok, class = controller.binder:Promise(child):Yield()
|
|
142
133
|
assert(ok, "child never bound")
|
|
143
134
|
|
|
144
135
|
local addedClass
|
|
@@ -155,18 +146,18 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
155
146
|
expect(addedClass).toEqual(class)
|
|
156
147
|
expect(collection:GetSize()).toEqual(1)
|
|
157
148
|
|
|
158
|
-
|
|
149
|
+
controller.destroy()
|
|
159
150
|
end)
|
|
160
151
|
|
|
161
152
|
it("fires ClassRemoved when a tracked child is reparented out", function()
|
|
162
|
-
local
|
|
153
|
+
local controller = setup()
|
|
163
154
|
|
|
164
|
-
local parent =
|
|
165
|
-
local child =
|
|
166
|
-
|
|
167
|
-
|
|
155
|
+
local parent = controller.newInstance()
|
|
156
|
+
local child = controller.newInstance(parent)
|
|
157
|
+
controller.binder:Tag(child)
|
|
158
|
+
controller.boot()
|
|
168
159
|
|
|
169
|
-
local collection =
|
|
160
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
170
161
|
expect(collection:GetSize()).toEqual(1)
|
|
171
162
|
|
|
172
163
|
local removedClass
|
|
@@ -174,8 +165,8 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
174
165
|
removedClass = c
|
|
175
166
|
end)
|
|
176
167
|
|
|
177
|
-
local class =
|
|
178
|
-
child.Parent =
|
|
168
|
+
local class = controller.binder:Get(child)
|
|
169
|
+
child.Parent = controller.container
|
|
179
170
|
if collection:GetSize() == 1 then
|
|
180
171
|
collection.ClassRemoved:Wait()
|
|
181
172
|
end
|
|
@@ -184,22 +175,22 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
184
175
|
expect(removedClass).toEqual(class)
|
|
185
176
|
expect(collection:GetSize()).toEqual(0)
|
|
186
177
|
|
|
187
|
-
|
|
178
|
+
controller.destroy()
|
|
188
179
|
end)
|
|
189
180
|
|
|
190
181
|
it("fires ClassRemoved when a tracked child is unbound", function()
|
|
191
|
-
local
|
|
182
|
+
local controller = setup()
|
|
192
183
|
|
|
193
|
-
local parent =
|
|
194
|
-
local child =
|
|
195
|
-
|
|
196
|
-
|
|
184
|
+
local parent = controller.newInstance()
|
|
185
|
+
local child = controller.newInstance(parent)
|
|
186
|
+
controller.binder:Tag(child)
|
|
187
|
+
controller.boot()
|
|
197
188
|
|
|
198
|
-
local collection =
|
|
189
|
+
local collection = controller.track(BoundChildCollection.new(controller.binder, parent))
|
|
199
190
|
expect(collection:GetSize()).toEqual(1)
|
|
200
191
|
|
|
201
192
|
local conn = collection.ClassRemoved:Connect(function() end)
|
|
202
|
-
|
|
193
|
+
controller.binder:Untag(child)
|
|
203
194
|
if collection:GetSize() == 1 then
|
|
204
195
|
collection.ClassRemoved:Wait()
|
|
205
196
|
end
|
|
@@ -207,6 +198,6 @@ describe("BoundChildCollection dynamic updates", function()
|
|
|
207
198
|
|
|
208
199
|
expect(collection:GetSize()).toEqual(0)
|
|
209
200
|
|
|
210
|
-
|
|
201
|
+
controller.destroy()
|
|
211
202
|
end)
|
|
212
203
|
end)
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--[[
|
|
3
|
-
Coverage for promiseBoundClass, a thin wrapper that delegates to Binder:Promise after
|
|
4
|
-
validating its arguments.
|
|
5
|
-
|
|
6
|
-
The binder is booted through a ServiceBag; the instance is tagged before start so it binds
|
|
7
|
-
synchronously and the promise resolves immediately.
|
|
8
|
-
|
|
9
3
|
@class promiseBoundClass.spec.lua
|
|
10
4
|
]]
|
|
11
5
|
|
|
@@ -85,51 +79,51 @@ end
|
|
|
85
79
|
|
|
86
80
|
describe("promiseBoundClass()", function()
|
|
87
81
|
it("resolves with the bound class", function()
|
|
88
|
-
local
|
|
82
|
+
local controller = setup()
|
|
89
83
|
|
|
90
|
-
local inst =
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
local inst = controller.newInstance()
|
|
85
|
+
controller.binder:Tag(inst)
|
|
86
|
+
controller.boot()
|
|
93
87
|
|
|
94
|
-
local ok, class = promiseBoundClass(
|
|
88
|
+
local ok, class = promiseBoundClass(controller.binder, inst):Yield()
|
|
95
89
|
assert(ok, "Never bound")
|
|
96
|
-
expect(class).toEqual(
|
|
90
|
+
expect(class).toEqual(controller.binder:Get(inst))
|
|
97
91
|
|
|
98
|
-
|
|
92
|
+
controller.destroy()
|
|
99
93
|
end)
|
|
100
94
|
|
|
101
95
|
it("resolves once an instance is bound after start", function()
|
|
102
|
-
local
|
|
96
|
+
local controller = setup()
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
local inst =
|
|
106
|
-
|
|
98
|
+
controller.boot()
|
|
99
|
+
local inst = controller.newInstance()
|
|
100
|
+
controller.binder:Tag(inst)
|
|
107
101
|
|
|
108
|
-
local ok = promiseBoundClass(
|
|
102
|
+
local ok = promiseBoundClass(controller.binder, inst):Yield()
|
|
109
103
|
expect(ok).toEqual(true)
|
|
110
104
|
|
|
111
|
-
|
|
105
|
+
controller.destroy()
|
|
112
106
|
end)
|
|
113
107
|
|
|
114
108
|
it("throws when the binder is not a binder", function()
|
|
115
|
-
local
|
|
116
|
-
|
|
109
|
+
local controller = setup()
|
|
110
|
+
controller.boot()
|
|
117
111
|
|
|
118
112
|
expect(function()
|
|
119
|
-
promiseBoundClass({} :: any,
|
|
113
|
+
promiseBoundClass({} :: any, controller.newInstance())
|
|
120
114
|
end).toThrow()
|
|
121
115
|
|
|
122
|
-
|
|
116
|
+
controller.destroy()
|
|
123
117
|
end)
|
|
124
118
|
|
|
125
119
|
it("throws when the instance is not an Instance", function()
|
|
126
|
-
local
|
|
127
|
-
|
|
120
|
+
local controller = setup()
|
|
121
|
+
controller.boot()
|
|
128
122
|
|
|
129
123
|
expect(function()
|
|
130
|
-
promiseBoundClass(
|
|
124
|
+
promiseBoundClass(controller.binder, 5 :: any)
|
|
131
125
|
end).toThrow()
|
|
132
126
|
|
|
133
|
-
|
|
127
|
+
controller.destroy()
|
|
134
128
|
end)
|
|
135
129
|
end)
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--[[
|
|
3
|
-
Coverage for BoundAncestorTracker, which exposes the bound class of a child's nearest bound
|
|
4
|
-
ancestor.
|
|
5
|
-
|
|
6
|
-
The binder is booted through a ServiceBag; the tracker is a plain BaseObject constructed
|
|
7
|
-
directly. Instances live under a workspace container so ancestry and CollectionService signals
|
|
8
|
-
fire. Ancestry changes and late binds are awaited event-driven via the tracker's Class.Changed
|
|
9
|
-
signal rather than a fixed sleep.
|
|
10
|
-
|
|
11
3
|
@class BoundAncestorTracker.spec.lua
|
|
12
4
|
]]
|
|
13
5
|
|
|
@@ -108,68 +100,68 @@ end
|
|
|
108
100
|
|
|
109
101
|
describe("BoundAncestorTracker tracking", function()
|
|
110
102
|
it("exposes the nearest bound ancestor's class", function()
|
|
111
|
-
local
|
|
103
|
+
local controller = setup()
|
|
112
104
|
|
|
113
|
-
local grandparent =
|
|
114
|
-
local parent =
|
|
115
|
-
local child =
|
|
116
|
-
|
|
117
|
-
|
|
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()
|
|
118
110
|
|
|
119
|
-
local tracker =
|
|
120
|
-
expect(tracker.Class.Value).toEqual(
|
|
111
|
+
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
112
|
+
expect(tracker.Class.Value).toEqual(controller.binder:Get(grandparent))
|
|
121
113
|
|
|
122
|
-
|
|
114
|
+
controller.destroy()
|
|
123
115
|
end)
|
|
124
116
|
|
|
125
117
|
it("has no value when no ancestor is bound", function()
|
|
126
|
-
local
|
|
118
|
+
local controller = setup()
|
|
127
119
|
|
|
128
|
-
local parent =
|
|
129
|
-
local child =
|
|
130
|
-
|
|
120
|
+
local parent = controller.newInstance()
|
|
121
|
+
local child = controller.newInstance(parent)
|
|
122
|
+
controller.boot()
|
|
131
123
|
|
|
132
|
-
local tracker =
|
|
124
|
+
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
133
125
|
expect(tracker.Class.Value).toBeNil()
|
|
134
126
|
|
|
135
|
-
|
|
127
|
+
controller.destroy()
|
|
136
128
|
end)
|
|
137
129
|
|
|
138
130
|
it("updates when an ancestor becomes bound", function()
|
|
139
|
-
local
|
|
131
|
+
local controller = setup()
|
|
140
132
|
|
|
141
133
|
-- The tracker resolves ancestors above the child's direct parent, so bind the grandparent.
|
|
142
|
-
local ancestor =
|
|
143
|
-
local parent =
|
|
144
|
-
local child =
|
|
145
|
-
|
|
134
|
+
local ancestor = controller.newInstance()
|
|
135
|
+
local parent = controller.newInstance(ancestor)
|
|
136
|
+
local child = controller.newInstance(parent)
|
|
137
|
+
controller.boot()
|
|
146
138
|
|
|
147
|
-
local tracker =
|
|
139
|
+
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
148
140
|
expect(tracker.Class.Value).toBeNil()
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
local value =
|
|
152
|
-
expect(value).toEqual(
|
|
142
|
+
controller.binder:Tag(ancestor)
|
|
143
|
+
local value = controller.awaitChange(tracker, nil)
|
|
144
|
+
expect(value).toEqual(controller.binder:Get(ancestor))
|
|
153
145
|
|
|
154
|
-
|
|
146
|
+
controller.destroy()
|
|
155
147
|
end)
|
|
156
148
|
|
|
157
149
|
it("clears the value when the child leaves the bound ancestry", function()
|
|
158
|
-
local
|
|
150
|
+
local controller = setup()
|
|
159
151
|
|
|
160
|
-
local ancestor =
|
|
161
|
-
local parent =
|
|
162
|
-
local child =
|
|
163
|
-
|
|
164
|
-
|
|
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()
|
|
165
157
|
|
|
166
|
-
local tracker =
|
|
167
|
-
local class =
|
|
158
|
+
local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
|
|
159
|
+
local class = controller.binder:Get(ancestor)
|
|
168
160
|
expect(tracker.Class.Value).toEqual(class)
|
|
169
161
|
|
|
170
|
-
child.Parent =
|
|
171
|
-
expect(
|
|
162
|
+
child.Parent = controller.container
|
|
163
|
+
expect(controller.awaitChange(tracker, class)).toBeNil()
|
|
172
164
|
|
|
173
|
-
|
|
165
|
+
controller.destroy()
|
|
174
166
|
end)
|
|
175
167
|
end)
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--[[
|
|
3
|
-
Coverage for BoundParentTracker, which exposes the bound class on a child's direct parent.
|
|
4
|
-
|
|
5
|
-
The binder is booted through a ServiceBag; the tracker is a plain BaseObject constructed
|
|
6
|
-
directly. Instances live under a workspace container so parent-change and CollectionService
|
|
7
|
-
signals fire. Parent changes and unbinds are awaited event-driven via the tracker's
|
|
8
|
-
Class.Changed signal rather than a fixed sleep.
|
|
9
|
-
|
|
10
3
|
@class BoundParentTracker.spec.lua
|
|
11
4
|
]]
|
|
12
5
|
|
|
@@ -74,7 +67,6 @@ local function setup()
|
|
|
74
67
|
serviceBag:Start()
|
|
75
68
|
end
|
|
76
69
|
|
|
77
|
-
-- Blocks until the tracker's value moves off `previous`.
|
|
78
70
|
local function awaitChange(tracker, previous)
|
|
79
71
|
if tracker.Class.Value == previous then
|
|
80
72
|
tracker.Class.Changed:Wait()
|
|
@@ -116,52 +108,52 @@ end)
|
|
|
116
108
|
|
|
117
109
|
describe("BoundParentTracker tracking", function()
|
|
118
110
|
it("exposes the bound class of the direct parent", function()
|
|
119
|
-
local
|
|
111
|
+
local controller = setup()
|
|
120
112
|
|
|
121
|
-
local parent =
|
|
122
|
-
local child =
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
local parent = controller.newInstance()
|
|
114
|
+
local child = controller.newInstance(parent)
|
|
115
|
+
controller.binder:Tag(parent)
|
|
116
|
+
controller.boot()
|
|
125
117
|
|
|
126
|
-
local tracker =
|
|
127
|
-
expect(tracker.Class.Value).toEqual(
|
|
118
|
+
local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
|
|
119
|
+
expect(tracker.Class.Value).toEqual(controller.binder:Get(parent))
|
|
128
120
|
|
|
129
|
-
|
|
121
|
+
controller.destroy()
|
|
130
122
|
end)
|
|
131
123
|
|
|
132
124
|
it("clears the value when the child is reparented off the bound parent", function()
|
|
133
|
-
local
|
|
125
|
+
local controller = setup()
|
|
134
126
|
|
|
135
|
-
local parent =
|
|
136
|
-
local child =
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
local parent = controller.newInstance()
|
|
128
|
+
local child = controller.newInstance(parent)
|
|
129
|
+
controller.binder:Tag(parent)
|
|
130
|
+
controller.boot()
|
|
139
131
|
|
|
140
|
-
local tracker =
|
|
141
|
-
local class =
|
|
132
|
+
local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
|
|
133
|
+
local class = controller.binder:Get(parent)
|
|
142
134
|
expect(tracker.Class.Value).toEqual(class)
|
|
143
135
|
|
|
144
|
-
child.Parent =
|
|
145
|
-
expect(
|
|
136
|
+
child.Parent = controller.container
|
|
137
|
+
expect(controller.awaitChange(tracker, class)).toBeNil()
|
|
146
138
|
|
|
147
|
-
|
|
139
|
+
controller.destroy()
|
|
148
140
|
end)
|
|
149
141
|
|
|
150
142
|
it("clears the value when the parent's class is unbound", function()
|
|
151
|
-
local
|
|
143
|
+
local controller = setup()
|
|
152
144
|
|
|
153
|
-
local parent =
|
|
154
|
-
local child =
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
local parent = controller.newInstance()
|
|
146
|
+
local child = controller.newInstance(parent)
|
|
147
|
+
controller.binder:Tag(parent)
|
|
148
|
+
controller.boot()
|
|
157
149
|
|
|
158
|
-
local tracker =
|
|
159
|
-
local class =
|
|
150
|
+
local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
|
|
151
|
+
local class = controller.binder:Get(parent)
|
|
160
152
|
expect(tracker.Class.Value).toEqual(class)
|
|
161
153
|
|
|
162
|
-
|
|
163
|
-
expect(
|
|
154
|
+
controller.binder:Untag(parent)
|
|
155
|
+
expect(controller.awaitChange(tracker, class)).toBeNil()
|
|
164
156
|
|
|
165
|
-
|
|
157
|
+
controller.destroy()
|
|
166
158
|
end)
|
|
167
159
|
end)
|