@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,658 @@
1
+ import { describe, test, expect, beforeEach } from "vitest";
2
+ import type {
3
+ ArrayItemBranch,
4
+ IdBranch,
5
+ BaseBuildContext,
6
+ SlotBranch,
7
+ SwitchBranch,
8
+ TemplateBranch,
9
+ } from "../types";
10
+ import { genId, globalIdRegistry } from "../id/generator";
11
+
12
+ describe("genId", () => {
13
+ beforeEach(() => {
14
+ // Reset the global registry before each test
15
+ globalIdRegistry.reset();
16
+ });
17
+ describe("no branch (custom ID case)", () => {
18
+ test("returns parentId when no branch is provided", () => {
19
+ const ctx: BaseBuildContext = {
20
+ parentId: "custom-id",
21
+ };
22
+
23
+ const result = genId(ctx);
24
+
25
+ expect(result).toBe("custom-id");
26
+ });
27
+
28
+ test("returns parentId when branch is undefined", () => {
29
+ const ctx: BaseBuildContext = {
30
+ parentId: "another-custom-id",
31
+ branch: undefined,
32
+ };
33
+
34
+ const result = genId(ctx);
35
+
36
+ expect(result).toBe("another-custom-id");
37
+ });
38
+
39
+ test("handles empty string parentId with no branch", () => {
40
+ const ctx: BaseBuildContext = {
41
+ parentId: "",
42
+ };
43
+
44
+ const result = genId(ctx);
45
+
46
+ expect(result).toBe("");
47
+ });
48
+
49
+ test("handles complex parentId with special characters", () => {
50
+ const ctx: BaseBuildContext = {
51
+ parentId: "parent_with-special.chars@123",
52
+ };
53
+
54
+ const result = genId(ctx);
55
+
56
+ expect(result).toBe("parent_with-special.chars@123");
57
+ });
58
+ });
59
+
60
+ describe("slot branch", () => {
61
+ test("generates ID for slot with parentId", () => {
62
+ const branch: SlotBranch = {
63
+ type: "slot",
64
+ name: "header",
65
+ };
66
+
67
+ const ctx: BaseBuildContext = {
68
+ parentId: "parent",
69
+ branch,
70
+ };
71
+
72
+ const result = genId(ctx);
73
+
74
+ expect(result).toBe("parent-header");
75
+ });
76
+
77
+ test("generates ID for slot with empty parentId", () => {
78
+ const branch: SlotBranch = {
79
+ type: "slot",
80
+ name: "footer",
81
+ };
82
+
83
+ const ctx: BaseBuildContext = {
84
+ parentId: "",
85
+ branch,
86
+ };
87
+
88
+ const result = genId(ctx);
89
+
90
+ expect(result).toBe("footer");
91
+ });
92
+
93
+ test("throws error for slot with empty name", () => {
94
+ const branch: SlotBranch = {
95
+ type: "slot",
96
+ name: "",
97
+ };
98
+
99
+ const ctx: BaseBuildContext = {
100
+ parentId: "parent",
101
+ branch,
102
+ };
103
+
104
+ expect(() => genId(ctx)).toThrow(
105
+ "genId: Slot branch requires a 'name' property",
106
+ );
107
+ });
108
+
109
+ test("handles slot with special characters in name", () => {
110
+ const branch: SlotBranch = {
111
+ type: "slot",
112
+ name: "slot_with-special.chars",
113
+ };
114
+
115
+ const ctx: BaseBuildContext = {
116
+ parentId: "parent",
117
+ branch,
118
+ };
119
+
120
+ const result = genId(ctx);
121
+
122
+ expect(result).toBe("parent-slot_with-special.chars");
123
+ });
124
+
125
+ test("handles slot with numeric name", () => {
126
+ const branch: SlotBranch = {
127
+ type: "slot",
128
+ name: "123",
129
+ };
130
+
131
+ const ctx: BaseBuildContext = {
132
+ parentId: "parent",
133
+ branch,
134
+ };
135
+
136
+ const result = genId(ctx);
137
+
138
+ expect(result).toBe("parent-123");
139
+ });
140
+ });
141
+
142
+ describe("array-item branch", () => {
143
+ test("generates ID for array item with positive index", () => {
144
+ const branch: ArrayItemBranch = {
145
+ type: "array-item",
146
+ index: 2,
147
+ };
148
+
149
+ const ctx: BaseBuildContext = {
150
+ parentId: "list",
151
+ branch,
152
+ };
153
+
154
+ const result = genId(ctx);
155
+
156
+ expect(result).toBe("list-2");
157
+ });
158
+
159
+ test("generates ID for array item with zero index", () => {
160
+ const branch: ArrayItemBranch = {
161
+ type: "array-item",
162
+ index: 0,
163
+ };
164
+
165
+ const ctx: BaseBuildContext = {
166
+ parentId: "array",
167
+ branch,
168
+ };
169
+
170
+ const result = genId(ctx);
171
+
172
+ expect(result).toBe("array-0");
173
+ });
174
+
175
+ test("throws error for array item with negative index", () => {
176
+ const branch: ArrayItemBranch = {
177
+ type: "array-item",
178
+ index: -1,
179
+ };
180
+
181
+ const ctx: BaseBuildContext = {
182
+ parentId: "items",
183
+ branch,
184
+ };
185
+
186
+ expect(() => genId(ctx)).toThrow(
187
+ "genId: Array-item index must be non-negative",
188
+ );
189
+ });
190
+
191
+ test("generates ID for array item with large index", () => {
192
+ const branch: ArrayItemBranch = {
193
+ type: "array-item",
194
+ index: 999999,
195
+ };
196
+
197
+ const ctx: BaseBuildContext = {
198
+ parentId: "bigArray",
199
+ branch,
200
+ };
201
+
202
+ const result = genId(ctx);
203
+
204
+ expect(result).toBe("bigArray-999999");
205
+ });
206
+
207
+ test("handles array item with empty parentId", () => {
208
+ const branch: ArrayItemBranch = {
209
+ type: "array-item",
210
+ index: 5,
211
+ };
212
+
213
+ const ctx: BaseBuildContext = {
214
+ parentId: "",
215
+ branch,
216
+ };
217
+
218
+ const result = genId(ctx);
219
+
220
+ expect(result).toBe("-5");
221
+ });
222
+ });
223
+
224
+ describe("template branch", () => {
225
+ test("generates ID for template with depth", () => {
226
+ const branch: TemplateBranch = {
227
+ type: "template",
228
+ depth: 1,
229
+ };
230
+
231
+ const ctx: BaseBuildContext = {
232
+ parentId: "template",
233
+ branch,
234
+ };
235
+
236
+ const result = genId(ctx);
237
+
238
+ expect(result).toBe("template-_index1_");
239
+ });
240
+
241
+ test("generates ID for template without depth (undefined)", () => {
242
+ const branch: TemplateBranch = {
243
+ type: "template",
244
+ };
245
+
246
+ const ctx: BaseBuildContext = {
247
+ parentId: "template",
248
+ branch,
249
+ };
250
+
251
+ const result = genId(ctx);
252
+
253
+ expect(result).toBe("template-_index_");
254
+ });
255
+
256
+ test("generates ID for template with zero depth", () => {
257
+ const branch: TemplateBranch = {
258
+ type: "template",
259
+ depth: 0,
260
+ };
261
+
262
+ const ctx: BaseBuildContext = {
263
+ parentId: "template",
264
+ branch,
265
+ };
266
+
267
+ const result = genId(ctx);
268
+
269
+ // Note: depth 0 is treated as falsy by the || operator, so it becomes empty string
270
+ expect(result).toBe("template-_index_");
271
+ });
272
+
273
+ test("documents depth 0 vs undefined behavior (both result in same ID)", () => {
274
+ const branchWithZero: TemplateBranch = {
275
+ type: "template",
276
+ depth: 0,
277
+ };
278
+
279
+ const branchWithUndefined: TemplateBranch = {
280
+ type: "template",
281
+ };
282
+
283
+ const ctx1: BaseBuildContext = {
284
+ parentId: "template",
285
+ branch: branchWithZero,
286
+ };
287
+
288
+ const ctx2: BaseBuildContext = {
289
+ parentId: "template",
290
+ branch: branchWithUndefined,
291
+ };
292
+
293
+ const result1 = genId(ctx1);
294
+ const result2 = genId(ctx2);
295
+
296
+ // Both depth: 0 and depth: undefined result in same base pattern,
297
+ // and template placeholders are allowed as duplicates
298
+ expect(result1).toBe("template-_index_");
299
+ expect(result2).toBe("template-_index_"); // Same ID, no collision
300
+ expect(result1).toBe(result2); // Should be the same
301
+ });
302
+
303
+ test("throws error for template with negative depth", () => {
304
+ const branch: TemplateBranch = {
305
+ type: "template",
306
+ depth: -2,
307
+ };
308
+
309
+ const ctx: BaseBuildContext = {
310
+ parentId: "template",
311
+ branch,
312
+ };
313
+
314
+ expect(() => genId(ctx)).toThrow(
315
+ "genId: Template depth must be non-negative",
316
+ );
317
+ });
318
+
319
+ test("generates ID for template with large depth", () => {
320
+ const branch: TemplateBranch = {
321
+ type: "template",
322
+ depth: 100,
323
+ };
324
+
325
+ const ctx: BaseBuildContext = {
326
+ parentId: "deepTemplate",
327
+ branch,
328
+ };
329
+
330
+ const result = genId(ctx);
331
+
332
+ expect(result).toBe("deepTemplate-_index100_");
333
+ });
334
+
335
+ test("handles template with empty parentId", () => {
336
+ const branch: TemplateBranch = {
337
+ type: "template",
338
+ depth: 3,
339
+ };
340
+
341
+ const ctx: BaseBuildContext = {
342
+ parentId: "",
343
+ branch,
344
+ };
345
+
346
+ const result = genId(ctx);
347
+
348
+ expect(result).toBe("-_index3_");
349
+ });
350
+ });
351
+
352
+ describe("switch branch", () => {
353
+ test("generates ID for static switch", () => {
354
+ const branch: SwitchBranch = {
355
+ type: "switch",
356
+ index: 0,
357
+ kind: "static",
358
+ };
359
+
360
+ const ctx: BaseBuildContext = {
361
+ parentId: "condition",
362
+ branch,
363
+ };
364
+
365
+ const result = genId(ctx);
366
+
367
+ expect(result).toBe("condition-staticSwitch-0");
368
+ });
369
+
370
+ test("generates ID for dynamic switch", () => {
371
+ const branch: SwitchBranch = {
372
+ type: "switch",
373
+ index: 1,
374
+ kind: "dynamic",
375
+ };
376
+
377
+ const ctx: BaseBuildContext = {
378
+ parentId: "condition",
379
+ branch,
380
+ };
381
+
382
+ const result = genId(ctx);
383
+
384
+ expect(result).toBe("condition-dynamicSwitch-1");
385
+ });
386
+
387
+ test("generates ID for switch with zero index", () => {
388
+ const branch: SwitchBranch = {
389
+ type: "switch",
390
+ index: 0,
391
+ kind: "dynamic",
392
+ };
393
+
394
+ const ctx: BaseBuildContext = {
395
+ parentId: "switch",
396
+ branch,
397
+ };
398
+
399
+ const result = genId(ctx);
400
+
401
+ expect(result).toBe("switch-dynamicSwitch-0");
402
+ });
403
+
404
+ test("throws error for switch with negative index", () => {
405
+ const branch: SwitchBranch = {
406
+ type: "switch",
407
+ index: -1,
408
+ kind: "static",
409
+ };
410
+
411
+ const ctx: BaseBuildContext = {
412
+ parentId: "negativeSwitch",
413
+ branch,
414
+ };
415
+
416
+ expect(() => genId(ctx)).toThrow(
417
+ "genId: Switch index must be non-negative",
418
+ );
419
+ });
420
+
421
+ test("generates ID for switch with large index", () => {
422
+ const branch: SwitchBranch = {
423
+ type: "switch",
424
+ index: 9999,
425
+ kind: "dynamic",
426
+ };
427
+
428
+ const ctx: BaseBuildContext = {
429
+ parentId: "bigSwitch",
430
+ branch,
431
+ };
432
+
433
+ const result = genId(ctx);
434
+
435
+ expect(result).toBe("bigSwitch-dynamicSwitch-9999");
436
+ });
437
+
438
+ test("handles switch with empty parentId", () => {
439
+ const branch: SwitchBranch = {
440
+ type: "switch",
441
+ index: 2,
442
+ kind: "static",
443
+ };
444
+
445
+ const ctx: BaseBuildContext = {
446
+ parentId: "",
447
+ branch,
448
+ };
449
+
450
+ const result = genId(ctx);
451
+
452
+ expect(result).toBe("-staticSwitch-2");
453
+ });
454
+ });
455
+
456
+ describe("error handling", () => {
457
+ test("throws error for unknown branch type", () => {
458
+ // Create an invalid branch type by casting
459
+ const invalidBranch = {
460
+ type: "unknown",
461
+ someProperty: "value",
462
+ } as unknown as IdBranch;
463
+
464
+ const ctx: BaseBuildContext = {
465
+ parentId: "parent",
466
+ branch: invalidBranch,
467
+ };
468
+
469
+ expect(() => genId(ctx)).toThrow("Unhandled branch type:");
470
+ });
471
+
472
+ test("error message includes the invalid branch object", () => {
473
+ const invalidBranch = {
474
+ type: "invalid-type",
475
+ data: "test",
476
+ } as unknown as IdBranch;
477
+
478
+ const ctx: BaseBuildContext = {
479
+ parentId: "parent",
480
+ branch: invalidBranch,
481
+ };
482
+
483
+ expect(() => genId(ctx)).toThrow(
484
+ 'genId: Unhandled branch type: {"type":"invalid-type","data":"test"}',
485
+ );
486
+ });
487
+ });
488
+
489
+ describe("edge cases and integration", () => {
490
+ test("handles complex parentId with all branch types", () => {
491
+ const complexParentId = "complex_parent-with.special@chars123";
492
+
493
+ const testCases = [
494
+ {
495
+ branch: { type: "slot", name: "test" } as SlotBranch,
496
+ expected: "complex_parent-with.special@chars123-test",
497
+ },
498
+ {
499
+ branch: { type: "array-item", index: 5 } as ArrayItemBranch,
500
+ expected: "complex_parent-with.special@chars123-5",
501
+ },
502
+ {
503
+ branch: { type: "template", depth: 2 } as TemplateBranch,
504
+ expected: "complex_parent-with.special@chars123-_index2_",
505
+ },
506
+ {
507
+ branch: {
508
+ type: "switch",
509
+ index: 3,
510
+ kind: "static",
511
+ } as SwitchBranch,
512
+ expected: "complex_parent-with.special@chars123-staticSwitch-3",
513
+ },
514
+ ];
515
+
516
+ testCases.forEach(({ branch, expected }) => {
517
+ const ctx: BaseBuildContext = {
518
+ parentId: complexParentId,
519
+ branch,
520
+ };
521
+
522
+ const result = genId(ctx);
523
+ expect(result).toBe(expected);
524
+ });
525
+ });
526
+
527
+ test("handles all branch types with empty parentId", () => {
528
+ const testCases = [
529
+ {
530
+ branch: { type: "slot", name: "empty" } as SlotBranch,
531
+ expected: "empty",
532
+ },
533
+ {
534
+ branch: { type: "array-item", index: 0 } as ArrayItemBranch,
535
+ expected: "-0",
536
+ },
537
+ {
538
+ branch: { type: "template", depth: 1 } as TemplateBranch,
539
+ expected: "-_index1_",
540
+ },
541
+ {
542
+ branch: {
543
+ type: "switch",
544
+ index: 0,
545
+ kind: "dynamic",
546
+ } as SwitchBranch,
547
+ expected: "-dynamicSwitch-0",
548
+ },
549
+ ];
550
+
551
+ testCases.forEach(({ branch, expected }) => {
552
+ const ctx: BaseBuildContext = {
553
+ parentId: "",
554
+ branch,
555
+ };
556
+
557
+ const result = genId(ctx);
558
+ expect(result).toBe(expected);
559
+ });
560
+ });
561
+
562
+ test("enforces uniqueness across multiple calls with same input", () => {
563
+ const ctx: BaseBuildContext = {
564
+ parentId: "consistent",
565
+ branch: { type: "slot", name: "test" },
566
+ };
567
+
568
+ const result1 = genId(ctx);
569
+ const result2 = genId(ctx);
570
+ const result3 = genId(ctx);
571
+
572
+ // With collision detection, subsequent calls get unique suffixes
573
+ expect(result1).toBe("consistent-test");
574
+ expect(result2).toBe("consistent-test-1");
575
+ expect(result3).toBe("consistent-test-2");
576
+
577
+ // All results should be unique
578
+ expect(result1).not.toBe(result2);
579
+ expect(result2).not.toBe(result3);
580
+ expect(result1).not.toBe(result3);
581
+ });
582
+
583
+ test("generates different IDs for different contexts", () => {
584
+ const contexts = [
585
+ {
586
+ parentId: "parent1",
587
+ branch: { type: "slot", name: "slot1" } as SlotBranch,
588
+ },
589
+ {
590
+ parentId: "parent2",
591
+ branch: { type: "slot", name: "slot1" } as SlotBranch,
592
+ },
593
+ {
594
+ parentId: "parent1",
595
+ branch: { type: "slot", name: "slot2" } as SlotBranch,
596
+ },
597
+ {
598
+ parentId: "parent1",
599
+ branch: { type: "array-item", index: 0 } as ArrayItemBranch,
600
+ },
601
+ ];
602
+
603
+ const results = contexts.map((ctx) => genId(ctx));
604
+
605
+ // All results should be unique
606
+ const uniqueResults = new Set(results);
607
+ expect(uniqueResults.size).toBe(results.length);
608
+
609
+ expect(results).toEqual([
610
+ "parent1-slot1",
611
+ "parent2-slot1",
612
+ "parent1-slot2",
613
+ "parent1-0",
614
+ ]);
615
+ });
616
+ });
617
+
618
+ describe("type safety and interface compliance", () => {
619
+ test("accepts all valid IdBranch types", () => {
620
+ const branches: IdBranch[] = [
621
+ { type: "slot", name: "test" },
622
+ { type: "array-item", index: 0 },
623
+ { type: "template", depth: 1 },
624
+ { type: "template" }, // depth is optional
625
+ { type: "switch", index: 0, kind: "static" },
626
+ { type: "switch", index: 1, kind: "dynamic" },
627
+ ];
628
+
629
+ branches.forEach((branch) => {
630
+ const ctx: BaseBuildContext = {
631
+ parentId: "test",
632
+ branch,
633
+ };
634
+
635
+ // Should not throw and should return a string
636
+ const result = genId(ctx);
637
+ expect(typeof result).toBe("string");
638
+ expect(result.length).toBeGreaterThan(0);
639
+ });
640
+ });
641
+
642
+ test("BaseBuildContext interface works with optional branch", () => {
643
+ // Test that BaseBuildContext works with just parentId
644
+ const ctx1: BaseBuildContext = {
645
+ parentId: "test",
646
+ };
647
+
648
+ // Test that BaseBuildContext works with parentId and branch
649
+ const ctx2: BaseBuildContext = {
650
+ parentId: "test",
651
+ branch: { type: "slot", name: "test" },
652
+ };
653
+
654
+ expect(() => genId(ctx1)).not.toThrow();
655
+ expect(() => genId(ctx2)).not.toThrow();
656
+ });
657
+ });
658
+ });