@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.
- package/dist/cjs/index.cjs +2396 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2276 -0
- package/dist/index.mjs +2276 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
- package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
- package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
- package/src/core/base-builder/__tests__/registry.test.ts +534 -0
- package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
- package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
- package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
- package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
- package/src/core/base-builder/__tests__/switch.test.ts +815 -0
- package/src/core/base-builder/__tests__/template.test.ts +596 -0
- package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
- package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
- package/src/core/base-builder/conditional/index.ts +64 -0
- package/src/core/base-builder/context.ts +152 -0
- package/src/core/base-builder/errors.ts +69 -0
- package/src/core/base-builder/fluent-builder-base.ts +308 -0
- package/src/core/base-builder/guards.ts +137 -0
- package/src/core/base-builder/id/generator.ts +290 -0
- package/src/core/base-builder/id/registry.ts +152 -0
- package/src/core/base-builder/index.ts +72 -0
- package/src/core/base-builder/resolution/path-resolver.ts +116 -0
- package/src/core/base-builder/resolution/pipeline.ts +103 -0
- package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -0
- package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
- package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
- package/src/core/base-builder/resolution/steps/builders.ts +84 -0
- package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
- package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
- package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
- package/src/core/base-builder/resolution/steps/switches.ts +71 -0
- package/src/core/base-builder/resolution/steps/templates.ts +40 -0
- package/src/core/base-builder/resolution/value-resolver.ts +333 -0
- package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
- package/src/core/base-builder/storage/value-storage.ts +282 -0
- package/src/core/base-builder/types.ts +266 -0
- package/src/core/base-builder/utils.ts +10 -0
- package/src/core/flow/__tests__/index.test.ts +292 -0
- package/src/core/flow/index.ts +118 -0
- package/src/core/index.ts +8 -0
- package/src/core/mocks/generated/action.builder.ts +92 -0
- package/src/core/mocks/generated/choice-item.builder.ts +120 -0
- package/src/core/mocks/generated/choice.builder.ts +134 -0
- package/src/core/mocks/generated/collection.builder.ts +93 -0
- package/src/core/mocks/generated/field-collection.builder.ts +86 -0
- package/src/core/mocks/generated/index.ts +10 -0
- package/src/core/mocks/generated/info.builder.ts +64 -0
- package/src/core/mocks/generated/input.builder.ts +63 -0
- package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
- package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
- package/src/core/mocks/generated/text.builder.ts +47 -0
- package/src/core/mocks/index.ts +1 -0
- package/src/core/mocks/types/action.ts +92 -0
- package/src/core/mocks/types/choice.ts +129 -0
- package/src/core/mocks/types/collection.ts +140 -0
- package/src/core/mocks/types/info.ts +7 -0
- package/src/core/mocks/types/input.ts +7 -0
- package/src/core/mocks/types/text.ts +5 -0
- package/src/core/schema/__tests__/index.test.ts +127 -0
- package/src/core/schema/index.ts +195 -0
- package/src/core/schema/types.ts +7 -0
- package/src/core/switch/__tests__/index.test.ts +156 -0
- package/src/core/switch/index.ts +81 -0
- package/src/core/tagged-template/README.md +448 -0
- package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
- package/src/core/tagged-template/__tests__/index.test.ts +190 -0
- package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
- package/src/core/tagged-template/binding.ts +95 -0
- package/src/core/tagged-template/expression.ts +92 -0
- package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
- package/src/core/tagged-template/index.ts +5 -0
- package/src/core/tagged-template/std.ts +472 -0
- package/src/core/tagged-template/types.ts +123 -0
- package/src/core/template/__tests__/index.test.ts +380 -0
- package/src/core/template/index.ts +196 -0
- package/src/core/utils/index.ts +160 -0
- package/src/fp/README.md +411 -0
- package/src/fp/__tests__/index.test.ts +1178 -0
- package/src/fp/index.ts +386 -0
- package/src/gen/common.ts +15 -0
- package/src/index.ts +5 -0
- package/src/types.ts +203 -0
- package/types/core/base-builder/conditional/index.d.ts +21 -0
- package/types/core/base-builder/context.d.ts +39 -0
- package/types/core/base-builder/errors.d.ts +45 -0
- package/types/core/base-builder/fluent-builder-base.d.ts +147 -0
- package/types/core/base-builder/guards.d.ts +58 -0
- package/types/core/base-builder/id/generator.d.ts +69 -0
- package/types/core/base-builder/id/registry.d.ts +93 -0
- package/types/core/base-builder/index.d.ts +9 -0
- package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
- package/types/core/base-builder/resolution/pipeline.d.ts +27 -0
- package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/nested-asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
- package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
- package/types/core/base-builder/resolution/value-resolver.d.ts +62 -0
- package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
- package/types/core/base-builder/storage/value-storage.d.ts +82 -0
- package/types/core/base-builder/types.d.ts +183 -0
- package/types/core/base-builder/utils.d.ts +2 -0
- package/types/core/flow/index.d.ts +23 -0
- package/types/core/index.d.ts +8 -0
- package/types/core/mocks/index.d.ts +2 -0
- package/types/core/mocks/types/action.d.ts +58 -0
- package/types/core/mocks/types/choice.d.ts +95 -0
- package/types/core/mocks/types/collection.d.ts +102 -0
- package/types/core/mocks/types/info.d.ts +7 -0
- package/types/core/mocks/types/input.d.ts +7 -0
- package/types/core/mocks/types/text.d.ts +5 -0
- package/types/core/schema/index.d.ts +34 -0
- package/types/core/schema/types.d.ts +5 -0
- package/types/core/switch/index.d.ts +21 -0
- package/types/core/tagged-template/binding.d.ts +19 -0
- package/types/core/tagged-template/expression.d.ts +11 -0
- package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
- package/types/core/tagged-template/index.d.ts +6 -0
- package/types/core/tagged-template/std.d.ts +174 -0
- package/types/core/tagged-template/types.d.ts +69 -0
- package/types/core/template/index.d.ts +97 -0
- package/types/core/utils/index.d.ts +47 -0
- package/types/fp/index.d.ts +149 -0
- package/types/gen/common.d.ts +6 -0
- package/types/index.d.ts +3 -0
- package/types/types.d.ts +163 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
IDRegistry,
|
|
4
|
+
createIdRegistry,
|
|
5
|
+
globalIdRegistry,
|
|
6
|
+
genId,
|
|
7
|
+
} from "../id/generator";
|
|
8
|
+
import { BaseBuildContext } from "../types";
|
|
9
|
+
|
|
10
|
+
describe("IDRegistry", () => {
|
|
11
|
+
let registry: IDRegistry;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
registry = new IDRegistry();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("ensureUnique", () => {
|
|
18
|
+
test("returns original ID when no collision", () => {
|
|
19
|
+
const id = registry.ensureUnique("test-id");
|
|
20
|
+
expect(id).toBe("test-id");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("appends counter when collision detected", () => {
|
|
24
|
+
const id1 = registry.ensureUnique("duplicate");
|
|
25
|
+
const id2 = registry.ensureUnique("duplicate");
|
|
26
|
+
const id3 = registry.ensureUnique("duplicate");
|
|
27
|
+
|
|
28
|
+
expect(id1).toBe("duplicate");
|
|
29
|
+
expect(id2).toBe("duplicate-1");
|
|
30
|
+
expect(id3).toBe("duplicate-2");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("handles complex ID patterns", () => {
|
|
34
|
+
const id1 = registry.ensureUnique("parent-slot-child");
|
|
35
|
+
const id2 = registry.ensureUnique("parent-slot-child");
|
|
36
|
+
|
|
37
|
+
expect(id1).toBe("parent-slot-child");
|
|
38
|
+
expect(id2).toBe("parent-slot-child-1");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("maintains separate counters for different base IDs", () => {
|
|
42
|
+
registry.ensureUnique("id-a");
|
|
43
|
+
registry.ensureUnique("id-a"); // id-a-1
|
|
44
|
+
registry.ensureUnique("id-b");
|
|
45
|
+
registry.ensureUnique("id-a"); // id-a-2
|
|
46
|
+
registry.ensureUnique("id-b"); // id-b-1
|
|
47
|
+
|
|
48
|
+
expect(registry.ensureUnique("id-a")).toBe("id-a-3");
|
|
49
|
+
expect(registry.ensureUnique("id-b")).toBe("id-b-2");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("handles empty string IDs", () => {
|
|
53
|
+
const id1 = registry.ensureUnique("");
|
|
54
|
+
const id2 = registry.ensureUnique("");
|
|
55
|
+
|
|
56
|
+
expect(id1).toBe("");
|
|
57
|
+
expect(id2).toBe("-1");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns ID as-is when registry is disabled", () => {
|
|
61
|
+
registry.setEnabled(false);
|
|
62
|
+
|
|
63
|
+
const id1 = registry.ensureUnique("test");
|
|
64
|
+
const id2 = registry.ensureUnique("test");
|
|
65
|
+
|
|
66
|
+
expect(id1).toBe("test");
|
|
67
|
+
expect(id2).toBe("test"); // No collision detection
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("allows template placeholder IDs as duplicates", () => {
|
|
71
|
+
const id1 = registry.ensureUnique("parent-_index_");
|
|
72
|
+
const id2 = registry.ensureUnique("parent-_index_");
|
|
73
|
+
const id3 = registry.ensureUnique("parent-_index1_");
|
|
74
|
+
const id4 = registry.ensureUnique("parent-_row_");
|
|
75
|
+
|
|
76
|
+
// Template placeholders should not trigger collision detection
|
|
77
|
+
expect(id1).toBe("parent-_index_");
|
|
78
|
+
expect(id2).toBe("parent-_index_");
|
|
79
|
+
expect(id3).toBe("parent-_index1_");
|
|
80
|
+
expect(id4).toBe("parent-_row_");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("enforces uniqueness for non-template IDs with similar patterns", () => {
|
|
84
|
+
const id1 = registry.ensureUnique("parent-_index_-field");
|
|
85
|
+
const id2 = registry.ensureUnique("parent-_index_-field");
|
|
86
|
+
const id3 = registry.ensureUnique("parent-something");
|
|
87
|
+
const id4 = registry.ensureUnique("parent-something");
|
|
88
|
+
|
|
89
|
+
// Non-template IDs should still enforce uniqueness
|
|
90
|
+
expect(id1).toBe("parent-_index_-field");
|
|
91
|
+
expect(id2).toBe("parent-_index_-field-1");
|
|
92
|
+
expect(id3).toBe("parent-something");
|
|
93
|
+
expect(id4).toBe("parent-something-1");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("has", () => {
|
|
98
|
+
test("returns false for unregistered IDs", () => {
|
|
99
|
+
expect(registry.has("unknown")).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("returns true for registered IDs", () => {
|
|
103
|
+
registry.ensureUnique("known");
|
|
104
|
+
expect(registry.has("known")).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("tracks modified IDs", () => {
|
|
108
|
+
registry.ensureUnique("base");
|
|
109
|
+
registry.ensureUnique("base"); // Creates "base-1"
|
|
110
|
+
|
|
111
|
+
expect(registry.has("base")).toBe(true);
|
|
112
|
+
expect(registry.has("base-1")).toBe(true);
|
|
113
|
+
expect(registry.has("base-2")).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("reset", () => {
|
|
118
|
+
test("clears all registered IDs", () => {
|
|
119
|
+
registry.ensureUnique("id1");
|
|
120
|
+
registry.ensureUnique("id2");
|
|
121
|
+
registry.ensureUnique("id3");
|
|
122
|
+
|
|
123
|
+
expect(registry.size()).toBe(3);
|
|
124
|
+
|
|
125
|
+
registry.reset();
|
|
126
|
+
|
|
127
|
+
expect(registry.size()).toBe(0);
|
|
128
|
+
expect(registry.has("id1")).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("allows reuse of IDs after reset", () => {
|
|
132
|
+
registry.ensureUnique("reusable");
|
|
133
|
+
registry.ensureUnique("reusable"); // Would be "reusable-1"
|
|
134
|
+
|
|
135
|
+
registry.reset();
|
|
136
|
+
|
|
137
|
+
const id2 = registry.ensureUnique("reusable");
|
|
138
|
+
expect(id2).toBe("reusable"); // Not "reusable-2"
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("size", () => {
|
|
143
|
+
test("returns 0 for empty registry", () => {
|
|
144
|
+
expect(registry.size()).toBe(0);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("counts unique registered IDs", () => {
|
|
148
|
+
registry.ensureUnique("a");
|
|
149
|
+
registry.ensureUnique("b");
|
|
150
|
+
registry.ensureUnique("a"); // Creates "a-1"
|
|
151
|
+
|
|
152
|
+
expect(registry.size()).toBe(3);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("getRegisteredIds", () => {
|
|
157
|
+
test("returns empty array for empty registry", () => {
|
|
158
|
+
expect(registry.getRegisteredIds()).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("returns all registered IDs", () => {
|
|
162
|
+
registry.ensureUnique("first");
|
|
163
|
+
registry.ensureUnique("second");
|
|
164
|
+
registry.ensureUnique("first"); // Creates "first-1"
|
|
165
|
+
|
|
166
|
+
const ids = registry.getRegisteredIds();
|
|
167
|
+
expect(ids).toContain("first");
|
|
168
|
+
expect(ids).toContain("second");
|
|
169
|
+
expect(ids).toContain("first-1");
|
|
170
|
+
expect(ids).toHaveLength(3);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("createIdRegistry", () => {
|
|
176
|
+
test("creates independent registry instances", () => {
|
|
177
|
+
const registry1 = createIdRegistry();
|
|
178
|
+
const registry2 = createIdRegistry();
|
|
179
|
+
|
|
180
|
+
registry1.ensureUnique("shared");
|
|
181
|
+
|
|
182
|
+
// registry2 should not know about registry1's IDs
|
|
183
|
+
const id = registry2.ensureUnique("shared");
|
|
184
|
+
expect(id).toBe("shared");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("respects enabled parameter", () => {
|
|
188
|
+
const disabled = createIdRegistry(false);
|
|
189
|
+
|
|
190
|
+
const id1 = disabled.ensureUnique("test");
|
|
191
|
+
const id2 = disabled.ensureUnique("test");
|
|
192
|
+
|
|
193
|
+
expect(id1).toBe("test");
|
|
194
|
+
expect(id2).toBe("test");
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("genId with IDRegistry integration", () => {
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
// Reset the global registry before each test
|
|
201
|
+
globalIdRegistry.reset();
|
|
202
|
+
globalIdRegistry.setEnabled(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("prevents ID collisions in slot branches", () => {
|
|
206
|
+
const ctx1: BaseBuildContext = {
|
|
207
|
+
parentId: "form",
|
|
208
|
+
branch: { type: "slot", name: "label" },
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const ctx2: BaseBuildContext = {
|
|
212
|
+
parentId: "form",
|
|
213
|
+
branch: { type: "slot", name: "label" },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const id1 = genId(ctx1);
|
|
217
|
+
const id2 = genId(ctx2);
|
|
218
|
+
|
|
219
|
+
expect(id1).toBe("form-label");
|
|
220
|
+
expect(id2).toBe("form-label-1");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("prevents collisions across different branch types", () => {
|
|
224
|
+
// These contexts would generate the same base ID
|
|
225
|
+
const slotCtx: BaseBuildContext = {
|
|
226
|
+
parentId: "parent",
|
|
227
|
+
branch: { type: "slot", name: "0" },
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const arrayCtx: BaseBuildContext = {
|
|
231
|
+
parentId: "parent",
|
|
232
|
+
branch: { type: "array-item", index: 0 },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const id1 = genId(slotCtx);
|
|
236
|
+
const id2 = genId(arrayCtx);
|
|
237
|
+
|
|
238
|
+
expect(id1).toBe("parent-0");
|
|
239
|
+
expect(id2).toBe("parent-0-1");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("handles complex nested contexts", () => {
|
|
243
|
+
const ctx1: BaseBuildContext = {
|
|
244
|
+
parentId: "collection-values-0",
|
|
245
|
+
branch: { type: "slot", name: "label" },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const ctx2: BaseBuildContext = {
|
|
249
|
+
parentId: "collection-values-0",
|
|
250
|
+
branch: { type: "slot", name: "label" },
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const id1 = genId(ctx1);
|
|
254
|
+
const id2 = genId(ctx2);
|
|
255
|
+
|
|
256
|
+
expect(id1).toBe("collection-values-0-label");
|
|
257
|
+
expect(id2).toBe("collection-values-0-label-1");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("warns about collisions in development", () => {
|
|
261
|
+
const originalEnv = process.env.NODE_ENV;
|
|
262
|
+
process.env.NODE_ENV = "development";
|
|
263
|
+
|
|
264
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
265
|
+
|
|
266
|
+
const ctx: BaseBuildContext = {
|
|
267
|
+
parentId: "test",
|
|
268
|
+
branch: { type: "slot", name: "slot" },
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
genId(ctx);
|
|
272
|
+
genId(ctx); // Should trigger collision warning
|
|
273
|
+
|
|
274
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
275
|
+
expect.stringContaining("ID collision detected"),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
warnSpy.mockRestore();
|
|
279
|
+
process.env.NODE_ENV = originalEnv;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("allows template placeholders as duplicates", () => {
|
|
283
|
+
const templateCtx1: BaseBuildContext = {
|
|
284
|
+
parentId: "list",
|
|
285
|
+
branch: { type: "template", depth: 0 },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const templateCtx2: BaseBuildContext = {
|
|
289
|
+
parentId: "list",
|
|
290
|
+
branch: { type: "template", depth: 0 },
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const id1 = genId(templateCtx1);
|
|
294
|
+
const id2 = genId(templateCtx2);
|
|
295
|
+
|
|
296
|
+
// Template placeholders should be allowed as duplicates
|
|
297
|
+
expect(id1).toBe("list-_index_");
|
|
298
|
+
expect(id2).toBe("list-_index_"); // Should be the same, not "list-_index_-1"
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("handles switch branches with collision detection", () => {
|
|
302
|
+
const switchCtx1: BaseBuildContext = {
|
|
303
|
+
parentId: "condition",
|
|
304
|
+
branch: { type: "switch", index: 0, kind: "static" },
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const switchCtx2: BaseBuildContext = {
|
|
308
|
+
parentId: "condition",
|
|
309
|
+
branch: { type: "switch", index: 0, kind: "static" },
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const id1 = genId(switchCtx1);
|
|
313
|
+
const id2 = genId(switchCtx2);
|
|
314
|
+
|
|
315
|
+
expect(id1).toBe("condition-staticSwitch-0");
|
|
316
|
+
expect(id2).toBe("condition-staticSwitch-0-1");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe("ID Registry with real-world scenarios", () => {
|
|
321
|
+
beforeEach(() => {
|
|
322
|
+
globalIdRegistry.reset();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("collection with duplicate slot names", () => {
|
|
326
|
+
// Simulates a collection with multiple items having the same structure
|
|
327
|
+
const contexts = [
|
|
328
|
+
{
|
|
329
|
+
parentId: "collection",
|
|
330
|
+
branch: { type: "slot" as const, name: "values" },
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
parentId: "collection-values",
|
|
334
|
+
branch: { type: "array-item" as const, index: 0 },
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
parentId: "collection-values-0",
|
|
338
|
+
branch: { type: "slot" as const, name: "label" },
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
parentId: "collection-values",
|
|
342
|
+
branch: { type: "array-item" as const, index: 1 },
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
parentId: "collection-values-1",
|
|
346
|
+
branch: { type: "slot" as const, name: "label" },
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
|
|
350
|
+
const ids = contexts.map((ctx) => genId(ctx as BaseBuildContext));
|
|
351
|
+
|
|
352
|
+
// All IDs should be unique
|
|
353
|
+
const uniqueIds = new Set(ids);
|
|
354
|
+
expect(uniqueIds.size).toBe(ids.length);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("deeply nested components with potential collisions", () => {
|
|
358
|
+
// Form > Section > Field > Validation > Error
|
|
359
|
+
const deepNesting: BaseBuildContext[] = [
|
|
360
|
+
{ parentId: "form", branch: { type: "slot", name: "sections" } },
|
|
361
|
+
{ parentId: "form-sections", branch: { type: "array-item", index: 0 } },
|
|
362
|
+
{ parentId: "form-sections-0", branch: { type: "slot", name: "fields" } },
|
|
363
|
+
{
|
|
364
|
+
parentId: "form-sections-0-fields",
|
|
365
|
+
branch: { type: "array-item", index: 0 },
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
parentId: "form-sections-0-fields-0",
|
|
369
|
+
branch: { type: "slot", name: "validation" },
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
parentId: "form-sections-0-fields-0-validation",
|
|
373
|
+
branch: { type: "slot", name: "error" },
|
|
374
|
+
},
|
|
375
|
+
// Duplicate path (e.g., from a template)
|
|
376
|
+
{
|
|
377
|
+
parentId: "form-sections-0-fields-0-validation",
|
|
378
|
+
branch: { type: "slot", name: "error" },
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
|
|
382
|
+
const ids = deepNesting.map((ctx) => genId(ctx));
|
|
383
|
+
const lastTwo = ids.slice(-2);
|
|
384
|
+
|
|
385
|
+
expect(lastTwo[0]).toBe("form-sections-0-fields-0-validation-error");
|
|
386
|
+
expect(lastTwo[1]).toBe("form-sections-0-fields-0-validation-error-1");
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("mixed static and dynamic content", () => {
|
|
390
|
+
const contexts: BaseBuildContext[] = [
|
|
391
|
+
// Static content
|
|
392
|
+
{ parentId: "page", branch: { type: "slot", name: "header" } },
|
|
393
|
+
// Dynamic switch
|
|
394
|
+
{
|
|
395
|
+
parentId: "page",
|
|
396
|
+
branch: { type: "switch", index: 0, kind: "dynamic" },
|
|
397
|
+
},
|
|
398
|
+
// Template-generated content
|
|
399
|
+
{ parentId: "page", branch: { type: "template", depth: 0 } },
|
|
400
|
+
// Another header (collision with first)
|
|
401
|
+
{ parentId: "page", branch: { type: "slot", name: "header" } },
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
const ids = contexts.map((ctx) => genId(ctx));
|
|
405
|
+
|
|
406
|
+
expect(ids[0]).toBe("page-header");
|
|
407
|
+
expect(ids[1]).toBe("page-dynamicSwitch-0");
|
|
408
|
+
expect(ids[2]).toBe("page-_index_");
|
|
409
|
+
expect(ids[3]).toBe("page-header-1");
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("Template Placeholder Handling", () => {
|
|
414
|
+
let registry: IDRegistry;
|
|
415
|
+
|
|
416
|
+
beforeEach(() => {
|
|
417
|
+
registry = new IDRegistry();
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("allows duplicate IDs ending with _index_", () => {
|
|
421
|
+
const id1 = registry.ensureUnique("list-_index_");
|
|
422
|
+
const id2 = registry.ensureUnique("list-_index_");
|
|
423
|
+
const id3 = registry.ensureUnique("list-_index_");
|
|
424
|
+
|
|
425
|
+
expect(id1).toBe("list-_index_");
|
|
426
|
+
expect(id2).toBe("list-_index_");
|
|
427
|
+
expect(id3).toBe("list-_index_");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("allows duplicate IDs ending with _index1_", () => {
|
|
431
|
+
const id1 = registry.ensureUnique("nested-_index1_");
|
|
432
|
+
const id2 = registry.ensureUnique("nested-_index1_");
|
|
433
|
+
|
|
434
|
+
expect(id1).toBe("nested-_index1_");
|
|
435
|
+
expect(id2).toBe("nested-_index1_");
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("allows duplicate IDs ending with _row_", () => {
|
|
439
|
+
const id1 = registry.ensureUnique("table-_row_");
|
|
440
|
+
const id2 = registry.ensureUnique("table-_row_");
|
|
441
|
+
|
|
442
|
+
expect(id1).toBe("table-_row_");
|
|
443
|
+
expect(id2).toBe("table-_row_");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("allows duplicate IDs ending with _item_", () => {
|
|
447
|
+
const id1 = registry.ensureUnique("list-_item_");
|
|
448
|
+
const id2 = registry.ensureUnique("list-_item_");
|
|
449
|
+
|
|
450
|
+
expect(id1).toBe("list-_item_");
|
|
451
|
+
expect(id2).toBe("list-_item_");
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test("enforces uniqueness for IDs with placeholder in middle", () => {
|
|
455
|
+
const id1 = registry.ensureUnique("list-_index_-field");
|
|
456
|
+
const id2 = registry.ensureUnique("list-_index_-field");
|
|
457
|
+
|
|
458
|
+
// Should enforce uniqueness because placeholder is not at the end
|
|
459
|
+
expect(id1).toBe("list-_index_-field");
|
|
460
|
+
expect(id2).toBe("list-_index_-field-1");
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("enforces uniqueness for IDs with _index prefix but not template placeholder", () => {
|
|
464
|
+
// _index is not a valid placeholder, only _index_ is
|
|
465
|
+
const id1 = registry.ensureUnique("list-_index");
|
|
466
|
+
const id2 = registry.ensureUnique("list-_index");
|
|
467
|
+
|
|
468
|
+
expect(id1).toBe("list-_index");
|
|
469
|
+
expect(id2).toBe("list-_index-1");
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe("Registry Edge Cases", () => {
|
|
474
|
+
let registry: IDRegistry;
|
|
475
|
+
|
|
476
|
+
beforeEach(() => {
|
|
477
|
+
registry = new IDRegistry();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("handles IDs with special characters", () => {
|
|
481
|
+
const id1 = registry.ensureUnique("id-with-special/chars");
|
|
482
|
+
const id2 = registry.ensureUnique("id-with-special/chars");
|
|
483
|
+
|
|
484
|
+
expect(id1).toBe("id-with-special/chars");
|
|
485
|
+
expect(id2).toBe("id-with-special/chars-1");
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test("handles very long IDs", () => {
|
|
489
|
+
const longId = "a".repeat(500);
|
|
490
|
+
const id1 = registry.ensureUnique(longId);
|
|
491
|
+
const id2 = registry.ensureUnique(longId);
|
|
492
|
+
|
|
493
|
+
expect(id1).toBe(longId);
|
|
494
|
+
expect(id2).toBe(longId + "-1");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("setEnabled(false) bypasses all checks", () => {
|
|
498
|
+
registry.setEnabled(false);
|
|
499
|
+
|
|
500
|
+
const id1 = registry.ensureUnique("test");
|
|
501
|
+
const id2 = registry.ensureUnique("test");
|
|
502
|
+
const id3 = registry.ensureUnique("test");
|
|
503
|
+
|
|
504
|
+
expect(id1).toBe("test");
|
|
505
|
+
expect(id2).toBe("test");
|
|
506
|
+
expect(id3).toBe("test");
|
|
507
|
+
|
|
508
|
+
// Also should not track them
|
|
509
|
+
registry.setEnabled(true);
|
|
510
|
+
const id4 = registry.ensureUnique("test");
|
|
511
|
+
expect(id4).toBe("test"); // First unique since registry was disabled
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("handles numeric-looking IDs", () => {
|
|
515
|
+
const id1 = registry.ensureUnique("123");
|
|
516
|
+
const id2 = registry.ensureUnique("123");
|
|
517
|
+
|
|
518
|
+
expect(id1).toBe("123");
|
|
519
|
+
expect(id2).toBe("123-1");
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("handles IDs with hyphens that look like collision suffixes", () => {
|
|
523
|
+
// First register "test-1" as a base ID
|
|
524
|
+
const id1 = registry.ensureUnique("test-1");
|
|
525
|
+
// Try to register "test" which would normally get "-1" suffix
|
|
526
|
+
const id2 = registry.ensureUnique("test");
|
|
527
|
+
const id3 = registry.ensureUnique("test");
|
|
528
|
+
|
|
529
|
+
expect(id1).toBe("test-1");
|
|
530
|
+
expect(id2).toBe("test");
|
|
531
|
+
// The registry uses a simple counter per base ID, so "test" -> "test-2" (not "test-1-1")
|
|
532
|
+
expect(id3).toBe("test-2");
|
|
533
|
+
});
|
|
534
|
+
});
|