@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,596 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import type { Template, Asset } from "@player-ui/types";
|
|
3
|
+
import { template, isTemplate, TEMPLATE_MARKER } from "../../template";
|
|
4
|
+
import {
|
|
5
|
+
type BaseBuildContext,
|
|
6
|
+
type SlotBranch,
|
|
7
|
+
resetGlobalIdSet,
|
|
8
|
+
} from "../index";
|
|
9
|
+
import { text } from "../../mocks";
|
|
10
|
+
import { binding as b } from "../../tagged-template";
|
|
11
|
+
|
|
12
|
+
// Mock BaseBuildContext
|
|
13
|
+
const mockParentCtx: BaseBuildContext = {
|
|
14
|
+
parentId: "parent-1",
|
|
15
|
+
branch: {
|
|
16
|
+
type: "slot",
|
|
17
|
+
name: "test",
|
|
18
|
+
} as SlotBranch,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("template integration with base-builder", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
resetGlobalIdSet();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("create a basic template configuration", () => {
|
|
27
|
+
const result = template({
|
|
28
|
+
data: "list.of.names",
|
|
29
|
+
output: "values",
|
|
30
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
31
|
+
})(mockParentCtx);
|
|
32
|
+
|
|
33
|
+
const expected: Template<{ asset: Asset<"text"> }> = {
|
|
34
|
+
data: "list.of.names",
|
|
35
|
+
output: "values",
|
|
36
|
+
value: {
|
|
37
|
+
asset: {
|
|
38
|
+
id: "parent-1-test-_index_-text",
|
|
39
|
+
type: "text",
|
|
40
|
+
value: "{{list.of.names._index_}}",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
expect(result).toEqual(expected);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("create a template with tagged template binding", () => {
|
|
49
|
+
const result = template({
|
|
50
|
+
data: b`list.of.names`,
|
|
51
|
+
output: "values",
|
|
52
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
53
|
+
})(mockParentCtx);
|
|
54
|
+
|
|
55
|
+
const expected: Template<{ asset: Asset<"text"> }> = {
|
|
56
|
+
data: "{{list.of.names}}",
|
|
57
|
+
output: "values",
|
|
58
|
+
value: {
|
|
59
|
+
asset: {
|
|
60
|
+
id: "parent-1-test-_index_-text",
|
|
61
|
+
type: "text",
|
|
62
|
+
value: "{{list.of.names._index_}}",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
expect(result).toEqual(expected);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("include dynamic flag when specified", () => {
|
|
71
|
+
const result = template({
|
|
72
|
+
data: "list.of.names",
|
|
73
|
+
output: "values",
|
|
74
|
+
dynamic: true,
|
|
75
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
76
|
+
})(mockParentCtx);
|
|
77
|
+
|
|
78
|
+
const expected: Template<{ asset: Asset<"text"> }> = {
|
|
79
|
+
data: "list.of.names",
|
|
80
|
+
output: "values",
|
|
81
|
+
dynamic: true,
|
|
82
|
+
value: {
|
|
83
|
+
asset: {
|
|
84
|
+
id: "parent-1-test-_index_-text",
|
|
85
|
+
type: "text",
|
|
86
|
+
value: "{{list.of.names._index_}}",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
expect(result).toEqual(expected);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("not include dynamic flag when it is false", () => {
|
|
95
|
+
const result = template({
|
|
96
|
+
data: "list.of.names",
|
|
97
|
+
output: "values",
|
|
98
|
+
dynamic: false,
|
|
99
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
100
|
+
})(mockParentCtx);
|
|
101
|
+
|
|
102
|
+
const expected: Template<{ asset: Asset<"text"> }> = {
|
|
103
|
+
data: "list.of.names",
|
|
104
|
+
output: "values",
|
|
105
|
+
value: {
|
|
106
|
+
asset: {
|
|
107
|
+
id: "parent-1-test-_index_-text",
|
|
108
|
+
type: "text",
|
|
109
|
+
value: "{{list.of.names._index_}}",
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual(expected);
|
|
115
|
+
expect(result).not.toHaveProperty("dynamic");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("pass the correct parent context to the value function", () => {
|
|
119
|
+
let capturedParentCtx: BaseBuildContext | null = null;
|
|
120
|
+
|
|
121
|
+
const valueWithCapture = (parentCtx: BaseBuildContext): Asset<"text"> => {
|
|
122
|
+
capturedParentCtx = parentCtx;
|
|
123
|
+
return {
|
|
124
|
+
id: "test",
|
|
125
|
+
type: "text",
|
|
126
|
+
value: "test",
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
template({
|
|
131
|
+
data: "list.of.names",
|
|
132
|
+
output: "values",
|
|
133
|
+
value: valueWithCapture,
|
|
134
|
+
})(mockParentCtx);
|
|
135
|
+
|
|
136
|
+
expect(capturedParentCtx).toEqual({
|
|
137
|
+
parentId: "parent-1-test",
|
|
138
|
+
branch: {
|
|
139
|
+
type: "template",
|
|
140
|
+
depth: 0,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Simulation of a multiple templates scenario (we test the output structure, not runtime behavior)
|
|
146
|
+
test("support the structure for multiple templates with the same output property", () => {
|
|
147
|
+
const template1 = template({
|
|
148
|
+
data: "list.of.names",
|
|
149
|
+
output: "values",
|
|
150
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
151
|
+
})(mockParentCtx);
|
|
152
|
+
|
|
153
|
+
const template2 = template({
|
|
154
|
+
data: "list.of.other-names",
|
|
155
|
+
output: "values",
|
|
156
|
+
value: text().withValue(b`list.of.other-names._index_`),
|
|
157
|
+
})(mockParentCtx);
|
|
158
|
+
|
|
159
|
+
expect(template1.output).toEqual(template2.output);
|
|
160
|
+
expect(template1.output).toBe("values");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Test complex nested asset structures
|
|
164
|
+
test("handle complex nested asset structures", () => {
|
|
165
|
+
interface CollectionAsset extends Asset<"collection"> {
|
|
166
|
+
items: Array<{ asset: Asset<"text"> }>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const collectionAsset = (): CollectionAsset => ({
|
|
170
|
+
id: "collection-_index_",
|
|
171
|
+
type: "collection",
|
|
172
|
+
items: [
|
|
173
|
+
{
|
|
174
|
+
asset: {
|
|
175
|
+
id: "item-_index_-0",
|
|
176
|
+
type: "text",
|
|
177
|
+
value: "{{list.of.names._index_.first}}",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
asset: {
|
|
182
|
+
id: "item-_index_-1",
|
|
183
|
+
type: "text",
|
|
184
|
+
value: "{{list.of.names._index_.last}}",
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const result = template({
|
|
191
|
+
data: "list.of.names",
|
|
192
|
+
output: "collections",
|
|
193
|
+
value: collectionAsset,
|
|
194
|
+
})(mockParentCtx);
|
|
195
|
+
|
|
196
|
+
const expected: Template<{ asset: CollectionAsset }> = {
|
|
197
|
+
data: "list.of.names",
|
|
198
|
+
output: "collections",
|
|
199
|
+
value: {
|
|
200
|
+
asset: {
|
|
201
|
+
id: "collection-_index_",
|
|
202
|
+
type: "collection",
|
|
203
|
+
items: [
|
|
204
|
+
{
|
|
205
|
+
asset: {
|
|
206
|
+
id: "item-_index_-0",
|
|
207
|
+
type: "text",
|
|
208
|
+
value: "{{list.of.names._index_.first}}",
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
asset: {
|
|
213
|
+
id: "item-_index_-1",
|
|
214
|
+
type: "text",
|
|
215
|
+
value: "{{list.of.names._index_.last}}",
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
expect(result).toEqual(expected);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Test the structure for dynamic template functionality
|
|
227
|
+
test("create a dynamic template that updates when data changes", () => {
|
|
228
|
+
const result = template({
|
|
229
|
+
data: "list.of.names",
|
|
230
|
+
output: "values",
|
|
231
|
+
dynamic: true,
|
|
232
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
233
|
+
})(mockParentCtx);
|
|
234
|
+
|
|
235
|
+
expect(result.dynamic).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("isTemplate type guard", () => {
|
|
240
|
+
beforeEach(() => {
|
|
241
|
+
resetGlobalIdSet();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("should return true for template functions", () => {
|
|
245
|
+
const templateFn = template({
|
|
246
|
+
data: "list.of.names",
|
|
247
|
+
output: "values",
|
|
248
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(isTemplate(templateFn)).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("should return false for non-template functions", () => {
|
|
255
|
+
const regularFunction = () => ({});
|
|
256
|
+
const arrowFunction = () => "test";
|
|
257
|
+
const objectWithFunction = { fn: () => {} };
|
|
258
|
+
|
|
259
|
+
expect(isTemplate(regularFunction)).toBe(false);
|
|
260
|
+
expect(isTemplate(arrowFunction)).toBe(false);
|
|
261
|
+
expect(isTemplate(objectWithFunction.fn)).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("should return false for non-functions", () => {
|
|
265
|
+
const string = "not a function";
|
|
266
|
+
const number = 42;
|
|
267
|
+
const object = { type: "test" };
|
|
268
|
+
const nullValue = null;
|
|
269
|
+
const undefinedValue = undefined;
|
|
270
|
+
|
|
271
|
+
expect(isTemplate(string)).toBe(false);
|
|
272
|
+
expect(isTemplate(number)).toBe(false);
|
|
273
|
+
expect(isTemplate(object)).toBe(false);
|
|
274
|
+
expect(isTemplate(nullValue)).toBe(false);
|
|
275
|
+
expect(isTemplate(undefinedValue)).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("should have TEMPLATE_MARKER symbol on template functions", () => {
|
|
279
|
+
const templateFn = template({
|
|
280
|
+
data: "list.of.names",
|
|
281
|
+
output: "values",
|
|
282
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
expect(TEMPLATE_MARKER in templateFn).toBe(true);
|
|
286
|
+
expect(
|
|
287
|
+
(templateFn as unknown as { [TEMPLATE_MARKER]: unknown })[
|
|
288
|
+
TEMPLATE_MARKER
|
|
289
|
+
],
|
|
290
|
+
).toBe(true);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("should not have TEMPLATE_MARKER symbol on regular functions", () => {
|
|
294
|
+
const regularFunction = () => ({});
|
|
295
|
+
|
|
296
|
+
expect(TEMPLATE_MARKER in regularFunction).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("should work with template functions that have been called", () => {
|
|
300
|
+
const templateFn = template({
|
|
301
|
+
data: "list.of.names",
|
|
302
|
+
output: "values",
|
|
303
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Call the template function
|
|
307
|
+
templateFn(mockParentCtx);
|
|
308
|
+
|
|
309
|
+
// The original function should still be identifiable as a template
|
|
310
|
+
expect(isTemplate(templateFn)).toBe(true);
|
|
311
|
+
expect(TEMPLATE_MARKER in templateFn).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("template with optional output", () => {
|
|
316
|
+
beforeEach(() => {
|
|
317
|
+
resetGlobalIdSet();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("should infer output from slot context", () => {
|
|
321
|
+
const contextWithSlot: BaseBuildContext = {
|
|
322
|
+
parentId: "parent-1",
|
|
323
|
+
branch: {
|
|
324
|
+
type: "slot",
|
|
325
|
+
name: "values-0", // This should infer "values" as the output
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const templateFn = template({
|
|
330
|
+
data: b`list.of.names`,
|
|
331
|
+
// No output provided - should be inferred
|
|
332
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const result = templateFn(contextWithSlot);
|
|
336
|
+
|
|
337
|
+
expect(result.output).toBe("values");
|
|
338
|
+
expect(result.data).toBe("{{list.of.names}}");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("should throw error when output cannot be inferred", () => {
|
|
342
|
+
const contextWithoutSlot: BaseBuildContext = {
|
|
343
|
+
parentId: "parent-1",
|
|
344
|
+
branch: {
|
|
345
|
+
type: "template",
|
|
346
|
+
depth: 0,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const templateFn = template({
|
|
351
|
+
data: b`list.of.names`,
|
|
352
|
+
// No output provided and context doesn't allow inference
|
|
353
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(() => templateFn(contextWithoutSlot)).toThrow(
|
|
357
|
+
"Template output must be provided or inferrable from context",
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("should use explicit output when provided", () => {
|
|
362
|
+
const contextWithSlot: BaseBuildContext = {
|
|
363
|
+
parentId: "parent-1",
|
|
364
|
+
branch: {
|
|
365
|
+
type: "slot",
|
|
366
|
+
name: "values-0",
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const templateFn = template({
|
|
371
|
+
data: b`list.of.names`,
|
|
372
|
+
output: "customOutput", // Explicit output should override inference
|
|
373
|
+
value: text().withValue(b`list.of.names._index_`),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const result = templateFn(contextWithSlot);
|
|
377
|
+
|
|
378
|
+
expect(result.output).toBe("customOutput");
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
describe("template context creation", () => {
|
|
383
|
+
beforeEach(() => {
|
|
384
|
+
resetGlobalIdSet();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("should create template context with depth 0", () => {
|
|
388
|
+
const context: BaseBuildContext = {
|
|
389
|
+
parentId: "parent",
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const templateFn = template({
|
|
393
|
+
data: "items",
|
|
394
|
+
output: "values",
|
|
395
|
+
value: (ctx: BaseBuildContext) => {
|
|
396
|
+
// Verify the context has template branch
|
|
397
|
+
expect(ctx.branch).toEqual({ type: "template", depth: 0 });
|
|
398
|
+
expect(ctx.parentId).toBe("parent");
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
id: "test",
|
|
402
|
+
type: "text",
|
|
403
|
+
value: "test",
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
templateFn(context);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("should generate proper template IDs with _index_ placeholder", () => {
|
|
412
|
+
const context: BaseBuildContext = {
|
|
413
|
+
parentId: "parent",
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const result = template({
|
|
417
|
+
data: "items",
|
|
418
|
+
output: "values",
|
|
419
|
+
value: text({ value: "test" }),
|
|
420
|
+
})(context);
|
|
421
|
+
|
|
422
|
+
expect(result.value.asset.id).toBe("parent-_index_-text");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("should support nested template depth tracking", () => {
|
|
426
|
+
const context: BaseBuildContext = {
|
|
427
|
+
parentId: "parent",
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// First level template
|
|
431
|
+
const level1Result = template({
|
|
432
|
+
data: "items",
|
|
433
|
+
output: "values",
|
|
434
|
+
value: text({ value: "test" }),
|
|
435
|
+
})(context);
|
|
436
|
+
|
|
437
|
+
expect(level1Result.value.asset.id).toBe("parent-_index_-text");
|
|
438
|
+
|
|
439
|
+
// Simulate nested template (depth 1)
|
|
440
|
+
const nestedContext: BaseBuildContext = {
|
|
441
|
+
parentId: "parent-item",
|
|
442
|
+
branch: { type: "template", depth: 1 },
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const nestedAsset = text({ value: "nested" }).build(nestedContext);
|
|
446
|
+
|
|
447
|
+
expect(nestedAsset.id).toBe("parent-item-_index1_-text");
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe("Nested Index Handling", () => {
|
|
452
|
+
beforeEach(() => {
|
|
453
|
+
resetGlobalIdSet();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("generates _index_ for depth 0", () => {
|
|
457
|
+
const context: BaseBuildContext = {
|
|
458
|
+
parentId: "list",
|
|
459
|
+
branch: { type: "template", depth: 0 },
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const asset = text({ value: "item" }).build(context);
|
|
463
|
+
expect(asset.id).toBe("list-_index_-text");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("generates _index1_ for depth 1", () => {
|
|
467
|
+
const context: BaseBuildContext = {
|
|
468
|
+
parentId: "list-item",
|
|
469
|
+
branch: { type: "template", depth: 1 },
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const asset = text({ value: "nested" }).build(context);
|
|
473
|
+
expect(asset.id).toBe("list-item-_index1_-text");
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("generates _index2_ for depth 2", () => {
|
|
477
|
+
const context: BaseBuildContext = {
|
|
478
|
+
parentId: "list-item-nested",
|
|
479
|
+
branch: { type: "template", depth: 2 },
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const asset = text({ value: "deeply-nested" }).build(context);
|
|
483
|
+
expect(asset.id).toBe("list-item-nested-_index2_-text");
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("handles template inside template (nested loops)", () => {
|
|
487
|
+
const outerContext: BaseBuildContext = {
|
|
488
|
+
parentId: "outer",
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// Outer template creates depth 0
|
|
492
|
+
const outerTemplate = template({
|
|
493
|
+
data: "outerItems",
|
|
494
|
+
output: "items",
|
|
495
|
+
value: (ctx: BaseBuildContext) => {
|
|
496
|
+
expect(ctx.branch).toEqual({ type: "template", depth: 0 });
|
|
497
|
+
|
|
498
|
+
// Inner context would have depth 1
|
|
499
|
+
const innerContext: BaseBuildContext = {
|
|
500
|
+
parentId: `${ctx.parentId}-_index_`,
|
|
501
|
+
branch: { type: "template", depth: 1 },
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Simulating building an asset inside the nested template
|
|
505
|
+
const innerAsset = text({ value: "inner" }).build(innerContext);
|
|
506
|
+
|
|
507
|
+
// Inner asset should use _index1_
|
|
508
|
+
expect(innerAsset.id).toBe("outer-_index_-_index1_-text");
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
id: ctx.parentId + "-_index_-outer",
|
|
512
|
+
type: "text" as const,
|
|
513
|
+
value: "outer item",
|
|
514
|
+
};
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
outerTemplate(outerContext);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe("Output Inference", () => {
|
|
523
|
+
beforeEach(() => {
|
|
524
|
+
resetGlobalIdSet();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("infers output from slot name with numeric suffix", () => {
|
|
528
|
+
const contextWithNumericSlot: BaseBuildContext = {
|
|
529
|
+
parentId: "parent",
|
|
530
|
+
branch: {
|
|
531
|
+
type: "slot",
|
|
532
|
+
name: "items-0",
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
const result = template({
|
|
537
|
+
data: "data.items",
|
|
538
|
+
// Output not specified - should be inferred from slot name
|
|
539
|
+
value: text({ value: "test" }),
|
|
540
|
+
})(contextWithNumericSlot);
|
|
541
|
+
|
|
542
|
+
expect(result.output).toBe("items");
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("infers output from slot name without numeric suffix", () => {
|
|
546
|
+
const contextWithSimpleSlot: BaseBuildContext = {
|
|
547
|
+
parentId: "parent",
|
|
548
|
+
branch: {
|
|
549
|
+
type: "slot",
|
|
550
|
+
name: "values",
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const result = template({
|
|
555
|
+
data: "data.values",
|
|
556
|
+
// Output not specified - should be inferred from slot name
|
|
557
|
+
value: text({ value: "test" }),
|
|
558
|
+
})(contextWithSimpleSlot);
|
|
559
|
+
|
|
560
|
+
expect(result.output).toBe("values");
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("throws when output cannot be inferred and not provided", () => {
|
|
564
|
+
const contextWithTemplateType: BaseBuildContext = {
|
|
565
|
+
parentId: "parent",
|
|
566
|
+
branch: { type: "template", depth: 0 },
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const templateFn = template({
|
|
570
|
+
data: "data.items",
|
|
571
|
+
// No output provided
|
|
572
|
+
value: text({ value: "test" }),
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
expect(() => templateFn(contextWithTemplateType)).toThrow(
|
|
576
|
+
"Template output must be provided or inferrable from context",
|
|
577
|
+
);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("throws when context has no branch", () => {
|
|
581
|
+
const contextWithoutBranch: BaseBuildContext = {
|
|
582
|
+
parentId: "parent",
|
|
583
|
+
// No branch
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const templateFn = template({
|
|
587
|
+
data: "data.items",
|
|
588
|
+
// No output provided
|
|
589
|
+
value: text({ value: "test" }),
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
expect(() => templateFn(contextWithoutBranch)).toThrow(
|
|
593
|
+
"Template output must be provided or inferrable from context",
|
|
594
|
+
);
|
|
595
|
+
});
|
|
596
|
+
});
|