feat(build): Remove support for nested contexts on a page.

This commit is contained in:
SamTolmay 2021-10-01 12:44:43 +02:00
parent 6bc03c86b8
commit b003b76182
No known key found for this signature in database
GPG Key ID: 655CB3F5AA745CF8
22 changed files with 1288 additions and 1369 deletions

View File

@ -1,44 +1,38 @@
{
"id": "404",
"type": "Context",
"type": "Result",
"style": {
"minHeight": "100vh"
},
"blocks": [
{
"id": "404_result",
"type": "Result",
"properties": {
"status": 404,
"title": "404",
"subTitle": "Sorry, the page you are visiting does not exist."
},
"areas": {
"extra": {
"blocks": [
{
"id": "home",
"type": "Button",
"properties": {
"title": "Go to home page",
"properties": {
"status": 404,
"title": "404",
"subTitle": "Sorry, the page you are visiting does not exist."
},
"areas": {
"extra": {
"blocks": [
{
"id": "home",
"type": "Button",
"properties": {
"title": "Go to home page",
"type": "Link",
"icon": "HomeOutlined"
},
"events": {
"onClick": [
{
"id": "home",
"type": "Link",
"icon": "HomeOutlined"
},
"events": {
"onClick": [
{
"id": "home",
"type": "Link",
"params": {
"home": true
}
}
]
"params": {
"home": true
}
}
}
]
]
}
}
}
]
}
]
}
}
}

View File

@ -35,48 +35,42 @@ test('addDefaultPages, no pages array', async () => {
expect(res).toEqual({
pages: [
{
blocks: [
{
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
id: '404_result',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
type: 'Result',
},
],
id: '404',
type: 'Result',
style: {
minHeight: '100vh',
},
type: 'Context',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
},
],
});
@ -88,105 +82,93 @@ test('addDefaultPages, empty pages array', async () => {
expect(res).toEqual({
pages: [
{
blocks: [
{
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
id: '404_result',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
type: 'Result',
},
],
id: '404',
type: 'Result',
style: {
minHeight: '100vh',
},
type: 'Context',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
},
],
});
});
test('addDefaultPages, pages without 404 page', async () => {
const components = { pages: [{ id: 'page1', type: 'Context' }] };
const components = { pages: [{ id: 'page1', type: 'PageHeaderMenu' }] };
const res = await addDefaultPages({ components, context });
expect(res).toEqual({
pages: [
{
id: 'page1',
type: 'Context',
type: 'PageHeaderMenu',
},
{
blocks: [
{
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
id: '404_result',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
type: 'Result',
},
],
id: '404',
type: 'Result',
style: {
minHeight: '100vh',
},
type: 'Context',
properties: {
status: 404,
subTitle: 'Sorry, the page you are visiting does not exist.',
title: '404',
},
areas: {
extra: {
blocks: [
{
events: {
onClick: [
{
id: 'home',
params: {
home: true,
},
type: 'Link',
},
],
},
id: 'home',
properties: {
icon: 'HomeOutlined',
title: 'Go to home page',
type: 'Link',
},
type: 'Button',
},
],
},
},
},
],
});
@ -195,8 +177,8 @@ test('addDefaultPages, pages without 404 page', async () => {
test('addDefaultPages, pages with 404 page, should not overwrite', async () => {
const components = {
pages: [
{ id: 'page1', type: 'Context' },
{ id: '404', type: 'Context' },
{ id: 'page1', type: 'PageHeaderMenu' },
{ id: '404', type: 'PageHeaderMenu' },
],
};
const res = await addDefaultPages({ components, context });
@ -204,11 +186,11 @@ test('addDefaultPages, pages with 404 page, should not overwrite', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'PageHeaderMenu',
},
{
id: '404',
type: 'Context',
type: 'PageHeaderMenu',
},
],
});
@ -216,7 +198,7 @@ test('addDefaultPages, pages with 404 page, should not overwrite', async () => {
test('addDefaultPages, pages not an array', async () => {
const components = {
pages: { id: 'page1', type: 'Context' },
pages: { id: 'page1', type: 'PageHeaderMenu' },
};
await expect(addDefaultPages({ components, context })).rejects.toThrow(
'lowdefy.pages is not an array.'

View File

@ -1,114 +0,0 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { set, type } from '@lowdefy/helpers';
import buildRequests from './buildRequests';
import setBlockMeta from './setBlockMeta';
async function buildBlock(block, blockContext) {
if (!type.isObject(block)) {
throw new Error(
`Expected block to be an object on ${blockContext.pageId}. Received ${JSON.stringify(block)}`
);
}
if (!type.isString(block.id)) {
if (type.isUndefined(block.id)) {
throw new Error(`Block id missing at page "${blockContext.pageId}".`);
}
throw new Error(
`Block id is not a string at page "${blockContext.pageId}". Received ${JSON.stringify(
block.id
)}.`
);
}
block.blockId = block.id;
block.id = `block:${blockContext.pageId}:${block.id}`;
await setBlockMeta(block, blockContext);
let newBlockContext = blockContext;
if (block.meta.category === 'context') {
newBlockContext = {
auth: blockContext.auth,
contextId: block.blockId,
getMeta: blockContext.getMeta,
pageId: blockContext.pageId,
requests: [],
};
}
buildRequests(block, newBlockContext);
if (block.meta.category === 'context') {
block.requests = newBlockContext.requests;
}
if (block.events) {
Object.keys(block.events).map((key) => {
if (type.isArray(block.events[key])) {
block.events[key] = {
try: block.events[key],
catch: [],
};
}
if (!type.isArray(block.events[key].try)) {
throw new Error(
`Events must be an array of actions at ${block.blockId} in events ${key} on page ${
newBlockContext.pageId
}. Received ${JSON.stringify(block.events[key].try)}`
);
}
if (!type.isArray(block.events[key].catch) && !type.isNone(block.events[key].catch)) {
throw new Error(
`Catch events must be an array of actions at ${block.blockId} in events ${key} on page ${
newBlockContext.pageId
}. Received ${JSON.stringify(block.events[key].catch)}`
);
}
});
}
if (!type.isNone(block.blocks)) {
if (!type.isArray(block.blocks)) {
throw new Error(
`Blocks at ${block.blockId} on page ${
newBlockContext.pageId
} is not an array. Received ${JSON.stringify(block.blocks)}`
);
}
set(block, 'areas.content.blocks', block.blocks);
delete block.blocks;
}
if (type.isObject(block.areas)) {
let promises = [];
Object.keys(block.areas).forEach((key) => {
if (type.isNone(block.areas[key].blocks)) {
block.areas[key].blocks = [];
}
if (!type.isArray(block.areas[key].blocks)) {
throw new Error(
`Expected blocks to be an array at ${block.blockId} in area ${key} on page ${
newBlockContext.pageId
}. Received ${JSON.stringify(block.areas[key].blocks)}`
);
}
const blockPromises = block.areas[key].blocks.map(async (blk) => {
await buildBlock(blk, newBlockContext);
});
promises = promises.concat(blockPromises);
});
await Promise.all(promises);
}
}
export default buildBlock;

View File

@ -0,0 +1,37 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import buildEvents from './buildEvents';
import buildRequests from './buildRequests';
import buildSubBlocks from './buildSubBlocks';
import getOperators from './getOperators';
import moveSubBlocksToArea from './moveSubBlocksToArea';
import setBlockId from './setBlockId';
import setBlockMeta from './setBlockMeta';
import validateBlock from './validateBlock';
async function buildBlock(block, pageContext) {
validateBlock(block, pageContext);
getOperators(block, pageContext);
setBlockId(block, pageContext);
await setBlockMeta(block, pageContext);
buildEvents(block, pageContext);
buildRequests(block, pageContext);
moveSubBlocksToArea(block, pageContext);
await buildSubBlocks(block, pageContext);
}
export default buildBlock;

View File

@ -0,0 +1,48 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
function buildEvents(block, pageContext) {
if (block.events) {
Object.keys(block.events).map((key) => {
if (type.isArray(block.events[key])) {
block.events[key] = {
try: block.events[key],
catch: [],
};
}
if (!type.isArray(block.events[key].try)) {
throw new Error(
`Events must be an array of actions at "${block.blockId}" in event "${key}" on page "${
pageContext.pageId
}". Received ${JSON.stringify(block.events[key].try)}`
);
}
if (!type.isArray(block.events[key].catch) && !type.isNone(block.events[key].catch)) {
throw new Error(
`Catch events must be an array of actions at "${
block.blockId
}" in event "${key}" on page "${pageContext.pageId}". Received ${JSON.stringify(
block.events[key].catch
)}`
);
}
});
}
}
export default buildEvents;

View File

@ -0,0 +1,322 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { get } from '@lowdefy/helpers';
import buildPages from '../buildPages';
import testContext from '../../../test/testContext';
const mockLogWarn = jest.fn();
const mockLog = jest.fn();
const logger = {
warn: mockLogWarn,
log: mockLog,
};
const blockMetas = {
Container: {
category: 'container',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Container',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
List: {
category: 'list',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'List',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Input: {
category: 'input',
valueType: 'string',
loading: {
type: 'SkeletonInput',
},
moduleFederation: {
scope: 'blocks',
module: 'Input',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Display: {
category: 'display',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Display',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
};
const outputMetas = {
Container: {
category: 'container',
moduleFederation: {
scope: 'blocks',
module: 'Container',
url: 'https://example.com/remoteEntry.js',
},
loading: {
type: 'Spinner',
},
},
List: {
category: 'list',
moduleFederation: {
scope: 'blocks',
module: 'List',
url: 'https://example.com/remoteEntry.js',
},
loading: {
type: 'Spinner',
},
valueType: 'array',
},
Input: {
category: 'input',
moduleFederation: {
scope: 'blocks',
module: 'Input',
url: 'https://example.com/remoteEntry.js',
},
valueType: 'string',
loading: {
type: 'SkeletonInput',
},
},
Display: {
category: 'display',
moduleFederation: {
scope: 'blocks',
module: 'Display',
url: 'https://example.com/remoteEntry.js',
},
loading: {
type: 'Spinner',
},
},
};
const auth = {
public: true,
};
const getMeta = (type) => {
const meta = blockMetas[type];
if (!meta) {
return null;
}
return Promise.resolve(meta);
};
const context = testContext({ logger, getMeta });
beforeEach(() => {
mockLogWarn.mockReset();
mockLog.mockReset();
});
test('block events actions array should map to try catch', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: [
{
id: 'action_1',
type: 'Reset',
},
],
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.try')).toEqual([
{
id: 'action_1',
type: 'Reset',
},
]);
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.catch')).toEqual([]);
});
test('block events actions as try catch arrays', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: [
{
id: 'action_1',
type: 'Reset',
},
],
catch: [
{
id: 'action_1',
type: 'Retry',
},
],
},
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.try')).toEqual([
{
id: 'action_1',
type: 'Reset',
},
]);
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.catch')).toEqual([
{
id: 'action_1',
type: 'Retry',
},
]);
});
test('block events actions try not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: {
id: 'action_1',
type: 'Reset',
},
},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Events must be an array of actions at "block_1" in event "onClick" on page "page_1". Received {"id":"action_1","type":"Reset"}'
);
});
test('block events actions not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Events must be an array of actions at "block_1" in event "onClick" on page "page_1". Received undefined'
);
});
test('block events actions catch not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: [],
catch: {
id: 'action_1',
type: 'Reset',
},
},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Catch events must be an array of actions at "block_1" in event "onClick" on page "page_1". Received {"id":"action_1","type":"Reset"}'
);
});

View File

@ -16,8 +16,8 @@
import { type } from '@lowdefy/helpers';
function buildRequest(request, blockContext) {
const { auth, contextId, pageId } = blockContext;
function buildRequest(request, pageContext) {
const { auth, pageId } = pageContext;
if (!type.isString(request.id)) {
if (type.isUndefined(request.id)) {
throw new Error(`Request id missing at page "${pageId}".`);
@ -40,25 +40,16 @@ function buildRequest(request, blockContext) {
request.auth = auth;
request.requestId = request.id;
request.contextId = contextId;
request.id = `request:${pageId}:${contextId}:${request.id}`;
blockContext.requests.push(request);
request.pageId = pageId;
request.id = `request:${pageId}:${request.id}`;
pageContext.requests.push(request);
}
function buildRequests(block, blockContext) {
if (!type.isNone(block.requests)) {
if (!type.isArray(block.requests)) {
throw new Error(
`Requests is not an array at ${block.blockId} on page ${
blockContext.pageId
}. Received ${JSON.stringify(block.requests)}`
);
}
block.requests.forEach((request) => {
buildRequest(request, blockContext);
});
delete block.requests;
}
function buildRequests(block, pageContext) {
(block.requests || []).forEach((request) => {
buildRequest(request, pageContext);
});
delete block.requests;
}
export default buildRequests;

View File

@ -0,0 +1,44 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
import buildBlock from './buildBlock';
async function buildSubBlocks(block, pageContext) {
if (type.isObject(block.areas)) {
let promises = [];
Object.keys(block.areas).forEach((key) => {
if (type.isNone(block.areas[key].blocks)) {
block.areas[key].blocks = [];
}
if (!type.isArray(block.areas[key].blocks)) {
throw new Error(
`Expected blocks to be an array at ${block.blockId} in area ${key} on page ${
pageContext.pageId
}. Received ${JSON.stringify(block.areas[key].blocks)}`
);
}
const blockPromises = block.areas[key].blocks.map(async (blk) => {
await buildBlock(blk, pageContext);
});
promises = promises.concat(blockPromises);
});
await Promise.all(promises);
}
}
export default buildSubBlocks;

View File

@ -0,0 +1,42 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
function getOperators(block, pageContext) {
// eslint-disable-next-line no-unused-vars
const { requests, blocks, areas, ...webBlock } = block;
function getOperatorsReviver(_, value) {
if (type.isObject(value) && Object.keys(value).length === 1) {
const key = Object.keys(value)[0];
const [op] = key.split('.');
const operator = op.replace(/^(_+)/gm, '_');
if (operator.length > 1 && operator[0] === '_') {
pageContext.operators.add(operator);
}
}
return value;
}
JSON.parse(JSON.stringify(webBlock), getOperatorsReviver);
(requests || []).forEach((request) => {
JSON.parse(JSON.stringify(request.payload || {}), getOperatorsReviver);
});
}
export default getOperators;

View File

@ -0,0 +1,246 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { get } from '@lowdefy/helpers';
import buildPages from '../buildPages';
import testContext from '../../../test/testContext';
const mockLogWarn = jest.fn();
const mockLog = jest.fn();
const logger = {
warn: mockLogWarn,
log: mockLog,
};
const blockMetas = {
Container: {
category: 'container',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Container',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
List: {
category: 'list',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'List',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Input: {
category: 'input',
valueType: 'string',
loading: {
type: 'SkeletonInput',
},
moduleFederation: {
scope: 'blocks',
module: 'Input',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Display: {
category: 'display',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Display',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
};
const auth = {
public: true,
};
const getMeta = (type) => {
const meta = blockMetas[type];
if (!meta) {
return null;
}
return Promise.resolve(meta);
};
const context = testContext({ logger, getMeta });
beforeEach(() => {
mockLogWarn.mockReset();
mockLog.mockReset();
});
test('set empty operators array if no operators on page', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
blocks: [
{
id: 'block_1',
type: 'Container',
},
{
id: 'block_2',
type: 'Container',
blocks: [
{
id: 'block_3',
type: 'Display',
},
],
},
{
id: 'block_4',
type: 'Display',
},
],
},
{
id: 'block_5',
type: 'Display',
auth,
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual([]);
});
test('set all operators for the page', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
properties: {
a: { _op_1: {} },
},
blocks: [
{
id: 'block_1',
type: 'Display',
visible: {
__op_2: {},
},
properties: {
a: { _op_1: {} },
b: { _op_1: {} },
c: { _op_3: { __op_4: { ___op_5: {} } } },
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(new Set(get(res, 'pages.0.operators'))).toEqual(
new Set(['_op_1', '_op_2', '_op_3', '_op_4', '_op_5'])
);
});
test('exclude requests operators', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
requests: [
{
id: 'request_1',
properties: {
a: { _r_op_1: {} },
},
},
],
properties: {
a: { _op_1: {} },
},
blocks: [
{
id: 'block_1',
type: 'Display',
visible: {
_op_2: {},
},
properties: {
a: { _op_3: {} },
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(new Set(get(res, 'pages.0.operators'))).toEqual(new Set(['_op_1', '_op_2', '_op_3']));
});
test('include request payload operators', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Container',
auth,
requests: [
{
id: 'request_1',
payload: {
a: { _r_op_1: {} },
},
properties: {
a: { _r_op_2: {} },
},
},
],
properties: {
a: { _op_1: {} },
},
},
],
};
const res = await buildPages({ components, context });
expect(new Set(get(res, 'pages.0.operators'))).toEqual(new Set(['_op_1', '_r_op_1']));
});

View File

@ -0,0 +1,33 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { set, type } from '@lowdefy/helpers';
function moveSubBlocksToArea(block, pageContext) {
if (!type.isNone(block.blocks)) {
if (!type.isArray(block.blocks)) {
throw new Error(
`Blocks at ${block.blockId} on page ${
pageContext.pageId
} is not an array. Received ${JSON.stringify(block.blocks)}`
);
}
set(block, 'areas.content.blocks', block.blocks);
delete block.blocks;
}
}
export default moveSubBlocksToArea;

View File

@ -0,0 +1,22 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
function setBlockId(block, { pageId }) {
block.blockId = block.id;
block.id = `block:${pageId}:${block.id}`;
}
export default setBlockId;

View File

@ -14,25 +14,13 @@
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
async function setBlockMeta(block, { getMeta, pageId }) {
if (type.isNone(block.type)) {
throw new Error(`Block type is not defined at ${block.blockId} on page ${pageId}.`);
}
if (!type.isString(block.type)) {
throw new Error(
`Block type is not a string at ${block.blockId} on page ${pageId}. Received ${JSON.stringify(
block.type
)}`
);
}
const meta = await getMeta(block.type);
if (!meta) {
throw new Error(
`Invalid Block type at ${block.blockId} on page ${pageId}. Received ${JSON.stringify(
`Invalid block type at "${block.blockId}" on page "${pageId}". Received ${JSON.stringify(
block.type
)}`
)}.`
);
}
const { category, loading, moduleFederation, valueType } = meta;

View File

@ -0,0 +1,54 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
function validateBlock(block, { pageId }) {
if (!type.isObject(block)) {
throw new Error(
`Expected block to be an object on page "${pageId}". Received ${JSON.stringify(block)}.`
);
}
if (!type.isString(block.id)) {
if (type.isUndefined(block.id)) {
throw new Error(`Block id missing at page "${pageId}".`);
}
throw new Error(
`Block id is not a string at page "${pageId}". Received ${JSON.stringify(block.id)}.`
);
}
if (type.isNone(block.type)) {
throw new Error(`Block type is not defined at "${block.id}" on page "${pageId}".`);
}
if (!type.isString(block.type)) {
throw new Error(
`Block type is not a string at "${block.id}" on page "${pageId}". Received ${JSON.stringify(
block.type
)}.`
);
}
if (!type.isNone(block.requests)) {
if (!type.isArray(block.requests)) {
throw new Error(
`Requests is not an array at "${block.id}" on page "${pageId}". Received ${JSON.stringify(
block.requests
)}`
);
}
}
}
export default validateBlock;

View File

@ -0,0 +1,48 @@
/* eslint-disable no-param-reassign */
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
import buildBlock from './buildBlock/buildBlock';
async function buildPage({ page, index, context }) {
if (!type.isString(page.id)) {
if (type.isUndefined(page.id)) {
throw new Error(`Page id missing at page ${index}.`);
}
throw new Error(
`Page id is not a string at at page ${index}. Received ${JSON.stringify(page.id)}.`
);
}
page.pageId = page.id;
const requests = [];
const operators = new Set();
await buildBlock(page, {
auth: page.auth,
getMeta: context.getMeta,
operators,
pageId: page.pageId,
requests,
});
// set page.id since buildBlock sets id as well.
page.id = `page:${page.pageId}`;
page.requests = requests;
page.operators = [...operators];
}
export default buildPage;

View File

@ -17,48 +17,11 @@
*/
import { type } from '@lowdefy/helpers';
import buildBlock from './buildBlock';
import checkPageIsContext from './checkPageIsContext';
import fillContextOperators from './fillContextOperators';
/* Page and block build steps
Pages:
- set pageId = id
- set id = `page:${page.id}`
Blocks:
- set blockId = id
- set id = `block:${pageId}:${block.id}` if not a page
- set request ids
- set block meta
- set blocks to areas.content
- set operators list on context blocks
*/
import buildPage from './buildPage';
async function buildPages({ components, context }) {
const pages = type.isArray(components.pages) ? components.pages : [];
const pageBuildPromises = pages.map(async (page, i) => {
if (!type.isString(page.id)) {
if (type.isUndefined(page.id)) {
throw new Error(`Page id missing at page ${i}.`);
}
throw new Error(
`Page id is not a string at at page ${i}. Received ${JSON.stringify(page.id)}.`
);
}
page.pageId = page.id;
await checkPageIsContext(page, context);
await buildBlock(page, {
auth: page.auth,
pageId: page.pageId,
requests: [],
getMeta: context.getMeta,
});
// set page.id since buildBlock sets id as well.
page.id = `page:${page.pageId}`;
fillContextOperators(page);
});
const pageBuildPromises = pages.map((page, index) => buildPage({ page, index, context }));
await Promise.all(pageBuildPromises);
return components;
}

View File

@ -13,7 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { get } from '@lowdefy/helpers';
import buildPages from './buildPages';
import testContext from '../../test/testContext';
@ -26,21 +25,6 @@ const logger = {
};
const blockMetas = {
Context: {
category: 'context',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Context',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Container: {
category: 'container',
loading: {
@ -105,17 +89,6 @@ const blockMetas = {
};
const outputMetas = {
Context: {
category: 'context',
moduleFederation: {
scope: 'blocks',
module: 'Context',
url: 'https://example.com/remoteEntry.js',
},
loading: {
type: 'Spinner',
},
},
Container: {
category: 'container',
moduleFederation: {
@ -203,7 +176,7 @@ test('page does not have an id', async () => {
const components = {
pages: [
{
type: 'Context',
type: 'Container',
auth,
},
],
@ -216,7 +189,7 @@ test('page id is not a string', async () => {
pages: [
{
id: true,
type: 'Context',
type: 'Container',
auth,
},
],
@ -231,7 +204,7 @@ test('block does not have an id', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -251,7 +224,7 @@ test('block id is not a string', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -277,7 +250,7 @@ test('page type missing', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Page type is not defined at page1.'
'Block type is not defined at "page1" on page "page1".'
);
});
@ -286,7 +259,7 @@ test('block type missing', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -297,7 +270,7 @@ test('block type missing', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Block type is not defined at blockId on page page1.'
'Block type is not defined at "blockId" on page "page1".'
);
});
@ -312,7 +285,7 @@ test('invalid page type', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Invalid block type at page page1. Received "NotABlock"'
'Invalid block type at "page1" on page "page1". Received "NotABlock".'
);
});
@ -321,7 +294,7 @@ test('invalid block type', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -333,7 +306,7 @@ test('invalid block type', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Invalid Block type at blockId on page page1. Received "NotABlock"'
'Invalid block type at "blockId" on page "page1". Received "NotABlock".'
);
});
@ -348,7 +321,7 @@ test('page type not a string', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Page type is not a string at page1. Received 1'
'Block type is not a string at "page1" on page "page1". Received 1.'
);
});
@ -357,7 +330,7 @@ test('block type not a string', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -369,22 +342,7 @@ test('block type not a string', async () => {
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Block type is not a string at blockId on page page1. Received 1'
);
});
test('page type is not of category context', async () => {
const components = {
pages: [
{
id: 'page1',
type: 'Container',
auth,
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Page page1 is not of category "context". Received "Container"'
'Block type is not a string at "blockId" on page "page1". Received 1.'
);
});
@ -393,7 +351,7 @@ test('no blocks on page', async () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
},
],
@ -407,8 +365,8 @@ test('no blocks on page', async () => {
operators: [],
pageId: '1',
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
},
],
@ -420,7 +378,7 @@ test('blocks not an array', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
blocks: 'block_1',
},
],
@ -435,13 +393,13 @@ test('block not an object', async () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
blocks: ['block_1'],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Expected block to be an object on page1. Received "block_1"'
'Expected block to be an object on page "page1". Received "block_1".'
);
});
@ -450,7 +408,7 @@ test('block meta should include all meta fields', async () => {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -478,8 +436,8 @@ test('block meta should include all meta fields', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -515,7 +473,7 @@ test('nested blocks', async () => {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -541,8 +499,8 @@ test('nested blocks', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -579,7 +537,7 @@ describe('block areas', () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
areas: {
content: {
@ -599,7 +557,7 @@ describe('block areas', () => {
pages: [
{
id: 'page1',
type: 'Context',
type: 'Container',
auth,
areas: {
content: {},
@ -616,8 +574,8 @@ describe('block areas', () => {
blockId: 'page1',
operators: [],
pageId: 'page1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -634,7 +592,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
areas: {
content: {
@ -658,8 +616,8 @@ describe('block areas', () => {
blockId: '1',
operators: [],
pageId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -683,7 +641,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
areas: {
content: {
@ -708,8 +666,8 @@ describe('block areas', () => {
pageId: '1',
operators: [],
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -734,7 +692,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
areas: {
content: {
@ -766,8 +724,8 @@ describe('block areas', () => {
operators: [],
pageId: '1',
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -801,7 +759,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -831,8 +789,8 @@ describe('block areas', () => {
operators: [],
pageId: '1',
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -866,7 +824,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -904,8 +862,8 @@ describe('block areas', () => {
operators: [],
pageId: '1',
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -939,7 +897,7 @@ describe('block areas', () => {
pages: [
{
id: '1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -993,8 +951,8 @@ describe('block areas', () => {
operators: [],
pageId: '1',
blockId: '1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [],
areas: {
content: {
@ -1063,7 +1021,7 @@ test('add user defined loading to meta', async () => {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
loading: {
custom: true,
@ -1089,15 +1047,15 @@ test('add user defined loading to meta', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
type: 'Container',
loading: {
custom: true,
},
meta: {
category: 'context',
category: 'container',
moduleFederation: {
scope: 'blocks',
module: 'Context',
module: 'Container',
url: 'https://example.com/remoteEntry.js',
},
loading: {
@ -1135,365 +1093,3 @@ test('add user defined loading to meta', async () => {
],
});
});
describe('web operators', () => {
test('set empty operators array for every context', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'context_1',
type: 'Context',
},
{
id: 'context_2',
type: 'Context',
blocks: [
{
id: 'context_2_1',
type: 'Context',
},
{
id: 'block_2_2',
type: 'Display',
},
],
},
{
id: 'block_3',
type: 'Display',
},
],
},
{
id: 'page_2',
type: 'Context',
auth,
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual([]);
expect(get(res, 'pages.0.areas.content.blocks.0.operators')).toEqual([]);
expect(get(res, 'pages.0.areas.content.blocks.1.areas.content.blocks.0.operators')).toEqual([]);
expect(get(res, 'pages.1.operators')).toEqual([]);
});
test('set all operators for context', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
properties: {
a: { _c_op_1: {} },
},
blocks: [
{
id: 'block_1',
type: 'Display',
visible: {
_v_1: {},
},
properties: {
a: { _op_1: {} },
b: { _op_1: {} },
c: { _op_2: { __op_3: { ___op_4: {} } } },
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual([
'_c_op_1',
'_v_1',
'_op_1',
'_op_4',
'_op_3',
'_op_2',
]);
});
test('exclude requests operators', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
requests: [
{
id: 'request_1',
properties: {
a: { _r_op_1: {} },
},
},
],
properties: {
a: { _c_op_1: {} },
},
blocks: [
{
id: 'block_1',
type: 'Display',
visible: {
_v_1: {},
},
properties: {
a: { _op_1: {} },
b: { _op_1: {} },
c: { _op_2: { __op_3: { ___op_4: {} } } },
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual([
'_c_op_1',
'_v_1',
'_op_1',
'_op_4',
'_op_3',
'_op_2',
]);
});
test('include request payload operators', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
requests: [
{
id: 'request_1',
payload: {
a: { _r_op_1: {} },
},
properties: {
a: { _r_op_2: {} },
},
},
],
properties: {
a: { _op_1: {} },
},
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual(['_op_1', '_r_op_1']);
});
test('set operators specific to multiple contexts', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
properties: {
a: { _c_op_1: {} },
},
blocks: [
{
id: 'block_1',
type: 'Context',
visible: {
_v_1: {},
},
properties: {
a: { _op_1: {} },
b: { _op_1: {} },
c: { _op_2: { __op_3: { ___op_4: {} } } },
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.operators')).toEqual(['_c_op_1']);
expect(get(res, 'pages.0.areas.content.blocks.0.operators')).toEqual([
'_v_1',
'_op_1',
'_op_4',
'_op_3',
'_op_2',
]);
});
});
test('block events actions array should map to try catch', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: [
{
id: 'action_1',
type: 'Reset',
},
],
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.try')).toEqual([
{
id: 'action_1',
type: 'Reset',
},
]);
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.catch')).toEqual([]);
});
test('block events actions as try catch arrays', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: [
{
id: 'action_1',
type: 'Reset',
},
],
catch: [
{
id: 'action_1',
type: 'Retry',
},
],
},
},
},
],
},
],
};
const res = await buildPages({ components, context });
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.try')).toEqual([
{
id: 'action_1',
type: 'Reset',
},
]);
expect(get(res, 'pages.0.areas.content.blocks.0.events.onClick.catch')).toEqual([
{
id: 'action_1',
type: 'Retry',
},
]);
});
test('block events actions try not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: {
id: 'action_1',
type: 'Reset',
},
},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Events must be an array of actions at block_1 in events onClick on page page_1. Received {"id":"action_1","type":"Reset"}'
);
});
test('block events actions not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Events must be an array of actions at block_1 in events onClick on page page_1. Received undefined'
);
});
test('block events actions catch not an array', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'block_1',
type: 'Input',
events: {
onClick: {
try: [],
catch: {
id: 'action_1',
type: 'Reset',
},
},
},
},
],
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Catch events must be an array of actions at block_1 in events onClick on page page_1. Received {"id":"action_1","type":"Reset"}'
);
});

View File

@ -25,21 +25,6 @@ const logger = {
};
const blockMetas = {
Context: {
category: 'context',
loading: {
type: 'Spinner',
},
moduleFederation: {
scope: 'blocks',
module: 'Context',
url: 'https://example.com/remoteEntry.js',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: 'https://example.com/Container.json',
},
},
Container: {
category: 'container',
loading: {
@ -104,17 +89,6 @@ const blockMetas = {
};
const outputMetas = {
Context: {
category: 'context',
moduleFederation: {
scope: 'blocks',
module: 'Context',
url: 'https://example.com/remoteEntry.js',
},
loading: {
type: 'Spinner',
},
},
Container: {
category: 'container',
moduleFederation: {
@ -188,13 +162,13 @@ test('requests not an array', async () => {
{
id: 'page_1',
auth,
type: 'Context',
type: 'Container',
requests: 'requests',
},
],
};
await expect(buildPages({ components, context })).rejects.toThrow(
'Requests is not an array at page_1 on page page_1. Received "requests"'
'Requests is not an array at "page_1" on page "page_1". Received "requests"'
);
});
@ -204,7 +178,7 @@ test('request id missing', async () => {
{
id: 'page_1',
auth,
type: 'Context',
type: 'Container',
requests: [{ type: 'Request' }],
},
],
@ -220,7 +194,7 @@ test('request id not a string', async () => {
{
id: 'page_1',
auth,
type: 'Context',
type: 'Container',
requests: [{ id: true, type: 'Request' }],
},
],
@ -236,7 +210,7 @@ test('request id contains a "."', async () => {
{
id: 'page_1',
auth,
type: 'Context',
type: 'Container',
requests: [{ id: 'my.request', type: 'Request' }],
},
],
@ -252,7 +226,7 @@ test('request payload not an object', async () => {
{
id: 'page_1',
auth,
type: 'Context',
type: 'Container',
requests: [{ id: 'my_request', type: 'Request', payload: 'payload' }],
},
],
@ -267,7 +241,7 @@ test('give request an id', async () => {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
requests: [
{
@ -286,14 +260,14 @@ test('give request an id', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [
{
id: 'request:page_1:page_1:request_1',
id: 'request:page_1:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'page_1',
pageId: 'page_1',
payload: {},
},
],
@ -302,72 +276,12 @@ test('give request an id', async () => {
});
});
test('request on a context block not at root', async () => {
test('request on a sub-block', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'context',
type: 'Context',
requests: [
{
id: 'request_1',
},
],
},
],
},
],
};
const res = await buildPages({ components, context });
expect(res).toEqual({
pages: [
{
id: 'page:page_1',
auth: { public: true },
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
requests: [],
areas: {
content: {
blocks: [
{
id: 'block:page_1:context',
blockId: 'context',
type: 'Context',
operators: [],
meta: outputMetas.Context,
requests: [
{
id: 'request:page_1:context:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'context',
payload: {},
},
],
},
],
},
},
},
],
});
});
test('request on a non-context block', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
blocks: [
{
@ -392,14 +306,14 @@ test('request on a non-context block', async () => {
blockId: 'page_1',
operators: [],
pageId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [
{
id: 'request:page_1:page_1:request_1',
id: 'request:page_1:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'page_1',
pageId: 'page_1',
payload: {},
},
],
@ -420,212 +334,12 @@ test('request on a non-context block', async () => {
});
});
test('request on a non-context block below a context block not at root', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'context',
type: 'Context',
blocks: [
{
id: 'box',
type: 'Container',
requests: [
{
id: 'request_1',
},
],
},
],
},
],
},
],
};
const res = await buildPages({ components, context });
expect(res).toEqual({
pages: [
{
id: 'page:page_1',
auth: { public: true },
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
requests: [],
areas: {
content: {
blocks: [
{
id: 'block:page_1:context',
blockId: 'context',
type: 'Context',
operators: [],
meta: outputMetas.Context,
requests: [
{
id: 'request:page_1:context:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'context',
payload: {},
},
],
areas: {
content: {
blocks: [
{
id: 'block:page_1:box',
blockId: 'box',
meta: outputMetas.Container,
type: 'Container',
},
],
},
},
},
],
},
},
},
],
});
});
test('request on a non-context block below a context block and at root', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
auth,
blocks: [
{
id: 'context',
type: 'Context',
blocks: [
{
id: 'box-inner',
type: 'Container',
},
],
},
{
id: 'box',
type: 'Container',
requests: [
{
id: 'request_1',
},
],
},
],
},
],
};
const res = await buildPages({ components, context });
expect(res).toEqual({
pages: [
{
id: 'page:page_1',
auth: { public: true },
blockId: 'page_1',
type: 'Context',
meta: {
category: 'context',
loading: { type: 'Spinner' },
moduleFederation: {
module: 'Context',
scope: 'blocks',
url: 'https://example.com/remoteEntry.js',
},
},
operators: [],
pageId: 'page_1',
requests: [
{
id: 'request:page_1:page_1:request_1',
auth: { public: true },
contextId: 'page_1',
requestId: 'request_1',
payload: {},
},
],
areas: {
content: {
blocks: [
{
id: 'block:page_1:context',
blockId: 'context',
operators: [],
type: 'Context',
requests: [],
areas: {
content: {
blocks: [
{
blockId: 'box-inner',
id: 'block:page_1:box-inner',
meta: {
category: 'container',
loading: {
type: 'Spinner',
},
moduleFederation: {
module: 'Container',
scope: 'blocks',
url: 'https://example.com/remoteEntry.js',
},
},
type: 'Container',
},
],
},
},
meta: {
category: 'context',
loading: { type: 'Spinner' },
moduleFederation: {
module: 'Context',
scope: 'blocks',
url: 'https://example.com/remoteEntry.js',
},
},
},
{
id: 'block:page_1:box',
blockId: 'box',
type: 'Container',
meta: {
category: 'container',
loading: { type: 'Spinner' },
moduleFederation: {
module: 'Container',
scope: 'blocks',
url: 'https://example.com/remoteEntry.js',
},
},
},
],
},
},
},
],
});
});
test('multiple requests', async () => {
const components = {
pages: [
{
id: 'page_1',
type: 'Context',
type: 'Container',
auth,
requests: [
{
@ -647,21 +361,21 @@ test('multiple requests', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [
{
id: 'request:page_1:page_1:request_1',
id: 'request:page_1:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'page_1',
pageId: 'page_1',
payload: {},
},
{
id: 'request:page_1:page_1:request_2',
id: 'request:page_1:request_2',
auth: { public: true },
requestId: 'request_2',
contextId: 'page_1',
pageId: 'page_1',
payload: {},
},
],
@ -676,7 +390,7 @@ test('set auth to request', async () => {
{
id: 'page_1',
auth: { public: true },
type: 'Context',
type: 'Container',
requests: [
{
id: 'request_1',
@ -685,7 +399,7 @@ test('set auth to request', async () => {
},
{
id: 'page_2',
type: 'Context',
type: 'Container',
auth: { public: false },
requests: [
{
@ -704,14 +418,14 @@ test('set auth to request', async () => {
operators: [],
pageId: 'page_1',
blockId: 'page_1',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [
{
id: 'request:page_1:page_1:request_1',
id: 'request:page_1:request_1',
auth: { public: true },
requestId: 'request_1',
contextId: 'page_1',
pageId: 'page_1',
payload: {},
},
],
@ -722,14 +436,14 @@ test('set auth to request', async () => {
operators: [],
pageId: 'page_2',
blockId: 'page_2',
type: 'Context',
meta: outputMetas.Context,
type: 'Container',
meta: outputMetas.Container,
requests: [
{
id: 'request:page_2:page_2:request_2',
id: 'request:page_2:request_2',
auth: { public: false },
requestId: 'request_2',
contextId: 'page_2',
pageId: 'page_2',
payload: {},
},
],

View File

@ -1,41 +0,0 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
async function checkPageIsContext(page, { getMeta }) {
if (type.isNone(page.type)) {
throw new Error(`Page type is not defined at ${page.pageId}.`);
}
if (!type.isString(page.type)) {
throw new Error(
`Page type is not a string at ${page.pageId}. Received ${JSON.stringify(page.type)}`
);
}
const meta = await getMeta(page.type);
if (!meta) {
throw new Error(
`Invalid block type at page ${page.pageId}. Received ${JSON.stringify(page.type)}`
);
}
if (meta.category !== 'context') {
throw new Error(
`Page ${page.pageId} is not of category "context". Received ${JSON.stringify(page.type)}`
);
}
}
export default checkPageIsContext;

View File

@ -1,59 +0,0 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { get, type } from '@lowdefy/helpers';
function getContextOperators(block) {
const stripContext = (_, value) => {
if (get(value, 'meta.category') === 'context') {
return null;
}
return value;
};
const { requests, ...webBlock } = block;
webBlock.areas = JSON.parse(JSON.stringify(webBlock.areas || {}), stripContext);
const operators = new Set();
const pushOperators = (_, value) => {
if (type.isObject(value) && Object.keys(value).length === 1) {
const key = Object.keys(value)[0];
const [op] = key.split('.');
const operator = op.replace(/^(_+)/gm, '_');
if (operator.length > 1 && operator[0] === '_') {
operators.add(operator);
}
}
return value;
};
JSON.parse(JSON.stringify(webBlock), pushOperators);
requests.forEach((request) => {
JSON.parse(JSON.stringify(request.payload), pushOperators);
});
return [...operators];
}
function fillContextOperators(block) {
if (get(block, 'meta.category') === 'context') {
block.operators = getContextOperators(block);
}
Object.keys(block.areas || {}).forEach((key) => {
block.areas[key].blocks.map((blk) => {
fillContextOperators(blk);
});
});
}
export default fillContextOperators;

View File

@ -14,62 +14,29 @@
limitations under the License.
*/
import { serializer, type } from '@lowdefy/helpers';
import { type } from '@lowdefy/helpers';
function getRequestsOnBlock({ block, requests, pageId }) {
if (!type.isObject(block)) {
throw new Error(`Block is not an object on page "${pageId}".`);
}
if (!type.isNone(block.requests)) {
if (!type.isArray(block.requests)) {
throw new Error(`Requests is not an array on page "${pageId}".`);
}
block.requests.forEach((request) => {
requests.push(serializer.copy(request));
async function writeRequestsOnPage({ page, context }) {
return Promise.all(
page.requests.map(async (request) => {
await context.writeBuildArtifact({
filePath: `pages/${page.pageId}/requests/${request.requestId}.json`,
content: JSON.stringify(request, null, 2),
});
delete request.properties;
delete request.type;
delete request.connectionId;
delete request.auth;
});
}
if (type.isObject(block.areas)) {
Object.keys(block.areas).forEach((key) => {
if (!type.isArray(block.areas[key].blocks)) {
throw new Error(
`Blocks is not an array on page "${pageId}", block "${block.blockId}", area "${key}".`
);
}
block.areas[key].blocks.forEach((blk) => {
getRequestsOnBlock({ block: blk, requests, pageId });
});
});
}
}
async function writeRequestsOnPage({ page, context }) {
if (!type.isObject(page)) {
throw new Error(`Page is not an object.`);
}
const requests = [];
getRequestsOnBlock({ block: page, requests, pageId: page.pageId });
return requests.map(async (request) => {
await context.writeBuildArtifact({
filePath: `pages/${page.pageId}/requests/${request.contextId}/${request.requestId}.json`,
content: JSON.stringify(request, null, 2),
});
});
})
);
}
async function writeRequests({ components, context }) {
if (type.isNone(components.pages)) return;
if (!type.isArray(components.pages)) {
throw new Error(`Pages is not an array.`);
}
const writePromises = components.pages.map((page) => writeRequestsOnPage({ page, context }));
return Promise.all(writePromises);
}
export { getRequestsOnBlock };
// export { getRequestsOnBlock };
export default writeRequests;

View File

@ -33,10 +33,13 @@ test('writeRequests write request', async () => {
pageId: 'page1',
requests: [
{
id: 'request:page1:page1:request1',
id: 'request:page1:request1',
requestId: 'request1',
contextId: 'page1',
pageId: 'page1',
connectionId: 'connection1',
auth: { public: true },
type: 'Request',
payload: {},
properties: { key: 'value' },
},
],
@ -47,12 +50,17 @@ test('writeRequests write request', async () => {
expect(mockWriteBuildArtifact.mock.calls).toEqual([
[
{
filePath: 'pages/page1/requests/page1/request1.json',
filePath: 'pages/page1/requests/request1.json',
content: `{
"id": "request:page1:page1:request1",
"id": "request:page1:request1",
"requestId": "request1",
"contextId": "page1",
"pageId": "page1",
"connectionId": "connection1",
"auth": {
"public": true
},
"type": "Request",
"payload": {},
"properties": {
"key": "value"
}
@ -62,31 +70,34 @@ test('writeRequests write request', async () => {
]);
});
test('writeRequests write nested request', async () => {
test('writeRequests write multiple requests on a page', async () => {
const components = {
pages: [
{
id: 'page:page1',
pageId: 'page1',
areas: {
content: {
blocks: [
{
id: 'block:block1',
blockId: 'block1',
requests: [
{
id: 'request:page1:page1:request1',
requestId: 'request1',
contextId: 'page1',
connectionId: 'connection1',
properties: { key: 'value' },
},
],
},
],
requests: [
{
id: 'request:page1:request1',
requestId: 'request1',
pageId: 'page1',
connectionId: 'connection1',
auth: { public: true },
type: 'Request',
payload: {},
properties: { key: 'value' },
},
},
{
id: 'request:page1:request2',
requestId: 'request2',
pageId: 'page1',
connectionId: 'connection1',
auth: { public: true },
type: 'Request',
payload: {},
properties: { key: 'value' },
},
],
},
],
};
@ -94,12 +105,36 @@ test('writeRequests write nested request', async () => {
expect(mockWriteBuildArtifact.mock.calls).toEqual([
[
{
filePath: 'pages/page1/requests/page1/request1.json',
filePath: 'pages/page1/requests/request1.json',
content: `{
"id": "request:page1:page1:request1",
"id": "request:page1:request1",
"requestId": "request1",
"contextId": "page1",
"pageId": "page1",
"connectionId": "connection1",
"auth": {
"public": true
},
"type": "Request",
"payload": {},
"properties": {
"key": "value"
}
}`,
},
],
[
{
filePath: 'pages/page1/requests/request2.json',
content: `{
"id": "request:page1:request2",
"requestId": "request2",
"pageId": "page1",
"connectionId": "connection1",
"auth": {
"public": true
},
"type": "Request",
"payload": {},
"properties": {
"key": "value"
}
@ -109,19 +144,84 @@ test('writeRequests write nested request', async () => {
]);
});
test('writeRequests requests is not an array', async () => {
test('writeRequests write requests on a for multiple pages', async () => {
const components = {
pages: [
{
id: 'page:page1',
pageId: 'page1',
requests: 'requests',
requests: [
{
id: 'request:page1:request1',
requestId: 'request1',
pageId: 'page1',
connectionId: 'connection1',
auth: { public: true },
type: 'Request',
payload: {},
properties: { key: 'value' },
},
],
},
{
id: 'page:page2',
pageId: 'page2',
requests: [
{
id: 'request:page2:request1',
requestId: 'request1',
pageId: 'page2',
connectionId: 'connection1',
auth: { public: true },
type: 'Request',
payload: {},
properties: { key: 'value' },
},
],
},
],
};
await expect(writeRequests({ components, context })).rejects.toThrow(
'Requests is not an array on page "page1"'
);
await writeRequests({ components, context });
expect(mockWriteBuildArtifact.mock.calls).toEqual([
[
{
filePath: 'pages/page1/requests/request1.json',
content: `{
"id": "request:page1:request1",
"requestId": "request1",
"pageId": "page1",
"connectionId": "connection1",
"auth": {
"public": true
},
"type": "Request",
"payload": {},
"properties": {
"key": "value"
}
}`,
},
],
[
{
filePath: 'pages/page2/requests/request1.json',
content: `{
"id": "request:page2:request1",
"requestId": "request1",
"pageId": "page2",
"connectionId": "connection1",
"auth": {
"public": true
},
"type": "Request",
"payload": {},
"properties": {
"key": "value"
}
}`,
},
],
]);
});
test('writeRequests empty pages array', async () => {
@ -138,60 +238,6 @@ test('writeRequests no pages array', async () => {
expect(mockWriteBuildArtifact.mock.calls).toEqual([]);
});
test('writeRequests pages not an array', async () => {
const components = {
pages: 'pages',
};
await expect(writeRequests({ components, context })).rejects.toThrow('Pages is not an array.');
});
test('writeRequests page is not a object', async () => {
const components = {
pages: ['page'],
};
await expect(writeRequests({ components, context })).rejects.toThrow('Page is not an object.');
});
test('writeRequests to throw when blocks is not a array', async () => {
const components = {
pages: [
{
id: 'page:page1',
pageId: 'page1',
blockId: 'page1',
areas: {
content: {
blocks: 'blocks',
},
},
},
],
};
await expect(writeRequests({ components, context })).rejects.toThrow(
'Blocks is not an array on page "page1", block "page1", area "content".'
);
});
test('writeRequests to throw when block is not an object', async () => {
const components = {
pages: [
{
id: 'page:page1',
pageId: 'page1',
blockId: 'page1',
areas: {
content: {
blocks: ['block'],
},
},
},
],
};
await expect(writeRequests({ components, context })).rejects.toThrow(
'Block is not an object on page "page1".'
);
});
test('writeRequests deletes request properties', async () => {
const components = {
pages: [
@ -200,11 +246,20 @@ test('writeRequests deletes request properties', async () => {
pageId: 'page1',
requests: [
{
id: 'request:page1:page1:request1',
id: 'request:page1:request1',
requestId: 'request1',
type: 'RequestType',
connectionId: 'connection1',
payload: { payload: true },
payload: { payload: 1 },
auth: { public: true },
properties: { key: 'value' },
},
{
id: 'request:page1:request2',
requestId: 'request2',
type: 'RequestType',
connectionId: 'connection1',
payload: { payload: 2 },
auth: { public: true },
properties: { key: 'value' },
},
@ -215,17 +270,6 @@ test('writeRequests deletes request properties', async () => {
{
id: 'block:block1',
blockId: 'block1',
requests: [
{
id: 'request:request2',
requestId: 'request1',
type: 'RequestType',
connectionId: 'connection1',
payload: { payload: true },
auth: { public: true },
properties: { key: 'value' },
},
],
},
],
},
@ -241,9 +285,14 @@ test('writeRequests deletes request properties', async () => {
pageId: 'page1',
requests: [
{
id: 'request:page1:page1:request1',
id: 'request:page1:request1',
requestId: 'request1',
payload: { payload: true },
payload: { payload: 1 },
},
{
id: 'request:page1:request2',
requestId: 'request2',
payload: { payload: 2 },
},
],
areas: {
@ -252,13 +301,6 @@ test('writeRequests deletes request properties', async () => {
{
id: 'block:block1',
blockId: 'block1',
requests: [
{
id: 'request:request2',
requestId: 'request1',
payload: { payload: true },
},
],
},
],
},