@squiz/dxp-cli-next 5.31.0-develop.4 → 5.31.0-develop.6
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/lib/page/layouts/deploy/deploy.js +44 -9
- package/lib/page/layouts/deploy/deploy.spec.js +110 -19
- package/lib/page/layouts/dev/dev.js +18 -4
- package/lib/page/layouts/dev/dev.spec.js +117 -8
- package/lib/page/layouts/validation/index.d.ts +2 -0
- package/lib/page/layouts/validation/index.js +5 -1
- package/lib/page/layouts/validation/property-consistency.d.ts +7 -0
- package/lib/page/layouts/validation/property-consistency.js +92 -0
- package/lib/page/layouts/validation/property-consistency.spec.d.ts +1 -0
- package/lib/page/layouts/validation/property-consistency.spec.js +305 -0
- package/lib/page/layouts/validation/validateLayoutFormat.d.ts +2 -0
- package/lib/page/layouts/validation/validateLayoutFormat.js +27 -0
- package/lib/page/layouts/validation/validateLayoutFormat.spec.d.ts +1 -0
- package/lib/page/layouts/validation/validateLayoutFormat.spec.js +42 -0
- package/lib/page/layouts/validation/zone-consistency.d.ts +1 -1
- package/lib/page/layouts/validation/zone-consistency.js +10 -9
- package/lib/page/layouts/validation/zone-consistency.spec.js +32 -34
- package/lib/page/utils/definitions.d.ts +347 -50
- package/lib/page/utils/definitions.js +103 -22
- package/lib/page/utils/definitions.spec.js +516 -267
- package/lib/page/utils/normalize.d.ts +8 -0
- package/lib/page/utils/normalize.js +61 -0
- package/lib/page/utils/normalize.spec.d.ts +1 -0
- package/lib/page/utils/normalize.spec.js +315 -0
- package/lib/page/utils/parse-args.d.ts +20 -4
- package/lib/page/utils/parse-args.js +48 -13
- package/lib/page/utils/parse-args.spec.js +159 -21
- package/lib/page/utils/render.d.ts +27 -9
- package/lib/page/utils/render.js +66 -12
- package/lib/page/utils/render.spec.js +14 -14
- package/lib/page/utils/server.d.ts +1 -1
- package/lib/page/utils/server.js +2 -2
- package/lib/page/utils/server.spec.js +13 -13
- package/package.json +1 -1
|
@@ -37,8 +37,60 @@ const definitions_1 = require("./definitions");
|
|
|
37
37
|
const validation_1 = require("../templates/validation");
|
|
38
38
|
jest.mock('node:fs/promises');
|
|
39
39
|
describe('loadLayoutDefinition', () => {
|
|
40
|
-
const
|
|
41
|
-
const
|
|
40
|
+
const manifestJson = './some-dir/manifest.json';
|
|
41
|
+
const pageLayoutYaml = './some-dir/page-layout.yaml';
|
|
42
|
+
const jsonContent = JSON.stringify({
|
|
43
|
+
name: 'test-layout',
|
|
44
|
+
displayName: 'Test Layout',
|
|
45
|
+
description: 'A test layout',
|
|
46
|
+
zones: [
|
|
47
|
+
{
|
|
48
|
+
key: 'main',
|
|
49
|
+
displayName: 'Main Zone',
|
|
50
|
+
description: 'Main content area',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
properties: {
|
|
54
|
+
color: {
|
|
55
|
+
title: 'Color',
|
|
56
|
+
description: 'Color options',
|
|
57
|
+
type: 'string',
|
|
58
|
+
enum: ['red', 'blue'],
|
|
59
|
+
},
|
|
60
|
+
showHeader: {
|
|
61
|
+
title: 'Show Header',
|
|
62
|
+
description: 'Toggle header visibility',
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
},
|
|
65
|
+
customTitle: {
|
|
66
|
+
title: 'Custom Title',
|
|
67
|
+
description: 'Enter a custom title',
|
|
68
|
+
type: 'string',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
entry: 'template.hbs',
|
|
72
|
+
});
|
|
73
|
+
// missing "name" field
|
|
74
|
+
const jsonContentInvalidLayout = JSON.stringify({
|
|
75
|
+
displayName: 'Test Layout',
|
|
76
|
+
description: 'A test layout',
|
|
77
|
+
zones: [
|
|
78
|
+
{
|
|
79
|
+
key: 'main',
|
|
80
|
+
displayName: 'Main Zone',
|
|
81
|
+
description: 'Main content area',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
properties: {
|
|
85
|
+
color: {
|
|
86
|
+
title: 'Color',
|
|
87
|
+
description: 'Color options',
|
|
88
|
+
type: 'string',
|
|
89
|
+
enum: ['red', 'blue'],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
entry: 'template.hbs',
|
|
93
|
+
});
|
|
42
94
|
const yamlContent = `
|
|
43
95
|
name: test-layout
|
|
44
96
|
displayName: Test Layout
|
|
@@ -71,145 +123,56 @@ options:
|
|
|
71
123
|
values: ['red', 'blue']
|
|
72
124
|
entry: template.hbs
|
|
73
125
|
`;
|
|
74
|
-
const jsonContent = JSON.stringify({
|
|
75
|
-
name: 'test-layout',
|
|
76
|
-
displayName: 'Test Layout',
|
|
77
|
-
description: 'A test layout',
|
|
78
|
-
zones: {
|
|
79
|
-
main: {
|
|
80
|
-
displayName: 'Main Zone',
|
|
81
|
-
description: 'Main content area',
|
|
82
|
-
minNodes: 1,
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
options: {
|
|
86
|
-
color: {
|
|
87
|
-
displayName: 'Color',
|
|
88
|
-
description: 'Color options',
|
|
89
|
-
values: ['red', 'blue'],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
entry: 'template.hbs',
|
|
93
|
-
});
|
|
94
|
-
// missing "name" field
|
|
95
|
-
const jsonContentInvalidLayout = JSON.stringify({
|
|
96
|
-
displayName: 'Test Layout',
|
|
97
|
-
description: 'A test layout',
|
|
98
|
-
zones: {
|
|
99
|
-
main: {
|
|
100
|
-
displayName: 'Main Zone',
|
|
101
|
-
description: 'Main content area',
|
|
102
|
-
minNodes: 1,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
options: {
|
|
106
|
-
color: {
|
|
107
|
-
displayName: 'Color',
|
|
108
|
-
description: 'Color options',
|
|
109
|
-
values: ['red', 'blue'],
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
entry: 'template.hbs',
|
|
113
|
-
});
|
|
114
126
|
const templateContent = '<div>{{content}}</div>';
|
|
115
127
|
beforeEach(() => {
|
|
116
128
|
jest.resetAllMocks();
|
|
117
129
|
});
|
|
118
|
-
it('should load layout definition from
|
|
130
|
+
it('should load layout definition from manifest JSON file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
131
|
fs.readFile.mockImplementation((filePath) => {
|
|
120
|
-
if (filePath.endsWith('
|
|
121
|
-
return
|
|
132
|
+
if (filePath.endsWith('manifest.json')) {
|
|
133
|
+
return jsonContent;
|
|
122
134
|
}
|
|
123
135
|
if (filePath.endsWith('template.hbs')) {
|
|
124
136
|
return templateContent;
|
|
125
137
|
}
|
|
126
138
|
throw new Error('File not found');
|
|
127
139
|
});
|
|
128
|
-
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(
|
|
140
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(manifestJson);
|
|
129
141
|
expect(layoutDefinition).toEqual({
|
|
130
142
|
name: 'test-layout',
|
|
131
143
|
displayName: 'Test Layout',
|
|
132
144
|
description: 'A test layout',
|
|
133
|
-
zones:
|
|
134
|
-
|
|
145
|
+
zones: [
|
|
146
|
+
{
|
|
147
|
+
key: 'main',
|
|
135
148
|
displayName: 'Main Zone',
|
|
136
149
|
description: 'Main content area',
|
|
137
|
-
minNodes: 1,
|
|
138
150
|
},
|
|
139
|
-
|
|
140
|
-
|
|
151
|
+
],
|
|
152
|
+
properties: {
|
|
141
153
|
color: {
|
|
142
|
-
|
|
154
|
+
title: 'Color',
|
|
143
155
|
description: 'Color options',
|
|
144
|
-
|
|
156
|
+
type: 'string',
|
|
157
|
+
enum: ['red', 'blue'],
|
|
145
158
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
it('should load layout definition from JSON file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
-
fs.readFile.mockImplementation((filePath) => {
|
|
152
|
-
if (filePath.endsWith('page-layout.yaml')) {
|
|
153
|
-
throw { code: 'ENOENT' };
|
|
154
|
-
}
|
|
155
|
-
if (filePath.endsWith('page-layout.json')) {
|
|
156
|
-
return jsonContent;
|
|
157
|
-
}
|
|
158
|
-
if (filePath.endsWith('template.hbs')) {
|
|
159
|
-
return templateContent;
|
|
160
|
-
}
|
|
161
|
-
throw new Error('File not found');
|
|
162
|
-
});
|
|
163
|
-
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(paintLayoutFileJson);
|
|
164
|
-
expect(layoutDefinition).toEqual({
|
|
165
|
-
name: 'test-layout',
|
|
166
|
-
displayName: 'Test Layout',
|
|
167
|
-
description: 'A test layout',
|
|
168
|
-
zones: {
|
|
169
|
-
main: {
|
|
170
|
-
displayName: 'Main Zone',
|
|
171
|
-
description: 'Main content area',
|
|
172
|
-
minNodes: 1,
|
|
159
|
+
showHeader: {
|
|
160
|
+
title: 'Show Header',
|
|
161
|
+
description: 'Toggle header visibility',
|
|
162
|
+
type: 'boolean',
|
|
173
163
|
},
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
description: 'Color options',
|
|
179
|
-
values: ['red', 'blue'],
|
|
164
|
+
customTitle: {
|
|
165
|
+
title: 'Custom Title',
|
|
166
|
+
description: 'Enter a custom title',
|
|
167
|
+
type: 'string',
|
|
180
168
|
},
|
|
181
169
|
},
|
|
182
170
|
template: templateContent,
|
|
183
171
|
});
|
|
184
172
|
}));
|
|
185
|
-
it('should throw validation error if invalid data for
|
|
173
|
+
it('should throw validation error if invalid data for manifest JSON', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
186
174
|
fs.readFile.mockImplementation((filePath) => {
|
|
187
|
-
if (filePath.endsWith('
|
|
188
|
-
return yamlContentInvalidLayout;
|
|
189
|
-
}
|
|
190
|
-
if (filePath.endsWith('template.hbs')) {
|
|
191
|
-
return templateContent;
|
|
192
|
-
}
|
|
193
|
-
throw new Error('File not found');
|
|
194
|
-
});
|
|
195
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(paintLayoutFileYaml)).rejects
|
|
196
|
-
.toThrowErrorMatchingInlineSnapshot(`
|
|
197
|
-
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml: [
|
|
198
|
-
{
|
|
199
|
-
"code": "invalid_type",
|
|
200
|
-
"expected": "string",
|
|
201
|
-
"received": "undefined",
|
|
202
|
-
"path": [
|
|
203
|
-
"name"
|
|
204
|
-
],
|
|
205
|
-
"message": "Required"
|
|
206
|
-
}
|
|
207
|
-
]"
|
|
208
|
-
`);
|
|
209
|
-
}));
|
|
210
|
-
it('should throw validation error if invalid data for JSON', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
|
-
fs.readFile.mockImplementation((filePath) => {
|
|
212
|
-
if (filePath.endsWith('page-layout.json')) {
|
|
175
|
+
if (filePath.endsWith('manifest.json')) {
|
|
213
176
|
return jsonContentInvalidLayout;
|
|
214
177
|
}
|
|
215
178
|
if (filePath.endsWith('template.hbs')) {
|
|
@@ -217,49 +180,17 @@ entry: template.hbs
|
|
|
217
180
|
}
|
|
218
181
|
throw new Error('File not found');
|
|
219
182
|
});
|
|
220
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(
|
|
183
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects
|
|
221
184
|
.toThrowErrorMatchingInlineSnapshot(`
|
|
222
|
-
"Failed loading layout definition: Failed to parse ./some-dir/
|
|
223
|
-
{
|
|
224
|
-
"code": "invalid_type",
|
|
225
|
-
"expected": "string",
|
|
226
|
-
"received": "undefined",
|
|
227
|
-
"path": [
|
|
228
|
-
"name"
|
|
229
|
-
],
|
|
230
|
-
"message": "Required"
|
|
231
|
-
}
|
|
232
|
-
]"
|
|
233
|
-
`);
|
|
234
|
-
}));
|
|
235
|
-
it('should throw an error if YAML layout file has invalid YAML', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
236
|
-
fs.readFile.mockImplementation((filePath) => {
|
|
237
|
-
if (filePath.endsWith('page-layout.yaml')) {
|
|
238
|
-
return 'name: invalid yaml, entry: template.hbs';
|
|
239
|
-
}
|
|
240
|
-
if (filePath.endsWith('page-layout.json')) {
|
|
241
|
-
return jsonContent;
|
|
242
|
-
}
|
|
243
|
-
if (filePath.endsWith('template.hbs')) {
|
|
244
|
-
return templateContent;
|
|
245
|
-
}
|
|
246
|
-
throw new Error('File not found');
|
|
247
|
-
});
|
|
248
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(paintLayoutFileYaml)).rejects
|
|
249
|
-
.toThrowErrorMatchingInlineSnapshot(`
|
|
250
|
-
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml: Nested mappings are not allowed in compact mappings at line 1, column 7:
|
|
185
|
+
"Failed loading layout definition: Failed to parse ./some-dir/manifest.json:
|
|
251
186
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
"
|
|
187
|
+
Schema validation failed:
|
|
188
|
+
- Required at "name""
|
|
255
189
|
`);
|
|
256
190
|
}));
|
|
257
|
-
it('should throw an error if JSON layout file has invalid JSON', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
191
|
+
it('should throw an error if JSON layout file has invalid manifest JSON', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
258
192
|
fs.readFile.mockImplementation((filePath) => {
|
|
259
|
-
if (filePath.endsWith('
|
|
260
|
-
throw { code: 'ENOENT' };
|
|
261
|
-
}
|
|
262
|
-
if (filePath.endsWith('page-layout.json')) {
|
|
193
|
+
if (filePath.endsWith('manifest.json')) {
|
|
263
194
|
return '{invalid: json';
|
|
264
195
|
}
|
|
265
196
|
if (filePath.endsWith('template.hbs')) {
|
|
@@ -267,9 +198,9 @@ entry: template.hbs
|
|
|
267
198
|
}
|
|
268
199
|
throw new Error('File not found');
|
|
269
200
|
});
|
|
270
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(
|
|
201
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects
|
|
271
202
|
.toThrowErrorMatchingInlineSnapshot(`
|
|
272
|
-
"Failed loading layout definition: Failed to parse ./some-dir/
|
|
203
|
+
"Failed loading layout definition: Failed to parse ./some-dir/manifest.json: Flow map must end with a } at line 1, column 15:
|
|
273
204
|
|
|
274
205
|
{invalid: json
|
|
275
206
|
^
|
|
@@ -277,122 +208,38 @@ entry: template.hbs
|
|
|
277
208
|
`);
|
|
278
209
|
}));
|
|
279
210
|
it('should throw an error if layout file do not have valid extension', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
280
|
-
yield expect((0, definitions_1.loadLayoutDefinition)('/foo/
|
|
211
|
+
yield expect((0, definitions_1.loadLayoutDefinition)('/foo/manifest.html')).rejects.toThrow('Layout file must have a valid extension: [yaml|json]');
|
|
281
212
|
}));
|
|
282
213
|
it('should throw an error if template file is not found', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
283
214
|
fs.readFile.mockImplementation((filePath) => {
|
|
284
|
-
if (filePath.endsWith('
|
|
285
|
-
return
|
|
215
|
+
if (filePath.endsWith('manifest.json')) {
|
|
216
|
+
return jsonContent;
|
|
286
217
|
}
|
|
287
218
|
throw Error('File not found');
|
|
288
219
|
});
|
|
289
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(
|
|
290
|
-
}));
|
|
291
|
-
it('should load layout definition without options field from YAML file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
292
|
-
const yamlContentWithoutOptions = `
|
|
293
|
-
name: test-layout
|
|
294
|
-
displayName: Test Layout
|
|
295
|
-
description: A test layout
|
|
296
|
-
zones:
|
|
297
|
-
main:
|
|
298
|
-
displayName: Main Zone
|
|
299
|
-
description: Main content area
|
|
300
|
-
minNodes: 1
|
|
301
|
-
entry: template.hbs
|
|
302
|
-
`;
|
|
303
|
-
fs.readFile.mockImplementation((filePath) => {
|
|
304
|
-
if (filePath.endsWith('page-layout.yaml')) {
|
|
305
|
-
return yamlContentWithoutOptions;
|
|
306
|
-
}
|
|
307
|
-
if (filePath.endsWith('template.hbs')) {
|
|
308
|
-
return templateContent;
|
|
309
|
-
}
|
|
310
|
-
throw new Error('File not found');
|
|
311
|
-
});
|
|
312
|
-
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(paintLayoutFileYaml);
|
|
313
|
-
expect(layoutDefinition).toEqual({
|
|
314
|
-
name: 'test-layout',
|
|
315
|
-
displayName: 'Test Layout',
|
|
316
|
-
description: 'A test layout',
|
|
317
|
-
zones: {
|
|
318
|
-
main: {
|
|
319
|
-
displayName: 'Main Zone',
|
|
320
|
-
description: 'Main content area',
|
|
321
|
-
minNodes: 1,
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
template: templateContent,
|
|
325
|
-
});
|
|
326
|
-
}));
|
|
327
|
-
it('should load layout definition without maxNodes in zones from YAML file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
328
|
-
const yamlContentWithoutMaxNodes = `
|
|
329
|
-
name: test-layout
|
|
330
|
-
displayName: Test Layout
|
|
331
|
-
description: A test layout
|
|
332
|
-
zones:
|
|
333
|
-
main:
|
|
334
|
-
displayName: Main Zone
|
|
335
|
-
description: Main content area
|
|
336
|
-
minNodes: 1
|
|
337
|
-
sidebar:
|
|
338
|
-
displayName: Sidebar Zone
|
|
339
|
-
description: Sidebar content area
|
|
340
|
-
minNodes: 0
|
|
341
|
-
entry: template.hbs
|
|
342
|
-
`;
|
|
343
|
-
fs.readFile.mockImplementation((filePath) => {
|
|
344
|
-
if (filePath.endsWith('page-layout.yaml')) {
|
|
345
|
-
return yamlContentWithoutMaxNodes;
|
|
346
|
-
}
|
|
347
|
-
if (filePath.endsWith('template.hbs')) {
|
|
348
|
-
return templateContent;
|
|
349
|
-
}
|
|
350
|
-
throw new Error('File not found');
|
|
351
|
-
});
|
|
352
|
-
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(paintLayoutFileYaml);
|
|
353
|
-
expect(layoutDefinition).toEqual({
|
|
354
|
-
name: 'test-layout',
|
|
355
|
-
displayName: 'Test Layout',
|
|
356
|
-
description: 'A test layout',
|
|
357
|
-
zones: {
|
|
358
|
-
main: {
|
|
359
|
-
displayName: 'Main Zone',
|
|
360
|
-
description: 'Main content area',
|
|
361
|
-
minNodes: 1,
|
|
362
|
-
},
|
|
363
|
-
sidebar: {
|
|
364
|
-
displayName: 'Sidebar Zone',
|
|
365
|
-
description: 'Sidebar content area',
|
|
366
|
-
minNodes: 0,
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
template: templateContent,
|
|
370
|
-
});
|
|
220
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects.toThrow('Failed loading layout definition: Failed loading template file "template.hbs": File not found');
|
|
371
221
|
}));
|
|
372
|
-
it('should load layout definition without options and maxNodes from JSON file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
+
it('should load layout definition without options and maxNodes from manifest JSON file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
373
223
|
const jsonContentWithoutOptionsAndMaxNodes = JSON.stringify({
|
|
374
224
|
name: 'test-layout',
|
|
375
225
|
displayName: 'Test Layout',
|
|
376
226
|
description: 'A test layout',
|
|
377
|
-
zones:
|
|
378
|
-
|
|
227
|
+
zones: [
|
|
228
|
+
{
|
|
229
|
+
key: 'main',
|
|
379
230
|
displayName: 'Main Zone',
|
|
380
231
|
description: 'Main content area',
|
|
381
|
-
minNodes: 1,
|
|
382
232
|
},
|
|
383
|
-
|
|
233
|
+
{
|
|
234
|
+
key: 'sidebar',
|
|
384
235
|
displayName: 'Sidebar Zone',
|
|
385
236
|
description: 'Sidebar content area',
|
|
386
|
-
minNodes: 0,
|
|
387
237
|
},
|
|
388
|
-
|
|
238
|
+
],
|
|
389
239
|
entry: 'template.hbs',
|
|
390
240
|
});
|
|
391
241
|
fs.readFile.mockImplementation((filePath) => {
|
|
392
|
-
if (filePath.endsWith('
|
|
393
|
-
throw { code: 'ENOENT' };
|
|
394
|
-
}
|
|
395
|
-
if (filePath.endsWith('page-layout.json')) {
|
|
242
|
+
if (filePath.endsWith('manifest.json')) {
|
|
396
243
|
return jsonContentWithoutOptionsAndMaxNodes;
|
|
397
244
|
}
|
|
398
245
|
if (filePath.endsWith('template.hbs')) {
|
|
@@ -400,41 +247,443 @@ entry: template.hbs
|
|
|
400
247
|
}
|
|
401
248
|
throw new Error('File not found');
|
|
402
249
|
});
|
|
403
|
-
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(
|
|
250
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(manifestJson);
|
|
404
251
|
expect(layoutDefinition).toEqual({
|
|
405
252
|
name: 'test-layout',
|
|
406
253
|
displayName: 'Test Layout',
|
|
407
254
|
description: 'A test layout',
|
|
408
|
-
zones:
|
|
409
|
-
|
|
255
|
+
zones: [
|
|
256
|
+
{
|
|
257
|
+
key: 'main',
|
|
410
258
|
displayName: 'Main Zone',
|
|
411
259
|
description: 'Main content area',
|
|
412
|
-
minNodes: 1,
|
|
413
260
|
},
|
|
414
|
-
|
|
261
|
+
{
|
|
262
|
+
key: 'sidebar',
|
|
415
263
|
displayName: 'Sidebar Zone',
|
|
416
264
|
description: 'Sidebar content area',
|
|
417
|
-
minNodes: 0,
|
|
418
265
|
},
|
|
419
|
-
|
|
266
|
+
],
|
|
420
267
|
template: templateContent,
|
|
421
268
|
});
|
|
422
269
|
}));
|
|
270
|
+
describe('YAML format (deprecated)', () => {
|
|
271
|
+
it('should load layout definition from YAML file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
272
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
273
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
274
|
+
return yamlContent;
|
|
275
|
+
}
|
|
276
|
+
if (filePath.endsWith('template.hbs')) {
|
|
277
|
+
return templateContent;
|
|
278
|
+
}
|
|
279
|
+
throw new Error('File not found');
|
|
280
|
+
});
|
|
281
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(pageLayoutYaml);
|
|
282
|
+
expect(layoutDefinition).toEqual({
|
|
283
|
+
name: 'test-layout',
|
|
284
|
+
displayName: 'Test Layout',
|
|
285
|
+
description: 'A test layout',
|
|
286
|
+
zones: [
|
|
287
|
+
{
|
|
288
|
+
key: 'main',
|
|
289
|
+
displayName: 'Main Zone',
|
|
290
|
+
description: 'Main content area',
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
properties: {
|
|
294
|
+
color: {
|
|
295
|
+
title: 'Color',
|
|
296
|
+
description: 'Color options',
|
|
297
|
+
type: 'string',
|
|
298
|
+
enum: ['red', 'blue'],
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
template: templateContent,
|
|
302
|
+
});
|
|
303
|
+
}));
|
|
304
|
+
it('should throw validation error if invalid data for YAML', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
305
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
306
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
307
|
+
return yamlContentInvalidLayout;
|
|
308
|
+
}
|
|
309
|
+
if (filePath.endsWith('template.hbs')) {
|
|
310
|
+
return templateContent;
|
|
311
|
+
}
|
|
312
|
+
throw new Error('File not found');
|
|
313
|
+
});
|
|
314
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(pageLayoutYaml)).rejects
|
|
315
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
316
|
+
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml:
|
|
317
|
+
|
|
318
|
+
Schema validation failed:
|
|
319
|
+
- Required at "name""
|
|
320
|
+
`);
|
|
321
|
+
}));
|
|
322
|
+
it('should throw an error if YAML contains properties (instead of options)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
323
|
+
const yamlContentWithProperties = `
|
|
324
|
+
name: test-layout
|
|
325
|
+
displayName: Test Layout
|
|
326
|
+
description: A test layout
|
|
327
|
+
zones:
|
|
328
|
+
main:
|
|
329
|
+
displayName: Main Zone
|
|
330
|
+
description: Main content area
|
|
331
|
+
properties:
|
|
332
|
+
color:
|
|
333
|
+
title: Color
|
|
334
|
+
description: Color options
|
|
335
|
+
type: string
|
|
336
|
+
enum: ['red', 'blue']
|
|
337
|
+
entry: template.hbs
|
|
338
|
+
`;
|
|
339
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
340
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
341
|
+
return yamlContentWithProperties;
|
|
342
|
+
}
|
|
343
|
+
if (filePath.endsWith('template.hbs')) {
|
|
344
|
+
return templateContent;
|
|
345
|
+
}
|
|
346
|
+
throw new Error('File not found');
|
|
347
|
+
});
|
|
348
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(pageLayoutYaml)).rejects
|
|
349
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
350
|
+
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml:
|
|
351
|
+
|
|
352
|
+
Schema validation failed:
|
|
353
|
+
- Unrecognized key(s) in object: 'properties'"
|
|
354
|
+
`);
|
|
355
|
+
}));
|
|
356
|
+
it('should throw an error if YAML layout file has invalid YAML', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
357
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
358
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
359
|
+
return 'name: invalid yaml, entry: template.hbs';
|
|
360
|
+
}
|
|
361
|
+
if (filePath.endsWith('template.hbs')) {
|
|
362
|
+
return templateContent;
|
|
363
|
+
}
|
|
364
|
+
throw new Error('File not found');
|
|
365
|
+
});
|
|
366
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(pageLayoutYaml)).rejects
|
|
367
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
368
|
+
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml: Nested mappings are not allowed in compact mappings at line 1, column 7:
|
|
369
|
+
|
|
370
|
+
name: invalid yaml, entry: template.hbs
|
|
371
|
+
^
|
|
372
|
+
"
|
|
373
|
+
`);
|
|
374
|
+
}));
|
|
375
|
+
it('should load layout definition without options field from YAML file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
376
|
+
const yamlContentWithoutOptions = `
|
|
377
|
+
name: test-layout
|
|
378
|
+
displayName: Test Layout
|
|
379
|
+
description: A test layout
|
|
380
|
+
zones:
|
|
381
|
+
main:
|
|
382
|
+
displayName: Main Zone
|
|
383
|
+
description: Main content area
|
|
384
|
+
minNodes: 1
|
|
385
|
+
entry: template.hbs
|
|
386
|
+
`;
|
|
387
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
388
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
389
|
+
return yamlContentWithoutOptions;
|
|
390
|
+
}
|
|
391
|
+
if (filePath.endsWith('template.hbs')) {
|
|
392
|
+
return templateContent;
|
|
393
|
+
}
|
|
394
|
+
throw new Error('File not found');
|
|
395
|
+
});
|
|
396
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(pageLayoutYaml);
|
|
397
|
+
expect(layoutDefinition).toEqual({
|
|
398
|
+
name: 'test-layout',
|
|
399
|
+
displayName: 'Test Layout',
|
|
400
|
+
description: 'A test layout',
|
|
401
|
+
zones: [
|
|
402
|
+
{
|
|
403
|
+
key: 'main',
|
|
404
|
+
displayName: 'Main Zone',
|
|
405
|
+
description: 'Main content area',
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
template: templateContent,
|
|
409
|
+
});
|
|
410
|
+
}));
|
|
411
|
+
it('should load layout definition without maxNodes in zones from YAML file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
412
|
+
const yamlContentWithoutMaxNodes = `
|
|
413
|
+
name: test-layout
|
|
414
|
+
displayName: Test Layout
|
|
415
|
+
description: A test layout
|
|
416
|
+
zones:
|
|
417
|
+
main:
|
|
418
|
+
displayName: Main Zone
|
|
419
|
+
description: Main content area
|
|
420
|
+
minNodes: 1
|
|
421
|
+
sidebar:
|
|
422
|
+
displayName: Sidebar Zone
|
|
423
|
+
description: Sidebar content area
|
|
424
|
+
minNodes: 0
|
|
425
|
+
entry: template.hbs
|
|
426
|
+
`;
|
|
427
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
428
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
429
|
+
return yamlContentWithoutMaxNodes;
|
|
430
|
+
}
|
|
431
|
+
if (filePath.endsWith('template.hbs')) {
|
|
432
|
+
return templateContent;
|
|
433
|
+
}
|
|
434
|
+
throw new Error('File not found');
|
|
435
|
+
});
|
|
436
|
+
const layoutDefinition = yield (0, definitions_1.loadLayoutDefinition)(pageLayoutYaml);
|
|
437
|
+
expect(layoutDefinition).toEqual({
|
|
438
|
+
name: 'test-layout',
|
|
439
|
+
displayName: 'Test Layout',
|
|
440
|
+
description: 'A test layout',
|
|
441
|
+
zones: [
|
|
442
|
+
{
|
|
443
|
+
key: 'main',
|
|
444
|
+
displayName: 'Main Zone',
|
|
445
|
+
description: 'Main content area',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
key: 'sidebar',
|
|
449
|
+
displayName: 'Sidebar Zone',
|
|
450
|
+
description: 'Sidebar content area',
|
|
451
|
+
},
|
|
452
|
+
],
|
|
453
|
+
template: templateContent,
|
|
454
|
+
});
|
|
455
|
+
}));
|
|
456
|
+
});
|
|
423
457
|
});
|
|
424
458
|
describe('LayoutDefinitionParse', () => {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
459
|
+
describe('V1 format - zones objects and options (deprecated)', () => {
|
|
460
|
+
it('should allow zone.minNodes to be undefined', () => {
|
|
461
|
+
const layoutDefinition = definitions_1.InputLayoutDefinitionV1.parse({
|
|
462
|
+
name: 'test-layout',
|
|
463
|
+
displayName: 'Test Layout',
|
|
464
|
+
description: 'A test layout',
|
|
465
|
+
entry: 'template.hbs',
|
|
466
|
+
zones: {
|
|
467
|
+
main: {
|
|
468
|
+
displayName: 'Main Zone',
|
|
469
|
+
description: 'Main content area',
|
|
470
|
+
},
|
|
434
471
|
},
|
|
435
|
-
}
|
|
472
|
+
});
|
|
473
|
+
expect(layoutDefinition.zones.main.minNodes).toBe(0);
|
|
474
|
+
});
|
|
475
|
+
describe('option schema validation', () => {
|
|
476
|
+
it('should accept specified valueTypes', () => {
|
|
477
|
+
var _a, _b, _c;
|
|
478
|
+
const layoutDefinition = definitions_1.InputLayoutDefinitionV1.parse({
|
|
479
|
+
name: 'test-layout',
|
|
480
|
+
displayName: 'Test Layout',
|
|
481
|
+
description: 'A test layout',
|
|
482
|
+
entry: 'template.hbs',
|
|
483
|
+
zones: {
|
|
484
|
+
main: {
|
|
485
|
+
displayName: 'Main Zone',
|
|
486
|
+
description: 'Main content area',
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
options: {
|
|
490
|
+
theme: {
|
|
491
|
+
displayName: 'Theme',
|
|
492
|
+
description: 'Color theme',
|
|
493
|
+
valueType: 'string-enum',
|
|
494
|
+
values: ['light', 'dark'],
|
|
495
|
+
},
|
|
496
|
+
showSidebar: {
|
|
497
|
+
displayName: 'Show Sidebar',
|
|
498
|
+
description: 'Toggle sidebar',
|
|
499
|
+
valueType: 'boolean',
|
|
500
|
+
},
|
|
501
|
+
customCss: {
|
|
502
|
+
displayName: 'Custom CSS',
|
|
503
|
+
description: 'Enter custom CSS',
|
|
504
|
+
valueType: 'text',
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
expect((_a = layoutDefinition.options) === null || _a === void 0 ? void 0 : _a.theme.valueType).toBe('string-enum');
|
|
509
|
+
expect((_b = layoutDefinition.options) === null || _b === void 0 ? void 0 : _b.showSidebar.valueType).toBe('boolean');
|
|
510
|
+
expect((_c = layoutDefinition.options) === null || _c === void 0 ? void 0 : _c.customCss.valueType).toBe('text');
|
|
511
|
+
});
|
|
512
|
+
it('should reject invalid valueType', () => {
|
|
513
|
+
expect(() => definitions_1.InputLayoutDefinitionV1.parse({
|
|
514
|
+
name: 'test-layout',
|
|
515
|
+
displayName: 'Test Layout',
|
|
516
|
+
description: 'A test layout',
|
|
517
|
+
entry: 'template.hbs',
|
|
518
|
+
zones: {
|
|
519
|
+
main: {
|
|
520
|
+
displayName: 'Main Zone',
|
|
521
|
+
description: 'Main content area',
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
options: {
|
|
525
|
+
badOption: {
|
|
526
|
+
displayName: 'Bad Option',
|
|
527
|
+
description: 'Invalid valueType',
|
|
528
|
+
valueType: 'INVALID_TYPE',
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
})).toThrow();
|
|
532
|
+
});
|
|
533
|
+
it('should allow valueType and values to be optional', () => {
|
|
534
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
535
|
+
const layoutDefinition = definitions_1.InputLayoutDefinitionV1.parse({
|
|
536
|
+
name: 'test-layout',
|
|
537
|
+
displayName: 'Test Layout',
|
|
538
|
+
description: 'A test layout',
|
|
539
|
+
entry: 'template.hbs',
|
|
540
|
+
zones: {
|
|
541
|
+
main: {
|
|
542
|
+
displayName: 'Main Zone',
|
|
543
|
+
description: 'Main content area',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
options: {
|
|
547
|
+
withBoth: {
|
|
548
|
+
displayName: 'With Both',
|
|
549
|
+
description: 'Has valueType and values',
|
|
550
|
+
valueType: 'string-enum',
|
|
551
|
+
values: ['option1', 'option2'],
|
|
552
|
+
},
|
|
553
|
+
withValueTypeOnly: {
|
|
554
|
+
displayName: 'With ValueType Only',
|
|
555
|
+
description: 'Has valueType but no values',
|
|
556
|
+
valueType: 'boolean',
|
|
557
|
+
},
|
|
558
|
+
withValuesOnly: {
|
|
559
|
+
displayName: 'With Values Only',
|
|
560
|
+
description: 'Has values but no valueType (legacy)',
|
|
561
|
+
values: ['legacy1', 'legacy2'],
|
|
562
|
+
},
|
|
563
|
+
withNeither: {
|
|
564
|
+
displayName: 'With Neither',
|
|
565
|
+
description: 'Has neither valueType nor values',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
});
|
|
569
|
+
// Schema accepts all combinations - Business logic validation (e.g., boolean/text can't have values) happens on backend and reported in CLI on deploy.
|
|
570
|
+
expect((_a = layoutDefinition.options) === null || _a === void 0 ? void 0 : _a.withBoth.valueType).toBe('string-enum');
|
|
571
|
+
expect((_b = layoutDefinition.options) === null || _b === void 0 ? void 0 : _b.withBoth.values).toEqual([
|
|
572
|
+
'option1',
|
|
573
|
+
'option2',
|
|
574
|
+
]);
|
|
575
|
+
expect((_c = layoutDefinition.options) === null || _c === void 0 ? void 0 : _c.withValueTypeOnly.valueType).toBe('boolean');
|
|
576
|
+
expect((_d = layoutDefinition.options) === null || _d === void 0 ? void 0 : _d.withValueTypeOnly.values).toBeUndefined();
|
|
577
|
+
expect((_e = layoutDefinition.options) === null || _e === void 0 ? void 0 : _e.withValuesOnly.valueType).toBeUndefined();
|
|
578
|
+
expect((_f = layoutDefinition.options) === null || _f === void 0 ? void 0 : _f.withValuesOnly.values).toEqual([
|
|
579
|
+
'legacy1',
|
|
580
|
+
'legacy2',
|
|
581
|
+
]);
|
|
582
|
+
expect((_g = layoutDefinition.options) === null || _g === void 0 ? void 0 : _g.withNeither.valueType).toBeUndefined();
|
|
583
|
+
expect((_h = layoutDefinition.options) === null || _h === void 0 ? void 0 : _h.withNeither.values).toBeUndefined();
|
|
584
|
+
});
|
|
585
|
+
it('should reject properties (instead of options)', () => {
|
|
586
|
+
expect(() => definitions_1.InputLayoutDefinitionV1.parse({
|
|
587
|
+
name: 'test-layout',
|
|
588
|
+
displayName: 'Test Layout',
|
|
589
|
+
description: 'A test layout',
|
|
590
|
+
entry: 'template.hbs',
|
|
591
|
+
zones: {
|
|
592
|
+
main: {
|
|
593
|
+
displayName: 'Main Zone',
|
|
594
|
+
description: 'Main content area',
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
properties: {
|
|
598
|
+
color: {
|
|
599
|
+
title: 'Color',
|
|
600
|
+
description: 'Color options',
|
|
601
|
+
type: 'string',
|
|
602
|
+
enum: ['red', 'blue'],
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
})).toThrow();
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
describe('V2 format - zones arrays and properties', () => {
|
|
610
|
+
it('should not allow minNodes to be defined in a zone', () => {
|
|
611
|
+
expect(() => definitions_1.InputLayoutDefinitionV2.parse({
|
|
612
|
+
name: 'test-layout',
|
|
613
|
+
displayName: 'Test Layout',
|
|
614
|
+
description: 'A test layout',
|
|
615
|
+
entry: 'template.hbs',
|
|
616
|
+
zones: [
|
|
617
|
+
{
|
|
618
|
+
key: 'main',
|
|
619
|
+
displayName: 'Main Zone',
|
|
620
|
+
description: 'Main content area',
|
|
621
|
+
minNodes: 1,
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
})).toThrow();
|
|
625
|
+
});
|
|
626
|
+
describe('property schema validation', () => {
|
|
627
|
+
it('should accept specified types', () => {
|
|
628
|
+
var _a, _b, _c;
|
|
629
|
+
const layoutDefinition = definitions_1.InputLayoutDefinitionV2.parse({
|
|
630
|
+
name: 'test-layout',
|
|
631
|
+
displayName: 'Test Layout',
|
|
632
|
+
description: 'A test layout',
|
|
633
|
+
entry: 'template.hbs',
|
|
634
|
+
zones: [
|
|
635
|
+
{
|
|
636
|
+
key: 'main',
|
|
637
|
+
displayName: 'Main Zone',
|
|
638
|
+
description: 'Main content area',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
properties: {
|
|
642
|
+
theme: {
|
|
643
|
+
title: 'Theme',
|
|
644
|
+
description: 'Color theme',
|
|
645
|
+
type: 'string',
|
|
646
|
+
enum: ['light', 'dark'],
|
|
647
|
+
},
|
|
648
|
+
showSidebar: {
|
|
649
|
+
title: 'Show Sidebar',
|
|
650
|
+
description: 'Toggle sidebar',
|
|
651
|
+
type: 'boolean',
|
|
652
|
+
},
|
|
653
|
+
customCss: {
|
|
654
|
+
title: 'Custom CSS',
|
|
655
|
+
description: 'Enter custom CSS',
|
|
656
|
+
type: 'string',
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
expect((_a = layoutDefinition.properties) === null || _a === void 0 ? void 0 : _a.theme.type).toBe('string');
|
|
661
|
+
expect((_b = layoutDefinition.properties) === null || _b === void 0 ? void 0 : _b.showSidebar.type).toBe('boolean');
|
|
662
|
+
expect((_c = layoutDefinition.properties) === null || _c === void 0 ? void 0 : _c.customCss.type).toBe('string');
|
|
663
|
+
});
|
|
664
|
+
it('should reject invalid type', () => {
|
|
665
|
+
expect(() => definitions_1.InputLayoutDefinitionV2.parse({
|
|
666
|
+
name: 'test-layout',
|
|
667
|
+
displayName: 'Test Layout',
|
|
668
|
+
description: 'A test layout',
|
|
669
|
+
entry: 'template.hbs',
|
|
670
|
+
zones: [
|
|
671
|
+
{
|
|
672
|
+
key: 'main',
|
|
673
|
+
displayName: 'Main Zone',
|
|
674
|
+
description: 'Main content area',
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
properties: {
|
|
678
|
+
badProperty: {
|
|
679
|
+
title: 'Bad Property',
|
|
680
|
+
description: 'Invalid type',
|
|
681
|
+
type: 'INVALID_TYPE',
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
})).toThrow();
|
|
685
|
+
});
|
|
436
686
|
});
|
|
437
|
-
expect(layoutDefinition.zones.main.minNodes).toBe(0);
|
|
438
687
|
});
|
|
439
688
|
});
|
|
440
689
|
describe('validateTemplateFile', () => {
|