@quenty/instanceutils 13.29.2 → 13.30.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,10 @@
1
+ {
2
+ "targets": {
3
+ "test": {
4
+ "universeId": 9716264427,
5
+ "placeId": 140543867367971,
6
+ "project": "test/default.project.json",
7
+ "scriptTemplate": "test/scripts/Server/ServerMain.server.lua"
8
+ }
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/instanceutils",
3
- "version": "13.29.2",
3
+ "version": "13.30.0",
4
4
  "description": "Utility functions involving instances in Roblox",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -29,7 +29,7 @@
29
29
  "Quenty"
30
30
  ],
31
31
  "dependencies": {
32
- "@quenty/brio": "14.29.2",
32
+ "@quenty/brio": "14.30.0",
33
33
  "@quenty/loader": "10.11.0",
34
34
  "@quenty/maid": "3.9.0",
35
35
  "@quenty/nevermore-test-runner": "1.4.0",
@@ -40,5 +40,5 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "7672b52f13af6a10df1b189d19d6e2404b5b3e55"
43
+ "gitHead": "f4a374a0a294ee8900aa5cb68ab138b0acf3e0ae"
44
44
  }
@@ -192,23 +192,30 @@ function RxInstanceUtils.observePropertyBrio(
192
192
  end) :: any
193
193
  end
194
194
 
195
+ function RxInstanceUtils._toPredicate(className: string | Rx.Predicate<Instance>): Rx.Predicate<Instance>
196
+ if type(className) == "string" then
197
+ return function(instance: Instance)
198
+ return instance:IsA(className)
199
+ end
200
+ else
201
+ return className
202
+ end
203
+ end
204
+
195
205
  --[=[
196
206
  Observes the last child with a specific name.
197
-
198
- @param parent Instance
199
- @param className string
200
- @param name string
201
- @return Observable<Brio<Instance>>
202
207
  ]=]
203
208
  function RxInstanceUtils.observeLastNamedChildBrio(
204
209
  parent: Instance,
205
- className: string,
210
+ className: string | Rx.Predicate<Instance>,
206
211
  name: string
207
212
  ): Observable.Observable<Brio.Brio<Instance>>
208
213
  assert(typeof(parent) == "Instance", "Bad parent")
209
- assert(type(className) == "string", "Bad className")
214
+ assert(type(className) == "string" or type(className) == "function", "Bad className")
210
215
  assert(type(name) == "string", "Bad name")
211
216
 
217
+ local predicate = RxInstanceUtils._toPredicate(className)
218
+
212
219
  return Observable.new(function(sub)
213
220
  local topMaid = Maid.new()
214
221
  local validChildren = {}
@@ -231,7 +238,7 @@ function RxInstanceUtils.observeLastNamedChildBrio(
231
238
  end
232
239
 
233
240
  local function handleChild(child: Instance)
234
- if not child:IsA(className) then
241
+ if not predicate(child) then
235
242
  return
236
243
  end
237
244
 
@@ -271,26 +278,23 @@ end
271
278
 
272
279
  --[=[
273
280
  Observes the children with a specific name.
274
-
275
- @param parent Instance
276
- @param className string
277
- @param name string
278
- @return Observable<Brio<Instance>>
279
281
  ]=]
280
282
  function RxInstanceUtils.observeChildrenOfNameBrio(
281
283
  parent: Instance,
282
- className: string,
284
+ className: string | Rx.Predicate<Instance>,
283
285
  name: string
284
286
  ): Observable.Observable<Brio.Brio<Instance>>
285
287
  assert(typeof(parent) == "Instance", "Bad parent")
286
- assert(type(className) == "string", "Bad className")
288
+ assert(type(className) == "string" or type(className) == "function", "Bad className")
287
289
  assert(type(name) == "string", "Bad name")
288
290
 
291
+ local predicate = RxInstanceUtils._toPredicate(className)
292
+
289
293
  return Observable.new(function(sub)
290
294
  local topMaid = Maid.new()
291
295
 
292
296
  local function handleChild(child: Instance)
293
- if not child:IsA(className) then
297
+ if not predicate(child) then
294
298
  return
295
299
  end
296
300
 
@@ -328,10 +332,6 @@ end
328
332
 
329
333
  --[=[
330
334
  Observes all children of a specific class
331
-
332
- @param parent Instance
333
- @param className string
334
- @return Observable<Brio<Instance>>
335
335
  ]=]
336
336
  function RxInstanceUtils.observeChildrenOfClassBrio(
337
337
  parent: Instance,
@@ -7,6 +7,7 @@ local require = (require :: any)(
7
7
  game:GetService("ServerScriptService"):FindFirstChild("LoaderUtils", true).Parent
8
8
  ).bootstrapStory(script) :: typeof(require(script.Parent.loader).load(script))
9
9
 
10
+ local Brio = require("Brio")
10
11
  local Jest = require("Jest")
11
12
  local RxInstanceUtils = require("RxInstanceUtils")
12
13
 
@@ -27,3 +28,343 @@ describe("RxInstanceUtils.observeChildrenBrio", function()
27
28
  expect(externalResult).toEqual(nil)
28
29
  end)
29
30
  end)
31
+
32
+ describe("RxInstanceUtils.observeLastNamedChildBrio", function()
33
+ describe("with no children", function()
34
+ it("should not emit anything", function()
35
+ local parent = Instance.new("Folder")
36
+ local lastBrio = nil
37
+ local fireCount = 0
38
+
39
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
40
+ lastBrio = brio
41
+ fireCount = fireCount + 1
42
+ end)
43
+
44
+ expect(fireCount).toEqual(0)
45
+ expect(lastBrio).toBeNil()
46
+
47
+ sub:Destroy()
48
+ end)
49
+ end)
50
+
51
+ describe("with a matching child already present", function()
52
+ it("should emit a brio with the child", function()
53
+ local parent = Instance.new("Folder")
54
+ local child = Instance.new("Part")
55
+ child.Name = "Target"
56
+ child.Parent = parent
57
+
58
+ local lastBrio = nil
59
+ local fireCount = 0
60
+
61
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
62
+ lastBrio = brio
63
+ fireCount = fireCount + 1
64
+ end)
65
+
66
+ expect(fireCount).toEqual(1)
67
+ expect(lastBrio).never.toBeNil()
68
+ expect(Brio.isBrio(lastBrio)).toEqual(true)
69
+ expect(lastBrio:IsDead()).toEqual(false)
70
+ expect(lastBrio:GetValue()).toEqual(child)
71
+
72
+ sub:Destroy()
73
+ end)
74
+ end)
75
+
76
+ describe("with wrong className", function()
77
+ it("should not emit for a child with the wrong class", function()
78
+ local parent = Instance.new("Folder")
79
+ local child = Instance.new("Folder")
80
+ child.Name = "Target"
81
+ child.Parent = parent
82
+
83
+ local fireCount = 0
84
+
85
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function()
86
+ fireCount = fireCount + 1
87
+ end)
88
+
89
+ expect(fireCount).toEqual(0)
90
+
91
+ sub:Destroy()
92
+ end)
93
+ end)
94
+
95
+ describe("with wrong name", function()
96
+ it("should not emit for a child with the wrong name", function()
97
+ local parent = Instance.new("Folder")
98
+ local child = Instance.new("Part")
99
+ child.Name = "Wrong"
100
+ child.Parent = parent
101
+
102
+ local fireCount = 0
103
+
104
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function()
105
+ fireCount = fireCount + 1
106
+ end)
107
+
108
+ expect(fireCount).toEqual(0)
109
+
110
+ sub:Destroy()
111
+ end)
112
+ end)
113
+
114
+ describe("when a child is added after subscription", function()
115
+ it("should emit when a matching child is added", function()
116
+ local parent = Instance.new("Folder")
117
+ local lastBrio = nil
118
+ local fireCount = 0
119
+
120
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
121
+ lastBrio = brio
122
+ fireCount = fireCount + 1
123
+ end)
124
+
125
+ expect(fireCount).toEqual(0)
126
+
127
+ local child = Instance.new("Part")
128
+ child.Name = "Target"
129
+ child.Parent = parent
130
+
131
+ expect(fireCount).toEqual(1)
132
+ expect(lastBrio).never.toBeNil()
133
+ expect(Brio.isBrio(lastBrio)).toEqual(true)
134
+ expect(lastBrio:GetValue()).toEqual(child)
135
+
136
+ sub:Destroy()
137
+ end)
138
+ end)
139
+
140
+ describe("when a matching child is removed", function()
141
+ it("should kill the previous brio", function()
142
+ local parent = Instance.new("Folder")
143
+ local child = Instance.new("Part")
144
+ child.Name = "Target"
145
+ child.Parent = parent
146
+
147
+ local lastBrio = nil
148
+ local fireCount = 0
149
+
150
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
151
+ lastBrio = brio
152
+ fireCount = fireCount + 1
153
+ end)
154
+
155
+ expect(fireCount).toEqual(1)
156
+ local firstBrio = lastBrio
157
+
158
+ child.Parent = nil
159
+
160
+ expect(firstBrio:IsDead()).toEqual(true)
161
+
162
+ sub:Destroy()
163
+ end)
164
+ end)
165
+
166
+ describe("when child name changes to match", function()
167
+ it("should emit when a child is renamed to the target name", function()
168
+ local parent = Instance.new("Folder")
169
+ local child = Instance.new("Part")
170
+ child.Name = "Wrong"
171
+ child.Parent = parent
172
+
173
+ local lastBrio = nil
174
+ local fireCount = 0
175
+
176
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
177
+ lastBrio = brio
178
+ fireCount = fireCount + 1
179
+ end)
180
+
181
+ expect(fireCount).toEqual(0)
182
+
183
+ child.Name = "Target"
184
+
185
+ expect(fireCount).toEqual(1)
186
+ expect(lastBrio).never.toBeNil()
187
+ expect(lastBrio:GetValue()).toEqual(child)
188
+
189
+ sub:Destroy()
190
+ end)
191
+ end)
192
+
193
+ describe("when child name changes away from match", function()
194
+ it("should kill the brio when a child is renamed away", function()
195
+ local parent = Instance.new("Folder")
196
+ local child = Instance.new("Part")
197
+ child.Name = "Target"
198
+ child.Parent = parent
199
+
200
+ local lastBrio = nil
201
+ local fireCount = 0
202
+
203
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
204
+ lastBrio = brio
205
+ fireCount = fireCount + 1
206
+ end)
207
+
208
+ expect(fireCount).toEqual(1)
209
+ local firstBrio = lastBrio
210
+
211
+ child.Name = "NotTarget"
212
+
213
+ expect(firstBrio:IsDead()).toEqual(true)
214
+
215
+ sub:Destroy()
216
+ end)
217
+ end)
218
+
219
+ describe("with multiple matching children", function()
220
+ it("should emit a brio with one of the children", function()
221
+ local parent = Instance.new("Folder")
222
+ local child1 = Instance.new("Part")
223
+ child1.Name = "Target"
224
+ child1.Parent = parent
225
+ local child2 = Instance.new("Part")
226
+ child2.Name = "Target"
227
+ child2.Parent = parent
228
+
229
+ local lastBrio = nil
230
+ local fireCount = 0
231
+
232
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
233
+ lastBrio = brio
234
+ fireCount = fireCount + 1
235
+ end)
236
+
237
+ -- Should have emitted (possibly more than once as children are iterated)
238
+ expect(lastBrio).never.toBeNil()
239
+ expect(Brio.isBrio(lastBrio)).toEqual(true)
240
+ expect(lastBrio:IsDead()).toEqual(false)
241
+
242
+ local value = lastBrio:GetValue()
243
+ -- The emitted child should be one of the two matching children
244
+ expect(value:IsA("Part")).toEqual(true)
245
+ expect(value.Name).toEqual("Target")
246
+
247
+ sub:Destroy()
248
+ end)
249
+
250
+ it("should switch to remaining child when the emitted one is removed", function()
251
+ local parent = Instance.new("Folder")
252
+ local child1 = Instance.new("Part")
253
+ child1.Name = "Target"
254
+ child1.Parent = parent
255
+ local child2 = Instance.new("Part")
256
+ child2.Name = "Target"
257
+ child2.Parent = parent
258
+
259
+ local lastBrio = nil
260
+
261
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
262
+ lastBrio = brio
263
+ end)
264
+
265
+ local emittedChild = lastBrio:GetValue()
266
+ local otherChild = if emittedChild == child1 then child2 else child1
267
+
268
+ -- Remove the currently emitted child
269
+ emittedChild.Parent = nil
270
+
271
+ -- Should now emit the other child
272
+ expect(lastBrio).never.toBeNil()
273
+ expect(lastBrio:IsDead()).toEqual(false)
274
+ expect(lastBrio:GetValue()).toEqual(otherChild)
275
+
276
+ sub:Destroy()
277
+ end)
278
+
279
+ it("should emit nothing when all matching children are removed", function()
280
+ local parent = Instance.new("Folder")
281
+ local child1 = Instance.new("Part")
282
+ child1.Name = "Target"
283
+ child1.Parent = parent
284
+ local child2 = Instance.new("Part")
285
+ child2.Name = "Target"
286
+ child2.Parent = parent
287
+
288
+ local lastBrio = nil
289
+
290
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
291
+ lastBrio = brio
292
+ end)
293
+
294
+ expect(lastBrio).never.toBeNil()
295
+
296
+ child1.Parent = nil
297
+ child2.Parent = nil
298
+
299
+ -- The last brio should be dead since no matching children remain
300
+ expect(lastBrio:IsDead()).toEqual(true)
301
+
302
+ sub:Destroy()
303
+ end)
304
+ end)
305
+
306
+ describe("subscription cleanup", function()
307
+ it("should kill the brio when subscription is destroyed", function()
308
+ local parent = Instance.new("Folder")
309
+ local child = Instance.new("Part")
310
+ child.Name = "Target"
311
+ child.Parent = parent
312
+
313
+ local lastBrio = nil
314
+
315
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function(brio)
316
+ lastBrio = brio
317
+ end)
318
+
319
+ expect(lastBrio).never.toBeNil()
320
+ expect(lastBrio:IsDead()).toEqual(false)
321
+
322
+ sub:Destroy()
323
+
324
+ expect(lastBrio:IsDead()).toEqual(true)
325
+ end)
326
+ end)
327
+
328
+ describe("className inheritance", function()
329
+ it("should match subclasses via IsA", function()
330
+ local parent = Instance.new("Folder")
331
+ local child = Instance.new("Part")
332
+ child.Name = "Target"
333
+ child.Parent = parent
334
+
335
+ local lastBrio = nil
336
+ local fireCount = 0
337
+
338
+ -- Part IsA BasePart, so this should match
339
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "BasePart", "Target"):Subscribe(function(brio)
340
+ lastBrio = brio
341
+ fireCount = fireCount + 1
342
+ end)
343
+
344
+ expect(fireCount).toEqual(1)
345
+ expect(lastBrio:GetValue()).toEqual(child)
346
+
347
+ sub:Destroy()
348
+ end)
349
+ end)
350
+
351
+ describe("non-matching child added then removed", function()
352
+ it("should not emit for non-matching children", function()
353
+ local parent = Instance.new("Folder")
354
+ local fireCount = 0
355
+
356
+ local sub = RxInstanceUtils.observeLastNamedChildBrio(parent, "Part", "Target"):Subscribe(function()
357
+ fireCount = fireCount + 1
358
+ end)
359
+
360
+ local child = Instance.new("Folder")
361
+ child.Name = "Target"
362
+ child.Parent = parent
363
+ child.Parent = nil
364
+
365
+ expect(fireCount).toEqual(0)
366
+
367
+ sub:Destroy()
368
+ end)
369
+ end)
370
+ end)
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "InstanceUtilsTest",
3
+ "tree": {
4
+ "$className": "DataModel",
5
+ "ServerScriptService": {
6
+ "$properties": {
7
+ "LoadStringEnabled": true
8
+ },
9
+ "instanceutils": {
10
+ "$path": ".."
11
+ },
12
+ "Script": {
13
+ "$path": "scripts/Server"
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ --!nonstrict
2
+ --[[
3
+ @class ServerMain
4
+ ]]
5
+ local ServerScriptService = game:GetService("ServerScriptService")
6
+
7
+ local root = ServerScriptService.instanceutils
8
+ local loader = root:FindFirstChild("LoaderUtils", true).Parent
9
+ local require = require(loader).bootstrapGame(root)
10
+
11
+ local NevermoreTestRunnerUtils = require("NevermoreTestRunnerUtils")
12
+
13
+ if NevermoreTestRunnerUtils.runTestsIfNeededAsync(root) then
14
+ return
15
+ end