@quenty/binder 14.45.0 → 14.47.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 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
+ # [14.47.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/binder@14.46.0...@quenty/binder@14.47.0) (2026-09-23)
7
+
8
+ ### Bug Fixes
9
+
10
+ - Binder cleans up during construct/deconstruct moment ([404160e](https://github.com/Quenty/NevermoreEngine/commit/404160efddacbdf95eba0ede5e15066c5d71ed64))
11
+
12
+ # [14.46.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/binder@14.45.0...@quenty/binder@14.46.0) (2026-09-02)
13
+
14
+ ### Features
15
+
16
+ - Add RxBinderUtils.observeBoundAncestor<T>() and make sure everything is using binder:ObserveInstance() for performance ([9236c34](https://github.com/Quenty/NevermoreEngine/commit/9236c343fb438d75d29bcfcbaae71025fd344b0e))
17
+
6
18
  # [14.45.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/binder@14.44.0...@quenty/binder@14.45.0) (2026-08-28)
7
19
 
8
20
  **Note:** Version bump only for package @quenty/binder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/binder",
3
- "version": "14.45.0",
3
+ "version": "14.47.0",
4
4
  "description": "Utility object to Bind a class to Roblox object, and associated helper methods",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -42,11 +42,10 @@
42
42
  "@quenty/servicebag": "11.21.0",
43
43
  "@quenty/signal": "7.13.2",
44
44
  "@quenty/table": "3.9.2",
45
- "@quenty/valueobject": "13.38.0",
46
45
  "@quentystudios/jest-lua": "3.10.0-quenty.2"
47
46
  },
48
47
  "publishConfig": {
49
48
  "access": "public"
50
49
  },
51
- "gitHead": "346201e192e467cb71160f43029863853be855b5"
50
+ "gitHead": "31423d2314075bdd1f14ebbbc3eb637c5e0cb6ce"
52
51
  }
@@ -698,6 +698,12 @@ function Binder._add<T>(self: Binder<T>, inst: Instance)
698
698
  tostring(type(constructor) == "table" and constructor.ClassName or constructor)
699
699
  )
700
700
  )
701
+ if MaidTaskUtils.isValidTask(class) then
702
+ task.spawn(function()
703
+ MaidTaskUtils.doTask(class)
704
+ end)
705
+ end
706
+
701
707
  return
702
708
  end
703
709
 
@@ -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
- local class = self._binder:Get(inst)
132
- if not class then
133
- return
134
- end
126
+ self._maid[inst] = self._binder:ObserveInstance(inst, function(class)
127
+ self:_setChildClass(inst, class)
128
+ end)
135
129
 
136
- self:_addClass(class, doNotFire)
130
+ self:_setChildClass(inst, self._binder:Get(inst), doNotFire)
137
131
  end
138
132
 
139
- function BoundChildCollection._handleNewClassBound<T>(self: BoundChildCollection<T>, class: T, inst: Instance): ()
140
- if inst.Parent ~= self._parent then
141
- return
142
- end
133
+ function BoundChildCollection._removeChild<T>(self: BoundChildCollection<T>, inst: Instance): ()
134
+ self._maid[inst] = nil
143
135
 
144
- self:_addClass(class)
136
+ self:_setChildClass(inst, nil)
145
137
  end
146
138
 
147
- function BoundChildCollection._removeChild<T>(self: BoundChildCollection<T>, inst: Instance): ()
148
- local class = self._binder:Get(inst)
149
- if not class then
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
- self:_removeClass(class)
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?): ()
@@ -161,6 +161,96 @@ describe("BoundChildCollection dynamic updates", function()
161
161
  controller:Destroy()
162
162
  end)
163
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()
252
+ end)
253
+
164
254
  it("fires ClassRemoved when a tracked child is reparented out", function()
165
255
  local controller = setup()
166
256
 
@@ -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,179 +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 JestUtils = require("JestUtils")
13
- local Maid = require("Maid")
14
- local ServiceBag = require("ServiceBag")
15
-
16
- local describe = Jest.Globals.describe
17
- local expect = Jest.Globals.expect
18
- local it = Jest.Globals.it
19
-
20
- local specCounter = 0
21
-
22
- local function makeClass()
23
- local Class = {}
24
- Class.__index = Class
25
- Class.ClassName = "BoundAncestorTrackerSpecClass"
26
- function Class.new(inst)
27
- return setmetatable({ instance = inst }, Class)
28
- end
29
- function Class:Destroy() end
30
- return Class
31
- end
32
-
33
- local function setup()
34
- local maid = Maid.new()
35
-
36
- specCounter += 1
37
- local suffix = specCounter
38
-
39
- local serviceBag = ServiceBag.new()
40
- local container = Instance.new("Folder")
41
- container.Name = "BoundAncestorTrackerSpecContainer"
42
- container.Parent = workspace
43
-
44
- local instances: { Instance } = {}
45
- local cleanups: { any } = {}
46
- local booted = false
47
-
48
- local binder = Binder.new(string.format("BoundAncestorTrackerSpecTag_%d", suffix), makeClass() :: any)
49
-
50
- local function newInstance(parent: Instance?): Instance
51
- local inst = Instance.new("Folder")
52
- inst.Parent = parent or container
53
- table.insert(instances, inst)
54
- return inst
55
- end
56
-
57
- local function track(item: any): any
58
- table.insert(cleanups, item)
59
- return item
60
- end
61
-
62
- local function boot()
63
- assert(not booted, "Already booted")
64
- booted = true
65
-
66
- local provider = BinderProvider.new(string.format("BoundAncestorTrackerSpecProvider_%d", suffix), function(self)
67
- self:Add(binder)
68
- end)
69
- serviceBag:GetService(provider)
70
- serviceBag:Init()
71
- serviceBag:Start()
72
- end
73
-
74
- local function awaitChange(tracker, previous)
75
- if tracker.Class.Value == previous then
76
- tracker.Class.Changed:Wait()
77
- end
78
- return tracker.Class.Value
79
- end
80
-
81
- maid:GiveTask(function()
82
- for _, item in cleanups do
83
- pcall(function()
84
- item:Destroy()
85
- end)
86
- end
87
- serviceBag:Destroy()
88
- for _, inst in instances do
89
- pcall(function()
90
- inst:Destroy()
91
- end)
92
- end
93
- container:Destroy()
94
- end)
95
-
96
- local controller = {
97
- container = container,
98
- binder = binder,
99
- newInstance = newInstance,
100
- track = track,
101
- boot = boot,
102
- awaitChange = awaitChange,
103
- Destroy = function(_self)
104
- maid:DoCleaning()
105
- end,
106
- }
107
-
108
- maid:GiveTask(JestUtils.afterThis(controller))
109
-
110
- return controller
111
- end
112
-
113
- describe("BoundAncestorTracker tracking", function()
114
- it("exposes the nearest bound ancestor's class", function()
115
- local controller = setup()
116
-
117
- local grandparent = controller.newInstance()
118
- local parent = controller.newInstance(grandparent)
119
- local child = controller.newInstance(parent)
120
- controller.binder:Tag(grandparent)
121
- controller.boot()
122
-
123
- local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
124
- expect(tracker.Class.Value).toEqual(controller.binder:Get(grandparent))
125
-
126
- controller:Destroy()
127
- end)
128
-
129
- it("has no value when no ancestor is bound", function()
130
- local controller = setup()
131
-
132
- local parent = controller.newInstance()
133
- local child = controller.newInstance(parent)
134
- controller.boot()
135
-
136
- local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
137
- expect(tracker.Class.Value).toBeNil()
138
-
139
- controller:Destroy()
140
- end)
141
-
142
- it("updates when an ancestor becomes bound", function()
143
- local controller = setup()
144
-
145
- -- The tracker resolves ancestors above the child's direct parent, so bind the grandparent.
146
- local ancestor = controller.newInstance()
147
- local parent = controller.newInstance(ancestor)
148
- local child = controller.newInstance(parent)
149
- controller.boot()
150
-
151
- local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
152
- expect(tracker.Class.Value).toBeNil()
153
-
154
- controller.binder:Tag(ancestor)
155
- local value = controller.awaitChange(tracker, nil)
156
- expect(value).toEqual(controller.binder:Get(ancestor))
157
-
158
- controller:Destroy()
159
- end)
160
-
161
- it("clears the value when the child leaves the bound ancestry", function()
162
- local controller = setup()
163
-
164
- local ancestor = controller.newInstance()
165
- local parent = controller.newInstance(ancestor)
166
- local child = controller.newInstance(parent)
167
- controller.binder:Tag(ancestor)
168
- controller.boot()
169
-
170
- local tracker = controller.track(BoundAncestorTracker.new(controller.binder, child))
171
- local class = controller.binder:Get(ancestor)
172
- expect(tracker.Class.Value).toEqual(class)
173
-
174
- child.Parent = controller.container
175
- expect(controller.awaitChange(tracker, class)).toBeNil()
176
-
177
- controller:Destroy()
178
- end)
179
- 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
@@ -1,171 +0,0 @@
1
- --!strict
2
- --[[
3
- @class BoundParentTracker.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 BoundParentTracker = require("BoundParentTracker")
11
- local Jest = require("Jest")
12
- local JestUtils = require("JestUtils")
13
- local Maid = require("Maid")
14
- local ServiceBag = require("ServiceBag")
15
-
16
- local describe = Jest.Globals.describe
17
- local expect = Jest.Globals.expect
18
- local it = Jest.Globals.it
19
-
20
- local specCounter = 0
21
-
22
- local function makeClass()
23
- local Class = {}
24
- Class.__index = Class
25
- Class.ClassName = "BoundParentTrackerSpecClass"
26
- function Class.new(inst)
27
- return setmetatable({ instance = inst }, Class)
28
- end
29
- function Class:Destroy() end
30
- return Class
31
- end
32
-
33
- local function setup()
34
- local maid = Maid.new()
35
-
36
- specCounter += 1
37
- local suffix = specCounter
38
-
39
- local serviceBag = ServiceBag.new()
40
- local container = Instance.new("Folder")
41
- container.Name = "BoundParentTrackerSpecContainer"
42
- container.Parent = workspace
43
-
44
- local instances: { Instance } = {}
45
- local cleanups: { any } = {}
46
- local booted = false
47
-
48
- local binder = Binder.new(string.format("BoundParentTrackerSpecTag_%d", suffix), makeClass() :: any)
49
-
50
- local function newInstance(parent: Instance?): Instance
51
- local inst = Instance.new("Folder")
52
- inst.Parent = parent or container
53
- table.insert(instances, inst)
54
- return inst
55
- end
56
-
57
- local function track(item: any): any
58
- table.insert(cleanups, item)
59
- return item
60
- end
61
-
62
- local function boot()
63
- assert(not booted, "Already booted")
64
- booted = true
65
-
66
- local provider = BinderProvider.new(string.format("BoundParentTrackerSpecProvider_%d", suffix), function(self)
67
- self:Add(binder)
68
- end)
69
- serviceBag:GetService(provider)
70
- serviceBag:Init()
71
- serviceBag:Start()
72
- end
73
-
74
- local function awaitChange(tracker, previous)
75
- if tracker.Class.Value == previous then
76
- tracker.Class.Changed:Wait()
77
- end
78
- return tracker.Class.Value
79
- end
80
-
81
- maid:GiveTask(function()
82
- for _, item in cleanups do
83
- pcall(function()
84
- item:Destroy()
85
- end)
86
- end
87
- serviceBag:Destroy()
88
- for _, inst in instances do
89
- pcall(function()
90
- inst:Destroy()
91
- end)
92
- end
93
- container:Destroy()
94
- end)
95
-
96
- local controller = {
97
- container = container,
98
- binder = binder,
99
- newInstance = newInstance,
100
- track = track,
101
- boot = boot,
102
- awaitChange = awaitChange,
103
- Destroy = function(_self)
104
- maid:DoCleaning()
105
- end,
106
- }
107
-
108
- maid:GiveTask(JestUtils.afterThis(controller))
109
-
110
- return controller
111
- end
112
-
113
- describe("BoundParentTracker.new()", function()
114
- it("throws without a binder or child", function()
115
- expect(function()
116
- BoundParentTracker.new(nil :: any, Instance.new("Folder"))
117
- end).toThrow()
118
- end)
119
- end)
120
-
121
- describe("BoundParentTracker tracking", function()
122
- it("exposes the bound class of the direct parent", function()
123
- local controller = setup()
124
-
125
- local parent = controller.newInstance()
126
- local child = controller.newInstance(parent)
127
- controller.binder:Tag(parent)
128
- controller.boot()
129
-
130
- local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
131
- expect(tracker.Class.Value).toEqual(controller.binder:Get(parent))
132
-
133
- controller:Destroy()
134
- end)
135
-
136
- it("clears the value when the child is reparented off the bound parent", function()
137
- local controller = setup()
138
-
139
- local parent = controller.newInstance()
140
- local child = controller.newInstance(parent)
141
- controller.binder:Tag(parent)
142
- controller.boot()
143
-
144
- local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
145
- local class = controller.binder:Get(parent)
146
- expect(tracker.Class.Value).toEqual(class)
147
-
148
- child.Parent = controller.container
149
- expect(controller.awaitChange(tracker, class)).toBeNil()
150
-
151
- controller:Destroy()
152
- end)
153
-
154
- it("clears the value when the parent's class is unbound", function()
155
- local controller = setup()
156
-
157
- local parent = controller.newInstance()
158
- local child = controller.newInstance(parent)
159
- controller.binder:Tag(parent)
160
- controller.boot()
161
-
162
- local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
163
- local class = controller.binder:Get(parent)
164
- expect(tracker.Class.Value).toEqual(class)
165
-
166
- controller.binder:Untag(parent)
167
- expect(controller.awaitChange(tracker, class)).toBeNil()
168
-
169
- controller:Destroy()
170
- end)
171
- end)