@player-lang/functional-dsl-generator 0.0.2-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +2146 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2075 -0
- package/dist/index.mjs +2075 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/__tests__/__snapshots__/generator.test.ts.snap +886 -0
- package/src/__tests__/builder-class-generator.test.ts +627 -0
- package/src/__tests__/cli.test.ts +685 -0
- package/src/__tests__/default-value-generator.test.ts +365 -0
- package/src/__tests__/generator.test.ts +2860 -0
- package/src/__tests__/import-generator.test.ts +444 -0
- package/src/__tests__/path-utils.test.ts +174 -0
- package/src/__tests__/type-collector.test.ts +674 -0
- package/src/__tests__/type-transformer.test.ts +934 -0
- package/src/__tests__/utils.test.ts +597 -0
- package/src/builder-class-generator.ts +254 -0
- package/src/cli.ts +285 -0
- package/src/default-value-generator.ts +307 -0
- package/src/generator.ts +257 -0
- package/src/import-generator.ts +331 -0
- package/src/index.ts +38 -0
- package/src/path-utils.ts +155 -0
- package/src/ts-morph-type-finder.ts +319 -0
- package/src/type-categorizer.ts +131 -0
- package/src/type-collector.ts +296 -0
- package/src/type-resolver.ts +266 -0
- package/src/type-transformer.ts +487 -0
- package/src/utils.ts +762 -0
- package/types/builder-class-generator.d.ts +56 -0
- package/types/cli.d.ts +6 -0
- package/types/default-value-generator.d.ts +74 -0
- package/types/generator.d.ts +102 -0
- package/types/import-generator.d.ts +77 -0
- package/types/index.d.ts +12 -0
- package/types/path-utils.d.ts +65 -0
- package/types/ts-morph-type-finder.d.ts +73 -0
- package/types/type-categorizer.d.ts +46 -0
- package/types/type-collector.d.ts +62 -0
- package/types/type-resolver.d.ts +49 -0
- package/types/type-transformer.d.ts +74 -0
- package/types/utils.d.ts +205 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import type { ObjectType } from "@xlr-lib/xlr";
|
|
3
|
+
import {
|
|
4
|
+
extractBaseName,
|
|
5
|
+
parseNamespacedType,
|
|
6
|
+
findAssetWrapperPaths,
|
|
7
|
+
extendsAssetWrapper,
|
|
8
|
+
type TypeRegistry,
|
|
9
|
+
} from "../utils";
|
|
10
|
+
|
|
11
|
+
describe("extractBaseName", () => {
|
|
12
|
+
test("returns type name when no generics present", () => {
|
|
13
|
+
expect(extractBaseName("MyType")).toBe("MyType");
|
|
14
|
+
expect(extractBaseName("Asset")).toBe("Asset");
|
|
15
|
+
expect(extractBaseName("SingleBar")).toBe("SingleBar");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("extracts base name from simple generic", () => {
|
|
19
|
+
expect(extractBaseName("MyType<T>")).toBe("MyType");
|
|
20
|
+
expect(extractBaseName("Asset<string>")).toBe("Asset");
|
|
21
|
+
expect(extractBaseName("Array<number>")).toBe("Array");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("extracts base name from nested generics", () => {
|
|
25
|
+
expect(extractBaseName("Map<string, Array<T>>")).toBe("Map");
|
|
26
|
+
expect(extractBaseName("Promise<Array<Map<K, V>>>")).toBe("Promise");
|
|
27
|
+
expect(extractBaseName("Wrapper<Nested<Inner<T>>>")).toBe("Wrapper");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("handles multiple generic parameters", () => {
|
|
31
|
+
expect(extractBaseName("Map<K, V>")).toBe("Map");
|
|
32
|
+
expect(extractBaseName("Record<string, number>")).toBe("Record");
|
|
33
|
+
expect(extractBaseName("Tuple<A, B, C>")).toBe("Tuple");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("handles generic constraints in type arguments", () => {
|
|
37
|
+
expect(extractBaseName("ListItem<AnyAsset extends Asset>")).toBe(
|
|
38
|
+
"ListItem",
|
|
39
|
+
);
|
|
40
|
+
expect(extractBaseName("Container<T extends Base = Default>")).toBe(
|
|
41
|
+
"Container",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("handles empty string", () => {
|
|
46
|
+
expect(extractBaseName("")).toBe("");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("parseNamespacedType", () => {
|
|
51
|
+
test("returns null for non-namespaced types", () => {
|
|
52
|
+
expect(parseNamespacedType("MyType")).toBeNull();
|
|
53
|
+
expect(parseNamespacedType("Asset")).toBeNull();
|
|
54
|
+
expect(parseNamespacedType("SingleBar")).toBeNull();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("parses simple namespaced type", () => {
|
|
58
|
+
const result = parseNamespacedType("Validation.CrossfieldReference");
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
namespace: "Validation",
|
|
61
|
+
member: "CrossfieldReference",
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("parses namespaced type with nested namespace", () => {
|
|
66
|
+
const result = parseNamespacedType("Player.Types.Asset");
|
|
67
|
+
expect(result).toEqual({
|
|
68
|
+
namespace: "Player",
|
|
69
|
+
member: "Types.Asset",
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("parses single character namespace", () => {
|
|
74
|
+
const result = parseNamespacedType("V.Type");
|
|
75
|
+
expect(result).toEqual({
|
|
76
|
+
namespace: "V",
|
|
77
|
+
member: "Type",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("handles empty string", () => {
|
|
82
|
+
expect(parseNamespacedType("")).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("handles string with only dot", () => {
|
|
86
|
+
const result = parseNamespacedType(".Type");
|
|
87
|
+
expect(result).toEqual({
|
|
88
|
+
namespace: "",
|
|
89
|
+
member: "Type",
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("findAssetWrapperPaths", () => {
|
|
95
|
+
test("finds direct AssetWrapper property", () => {
|
|
96
|
+
const node: ObjectType = {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
label: { required: true, node: { type: "ref", ref: "AssetWrapper" } },
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
104
|
+
expect(paths).toEqual([["label"]]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("finds multiple AssetWrapper properties", () => {
|
|
108
|
+
const node: ObjectType = {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
header: { required: true, node: { type: "ref", ref: "AssetWrapper" } },
|
|
112
|
+
footer: {
|
|
113
|
+
required: false,
|
|
114
|
+
node: { type: "ref", ref: "AssetWrapper<TextAsset>" },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
120
|
+
expect(paths).toEqual([["header"], ["footer"]]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("finds AssetWrapper in array type", () => {
|
|
124
|
+
const node: ObjectType = {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
items: {
|
|
128
|
+
required: true,
|
|
129
|
+
node: {
|
|
130
|
+
type: "array",
|
|
131
|
+
elementType: { type: "ref", ref: "AssetWrapper" },
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
138
|
+
expect(paths).toEqual([["items"]]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("finds nested AssetWrapper via type registry", () => {
|
|
142
|
+
const headerType: ObjectType = {
|
|
143
|
+
type: "object",
|
|
144
|
+
name: "ContentCardHeader",
|
|
145
|
+
properties: {
|
|
146
|
+
left: { required: false, node: { type: "ref", ref: "AssetWrapper" } },
|
|
147
|
+
right: { required: false, node: { type: "ref", ref: "AssetWrapper" } },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const cardType: ObjectType = {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
header: {
|
|
155
|
+
required: false,
|
|
156
|
+
node: { type: "ref", ref: "ContentCardHeader" },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const typeRegistry: TypeRegistry = new Map([
|
|
162
|
+
["ContentCardHeader", headerType],
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const paths = findAssetWrapperPaths(cardType, typeRegistry);
|
|
166
|
+
expect(paths).toEqual([
|
|
167
|
+
["header", "left"],
|
|
168
|
+
["header", "right"],
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("finds deeply nested AssetWrapper paths", () => {
|
|
173
|
+
const slotType: ObjectType = {
|
|
174
|
+
type: "object",
|
|
175
|
+
name: "SlotConfig",
|
|
176
|
+
properties: {
|
|
177
|
+
content: { required: true, node: { type: "ref", ref: "AssetWrapper" } },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const sectionType: ObjectType = {
|
|
182
|
+
type: "object",
|
|
183
|
+
name: "Section",
|
|
184
|
+
properties: {
|
|
185
|
+
slot: { required: true, node: { type: "ref", ref: "SlotConfig" } },
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const pageType: ObjectType = {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
section: { required: true, node: { type: "ref", ref: "Section" } },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const typeRegistry: TypeRegistry = new Map([
|
|
197
|
+
["SlotConfig", slotType],
|
|
198
|
+
["Section", sectionType],
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
const paths = findAssetWrapperPaths(pageType, typeRegistry);
|
|
202
|
+
expect(paths).toEqual([["section", "slot", "content"]]);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("handles inline nested objects", () => {
|
|
206
|
+
const node: ObjectType = {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
config: {
|
|
210
|
+
required: true,
|
|
211
|
+
node: {
|
|
212
|
+
type: "object",
|
|
213
|
+
properties: {
|
|
214
|
+
icon: {
|
|
215
|
+
required: false,
|
|
216
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
225
|
+
expect(paths).toEqual([["config", "icon"]]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("handles circular references without infinite loop", () => {
|
|
229
|
+
const nodeA: ObjectType = {
|
|
230
|
+
type: "object",
|
|
231
|
+
name: "NodeA",
|
|
232
|
+
properties: {
|
|
233
|
+
child: { required: false, node: { type: "ref", ref: "NodeB" } },
|
|
234
|
+
slot: { required: false, node: { type: "ref", ref: "AssetWrapper" } },
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const nodeB: ObjectType = {
|
|
239
|
+
type: "object",
|
|
240
|
+
name: "NodeB",
|
|
241
|
+
properties: {
|
|
242
|
+
parent: { required: false, node: { type: "ref", ref: "NodeA" } },
|
|
243
|
+
icon: { required: false, node: { type: "ref", ref: "AssetWrapper" } },
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const typeRegistry: TypeRegistry = new Map([
|
|
248
|
+
["NodeA", nodeA],
|
|
249
|
+
["NodeB", nodeB],
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// Should not infinite loop and should find paths
|
|
253
|
+
const paths = findAssetWrapperPaths(nodeA, typeRegistry);
|
|
254
|
+
expect(paths).toContainEqual(["slot"]);
|
|
255
|
+
expect(paths).toContainEqual(["child", "icon"]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("returns empty array when no AssetWrapper found", () => {
|
|
259
|
+
const node: ObjectType = {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
name: { required: true, node: { type: "string" } },
|
|
263
|
+
count: { required: false, node: { type: "number" } },
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
268
|
+
expect(paths).toEqual([]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("handles union types with AssetWrapper", () => {
|
|
272
|
+
const node: ObjectType = {
|
|
273
|
+
type: "object",
|
|
274
|
+
properties: {
|
|
275
|
+
content: {
|
|
276
|
+
required: true,
|
|
277
|
+
node: {
|
|
278
|
+
type: "or",
|
|
279
|
+
or: [{ type: "ref", ref: "AssetWrapper" }, { type: "string" }],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const paths = findAssetWrapperPaths(node, new Map());
|
|
286
|
+
expect(paths).toEqual([["content"]]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("handles intersection types with AssetWrapper", () => {
|
|
290
|
+
const baseType: ObjectType = {
|
|
291
|
+
type: "object",
|
|
292
|
+
name: "BaseType",
|
|
293
|
+
properties: {
|
|
294
|
+
slot: { required: true, node: { type: "ref", ref: "AssetWrapper" } },
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const node: ObjectType = {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
mixed: {
|
|
302
|
+
required: true,
|
|
303
|
+
node: {
|
|
304
|
+
type: "and",
|
|
305
|
+
and: [
|
|
306
|
+
{ type: "ref", ref: "BaseType" },
|
|
307
|
+
{
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
extra: { required: true, node: { type: "string" } },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const typeRegistry: TypeRegistry = new Map([["BaseType", baseType]]);
|
|
320
|
+
|
|
321
|
+
const paths = findAssetWrapperPaths(node, typeRegistry);
|
|
322
|
+
expect(paths).toContainEqual(["mixed", "slot"]);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("finds direct ref extending AssetWrapper", () => {
|
|
326
|
+
const headerType: ObjectType = {
|
|
327
|
+
type: "object",
|
|
328
|
+
name: "Header",
|
|
329
|
+
properties: {
|
|
330
|
+
title: { required: true, node: { type: "string" } },
|
|
331
|
+
},
|
|
332
|
+
extends: { type: "ref", ref: "AssetWrapper<AnyAsset>" },
|
|
333
|
+
additionalProperties: false,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const tableType: ObjectType = {
|
|
337
|
+
type: "object",
|
|
338
|
+
properties: {
|
|
339
|
+
header: {
|
|
340
|
+
required: true,
|
|
341
|
+
node: { type: "ref", ref: "Header" },
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const typeRegistry: TypeRegistry = new Map([["Header", headerType]]);
|
|
347
|
+
|
|
348
|
+
const paths = findAssetWrapperPaths(tableType, typeRegistry);
|
|
349
|
+
expect(paths).toContainEqual(["header"]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("finds array of refs extending AssetWrapper", () => {
|
|
353
|
+
const headerType: ObjectType = {
|
|
354
|
+
type: "object",
|
|
355
|
+
name: "Header",
|
|
356
|
+
properties: {
|
|
357
|
+
title: { required: true, node: { type: "string" } },
|
|
358
|
+
},
|
|
359
|
+
extends: { type: "ref", ref: "AssetWrapper<Asset>" },
|
|
360
|
+
additionalProperties: false,
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const tableType: ObjectType = {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
headers: {
|
|
367
|
+
required: true,
|
|
368
|
+
node: {
|
|
369
|
+
type: "array",
|
|
370
|
+
elementType: { type: "ref", ref: "Header" },
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
const typeRegistry: TypeRegistry = new Map([["Header", headerType]]);
|
|
377
|
+
|
|
378
|
+
const paths = findAssetWrapperPaths(tableType, typeRegistry);
|
|
379
|
+
expect(paths).toContainEqual(["headers"]);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("finds nested AssetWrapper paths within type extending AssetWrapper", () => {
|
|
383
|
+
const headerType: ObjectType = {
|
|
384
|
+
type: "object",
|
|
385
|
+
name: "Header",
|
|
386
|
+
properties: {
|
|
387
|
+
icon: {
|
|
388
|
+
required: false,
|
|
389
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
extends: { type: "ref", ref: "AssetWrapper<Asset>" },
|
|
393
|
+
additionalProperties: false,
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const tableType: ObjectType = {
|
|
397
|
+
type: "object",
|
|
398
|
+
properties: {
|
|
399
|
+
header: {
|
|
400
|
+
required: true,
|
|
401
|
+
node: { type: "ref", ref: "Header" },
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const typeRegistry: TypeRegistry = new Map([["Header", headerType]]);
|
|
407
|
+
|
|
408
|
+
const paths = findAssetWrapperPaths(tableType, typeRegistry);
|
|
409
|
+
// Should find both the header itself (extends AssetWrapper) and header.icon
|
|
410
|
+
expect(paths).toContainEqual(["header"]);
|
|
411
|
+
expect(paths).toContainEqual(["header", "icon"]);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("finds AssetWrapper paths through Array<ObjectType> element types", () => {
|
|
415
|
+
// StaticFilter has label/value as AssetWrapper (doesn't extend AssetWrapper)
|
|
416
|
+
const staticFilterType: ObjectType = {
|
|
417
|
+
type: "object",
|
|
418
|
+
name: "StaticFilter",
|
|
419
|
+
properties: {
|
|
420
|
+
label: {
|
|
421
|
+
required: true,
|
|
422
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
423
|
+
},
|
|
424
|
+
value: {
|
|
425
|
+
required: true,
|
|
426
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
427
|
+
},
|
|
428
|
+
comparator: {
|
|
429
|
+
required: true,
|
|
430
|
+
node: { type: "string" },
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
additionalProperties: false,
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Header has staticFilters: Array<StaticFilter> (inline element type)
|
|
437
|
+
const headerType: ObjectType = {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: {
|
|
440
|
+
staticFilters: {
|
|
441
|
+
required: false,
|
|
442
|
+
node: {
|
|
443
|
+
type: "array",
|
|
444
|
+
elementType: staticFilterType,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const typeRegistry: TypeRegistry = new Map([
|
|
451
|
+
["StaticFilter", staticFilterType],
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
const paths = findAssetWrapperPaths(headerType, typeRegistry);
|
|
455
|
+
// Should recurse into Array<StaticFilter> and find the nested AssetWrapper paths
|
|
456
|
+
expect(paths).toContainEqual(["staticFilters", "label"]);
|
|
457
|
+
expect(paths).toContainEqual(["staticFilters", "value"]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("finds AssetWrapper paths through Array<RefType> element types", () => {
|
|
461
|
+
// Same test but with RefType element type instead of inline ObjectType
|
|
462
|
+
const staticFilterType: ObjectType = {
|
|
463
|
+
type: "object",
|
|
464
|
+
name: "StaticFilter",
|
|
465
|
+
properties: {
|
|
466
|
+
label: {
|
|
467
|
+
required: true,
|
|
468
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
469
|
+
},
|
|
470
|
+
value: {
|
|
471
|
+
required: true,
|
|
472
|
+
node: { type: "ref", ref: "AssetWrapper" },
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
additionalProperties: false,
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const headerType: ObjectType = {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
staticFilters: {
|
|
482
|
+
required: false,
|
|
483
|
+
node: {
|
|
484
|
+
type: "array",
|
|
485
|
+
elementType: { type: "ref", ref: "StaticFilter" },
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const typeRegistry: TypeRegistry = new Map([
|
|
492
|
+
["StaticFilter", staticFilterType],
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
const paths = findAssetWrapperPaths(headerType, typeRegistry);
|
|
496
|
+
expect(paths).toContainEqual(["staticFilters", "label"]);
|
|
497
|
+
expect(paths).toContainEqual(["staticFilters", "value"]);
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
describe("extendsAssetWrapper", () => {
|
|
502
|
+
test("returns true for direct AssetWrapper extension", () => {
|
|
503
|
+
const headerType: ObjectType = {
|
|
504
|
+
type: "object",
|
|
505
|
+
name: "Header",
|
|
506
|
+
properties: {},
|
|
507
|
+
extends: { type: "ref", ref: "AssetWrapper<Asset>" },
|
|
508
|
+
additionalProperties: false,
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const registry: TypeRegistry = new Map([["Header", headerType]]);
|
|
512
|
+
|
|
513
|
+
expect(extendsAssetWrapper({ type: "ref", ref: "Header" }, registry)).toBe(
|
|
514
|
+
true,
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test("returns true for transitive AssetWrapper extension", () => {
|
|
519
|
+
const baseType: ObjectType = {
|
|
520
|
+
type: "object",
|
|
521
|
+
name: "ListItemBase",
|
|
522
|
+
properties: {},
|
|
523
|
+
extends: { type: "ref", ref: "AssetWrapper<AnyAsset>" },
|
|
524
|
+
additionalProperties: false,
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const derivedType: ObjectType = {
|
|
528
|
+
type: "object",
|
|
529
|
+
name: "ListItem",
|
|
530
|
+
properties: {
|
|
531
|
+
help: { required: false, node: { type: "string" } },
|
|
532
|
+
},
|
|
533
|
+
extends: { type: "ref", ref: "ListItemBase" },
|
|
534
|
+
additionalProperties: false,
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const registry: TypeRegistry = new Map([
|
|
538
|
+
["ListItemBase", baseType],
|
|
539
|
+
["ListItem", derivedType],
|
|
540
|
+
]);
|
|
541
|
+
|
|
542
|
+
expect(
|
|
543
|
+
extendsAssetWrapper({ type: "ref", ref: "ListItem" }, registry),
|
|
544
|
+
).toBe(true);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test("returns false for non-extending types", () => {
|
|
548
|
+
const normalType: ObjectType = {
|
|
549
|
+
type: "object",
|
|
550
|
+
name: "Metadata",
|
|
551
|
+
properties: {
|
|
552
|
+
title: { required: true, node: { type: "string" } },
|
|
553
|
+
},
|
|
554
|
+
additionalProperties: false,
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
const registry: TypeRegistry = new Map([["Metadata", normalType]]);
|
|
558
|
+
|
|
559
|
+
expect(
|
|
560
|
+
extendsAssetWrapper({ type: "ref", ref: "Metadata" }, registry),
|
|
561
|
+
).toBe(false);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("handles circular references without infinite loop", () => {
|
|
565
|
+
const typeA: ObjectType = {
|
|
566
|
+
type: "object",
|
|
567
|
+
name: "TypeA",
|
|
568
|
+
properties: {},
|
|
569
|
+
extends: { type: "ref", ref: "TypeB" },
|
|
570
|
+
additionalProperties: false,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const typeB: ObjectType = {
|
|
574
|
+
type: "object",
|
|
575
|
+
name: "TypeB",
|
|
576
|
+
properties: {},
|
|
577
|
+
extends: { type: "ref", ref: "TypeA" },
|
|
578
|
+
additionalProperties: false,
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const registry: TypeRegistry = new Map([
|
|
582
|
+
["TypeA", typeA],
|
|
583
|
+
["TypeB", typeB],
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
// Should not infinite loop - returns false since neither extends AssetWrapper
|
|
587
|
+
expect(extendsAssetWrapper({ type: "ref", ref: "TypeA" }, registry)).toBe(
|
|
588
|
+
false,
|
|
589
|
+
);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("returns false for non-ref node types", () => {
|
|
593
|
+
const registry: TypeRegistry = new Map();
|
|
594
|
+
|
|
595
|
+
expect(extendsAssetWrapper({ type: "string" }, registry)).toBe(false);
|
|
596
|
+
});
|
|
597
|
+
});
|