@player-tools/fluent 0.12.1--canary.241.6077

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.
Files changed (134) hide show
  1. package/dist/cjs/index.cjs +2396 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/index.legacy-esm.js +2276 -0
  4. package/dist/index.mjs +2276 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +38 -0
  7. package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
  8. package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
  9. package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
  10. package/src/core/base-builder/__tests__/registry.test.ts +534 -0
  11. package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
  12. package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
  13. package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
  14. package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
  15. package/src/core/base-builder/__tests__/switch.test.ts +815 -0
  16. package/src/core/base-builder/__tests__/template.test.ts +596 -0
  17. package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
  18. package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
  19. package/src/core/base-builder/conditional/index.ts +64 -0
  20. package/src/core/base-builder/context.ts +152 -0
  21. package/src/core/base-builder/errors.ts +69 -0
  22. package/src/core/base-builder/fluent-builder-base.ts +308 -0
  23. package/src/core/base-builder/guards.ts +137 -0
  24. package/src/core/base-builder/id/generator.ts +290 -0
  25. package/src/core/base-builder/id/registry.ts +152 -0
  26. package/src/core/base-builder/index.ts +72 -0
  27. package/src/core/base-builder/resolution/path-resolver.ts +116 -0
  28. package/src/core/base-builder/resolution/pipeline.ts +103 -0
  29. package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -0
  30. package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
  31. package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
  32. package/src/core/base-builder/resolution/steps/builders.ts +84 -0
  33. package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
  34. package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
  35. package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
  36. package/src/core/base-builder/resolution/steps/switches.ts +71 -0
  37. package/src/core/base-builder/resolution/steps/templates.ts +40 -0
  38. package/src/core/base-builder/resolution/value-resolver.ts +333 -0
  39. package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
  40. package/src/core/base-builder/storage/value-storage.ts +282 -0
  41. package/src/core/base-builder/types.ts +266 -0
  42. package/src/core/base-builder/utils.ts +10 -0
  43. package/src/core/flow/__tests__/index.test.ts +292 -0
  44. package/src/core/flow/index.ts +118 -0
  45. package/src/core/index.ts +8 -0
  46. package/src/core/mocks/generated/action.builder.ts +92 -0
  47. package/src/core/mocks/generated/choice-item.builder.ts +120 -0
  48. package/src/core/mocks/generated/choice.builder.ts +134 -0
  49. package/src/core/mocks/generated/collection.builder.ts +93 -0
  50. package/src/core/mocks/generated/field-collection.builder.ts +86 -0
  51. package/src/core/mocks/generated/index.ts +10 -0
  52. package/src/core/mocks/generated/info.builder.ts +64 -0
  53. package/src/core/mocks/generated/input.builder.ts +63 -0
  54. package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
  55. package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
  56. package/src/core/mocks/generated/text.builder.ts +47 -0
  57. package/src/core/mocks/index.ts +1 -0
  58. package/src/core/mocks/types/action.ts +92 -0
  59. package/src/core/mocks/types/choice.ts +129 -0
  60. package/src/core/mocks/types/collection.ts +140 -0
  61. package/src/core/mocks/types/info.ts +7 -0
  62. package/src/core/mocks/types/input.ts +7 -0
  63. package/src/core/mocks/types/text.ts +5 -0
  64. package/src/core/schema/__tests__/index.test.ts +127 -0
  65. package/src/core/schema/index.ts +195 -0
  66. package/src/core/schema/types.ts +7 -0
  67. package/src/core/switch/__tests__/index.test.ts +156 -0
  68. package/src/core/switch/index.ts +81 -0
  69. package/src/core/tagged-template/README.md +448 -0
  70. package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
  71. package/src/core/tagged-template/__tests__/index.test.ts +190 -0
  72. package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
  73. package/src/core/tagged-template/binding.ts +95 -0
  74. package/src/core/tagged-template/expression.ts +92 -0
  75. package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
  76. package/src/core/tagged-template/index.ts +5 -0
  77. package/src/core/tagged-template/std.ts +472 -0
  78. package/src/core/tagged-template/types.ts +123 -0
  79. package/src/core/template/__tests__/index.test.ts +380 -0
  80. package/src/core/template/index.ts +196 -0
  81. package/src/core/utils/index.ts +160 -0
  82. package/src/fp/README.md +411 -0
  83. package/src/fp/__tests__/index.test.ts +1178 -0
  84. package/src/fp/index.ts +386 -0
  85. package/src/gen/common.ts +15 -0
  86. package/src/index.ts +5 -0
  87. package/src/types.ts +203 -0
  88. package/types/core/base-builder/conditional/index.d.ts +21 -0
  89. package/types/core/base-builder/context.d.ts +39 -0
  90. package/types/core/base-builder/errors.d.ts +45 -0
  91. package/types/core/base-builder/fluent-builder-base.d.ts +147 -0
  92. package/types/core/base-builder/guards.d.ts +58 -0
  93. package/types/core/base-builder/id/generator.d.ts +69 -0
  94. package/types/core/base-builder/id/registry.d.ts +93 -0
  95. package/types/core/base-builder/index.d.ts +9 -0
  96. package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
  97. package/types/core/base-builder/resolution/pipeline.d.ts +27 -0
  98. package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
  99. package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
  100. package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
  101. package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
  102. package/types/core/base-builder/resolution/steps/nested-asset-wrappers.d.ts +14 -0
  103. package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
  104. package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
  105. package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
  106. package/types/core/base-builder/resolution/value-resolver.d.ts +62 -0
  107. package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
  108. package/types/core/base-builder/storage/value-storage.d.ts +82 -0
  109. package/types/core/base-builder/types.d.ts +183 -0
  110. package/types/core/base-builder/utils.d.ts +2 -0
  111. package/types/core/flow/index.d.ts +23 -0
  112. package/types/core/index.d.ts +8 -0
  113. package/types/core/mocks/index.d.ts +2 -0
  114. package/types/core/mocks/types/action.d.ts +58 -0
  115. package/types/core/mocks/types/choice.d.ts +95 -0
  116. package/types/core/mocks/types/collection.d.ts +102 -0
  117. package/types/core/mocks/types/info.d.ts +7 -0
  118. package/types/core/mocks/types/input.d.ts +7 -0
  119. package/types/core/mocks/types/text.d.ts +5 -0
  120. package/types/core/schema/index.d.ts +34 -0
  121. package/types/core/schema/types.d.ts +5 -0
  122. package/types/core/switch/index.d.ts +21 -0
  123. package/types/core/tagged-template/binding.d.ts +19 -0
  124. package/types/core/tagged-template/expression.d.ts +11 -0
  125. package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
  126. package/types/core/tagged-template/index.d.ts +6 -0
  127. package/types/core/tagged-template/std.d.ts +174 -0
  128. package/types/core/tagged-template/types.d.ts +69 -0
  129. package/types/core/template/index.d.ts +97 -0
  130. package/types/core/utils/index.d.ts +47 -0
  131. package/types/fp/index.d.ts +149 -0
  132. package/types/gen/common.d.ts +6 -0
  133. package/types/index.d.ts +3 -0
  134. package/types/types.d.ts +163 -0
@@ -0,0 +1,815 @@
1
+ import { describe, test, expect, beforeEach } from "vitest";
2
+ import { FluentBuilderBase } from "../fluent-builder-base";
3
+ import { BaseBuildContext, FluentBuilder } from "../types";
4
+ import { resetGlobalIdSet } from "../id/generator";
5
+ import { expression } from "../../tagged-template";
6
+ import type { Asset } from "@player-ui/types";
7
+
8
+ interface TestAsset extends Asset<"test"> {
9
+ value: string;
10
+ }
11
+
12
+ class TestAssetBuilder
13
+ extends FluentBuilderBase<TestAsset>
14
+ implements FluentBuilder<TestAsset, BaseBuildContext>
15
+ {
16
+ constructor() {
17
+ super({ type: "test" });
18
+ }
19
+
20
+ value(v: string): this {
21
+ return this.set("value", v);
22
+ }
23
+
24
+ id(v: string): this {
25
+ return this.set("id", v);
26
+ }
27
+
28
+ build(context?: BaseBuildContext): TestAsset {
29
+ return this.buildWithDefaults({ type: "test", value: "" }, context);
30
+ }
31
+ }
32
+
33
+ interface CollectionAsset extends Asset<"collection"> {
34
+ values?: Array<{ asset: Asset }>;
35
+ }
36
+
37
+ class CollectionBuilder
38
+ extends FluentBuilderBase<CollectionAsset>
39
+ implements FluentBuilder<CollectionAsset, BaseBuildContext>
40
+ {
41
+ // Mark 'values' as an array property to test array wrapping logic
42
+ static __arrayProperties__ = new Set(["values"]);
43
+
44
+ constructor() {
45
+ super({ type: "collection" });
46
+ }
47
+
48
+ withValues(v: Array<FluentBuilder<Asset, BaseBuildContext> | Asset>): this {
49
+ return this.set("values", { asset: v });
50
+ }
51
+
52
+ id(v: string): this {
53
+ return this.set("id", v);
54
+ }
55
+
56
+ build(context?: BaseBuildContext): CollectionAsset {
57
+ return this.buildWithDefaults({ type: "collection" }, context);
58
+ }
59
+ }
60
+
61
+ describe("Switch Integration with FluentBuilderBase", () => {
62
+ beforeEach(() => {
63
+ resetGlobalIdSet();
64
+ });
65
+
66
+ describe("Basic switch() method", () => {
67
+ test("can add a switch to a builder using the switch() method", () => {
68
+ const builder = new TestAssetBuilder()
69
+ .value("original")
70
+ .switch(["value"], {
71
+ cases: [
72
+ {
73
+ case: expression`user.lang === 'es'`,
74
+ asset: new TestAssetBuilder().value("Hola"),
75
+ },
76
+ { case: true, asset: new TestAssetBuilder().value("Hello") },
77
+ ],
78
+ });
79
+
80
+ const result = builder.build({ parentId: "test" });
81
+
82
+ expect(result).toEqual({
83
+ id: "test-test",
84
+ type: "test",
85
+ value: {
86
+ staticSwitch: [
87
+ {
88
+ case: "@[user.lang === 'es']@",
89
+ asset: {
90
+ value: "Hola",
91
+ type: "test",
92
+ id: "test-test-value-staticSwitch-0-test",
93
+ },
94
+ },
95
+ {
96
+ case: true,
97
+ asset: {
98
+ value: "Hello",
99
+ type: "test",
100
+ id: "test-test-value-staticSwitch-1-test",
101
+ },
102
+ },
103
+ ],
104
+ },
105
+ });
106
+ });
107
+
108
+ test("can add dynamic switch", () => {
109
+ const builder = new TestAssetBuilder()
110
+ .value("original")
111
+ .switch(["value"], {
112
+ cases: [{ case: true, asset: new TestAssetBuilder().value("Hi") }],
113
+ isDynamic: true,
114
+ });
115
+
116
+ const result = builder.build({ parentId: "test" });
117
+
118
+ expect(result).toEqual({
119
+ id: "test-test",
120
+ type: "test",
121
+ value: {
122
+ dynamicSwitch: [
123
+ {
124
+ case: true,
125
+ asset: {
126
+ value: "Hi",
127
+ type: "test",
128
+ id: "test-test-value-dynamicSwitch-0-test",
129
+ },
130
+ },
131
+ ],
132
+ },
133
+ });
134
+ });
135
+
136
+ test("preserves existing asset id when provided", () => {
137
+ const builder = new TestAssetBuilder()
138
+ .value("original")
139
+ .switch(["value"], {
140
+ cases: [
141
+ {
142
+ case: true,
143
+ asset: new TestAssetBuilder().value("Hello").id("custom-id"),
144
+ },
145
+ ],
146
+ });
147
+
148
+ const result = builder.build({ parentId: "test" });
149
+
150
+ expect(result.value).toEqual({
151
+ staticSwitch: [
152
+ {
153
+ case: true,
154
+ asset: {
155
+ value: "Hello",
156
+ type: "test",
157
+ id: "custom-id",
158
+ },
159
+ },
160
+ ],
161
+ });
162
+ });
163
+
164
+ test("handles multiple cases with sequential indexing", () => {
165
+ const builder = new TestAssetBuilder()
166
+ .value("original")
167
+ .switch(["value"], {
168
+ cases: [
169
+ {
170
+ case: expression`name.first === 'John'`,
171
+ asset: new TestAssetBuilder().value("First"),
172
+ },
173
+ {
174
+ case: expression`name.first === 'Jane'`,
175
+ asset: new TestAssetBuilder().value("Second"),
176
+ },
177
+ { case: true, asset: new TestAssetBuilder().value("Default") },
178
+ ],
179
+ });
180
+
181
+ const result = builder.build({ parentId: "test" });
182
+
183
+ expect(result.value).toEqual({
184
+ staticSwitch: [
185
+ {
186
+ case: "@[name.first === 'John']@",
187
+ asset: {
188
+ value: "First",
189
+ type: "test",
190
+ id: "test-test-value-staticSwitch-0-test",
191
+ },
192
+ },
193
+ {
194
+ case: "@[name.first === 'Jane']@",
195
+ asset: {
196
+ value: "Second",
197
+ type: "test",
198
+ id: "test-test-value-staticSwitch-1-test",
199
+ },
200
+ },
201
+ {
202
+ case: true,
203
+ asset: {
204
+ value: "Default",
205
+ type: "test",
206
+ id: "test-test-value-staticSwitch-2-test",
207
+ },
208
+ },
209
+ ],
210
+ });
211
+ });
212
+ });
213
+
214
+ describe("Switch with nested paths", () => {
215
+ test("can replace array element with switch", () => {
216
+ const builder = new CollectionBuilder()
217
+ .withValues([
218
+ new TestAssetBuilder().value("Item 1"),
219
+ new TestAssetBuilder().value("Item 2"),
220
+ new TestAssetBuilder().value("Item 3"),
221
+ ])
222
+ .switch(["values", 1], {
223
+ cases: [
224
+ {
225
+ case: expression`user.isAdmin`,
226
+ asset: new TestAssetBuilder().value("Admin Item"),
227
+ },
228
+ { case: true, asset: new TestAssetBuilder().value("Default") },
229
+ ],
230
+ });
231
+
232
+ const result = builder.build({ parentId: "test" });
233
+
234
+ expect(result).toEqual({
235
+ id: "test-collection",
236
+ type: "collection",
237
+ values: [
238
+ {
239
+ asset: {
240
+ id: "test-collection-values-0-test",
241
+ type: "test",
242
+ value: "Item 1",
243
+ },
244
+ },
245
+ {
246
+ staticSwitch: [
247
+ {
248
+ case: "@[user.isAdmin]@",
249
+ asset: {
250
+ value: "Admin Item",
251
+ type: "test",
252
+ id: "test-collection-values-staticSwitch-0-test",
253
+ },
254
+ },
255
+ {
256
+ case: true,
257
+ asset: {
258
+ value: "Default",
259
+ type: "test",
260
+ id: "test-collection-values-staticSwitch-1-test",
261
+ },
262
+ },
263
+ ],
264
+ },
265
+ {
266
+ asset: {
267
+ id: "test-collection-values-2-test",
268
+ type: "test",
269
+ value: "Item 3",
270
+ },
271
+ },
272
+ ],
273
+ });
274
+ });
275
+
276
+ test("can add multiple switches at different array indices", () => {
277
+ const builder = new CollectionBuilder()
278
+ .withValues([
279
+ new TestAssetBuilder().value("Item 1"),
280
+ new TestAssetBuilder().value("Item 2"),
281
+ new TestAssetBuilder().value("Item 3"),
282
+ ])
283
+ .switch(["values", 0], {
284
+ cases: [
285
+ {
286
+ case: expression`showSpecial`,
287
+ asset: new TestAssetBuilder().value("Special First"),
288
+ },
289
+ {
290
+ case: true,
291
+ asset: new TestAssetBuilder().value("Regular First"),
292
+ },
293
+ ],
294
+ })
295
+ .switch(["values", 2], {
296
+ cases: [
297
+ {
298
+ case: expression`user.isPremium`,
299
+ asset: new TestAssetBuilder().value("Premium Last"),
300
+ },
301
+ {
302
+ case: true,
303
+ asset: new TestAssetBuilder().value("Standard Last"),
304
+ },
305
+ ],
306
+ });
307
+
308
+ const result = builder.build({ parentId: "test" });
309
+
310
+ expect(result).toEqual({
311
+ id: "test-collection",
312
+ type: "collection",
313
+ values: [
314
+ {
315
+ staticSwitch: [
316
+ {
317
+ case: "@[showSpecial]@",
318
+ asset: {
319
+ value: "Special First",
320
+ type: "test",
321
+ id: "test-collection-values-staticSwitch-0-test",
322
+ },
323
+ },
324
+ {
325
+ case: true,
326
+ asset: {
327
+ value: "Regular First",
328
+ type: "test",
329
+ id: "test-collection-values-staticSwitch-1-test",
330
+ },
331
+ },
332
+ ],
333
+ },
334
+ {
335
+ asset: {
336
+ id: "test-collection-values-1-test",
337
+ type: "test",
338
+ value: "Item 2",
339
+ },
340
+ },
341
+ {
342
+ staticSwitch: [
343
+ {
344
+ case: "@[user.isPremium]@",
345
+ asset: {
346
+ value: "Premium Last",
347
+ type: "test",
348
+ id: "test-collection-values-staticSwitch-2-test",
349
+ },
350
+ },
351
+ {
352
+ case: true,
353
+ asset: {
354
+ value: "Standard Last",
355
+ type: "test",
356
+ id: "test-collection-values-staticSwitch-3-test",
357
+ },
358
+ },
359
+ ],
360
+ },
361
+ ],
362
+ });
363
+ });
364
+ });
365
+
366
+ describe("Switch reusability", () => {
367
+ test("can reuse the same builder with different contexts", () => {
368
+ const builder = new TestAssetBuilder()
369
+ .value("original")
370
+ .switch(["value"], {
371
+ cases: [{ case: true, asset: new TestAssetBuilder().value("Hi") }],
372
+ });
373
+
374
+ const result1 = builder.build({ parentId: "test1" });
375
+ const result2 = builder.build({ parentId: "test2" });
376
+
377
+ expect(result1.id).toBe("test1-test");
378
+ expect(result2.id).toBe("test2-test");
379
+
380
+ const switch1: unknown = result1.value;
381
+ const switch2: unknown = result2.value;
382
+
383
+ expect(switch1).toHaveProperty("staticSwitch");
384
+ expect(switch2).toHaveProperty("staticSwitch");
385
+
386
+ if (
387
+ typeof switch1 === "object" &&
388
+ switch1 !== null &&
389
+ "staticSwitch" in switch1 &&
390
+ typeof switch2 === "object" &&
391
+ switch2 !== null &&
392
+ "staticSwitch" in switch2
393
+ ) {
394
+ const staticSwitch1 = switch1.staticSwitch;
395
+ const staticSwitch2 = switch2.staticSwitch;
396
+
397
+ if (
398
+ Array.isArray(staticSwitch1) &&
399
+ Array.isArray(staticSwitch2) &&
400
+ staticSwitch1.length > 0 &&
401
+ staticSwitch2.length > 0
402
+ ) {
403
+ const case1 = staticSwitch1[0];
404
+ const case2 = staticSwitch2[0];
405
+
406
+ if (typeof case1 === "object" && case1 !== null && "asset" in case1) {
407
+ const asset1 = case1.asset;
408
+ if (
409
+ typeof asset1 === "object" &&
410
+ asset1 !== null &&
411
+ "id" in asset1
412
+ ) {
413
+ expect(asset1.id).toBe("test1-test-value-staticSwitch-0-test");
414
+ }
415
+ }
416
+
417
+ if (typeof case2 === "object" && case2 !== null && "asset" in case2) {
418
+ const asset2 = case2.asset;
419
+ if (
420
+ typeof asset2 === "object" &&
421
+ asset2 !== null &&
422
+ "id" in asset2
423
+ ) {
424
+ expect(asset2.id).toBe("test2-test-value-staticSwitch-0-test");
425
+ }
426
+ }
427
+ }
428
+ }
429
+ });
430
+ });
431
+
432
+ describe("Switch with string case expressions", () => {
433
+ test("handles string case expressions", () => {
434
+ const builder = new TestAssetBuilder()
435
+ .value("original")
436
+ .switch(["value"], {
437
+ cases: [
438
+ {
439
+ case: "{{foo}} === 'bar'",
440
+ asset: new TestAssetBuilder().value("Match"),
441
+ },
442
+ { case: true, asset: new TestAssetBuilder().value("No Match") },
443
+ ],
444
+ });
445
+
446
+ const result = builder.build({ parentId: "test" });
447
+
448
+ expect(result.value).toEqual({
449
+ staticSwitch: [
450
+ {
451
+ case: "{{foo}} === 'bar'",
452
+ asset: {
453
+ value: "Match",
454
+ type: "test",
455
+ id: "test-test-value-staticSwitch-0-test",
456
+ },
457
+ },
458
+ {
459
+ case: true,
460
+ asset: {
461
+ value: "No Match",
462
+ type: "test",
463
+ id: "test-test-value-staticSwitch-1-test",
464
+ },
465
+ },
466
+ ],
467
+ });
468
+ });
469
+
470
+ test("handles boolean case expressions", () => {
471
+ const builder = new TestAssetBuilder()
472
+ .value("original")
473
+ .switch(["value"], {
474
+ cases: [
475
+ { case: false, asset: new TestAssetBuilder().value("False") },
476
+ { case: true, asset: new TestAssetBuilder().value("True") },
477
+ ],
478
+ });
479
+
480
+ const result = builder.build({ parentId: "test" });
481
+
482
+ expect(result.value).toEqual({
483
+ staticSwitch: [
484
+ {
485
+ case: false,
486
+ asset: {
487
+ value: "False",
488
+ type: "test",
489
+ id: "test-test-value-staticSwitch-0-test",
490
+ },
491
+ },
492
+ {
493
+ case: true,
494
+ asset: {
495
+ value: "True",
496
+ type: "test",
497
+ id: "test-test-value-staticSwitch-1-test",
498
+ },
499
+ },
500
+ ],
501
+ });
502
+ });
503
+ });
504
+
505
+ describe("Switch array wrapping behavior (path.length check)", () => {
506
+ test("switches on entire array property should be wrapped in array", () => {
507
+ // When switching the entire 'values' array property (path.length === 1),
508
+ // the switch result should be wrapped in an array because 'values' is an array type
509
+ const builder = new CollectionBuilder().switch(["values"], {
510
+ cases: [
511
+ {
512
+ case: expression`user.isAdmin`,
513
+ asset: new TestAssetBuilder().value("Admin Only"),
514
+ },
515
+ {
516
+ case: true,
517
+ asset: new TestAssetBuilder().value("Default"),
518
+ },
519
+ ],
520
+ });
521
+
522
+ const result = builder.build({ parentId: "test" });
523
+
524
+ // The switch should be wrapped in an array: [{ staticSwitch: [...] }]
525
+ expect(result.values).toBeInstanceOf(Array);
526
+ expect(result.values).toHaveLength(1);
527
+ expect(result.values?.[0]).toHaveProperty("staticSwitch");
528
+ expect(result.values?.[0]).toEqual({
529
+ staticSwitch: [
530
+ {
531
+ case: "@[user.isAdmin]@",
532
+ asset: {
533
+ value: "Admin Only",
534
+ type: "test",
535
+ id: "test-collection-values-staticSwitch-0-test",
536
+ },
537
+ },
538
+ {
539
+ case: true,
540
+ asset: {
541
+ value: "Default",
542
+ type: "test",
543
+ id: "test-collection-values-staticSwitch-1-test",
544
+ },
545
+ },
546
+ ],
547
+ });
548
+ });
549
+
550
+ test("switches on specific array element should NOT be double-wrapped", () => {
551
+ // When switching a specific element in the array (path.length > 1),
552
+ // the switch result should NOT be wrapped in an additional array
553
+ const builder = new CollectionBuilder()
554
+ .withValues([
555
+ new TestAssetBuilder().value("Item 1"),
556
+ new TestAssetBuilder().value("Item 2"),
557
+ ])
558
+ .switch(["values", 1], {
559
+ cases: [
560
+ {
561
+ case: expression`user.isAdmin`,
562
+ asset: new TestAssetBuilder().value("Admin Item"),
563
+ },
564
+ {
565
+ case: true,
566
+ asset: new TestAssetBuilder().value("Default"),
567
+ },
568
+ ],
569
+ });
570
+
571
+ const result = builder.build({ parentId: "test" });
572
+
573
+ // The switch at index 1 should NOT be double-wrapped
574
+ // It should be: [{ asset: ... }, { staticSwitch: [...] }]
575
+ // NOT: [{ asset: ... }, [{ staticSwitch: [...] }]]
576
+ expect(result.values).toBeInstanceOf(Array);
577
+ expect(result.values).toHaveLength(2);
578
+
579
+ // First element should be the original asset
580
+ expect(result.values?.[0]).toEqual({
581
+ asset: {
582
+ id: "test-collection-values-0-test",
583
+ type: "test",
584
+ value: "Item 1",
585
+ },
586
+ });
587
+
588
+ // Second element should be the switch (NOT wrapped in an extra array)
589
+ expect(result.values?.[1]).toHaveProperty("staticSwitch");
590
+ expect(result.values?.[1]).not.toBeInstanceOf(Array);
591
+ expect(result.values?.[1]).toEqual({
592
+ staticSwitch: [
593
+ {
594
+ case: "@[user.isAdmin]@",
595
+ asset: {
596
+ value: "Admin Item",
597
+ type: "test",
598
+ id: "test-collection-values-staticSwitch-0-test",
599
+ },
600
+ },
601
+ {
602
+ case: true,
603
+ asset: {
604
+ value: "Default",
605
+ type: "test",
606
+ id: "test-collection-values-staticSwitch-1-test",
607
+ },
608
+ },
609
+ ],
610
+ });
611
+ });
612
+
613
+ test("multiple switches on different array indices should all be unwrapped", () => {
614
+ // Verify that multiple switches at different array indices all avoid double-wrapping
615
+ const builder = new CollectionBuilder()
616
+ .withValues([
617
+ new TestAssetBuilder().value("Item 1"),
618
+ new TestAssetBuilder().value("Item 2"),
619
+ new TestAssetBuilder().value("Item 3"),
620
+ ])
621
+ .switch(["values", 0], {
622
+ cases: [
623
+ {
624
+ case: expression`showFirst`,
625
+ asset: new TestAssetBuilder().value("First Switch"),
626
+ },
627
+ {
628
+ case: true,
629
+ asset: new TestAssetBuilder().value("First Default"),
630
+ },
631
+ ],
632
+ })
633
+ .switch(["values", 2], {
634
+ cases: [
635
+ {
636
+ case: expression`showLast`,
637
+ asset: new TestAssetBuilder().value("Last Switch"),
638
+ },
639
+ { case: true, asset: new TestAssetBuilder().value("Last Default") },
640
+ ],
641
+ });
642
+
643
+ const result = builder.build({ parentId: "test" });
644
+
645
+ expect(result.values).toBeInstanceOf(Array);
646
+ expect(result.values).toHaveLength(3);
647
+
648
+ // All array elements should contain switches, not arrays of switches
649
+ expect(result.values?.[0]).toHaveProperty("staticSwitch");
650
+ expect(result.values?.[0]).not.toBeInstanceOf(Array);
651
+
652
+ expect(result.values?.[1]).toEqual({
653
+ asset: {
654
+ id: "test-collection-values-1-test",
655
+ type: "test",
656
+ value: "Item 2",
657
+ },
658
+ });
659
+
660
+ expect(result.values?.[2]).toHaveProperty("staticSwitch");
661
+ expect(result.values?.[2]).not.toBeInstanceOf(Array);
662
+ });
663
+ });
664
+
665
+ describe("Case Index Offsets", () => {
666
+ test("first switch cases start at index 0", () => {
667
+ const builder = new TestAssetBuilder()
668
+ .value("original")
669
+ .switch(["value"], {
670
+ cases: [
671
+ {
672
+ case: expression`cond1`,
673
+ asset: new TestAssetBuilder().value("First"),
674
+ },
675
+ { case: true, asset: new TestAssetBuilder().value("Default") },
676
+ ],
677
+ });
678
+
679
+ const result = builder.build({ parentId: "test" });
680
+
681
+ // Case indices appear in generated IDs as staticSwitch-N
682
+ expect(result.value).toHaveProperty("staticSwitch");
683
+ if (
684
+ typeof result.value === "object" &&
685
+ result.value !== null &&
686
+ "staticSwitch" in result.value &&
687
+ Array.isArray(result.value.staticSwitch)
688
+ ) {
689
+ const cases = result.value.staticSwitch as Array<{
690
+ asset: { id: string };
691
+ }>;
692
+ expect(cases[0].asset.id).toContain("staticSwitch-0");
693
+ expect(cases[1].asset.id).toContain("staticSwitch-1");
694
+ }
695
+ });
696
+
697
+ test("second switch cases start after first switch", () => {
698
+ // When multiple switches exist on different properties,
699
+ // their case indices should be offset
700
+ const builder = new CollectionBuilder()
701
+ .withValues([new TestAssetBuilder().value("Item 1")])
702
+ .switch(["values", 0], {
703
+ cases: [
704
+ {
705
+ case: expression`cond1`,
706
+ asset: new TestAssetBuilder().value("First"),
707
+ },
708
+ {
709
+ case: expression`cond2`,
710
+ asset: new TestAssetBuilder().value("Second"),
711
+ },
712
+ { case: true, asset: new TestAssetBuilder().value("Third") },
713
+ ],
714
+ });
715
+
716
+ const result = builder.build({ parentId: "test" });
717
+
718
+ // With 3 cases in the first switch, indices 0-2 are used
719
+ if (
720
+ result.values &&
721
+ Array.isArray(result.values) &&
722
+ result.values[0] &&
723
+ typeof result.values[0] === "object" &&
724
+ "staticSwitch" in result.values[0] &&
725
+ Array.isArray(result.values[0].staticSwitch)
726
+ ) {
727
+ const cases = result.values[0].staticSwitch as Array<{
728
+ asset: { id: string };
729
+ }>;
730
+ expect(cases).toHaveLength(3);
731
+ expect(cases[0].asset.id).toContain("staticSwitch-0");
732
+ expect(cases[1].asset.id).toContain("staticSwitch-1");
733
+ expect(cases[2].asset.id).toContain("staticSwitch-2");
734
+ }
735
+ });
736
+
737
+ test("case indices appear in generated IDs", () => {
738
+ const builder = new TestAssetBuilder()
739
+ .value("original")
740
+ .switch(["value"], {
741
+ cases: [
742
+ { case: expression`a`, asset: new TestAssetBuilder().value("A") },
743
+ { case: expression`b`, asset: new TestAssetBuilder().value("B") },
744
+ { case: expression`c`, asset: new TestAssetBuilder().value("C") },
745
+ { case: expression`d`, asset: new TestAssetBuilder().value("D") },
746
+ { case: true, asset: new TestAssetBuilder().value("E") },
747
+ ],
748
+ });
749
+
750
+ const result = builder.build({ parentId: "test" });
751
+
752
+ if (
753
+ typeof result.value === "object" &&
754
+ result.value !== null &&
755
+ "staticSwitch" in result.value &&
756
+ Array.isArray(result.value.staticSwitch)
757
+ ) {
758
+ const cases = result.value.staticSwitch as Array<{
759
+ asset: { id: string };
760
+ }>;
761
+ expect(cases).toHaveLength(5);
762
+ expect(cases[0].asset.id).toBe("test-test-value-staticSwitch-0-test");
763
+ expect(cases[1].asset.id).toBe("test-test-value-staticSwitch-1-test");
764
+ expect(cases[2].asset.id).toBe("test-test-value-staticSwitch-2-test");
765
+ expect(cases[3].asset.id).toBe("test-test-value-staticSwitch-3-test");
766
+ expect(cases[4].asset.id).toBe("test-test-value-staticSwitch-4-test");
767
+ }
768
+ });
769
+
770
+ test("dynamic switch case indices follow same pattern", () => {
771
+ const builder = new TestAssetBuilder()
772
+ .value("original")
773
+ .switch(["value"], {
774
+ cases: [
775
+ {
776
+ case: expression`cond`,
777
+ asset: new TestAssetBuilder().value("A"),
778
+ },
779
+ { case: true, asset: new TestAssetBuilder().value("B") },
780
+ ],
781
+ isDynamic: true,
782
+ });
783
+
784
+ const result = builder.build({ parentId: "test" });
785
+
786
+ if (
787
+ typeof result.value === "object" &&
788
+ result.value !== null &&
789
+ "dynamicSwitch" in result.value &&
790
+ Array.isArray(result.value.dynamicSwitch)
791
+ ) {
792
+ const cases = result.value.dynamicSwitch as Array<{
793
+ asset: { id: string };
794
+ }>;
795
+ expect(cases[0].asset.id).toBe("test-test-value-dynamicSwitch-0-test");
796
+ expect(cases[1].asset.id).toBe("test-test-value-dynamicSwitch-1-test");
797
+ }
798
+ });
799
+ });
800
+
801
+ describe("Array Property Handling", () => {
802
+ test("wraps result in array for array-type properties", () => {
803
+ // When switching an entire array property, result should be wrapped
804
+ const builder = new CollectionBuilder().switch(["values"], {
805
+ cases: [{ case: true, asset: new TestAssetBuilder().value("Test") }],
806
+ });
807
+
808
+ const result = builder.build({ parentId: "test" });
809
+
810
+ // values should be an array containing the switch result
811
+ expect(result.values).toBeInstanceOf(Array);
812
+ expect(result.values).toHaveLength(1);
813
+ });
814
+ });
815
+ });