@appsemble/utils 0.29.9 → 0.29.11
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/README.md +3 -3
- package/api/components/schemas/AppDefinition.js +1 -0
- package/api/components/schemas/ContainerPageDefinition.d.ts +1 -0
- package/api/components/schemas/ContainerPageDefinition.js +55 -0
- package/api/components/schemas/index.d.ts +1 -0
- package/api/components/schemas/index.js +1 -0
- package/findPageByName.d.ts +2 -0
- package/findPageByName.js +15 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/iterApp.js +7 -0
- package/iterApp.test.js +27 -0
- package/package.json +2 -2
- package/validateAppMessages.js +1 -1
- package/validation.js +35 -3
- package/validation.test.js +32 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#  Appsemble Utilities
|
|
2
2
|
|
|
3
3
|
> Internal utility functions used across multiple Appsemble projects.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@appsemble/utils)
|
|
6
|
-
[](https://gitlab.com/appsemble/appsemble/-/releases/0.29.11)
|
|
7
7
|
[](https://prettier.io)
|
|
8
8
|
|
|
9
9
|
## Table of Contents
|
|
@@ -26,5 +26,5 @@ not guaranteed.
|
|
|
26
26
|
|
|
27
27
|
## License
|
|
28
28
|
|
|
29
|
-
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.29.
|
|
29
|
+
[LGPL-3.0-only](https://gitlab.com/appsemble/appsemble/-/blob/0.29.11/LICENSE.md) ©
|
|
30
30
|
[Appsemble](https://appsemble.com)
|
|
@@ -96,6 +96,7 @@ The most basic resource has a \`schema\` property and defines the minimal securi
|
|
|
96
96
|
{ $ref: '#/components/schemas/TabsPageDefinition' },
|
|
97
97
|
{ $ref: '#/components/schemas/FlowPageDefinition' },
|
|
98
98
|
{ $ref: '#/components/schemas/LoopPageDefinition' },
|
|
99
|
+
{ $ref: '#/components/schemas/ContainerPageDefinition' },
|
|
99
100
|
],
|
|
100
101
|
},
|
|
101
102
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ContainerPageDefinition: import("openapi-types").OpenAPIV3.SchemaObject;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BasePageDefinition } from './BasePageDefinition.js';
|
|
2
|
+
import { extendJSONSchema } from './utils.js';
|
|
3
|
+
export const ContainerPageDefinition = extendJSONSchema(BasePageDefinition, {
|
|
4
|
+
type: 'object',
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
description: `Use this page type to group pages in the menu, this doesn't actually group pages for now. Following is an example of how this can be used
|
|
7
|
+
\`\`\`yaml
|
|
8
|
+
pages:
|
|
9
|
+
- name: Page 1
|
|
10
|
+
type: container
|
|
11
|
+
pages:
|
|
12
|
+
- name: Contained page 1
|
|
13
|
+
blocks:
|
|
14
|
+
- type: action-button
|
|
15
|
+
version: 0.29.8
|
|
16
|
+
parameters:
|
|
17
|
+
icon: git-alt
|
|
18
|
+
actions:
|
|
19
|
+
onClick:
|
|
20
|
+
type: link
|
|
21
|
+
to: Contained page 2
|
|
22
|
+
- name: Contained page 2
|
|
23
|
+
blocks:
|
|
24
|
+
- type: action-button
|
|
25
|
+
version: 0.29.8
|
|
26
|
+
parameters:
|
|
27
|
+
icon: git-alt
|
|
28
|
+
actions:
|
|
29
|
+
onClick:
|
|
30
|
+
type: link
|
|
31
|
+
to: Contained page 1
|
|
32
|
+
\`\`\`
|
|
33
|
+
`,
|
|
34
|
+
required: ['type', 'pages'],
|
|
35
|
+
properties: {
|
|
36
|
+
type: {
|
|
37
|
+
enum: ['container'],
|
|
38
|
+
},
|
|
39
|
+
pages: {
|
|
40
|
+
type: 'array',
|
|
41
|
+
minItems: 1,
|
|
42
|
+
description: 'The pages of the app.',
|
|
43
|
+
items: {
|
|
44
|
+
anyOf: [
|
|
45
|
+
{ $ref: '#/components/schemas/PageDefinition' },
|
|
46
|
+
{ $ref: '#/components/schemas/TabsPageDefinition' },
|
|
47
|
+
{ $ref: '#/components/schemas/FlowPageDefinition' },
|
|
48
|
+
{ $ref: '#/components/schemas/LoopPageDefinition' },
|
|
49
|
+
{ $ref: '#/components/schemas/ContainerPageDefinition' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=ContainerPageDefinition.js.map
|
|
@@ -20,6 +20,7 @@ export * from './BlockVersion.js';
|
|
|
20
20
|
export * from './ConditionActionDefinition.js';
|
|
21
21
|
export * from './CronDefinition.js';
|
|
22
22
|
export * from './ContainerDefinition.js';
|
|
23
|
+
export * from './ContainerPageDefinition.js';
|
|
23
24
|
export * from './CustomFontDefinition.js';
|
|
24
25
|
export * from './DialogActionDefinition.js';
|
|
25
26
|
export * from './DialogErrorActionDefinition.js';
|
|
@@ -20,6 +20,7 @@ export * from './BlockVersion.js';
|
|
|
20
20
|
export * from './ConditionActionDefinition.js';
|
|
21
21
|
export * from './CronDefinition.js';
|
|
22
22
|
export * from './ContainerDefinition.js';
|
|
23
|
+
export * from './ContainerPageDefinition.js';
|
|
23
24
|
export * from './CustomFontDefinition.js';
|
|
24
25
|
export * from './DialogActionDefinition.js';
|
|
25
26
|
export * from './DialogErrorActionDefinition.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function findPageByName(pages, name) {
|
|
2
|
+
for (const page of pages) {
|
|
3
|
+
if (page.name === name) {
|
|
4
|
+
return page;
|
|
5
|
+
}
|
|
6
|
+
if (page.type === 'container' && page.pages) {
|
|
7
|
+
const found = findPageByName(page.pages, name);
|
|
8
|
+
if (found) {
|
|
9
|
+
return found;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=findPageByName.js.map
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/iterApp.js
CHANGED
|
@@ -119,6 +119,13 @@ export function iterPage(page, callbacks, prefix = []) {
|
|
|
119
119
|
return (result ||
|
|
120
120
|
['steps.first', 'steps', 'steps.last'].some((suffix) => iterBlockList(page.foreach.blocks, callbacks, [...prefix, suffix, 'blocks'])));
|
|
121
121
|
}
|
|
122
|
+
if (page.type === 'container') {
|
|
123
|
+
let result = false;
|
|
124
|
+
for (const containerPage of page.pages) {
|
|
125
|
+
result = iterPage(containerPage, callbacks, prefix);
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
122
129
|
return iterBlockList(page.blocks, callbacks, [...prefix, 'blocks']);
|
|
123
130
|
}
|
|
124
131
|
/**
|
package/iterApp.test.js
CHANGED
|
@@ -235,6 +235,33 @@ describe('iterPage', () => {
|
|
|
235
235
|
expect(onPage).toHaveBeenCalledWith(page, []);
|
|
236
236
|
expect(result).toBe(false);
|
|
237
237
|
});
|
|
238
|
+
it('should iterate over a container page', () => {
|
|
239
|
+
const onBlockList = vi.fn();
|
|
240
|
+
const onPage = vi.fn();
|
|
241
|
+
const page = {
|
|
242
|
+
name: 'Container Page',
|
|
243
|
+
type: 'container',
|
|
244
|
+
pages: [
|
|
245
|
+
{
|
|
246
|
+
name: 'Page 1',
|
|
247
|
+
blocks: [],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'Page 2',
|
|
251
|
+
blocks: [],
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
const result = iterPage(page, { onBlockList, onPage });
|
|
256
|
+
expect(onBlockList).toHaveBeenCalledWith(page.pages[0].blocks, [
|
|
257
|
+
'blocks',
|
|
258
|
+
]);
|
|
259
|
+
expect(onBlockList).toHaveBeenCalledWith(page.pages[1].blocks, [
|
|
260
|
+
'blocks',
|
|
261
|
+
]);
|
|
262
|
+
expect(onPage).toHaveBeenCalledWith(page, []);
|
|
263
|
+
expect(result).toBe(false);
|
|
264
|
+
});
|
|
238
265
|
it('should abort if onPage returns true', () => {
|
|
239
266
|
const onBlockList = vi.fn();
|
|
240
267
|
const onPage = vi.fn().mockReturnValue(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appsemble/utils",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.11",
|
|
4
4
|
"description": "Utility functions used in Appsemble internally",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"app",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test": "vitest"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@appsemble/types": "0.29.
|
|
40
|
+
"@appsemble/types": "0.29.11",
|
|
41
41
|
"axios": "^1.0.0",
|
|
42
42
|
"cron-parser": "^4.0.0",
|
|
43
43
|
"date-fns": "^2.0.0",
|
package/validateAppMessages.js
CHANGED
|
@@ -46,7 +46,7 @@ export function validateMessages(messages, app) {
|
|
|
46
46
|
}
|
|
47
47
|
const coreMessages = (_a = messages.core) !== null && _a !== void 0 ? _a : {};
|
|
48
48
|
for (const [key, value] of Object.entries(coreMessages)) {
|
|
49
|
-
if (
|
|
49
|
+
if (typeof value !== 'string') {
|
|
50
50
|
throw new AppMessageValidationError(`Invalid translation key: core.${key}`);
|
|
51
51
|
}
|
|
52
52
|
}
|
package/validation.js
CHANGED
|
@@ -3,7 +3,7 @@ import { ValidationError, Validator } from 'jsonschema';
|
|
|
3
3
|
import languageTags from 'language-tags';
|
|
4
4
|
import { getAppBlocks, normalizeBlockName } from './blockUtils.js';
|
|
5
5
|
import { has } from './has.js';
|
|
6
|
-
import { partialNormalized } from './index.js';
|
|
6
|
+
import { findPageByName, normalize, partialNormalized } from './index.js';
|
|
7
7
|
import { iterApp } from './iterApp.js';
|
|
8
8
|
import { serverActions } from './serverActions.js';
|
|
9
9
|
/**
|
|
@@ -34,6 +34,37 @@ function validateJSONSchema(schema, prefix, report) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates the pages in the app definition to ensure there are no duplicate page names.
|
|
39
|
+
*
|
|
40
|
+
* @param definition The definition of the app
|
|
41
|
+
* @param report A function used to report a value.
|
|
42
|
+
*/
|
|
43
|
+
function validateUniquePageNames(definition, report) {
|
|
44
|
+
if (!definition.pages) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const pageNames = new Map();
|
|
48
|
+
function checkPages(pages, parentPath = []) {
|
|
49
|
+
for (const page of pages) {
|
|
50
|
+
const pageName = page.name;
|
|
51
|
+
const normalizedPageName = normalize(page.name);
|
|
52
|
+
const pagePath = [...parentPath, pageName];
|
|
53
|
+
if (pageNames.has(normalizedPageName)) {
|
|
54
|
+
const paths = pageNames.get(normalizedPageName);
|
|
55
|
+
paths.push(pagePath);
|
|
56
|
+
report(pageName, 'is a duplicate page name', pagePath);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
pageNames.set(normalizedPageName, [pagePath]);
|
|
60
|
+
}
|
|
61
|
+
if (page.type === 'container') {
|
|
62
|
+
checkPages(page.pages, pagePath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
checkPages(definition.pages);
|
|
67
|
+
}
|
|
37
68
|
function validateUsersSchema(definition, report) {
|
|
38
69
|
var _a;
|
|
39
70
|
if (!definition.users) {
|
|
@@ -462,7 +493,7 @@ function validateLanguage({ defaultLanguage }, report) {
|
|
|
462
493
|
}
|
|
463
494
|
}
|
|
464
495
|
function validateDefaultPage({ defaultPage, pages }, report) {
|
|
465
|
-
const page = pages
|
|
496
|
+
const page = findPageByName(pages, defaultPage);
|
|
466
497
|
if (!page) {
|
|
467
498
|
report(defaultPage, 'does not refer to an existing page', ['defaultPage']);
|
|
468
499
|
return;
|
|
@@ -606,7 +637,7 @@ function validateActions(definition, report) {
|
|
|
606
637
|
return;
|
|
607
638
|
}
|
|
608
639
|
const [toBase, toSub] = [].concat(to);
|
|
609
|
-
const toPage = definition.pages
|
|
640
|
+
const toPage = findPageByName(definition.pages, toBase);
|
|
610
641
|
if (!toPage) {
|
|
611
642
|
report(to, 'refers to a page that doesn’t exist', [...path, 'to']);
|
|
612
643
|
return;
|
|
@@ -833,6 +864,7 @@ export async function validateAppDefinition(definition, getBlockVersions, contro
|
|
|
833
864
|
validateBlocks(definition, blockVersionMap, report);
|
|
834
865
|
validateActions(definition, report);
|
|
835
866
|
validateEvents(definition, blockVersionMap, report);
|
|
867
|
+
validateUniquePageNames(definition, report);
|
|
836
868
|
}
|
|
837
869
|
catch (error) {
|
|
838
870
|
report(null, `Unexpected error: ${error instanceof Error ? error.message : error}`, []);
|
package/validation.test.js
CHANGED
|
@@ -43,6 +43,16 @@ function createTestApp() {
|
|
|
43
43
|
{ name: 'Step B', blocks: [] },
|
|
44
44
|
],
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: 'Container Page 1',
|
|
48
|
+
type: 'container',
|
|
49
|
+
pages: [
|
|
50
|
+
{
|
|
51
|
+
name: 'Contained Page',
|
|
52
|
+
blocks: [],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
46
56
|
],
|
|
47
57
|
};
|
|
48
58
|
}
|
|
@@ -85,6 +95,24 @@ describe('validateAppDefinition', () => {
|
|
|
85
95
|
]),
|
|
86
96
|
]);
|
|
87
97
|
});
|
|
98
|
+
it('should report duplicate page names', async () => {
|
|
99
|
+
const app = createTestApp();
|
|
100
|
+
app.pages.push({
|
|
101
|
+
name: 'Container Page',
|
|
102
|
+
type: 'container',
|
|
103
|
+
pages: [{ name: 'Test Page', blocks: [] }],
|
|
104
|
+
});
|
|
105
|
+
const result = await validateAppDefinition(app, () => [
|
|
106
|
+
{ name: '@appsemble/test', version: '0.0.0', files: [], languages: [] },
|
|
107
|
+
]);
|
|
108
|
+
expect(result.valid).toBe(false);
|
|
109
|
+
expect(result.errors).toStrictEqual([
|
|
110
|
+
new ValidationError('is a duplicate page name', 'Test Page', undefined, [
|
|
111
|
+
'Container Page',
|
|
112
|
+
'Test Page',
|
|
113
|
+
]),
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
88
116
|
it('should validate block parameters', async () => {
|
|
89
117
|
const app = createTestApp();
|
|
90
118
|
app.pages[0].blocks.push({
|
|
@@ -1445,6 +1473,10 @@ describe('validateAppDefinition', () => {
|
|
|
1445
1473
|
]),
|
|
1446
1474
|
]);
|
|
1447
1475
|
});
|
|
1476
|
+
it('should check if the default page exists inside contained page', async () => {
|
|
1477
|
+
const result = await validateAppDefinition({ ...createTestApp(), defaultPage: 'Contained Page' }, () => []);
|
|
1478
|
+
expect(result.valid).toBe(true);
|
|
1479
|
+
});
|
|
1448
1480
|
it('should validate the default page doesn’t specify parameters', async () => {
|
|
1449
1481
|
const result = await validateAppDefinition({ ...createTestApp(), defaultPage: 'Page with parameters' }, () => []);
|
|
1450
1482
|
expect(result.valid).toBe(false);
|