mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-04-06 15:30:30 +08:00
commit
d050e178e1
@ -17,9 +17,11 @@
|
||||
import { type } from '@lowdefy/helpers';
|
||||
import page404 from './404.json';
|
||||
|
||||
const defaultPages = [page404];
|
||||
|
||||
async function addDefaultPages({ components }) {
|
||||
// If not copied, the same object is mutated by build every time
|
||||
// build runs for dev server. See #647
|
||||
const defaultPages = [JSON.parse(JSON.stringify(page404))];
|
||||
|
||||
if (type.isNone(components.pages)) {
|
||||
components.pages = [];
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ test('addDefaultPages, pages with 404 page, should not overwrite', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('addDefaultPages, pages not an ', async () => {
|
||||
test('addDefaultPages, pages not an array', async () => {
|
||||
const components = {
|
||||
pages: { id: 'page1', type: 'Context' },
|
||||
};
|
||||
@ -222,3 +222,13 @@ test('addDefaultPages, pages not an ', async () => {
|
||||
'lowdefy.pages is not an array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('addDefaultPages, pages are copied', async () => {
|
||||
const components1 = {};
|
||||
const res1 = await addDefaultPages({ components: components1, context });
|
||||
const page1 = res1.pages[0];
|
||||
page1.id = 'page:404';
|
||||
const components2 = {};
|
||||
const res2 = await addDefaultPages({ components: components2, context });
|
||||
expect(res2.pages[0].id).toEqual('404');
|
||||
});
|
||||
|
@ -24,8 +24,15 @@ async function buildBlock(block, blockContext) {
|
||||
`Expected block to be an object on ${blockContext.pageId}. Received ${JSON.stringify(block)}`
|
||||
);
|
||||
}
|
||||
if (type.isUndefined(block.id)) {
|
||||
throw new Error(`Block id missing at page ${blockContext.pageId}`);
|
||||
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}`;
|
||||
|
@ -39,8 +39,13 @@ Blocks:
|
||||
async function buildPages({ components, context }) {
|
||||
const pages = type.isArray(components.pages) ? components.pages : [];
|
||||
const pageBuildPromises = pages.map(async (page, i) => {
|
||||
if (type.isUndefined(page.id)) {
|
||||
throw new Error(`Page id missing at 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.metaLoader);
|
||||
|
@ -212,7 +212,22 @@ test('page does not have an id', async () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow('Page id missing at page 0');
|
||||
await expect(buildPages({ components, context })).rejects.toThrow('Page id missing at page 0.');
|
||||
});
|
||||
|
||||
test('page id is not a string', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
{
|
||||
id: true,
|
||||
type: 'Context',
|
||||
auth,
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Page id is not a string at at page 0. Received true.'
|
||||
);
|
||||
});
|
||||
|
||||
test('block does not have an id', async () => {
|
||||
@ -231,7 +246,28 @@ test('block does not have an id', async () => {
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Block id missing at page page1'
|
||||
'Block id missing at page "page1".'
|
||||
);
|
||||
});
|
||||
|
||||
test('block id is not a string', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
{
|
||||
id: 'page1',
|
||||
type: 'Context',
|
||||
auth,
|
||||
blocks: [
|
||||
{
|
||||
id: true,
|
||||
type: 'Input',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Block id is not a string at page "page1". Received true.'
|
||||
);
|
||||
});
|
||||
|
||||
@ -1043,6 +1079,54 @@ describe('build requests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('request id missing', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
{
|
||||
id: 'page_1',
|
||||
auth,
|
||||
type: 'Context',
|
||||
requests: [{ type: 'Request' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Request id missing at page "page_1".'
|
||||
);
|
||||
});
|
||||
|
||||
test('request id not a string', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
{
|
||||
id: 'page_1',
|
||||
auth,
|
||||
type: 'Context',
|
||||
requests: [{ id: true, type: 'Request' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Request id is not a string at page "page_1". Received true.'
|
||||
);
|
||||
});
|
||||
|
||||
test('request id contains a "."', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
{
|
||||
id: 'page_1',
|
||||
auth,
|
||||
type: 'Context',
|
||||
requests: [{ id: 'my.request', type: 'Request' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
await expect(buildPages({ components, context })).rejects.toThrow(
|
||||
'Request id "my.request" should not include a period (".").'
|
||||
);
|
||||
});
|
||||
|
||||
test('give request an id', async () => {
|
||||
const components = {
|
||||
pages: [
|
||||
|
@ -16,6 +16,28 @@
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
|
||||
function buildRequest({ request, blockContext }) {
|
||||
if (!type.isString(request.id)) {
|
||||
if (type.isUndefined(request.id)) {
|
||||
throw new Error(`Request id missing at page "${blockContext.pageId}".`);
|
||||
}
|
||||
throw new Error(
|
||||
`Request id is not a string at page "${blockContext.pageId}". Received ${JSON.stringify(
|
||||
request.id
|
||||
)}.`
|
||||
);
|
||||
}
|
||||
if (request.id.includes('.')) {
|
||||
throw new Error(`Request id "${request.id}" should not include a period (".").`);
|
||||
}
|
||||
|
||||
request.auth = blockContext.auth;
|
||||
request.requestId = request.id;
|
||||
request.contextId = blockContext.contextId;
|
||||
request.id = `request:${blockContext.pageId}:${blockContext.contextId}:${request.id}`;
|
||||
blockContext.requests.push(request);
|
||||
}
|
||||
|
||||
function buildRequests(block, blockContext) {
|
||||
if (!type.isNone(block.requests)) {
|
||||
if (!type.isArray(block.requests)) {
|
||||
@ -26,11 +48,7 @@ function buildRequests(block, blockContext) {
|
||||
);
|
||||
}
|
||||
block.requests.forEach((request) => {
|
||||
request.auth = blockContext.auth;
|
||||
request.requestId = request.id;
|
||||
request.contextId = blockContext.contextId;
|
||||
request.id = `request:${blockContext.pageId}:${blockContext.contextId}:${request.id}`;
|
||||
blockContext.requests.push(request);
|
||||
buildRequest({ request, blockContext });
|
||||
});
|
||||
delete block.requests;
|
||||
}
|
||||
|
40
packages/docs/actions/Wait.yaml
Normal file
40
packages/docs/actions/Wait.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
|
||||
_ref:
|
||||
path: templates/actions.yaml.njk
|
||||
vars:
|
||||
pageId: Wait
|
||||
pageTitle: Wait
|
||||
filePath: actions/Wait.yaml
|
||||
types: |
|
||||
```
|
||||
(params: {
|
||||
ms: integer,
|
||||
}): void
|
||||
```
|
||||
description: |
|
||||
The `Wait` waits for the set number of milliseconds before returning.
|
||||
params: |
|
||||
###### object
|
||||
- `ms: integer`: __Required__ - The number of milliseconds to wait.
|
||||
|
||||
examples: |
|
||||
###### Wait for 500 milliseconds:
|
||||
```yaml
|
||||
- id: wait
|
||||
type: Wait
|
||||
params:
|
||||
ms: 500
|
||||
```
|
@ -1,20 +1 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2.6.3/dist/cdn/docsearch.min.js"></script>
|
||||
<script>
|
||||
let time = 500;
|
||||
function setDocsearh() {
|
||||
try {
|
||||
console.log('try', time);
|
||||
docsearch({
|
||||
apiKey: '4e88995ba28e39b8ed2bcfb6639379a1',
|
||||
indexName: 'lowdefy',
|
||||
inputSelector: '#docsearch_input',
|
||||
debug: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setTimeout(setDocsearh, time);
|
||||
time = time * 2;
|
||||
}
|
||||
}
|
||||
setDocsearh();
|
||||
</script>
|
||||
|
@ -400,8 +400,8 @@ _ref:
|
||||
- `ignoreUndefined: boolean`: Default: `false` - Specify if the BSON serializer should ignore undefined fields.
|
||||
- `j: boolean`: Specify a journal write concern.
|
||||
- `upsert: boolean`: Default: `false` - Insert document if no match is found.
|
||||
- `w: number | string`: _Integer_ **|** _String_ - The write concern
|
||||
- `wtimeout: number`: _Integer_ - The write concern timeout.
|
||||
- `w: integer | string`: The write concern
|
||||
- `wtimeout: integer`: The write concern timeout.
|
||||
|
||||
|
||||
#### Examples
|
||||
|
@ -549,6 +549,9 @@
|
||||
- id: Validate
|
||||
type: MenuLink
|
||||
pageId: Validate
|
||||
- id: Wait
|
||||
type: MenuLink
|
||||
pageId: Wait
|
||||
- id: operators
|
||||
type: MenuGroup
|
||||
properties:
|
||||
|
@ -25,7 +25,7 @@ _ref:
|
||||
(requestId: string): any
|
||||
```
|
||||
description: |
|
||||
The `_request` operator returns the response value of a request. If the request has not yet been call, or is still executing, the returned value is `null`.
|
||||
The `_request` operator returns the response value of a request. If the request has not yet been call, or is still executing, the returned value is `null`. Dot notation and [block list indexes](/lists) are supported.
|
||||
arguments: |
|
||||
###### string
|
||||
The id of the request.
|
||||
@ -35,3 +35,16 @@ _ref:
|
||||
_request: my_request
|
||||
```
|
||||
Returns: The response returned by the request.
|
||||
|
||||
###### Using dot notation to get the data object from the response:
|
||||
```yaml
|
||||
_request: my_request.data
|
||||
```
|
||||
###### Using dot notation to get the first element of an array response:
|
||||
```yaml
|
||||
_request: array_request.0
|
||||
```
|
||||
###### Using dot notation and block list indexes to get the name field from the element corresponding to the block index of an array response:
|
||||
```yaml
|
||||
_request: array_request.$.name
|
||||
```
|
||||
|
@ -146,6 +146,7 @@
|
||||
- _ref: actions/SetGlobal.yaml
|
||||
- _ref: actions/SetState.yaml
|
||||
- _ref: actions/Validate.yaml
|
||||
- _ref: actions/Wait.yaml
|
||||
|
||||
- _ref: operators/_actions.yaml
|
||||
- _ref: operators/_and.yaml
|
||||
|
33
packages/docs/public/modules/connectDocsearch.js
Normal file
33
packages/docs/public/modules/connectDocsearch.js
Normal 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.
|
||||
*/
|
||||
|
||||
function connectDocsearch() {
|
||||
let time = 100;
|
||||
try {
|
||||
// eslint-disable-next-line no-undef
|
||||
docsearch({
|
||||
apiKey: '4e88995ba28e39b8ed2bcfb6639379a1',
|
||||
indexName: 'lowdefy',
|
||||
inputSelector: '#docsearch_input',
|
||||
debug: true,
|
||||
});
|
||||
} catch (err) {
|
||||
setTimeout(connectDocsearch, time);
|
||||
time = time * 2;
|
||||
}
|
||||
}
|
||||
|
||||
export default connectDocsearch;
|
@ -1,3 +1,5 @@
|
||||
import connectDocsearch from './connectDocsearch.js';
|
||||
import filterDefaultValue from './filterDefaultValue.js';
|
||||
|
||||
window.lowdefy.registerJsAction('connectDocsearch', connectDocsearch);
|
||||
window.lowdefy.registerJsOperator('filterDefaultValue', filterDefaultValue);
|
||||
|
@ -75,6 +75,10 @@ events:
|
||||
_var: init_state_values
|
||||
|
||||
onEnterAsync:
|
||||
- id: connect_docsearch
|
||||
type: JsAction
|
||||
params:
|
||||
name: connectDocsearch
|
||||
- id: post_telemetry
|
||||
type: Request
|
||||
messages:
|
||||
@ -85,7 +89,7 @@ properties:
|
||||
title: {{ block_type }}
|
||||
header:
|
||||
theme: light
|
||||
menu:
|
||||
menu:
|
||||
forceSubMenuRender: true
|
||||
sider:
|
||||
width: 256px
|
||||
|
6
packages/docs/templates/general.yaml.njk
vendored
6
packages/docs/templates/general.yaml.njk
vendored
@ -44,6 +44,10 @@ events:
|
||||
type: string
|
||||
length: 16
|
||||
onEnterAsync:
|
||||
- id: connect_docsearch
|
||||
type: JsAction
|
||||
params:
|
||||
name: connectDocsearch
|
||||
- id: post_telemetry
|
||||
type: Request
|
||||
messages:
|
||||
@ -54,7 +58,7 @@ properties:
|
||||
title: {{ pageTitle }}
|
||||
header:
|
||||
theme: light
|
||||
menu:
|
||||
menu:
|
||||
forceSubMenuRender: true
|
||||
sider:
|
||||
width: 256px
|
||||
|
26
packages/engine/src/actions/Wait.js
Normal file
26
packages/engine/src/actions/Wait.js
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
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 Wait({ params }) {
|
||||
if (!type.isInt(params.ms)) {
|
||||
throw new Error(`Wait action "ms" param should be an integer.`);
|
||||
}
|
||||
return new Promise((resolve) => setTimeout(resolve, params.ms));
|
||||
}
|
||||
|
||||
export default Wait;
|
@ -26,6 +26,7 @@ import ScrollTo from './ScrollTo';
|
||||
import SetGlobal from './SetGlobal';
|
||||
import SetState from './SetState';
|
||||
import Validate from './Validate';
|
||||
import Wait from './Wait';
|
||||
|
||||
export default {
|
||||
CallMethod,
|
||||
@ -40,4 +41,5 @@ export default {
|
||||
SetGlobal,
|
||||
SetState,
|
||||
Validate,
|
||||
Wait,
|
||||
};
|
||||
|
91
packages/engine/test/Actions/Wait.test.js
Normal file
91
packages/engine/test/Actions/Wait.test.js
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 testContext from '../testContext';
|
||||
|
||||
const pageId = 'one';
|
||||
|
||||
const lowdefy = {
|
||||
auth: {
|
||||
login: jest.fn(),
|
||||
},
|
||||
pageId,
|
||||
};
|
||||
|
||||
const RealDate = Date;
|
||||
const mockDate = jest.fn(() => ({ date: 0 }));
|
||||
mockDate.now = jest.fn(() => 0);
|
||||
|
||||
beforeEach(() => {
|
||||
global.Date = mockDate;
|
||||
lowdefy.auth.login.mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
global.Date = RealDate;
|
||||
});
|
||||
|
||||
const timeout = (ms) => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
test('Wait', async () => {
|
||||
const rootBlock = {
|
||||
blockId: 'root',
|
||||
meta: {
|
||||
category: 'context',
|
||||
},
|
||||
areas: {
|
||||
content: {
|
||||
blocks: [
|
||||
{
|
||||
blockId: 'button',
|
||||
type: 'Button',
|
||||
meta: {
|
||||
category: 'display',
|
||||
valueType: 'string',
|
||||
},
|
||||
events: {
|
||||
onClick: [
|
||||
{
|
||||
id: 'a',
|
||||
type: 'Wait',
|
||||
params: { ms: 500 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const context = await testContext({
|
||||
lowdefy,
|
||||
rootBlock,
|
||||
});
|
||||
const { button } = context.RootBlocks.map;
|
||||
let resolved = false;
|
||||
button.triggerEvent({ name: 'onClick' }).then(() => {
|
||||
resolved = true;
|
||||
});
|
||||
expect(resolved).toBe(false);
|
||||
await timeout(100);
|
||||
expect(resolved).toBe(false);
|
||||
await timeout(300);
|
||||
expect(resolved).toBe(false);
|
||||
await timeout(150);
|
||||
expect(resolved).toBe(true);
|
||||
});
|
@ -14,9 +14,9 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { type } from '@lowdefy/helpers';
|
||||
import { applyArrayIndices, get, serializer, type } from '@lowdefy/helpers';
|
||||
|
||||
function _request({ params, requests, location }) {
|
||||
function _request({ arrayIndices, params, requests, location }) {
|
||||
if (!type.isString(params)) {
|
||||
throw new Error(
|
||||
`Operator Error: _request accepts a string value. Received: ${JSON.stringify(
|
||||
@ -24,10 +24,18 @@ function _request({ params, requests, location }) {
|
||||
)} at ${location}.`
|
||||
);
|
||||
}
|
||||
if (params in requests && !requests[params].loading) {
|
||||
return requests[params].response;
|
||||
const splitKey = params.split('.');
|
||||
const [requestId, ...keyParts] = splitKey;
|
||||
if (requestId in requests && !requests[requestId].loading) {
|
||||
if (splitKey.length === 1) {
|
||||
return serializer.copy(requests[requestId].response);
|
||||
}
|
||||
const key = keyParts.reduce((acc, value) => (acc === '' ? value : acc.concat('.', value)), '');
|
||||
return get(requests[requestId].response, applyArrayIndices(arrayIndices, key), {
|
||||
copy: true,
|
||||
});
|
||||
}
|
||||
return null; // return null for all requests which has not been filled on init
|
||||
return null;
|
||||
}
|
||||
|
||||
export default _request;
|
||||
|
@ -515,7 +515,7 @@ describe('parse operators', () => {
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test.only('parse _js operator retuning a function', async () => {
|
||||
test('parse _js operator retuning a function', async () => {
|
||||
const test_fn = () => (a, b) => a + b;
|
||||
const mockFn = jest.fn().mockImplementation(test_fn);
|
||||
context.lowdefy.imports.jsOperators.test_fn = mockFn;
|
||||
|
@ -84,3 +84,21 @@ test('_request loading true', async () => {
|
||||
expect(res.output).toBe(null);
|
||||
expect(res.errors).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
test('_request dot notation', async () => {
|
||||
const input = { _request: 'arr.0.a' };
|
||||
const parser = new WebParser({ context, contexts });
|
||||
await parser.init();
|
||||
const res = parser.parse({ input, location: 'locationId', arrayIndices });
|
||||
expect(res.output).toEqual('request a1');
|
||||
expect(res.errors).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
||||
test('_request dot notation with arrayindices', async () => {
|
||||
const input = { _request: 'arr.$.a' };
|
||||
const parser = new WebParser({ context, contexts });
|
||||
await parser.init();
|
||||
const res = parser.parse({ input, location: 'locationId', arrayIndices });
|
||||
expect(res.output).toEqual('request a2');
|
||||
expect(res.errors).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user