@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.
@@ -0,0 +1,167 @@
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)
@@ -0,0 +1,159 @@
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 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 = "BoundParentTrackerSpecClass"
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 = "BoundParentTrackerSpecContainer"
38
+ container.Parent = workspace
39
+
40
+ local instances = {}
41
+ local cleanups = {}
42
+ local booted = false
43
+
44
+ local binder = Binder.new(string.format("BoundParentTrackerSpecTag_%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("BoundParentTrackerSpecProvider_%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("BoundParentTracker.new()", function()
102
+ it("throws without a binder or child", function()
103
+ expect(function()
104
+ BoundParentTracker.new(nil :: any, Instance.new("Folder"))
105
+ end).toThrow()
106
+ end)
107
+ end)
108
+
109
+ describe("BoundParentTracker tracking", function()
110
+ it("exposes the bound class of the direct parent", function()
111
+ local controller = setup()
112
+
113
+ local parent = controller.newInstance()
114
+ local child = controller.newInstance(parent)
115
+ controller.binder:Tag(parent)
116
+ controller.boot()
117
+
118
+ local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
119
+ expect(tracker.Class.Value).toEqual(controller.binder:Get(parent))
120
+
121
+ controller.destroy()
122
+ end)
123
+
124
+ it("clears the value when the child is reparented off the bound parent", function()
125
+ local controller = setup()
126
+
127
+ local parent = controller.newInstance()
128
+ local child = controller.newInstance(parent)
129
+ controller.binder:Tag(parent)
130
+ controller.boot()
131
+
132
+ local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
133
+ local class = controller.binder:Get(parent)
134
+ expect(tracker.Class.Value).toEqual(class)
135
+
136
+ child.Parent = controller.container
137
+ expect(controller.awaitChange(tracker, class)).toBeNil()
138
+
139
+ controller.destroy()
140
+ end)
141
+
142
+ it("clears the value when the parent's class is unbound", function()
143
+ local controller = setup()
144
+
145
+ local parent = controller.newInstance()
146
+ local child = controller.newInstance(parent)
147
+ controller.binder:Tag(parent)
148
+ controller.boot()
149
+
150
+ local tracker = controller.track(BoundParentTracker.new(controller.binder, child))
151
+ local class = controller.binder:Get(parent)
152
+ expect(tracker.Class.Value).toEqual(class)
153
+
154
+ controller.binder:Untag(parent)
155
+ expect(controller.awaitChange(tracker, class)).toBeNil()
156
+
157
+ controller.destroy()
158
+ end)
159
+ end)