Merge pull request #1095 from lowdefy/plugins-actions

feat: Added plugins actions-core.
This commit is contained in:
Sam 2022-02-16 10:21:48 +02:00 committed by GitHub
commit aca7b9eb56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
116 changed files with 6416 additions and 689 deletions

46
.pnp.cjs generated
View File

@ -54,6 +54,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"name": "@lowdefy/operators",
"reference": "workspace:packages/operators"
},
{
"name": "@lowdefy/actions-core",
"reference": "workspace:packages/plugins/actions/actions-core"
},
{
"name": "@lowdefy/blocks-antd",
"reference": "workspace:packages/plugins/blocks/blocks-antd"
@ -178,6 +182,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"enableTopLevelFallback": true,
"ignorePatternData": "(^(?:\\.yarn\\/sdks(?:\\/(?!\\.{1,2}(?:\\/|$))(?:(?:(?!(?:^|\\/)\\.{1,2}(?:\\/|$)).)*?)|$))$)",
"fallbackExclusionList": [
["@lowdefy/actions-core", ["workspace:packages/plugins/actions/actions-core"]],
["@lowdefy/ajv", ["workspace:packages/utils/ajv"]],
["@lowdefy/api", ["workspace:packages/api"]],
["@lowdefy/block-dev", ["workspace:packages/utils/block-dev"]],
@ -2794,6 +2799,22 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD",
}]
]],
["@lowdefy/actions-core", [
["workspace:packages/plugins/actions/actions-core", {
"packageLocation": "./packages/plugins/actions/actions-core/",
"packageDependencies": [
["@lowdefy/actions-core", "workspace:packages/plugins/actions/actions-core"],
["@lowdefy/engine", "workspace:packages/engine"],
["@lowdefy/helpers", "workspace:packages/utils/helpers"],
["@lowdefy/operators", "workspace:packages/operators"],
["@swc/cli", "virtual:babee6e81435a5d101529cd67f2c6b175f4db37a4ab0b58df15adf73dd11be8917ac14caf44ab4e6882a92c61661055072365b349016e85173e049f006fc2305#npm:0.1.55"],
["@swc/core", "npm:1.2.135"],
["@swc/jest", "virtual:babee6e81435a5d101529cd67f2c6b175f4db37a4ab0b58df15adf73dd11be8917ac14caf44ab4e6882a92c61661055072365b349016e85173e049f006fc2305#npm:0.2.17"],
["jest", "virtual:42e288f3c714e1d86d12bf40fb17a72864641a713e0a167e85a3dab47534d11bcd2e71357e431b2f4737000bc9432f95c60dff0c9bfb212cd46e1f0f5bf0ad9f#npm:27.4.7"]
],
"linkType": "SOFT",
}]
]],
["@lowdefy/ajv", [
["workspace:packages/utils/ajv", {
"packageLocation": "./packages/utils/ajv/",
@ -3050,6 +3071,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"packageDependencies": [
["@lowdefy/build", "workspace:packages/build"],
["@jest/globals", "npm:27.5.1"],
["@lowdefy/actions-core", "workspace:packages/plugins/actions/actions-core"],
["@lowdefy/ajv", "workspace:packages/utils/ajv"],
["@lowdefy/blocks-antd", "workspace:packages/plugins/blocks/blocks-antd"],
["@lowdefy/blocks-basic", "workspace:packages/plugins/blocks/blocks-basic"],
@ -3243,6 +3265,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["@lowdefy/engine", "workspace:packages/engine"],
["@lowdefy/helpers", "workspace:packages/utils/helpers"],
["@lowdefy/operators", "workspace:packages/operators"],
["@lowdefy/operators-js", "workspace:packages/plugins/operators/operators-js"],
["@swc/cli", "virtual:babee6e81435a5d101529cd67f2c6b175f4db37a4ab0b58df15adf73dd11be8917ac14caf44ab4e6882a92c61661055072365b349016e85173e049f006fc2305#npm:0.1.55"],
["@swc/core", "npm:1.2.135"],
["@swc/jest", "virtual:babee6e81435a5d101529cd67f2c6b175f4db37a4ab0b58df15adf73dd11be8917ac14caf44ab4e6882a92c61661055072365b349016e85173e049f006fc2305#npm:0.2.17"],
@ -10483,6 +10506,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
}]
]],
["jest", [
["npm:27.4.7", {
"packageLocation": "./.yarn/cache/jest-npm-27.4.7-cdda9da561-28ce948b30.zip/node_modules/jest/",
"packageDependencies": [
["jest", "npm:27.4.7"]
],
"linkType": "SOFT",
}],
["npm:27.5.1", {
"packageLocation": "./.yarn/cache/jest-npm-27.5.1-bacad4fe2a-96f1d69042.zip/node_modules/jest/",
"packageDependencies": [
@ -10490,6 +10520,22 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
],
"linkType": "SOFT",
}],
["virtual:42e288f3c714e1d86d12bf40fb17a72864641a713e0a167e85a3dab47534d11bcd2e71357e431b2f4737000bc9432f95c60dff0c9bfb212cd46e1f0f5bf0ad9f#npm:27.4.7", {
"packageLocation": "./.yarn/__virtual__/jest-virtual-4325f46d83/0/cache/jest-npm-27.4.7-cdda9da561-28ce948b30.zip/node_modules/jest/",
"packageDependencies": [
["jest", "virtual:42e288f3c714e1d86d12bf40fb17a72864641a713e0a167e85a3dab47534d11bcd2e71357e431b2f4737000bc9432f95c60dff0c9bfb212cd46e1f0f5bf0ad9f#npm:27.4.7"],
["@jest/core", "virtual:af105d7a2bc799821f67e8114057f53830abc6c7c9faa96846247605351f361b4158d347970d00ebc3c04dbab6e46e1c1e24df193c1caf1f7d80a00c65068a2a#npm:27.5.1"],
["@types/node-notifier", null],
["import-local", "npm:3.1.0"],
["jest-cli", "virtual:af105d7a2bc799821f67e8114057f53830abc6c7c9faa96846247605351f361b4158d347970d00ebc3c04dbab6e46e1c1e24df193c1caf1f7d80a00c65068a2a#npm:27.5.1"],
["node-notifier", null]
],
"packagePeers": [
"@types/node-notifier",
"node-notifier"
],
"linkType": "HARD",
}],
["virtual:babee6e81435a5d101529cd67f2c6b175f4db37a4ab0b58df15adf73dd11be8917ac14caf44ab4e6882a92c61661055072365b349016e85173e049f006fc2305#npm:27.5.1", {
"packageLocation": "./.yarn/__virtual__/jest-virtual-af105d7a2b/0/cache/jest-npm-27.5.1-bacad4fe2a-96f1d69042.zip/node_modules/jest/",
"packageDependencies": [

Binary file not shown.

View File

@ -57,6 +57,7 @@
},
"devDependencies": {
"@jest/globals": "27.5.1",
"@lowdefy/actions-core": "4.0.0-alpha.6",
"@lowdefy/blocks-antd": "4.0.0-alpha.6",
"@lowdefy/blocks-basic": "4.0.0-alpha.6",
"@lowdefy/blocks-color-selectors": "4.0.0-alpha.6",

View File

@ -53,12 +53,12 @@ function buildTypes({ components, context }) {
},
};
// buildTypeClass({
// counter: typeCounters.actions,
// definitions: context.types.actions,
// store: components.types.actions,
// typeClass: 'Action',
// });
buildTypeClass(context, {
counter: typeCounters.actions,
definitions: context.types.actions,
store: components.types.actions,
typeClass: 'Action',
});
buildTypeClass(context, {
counter: typeCounters.blocks,

View File

@ -0,0 +1,29 @@
/*
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 generateImportFile from './generateImportFile.js';
async function writeActionImports({ components, context }) {
await context.writeBuildArtifact(
'plugins/actions.js',
generateImportFile({
types: components.types.actions,
importPath: 'actions',
})
);
}
export default writeActionImports;

View File

@ -38,6 +38,7 @@ import validateApp from './build/validateApp.js';
import validateConfig from './build/validateConfig.js';
import updateServerPackageJson from './build/updateServerPackageJson.js';
import writeApp from './build/writeApp.js';
import writeActionImports from './build/writePluginImports/writeActionImports.js';
import writeBlockImports from './build/writePluginImports/writeBlockImports.js';
import writeConfig from './build/writeConfig.js';
import writeConnectionImports from './build/writePluginImports/writeConnectionImports.js';

View File

@ -20,6 +20,7 @@ import { type } from '@lowdefy/helpers';
import { readFile, writeFile } from '@lowdefy/node-utils';
const defaultPackages = [
'@lowdefy/actions-core',
'@lowdefy/blocks-antd',
'@lowdefy/blocks-basic',
'@lowdefy/blocks-color-selectors',

View File

@ -42,6 +42,7 @@
"@lowdefy/operators": "4.0.0-alpha.6"
},
"devDependencies": {
"@lowdefy/operators-js": "4.0.0-alpha.6",
"@swc/cli": "0.1.55",
"@swc/core": "1.2.135",
"@swc/jest": "0.2.17",

View File

@ -15,7 +15,7 @@
*/
import { type } from '@lowdefy/helpers';
import actions from './actions/index.js';
import getActionMethods from './actions/getActionMethods.js';
class Actions {
constructor(context) {
@ -24,7 +24,7 @@ class Actions {
this.callActionLoop = this.callActionLoop.bind(this);
this.callActions = this.callActions.bind(this);
this.displayMessage = this.displayMessage.bind(this);
this.actions = actions;
this.actions = context._internal.lowdefy._internal.actions;
}
async callAsyncAction({ action, arrayIndices, block, event, index, responses }) {
@ -126,7 +126,7 @@ class Actions {
}
async callAction({ action, arrayIndices, block, event, index, responses }) {
if (!actions[action.type]) {
if (!this.actions[action.type]) {
throw {
error: new Error(`Invalid action type "${action.type}" at "${block.blockId}".`),
type: action.type,
@ -155,12 +155,17 @@ class Actions {
status: 'loading',
});
try {
response = await actions[action.type]({
arrayIndices,
blockId: block.blockId,
context: this.context,
event,
response = await this.actions[action.type]({
methods: getActionMethods({
actions: responses,
arrayIndices,
blockId: block.blockId,
context: this.context,
event,
}),
document: this.context._internal.lowdefy._internal.document,
params: parsedAction.params,
window: this.context._internal.lowdefy._internal.window,
});
} catch (error) {
responses[action.id] = { error, index, type: action.type };

View File

@ -31,7 +31,7 @@ class Requests {
}
callRequests({ actions, arrayIndices, event, params } = {}) {
if (params.all === true) {
if (type.isObject(params) && params.all === true) {
return Promise.all(
Object.keys(this.requestConfig).map((requestId) =>
this.callRequest({ requestId, event, arrayIndices })

View File

@ -1,42 +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 { applyArrayIndices, type } from '@lowdefy/helpers';
// context, event, params
async function CallMethod({ arrayIndices, context, params }) {
const { blockId, method, args = [] } = params;
const blockMethod =
context._internal.RootBlocks.map[applyArrayIndices(arrayIndices, blockId)].methods[method];
if (!type.isArray(args)) {
throw new Error(
`Failed to call method "${method}" on block "${blockId}": "args" should be an array. Received "${JSON.stringify(
params
)}".`
);
}
if (!type.isFunction(blockMethod)) {
throw new Error(
`Failed to call method "${method}" on block "${blockId}". Check if "${method}" is a valid block method for block "${blockId}". Received "${JSON.stringify(
params
)}".`
);
}
return blockMethod(...args);
}
export default CallMethod;

View File

@ -1,61 +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, serializer } from '@lowdefy/helpers';
import actionFns from './index.js';
async function JsAction({ context, event, params, arrayIndices, blockId }) {
if (!type.isString(params.name)) {
throw new Error(`JsAction requires a string for 'params.name'.`);
}
if (!type.isNone(params.args) && !type.isArray(params.args)) {
throw new Error(`JsAction requires a array for 'params.args'.`);
}
if (!type.isFunction(context.lowdefy.imports.jsActions[params.name])) {
throw new Error(`JsAction ${params.name} is not a function.`);
}
const actions = {};
Object.keys(actionFns).forEach((name) => {
actions[name] = (actionParams) =>
actionFns[name]({
arrayIndices,
blockId,
context,
event,
params: actionParams,
});
});
return context.lowdefy.imports.jsActions[params.name](
{
...serializer.copy({
global: context.lowdefy.lowdefyGlobal,
input: context.lowdefy.inputs[context.id],
state: context.state,
urlQuery: context.lowdefy.urlQuery,
user: context.lowdefy.user,
}),
actions,
contextId: context.id,
pageId: context.pageId,
requests: { ...context.requests },
},
...(params.args || [])
);
}
export default JsAction;

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 { applyArrayIndices, type } from '@lowdefy/helpers';
function createCallMethod({ arrayIndices, context }) {
return function callMethod(params) {
const { blockId, method, args = [] } = params;
const blockMethod =
context._internal.RootBlocks.map[applyArrayIndices(arrayIndices, blockId)].methods[method];
if (!type.isArray(args)) {
throw new Error(
`Failed to call method "${method}" on block "${blockId}": "args" should be an array. Received "${JSON.stringify(
params
)}".`
);
}
if (!type.isFunction(blockMethod)) {
throw new Error(
`Failed to call method "${method}" on block "${blockId}". Check if "${method}" is a valid block method for block "${blockId}". Received "${JSON.stringify(
params
)}".`
);
}
return blockMethod(...args);
};
}
export default createCallMethod;

View File

@ -14,9 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from '../../test/testContext.js';
const lowdefy = {};
const lowdefy = {
_internal: {
actions: {
CallMethod: ({ methods: { callMethod }, params }) => {
return callMethod(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
List: { meta: { category: 'list' } },
TextInput: { meta: { category: 'input' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
@ -83,7 +96,6 @@ test('CallMethod with no args, synchronous method', async () => {
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const textInput = context._internal.RootBlocks.map['block:root:textInput:0'];
textInput.registerMethod('blockMethod', blockMethod);
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
@ -148,7 +160,11 @@ test('CallMethod method return a promise', async () => {
{
id: 'a',
type: 'CallMethod',
params: { blockId: 'textInput', method: 'blockMethod', args: ['arg'] },
params: {
blockId: 'textInput',
method: 'blockMethod',
args: ['arg'],
},
},
],
},
@ -164,7 +180,6 @@ test('CallMethod method return a promise', async () => {
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const textInput = context._internal.RootBlocks.map['block:root:textInput:0'];
textInput.registerMethod('blockMethod', blockMethod);
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
@ -237,7 +252,6 @@ test('CallMethod with args not an array', async () => {
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const textInput = context._internal.RootBlocks.map['block:root:textInput:0'];
textInput.registerMethod('blockMethod', blockMethod);
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
@ -250,7 +264,7 @@ test('CallMethod with args not an array', async () => {
id: 'a',
params: {
args: 'arg',
blockId: 'textInput',
blockId: 'block:root:textInput:0',
method: 'blockMethod',
},
type: 'CallMethod',
@ -312,7 +326,11 @@ test('CallMethod with multiple positional args, synchronous method', async () =>
{
id: 'a',
type: 'CallMethod',
params: { blockId: 'textInput', method: 'blockMethod', args: ['arg1', 'arg2'] },
params: {
blockId: 'textInput',
method: 'blockMethod',
args: ['arg1', 'arg2'],
},
},
],
},
@ -328,7 +346,6 @@ test('CallMethod with multiple positional args, synchronous method', async () =>
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const textInput = context._internal.RootBlocks.map['block:root:textInput:0'];
textInput.registerMethod('blockMethod', blockMethod);
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
@ -402,7 +419,11 @@ test('CallMethod of block in array by explicit id', async () => {
{
id: 'a',
type: 'CallMethod',
params: { blockId: 'list.0.textInput', method: 'blockMethod', args: ['arg'] },
params: {
blockId: 'list.0.textInput',
method: 'blockMethod',
args: ['arg'],
},
},
],
},
@ -416,11 +437,9 @@ test('CallMethod of block in array by explicit id', async () => {
rootBlock,
initState: { list: [{ textInput: '0' }, { textInput: '1' }] },
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const textInput0 = context._internal.RootBlocks.map['block:root:list.0.textInput:0'];
const textInput1 = context._internal.RootBlocks.map['block:root:list.1.textInput:0'];
textInput0.registerMethod('blockMethod', blockMethod0);
textInput1.registerMethod('blockMethod', blockMethod1);
await button.triggerEvent({ name: 'onClick' });
@ -496,18 +515,15 @@ test('CallMethod of block in array by block with same indices and id pattern', a
rootBlock,
initState: { list: [{ textInput: '0' }, { textInput: '1' }] },
});
const textInput0 = context._internal.RootBlocks.map['block:root:list.0.textInput:0'];
const textInput1 = context._internal.RootBlocks.map['block:root:list.1.textInput:0'];
const button0 = context._internal.RootBlocks.map['block:root:list.0.button:0'];
const button1 = context._internal.RootBlocks.map['block:root:list.1.button:0'];
textInput0.registerMethod('blockMethod', blockMethod0);
textInput1.registerMethod('blockMethod', blockMethod1);
await button1.triggerEvent({ name: 'onClick' });
expect(blockMethod0.mock.calls).toEqual([]);
expect(blockMethod1.mock.calls).toEqual([['arg']]);
await button0.triggerEvent({ name: 'onClick' });
expect(blockMethod0.mock.calls).toEqual([['arg']]);
expect(blockMethod1.mock.calls).toEqual([['arg']]);
@ -561,7 +577,6 @@ test('CallMethod with method does not exist', async () => {
initState: { textInput: 'init' },
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
@ -572,7 +587,7 @@ test('CallMethod with method does not exist', async () => {
action: {
id: 'a',
params: {
blockId: 'textInput',
blockId: 'block:root:textInput:0',
method: 'no-method',
},
type: 'CallMethod',

View File

@ -14,13 +14,10 @@
limitations under the License.
*/
function Message({ context, params = {} }) {
context._internal.lowdefy._internal.displayMessage({
content: params.content || 'Success',
duration: params.duration,
icon: params.icon,
status: params.status,
});
function createDisplayMessage({ context }) {
return function displayMessage(params = {}) {
context._internal.lowdefy._internal.displayMessage(params);
};
}
export default Message;
export default createDisplayMessage;

View File

@ -14,18 +14,29 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from '../../test/testContext.js';
// Mock message
const mockMessage = jest.fn(() => () => undefined);
const lowdefy = {
_internal: {
actions: {
DisplayMessage: ({ methods: { displayMessage }, params }) => {
return displayMessage({
...params,
content: params ? params.content : 'Success',
});
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
displayMessage: mockMessage,
},
};
test('Message with content', async () => {
test('DisplayMessage with content', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
@ -47,7 +58,7 @@ test('Message with content', async () => {
onClick: [
{
id: 'a',
type: 'Message',
type: 'DisplayMessage',
params: { content: 'test' },
},
],
@ -72,7 +83,7 @@ test('Message with content', async () => {
]);
});
test('Message with all params', async () => {
test('DisplayMessage with all params', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
@ -94,7 +105,7 @@ test('Message with all params', async () => {
onClick: [
{
id: 'a',
type: 'Message',
type: 'DisplayMessage',
params: {
content: 'content',
duration: 6,
@ -127,7 +138,7 @@ test('Message with all params', async () => {
]);
});
test('Message with no params', async () => {
test('DisplayMessage with no params', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
@ -149,7 +160,7 @@ test('Message with no params', async () => {
onClick: [
{
id: 'a',
type: 'Message',
type: 'DisplayMessage',
},
],
},

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetActions({ actions, arrayIndices, blockId }) {
return function getActions(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: actions,
method: 'getActions',
params,
});
};
}
export default createGetActions;

View File

@ -14,11 +14,21 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import actionFns from '../../src/actions/index.js';
import testContext from '../../test/testContext.js';
const pageId = 'one';
const lowdefy = { pageId };
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getActions }, params }) => {
return getActions(params);
},
Custom: () => 'response',
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
@ -28,7 +38,7 @@ mockDate.now = jest.fn(() => 0);
console.log = () => {};
console.error = () => {};
beforeAll(() => {
beforeEach(() => {
global.Date = mockDate;
});
@ -36,7 +46,7 @@ afterAll(() => {
global.Date = RealDate;
});
test('JsAction with no args, synchronous fn', async () => {
test('getActions params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
@ -46,6 +56,7 @@ test('JsAction with no args, synchronous fn', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -56,10 +67,13 @@ test('JsAction with no args, synchronous fn', async () => {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 'test_fn',
},
type: 'Custom',
params: true,
},
{
id: 'b',
type: 'Action',
params: true,
},
],
},
@ -72,31 +86,38 @@ test('JsAction with no args, synchronous fn', async () => {
lowdefy,
rootBlock,
});
const mockFn = jest.fn(() => 'js_fn');
context.lowdefy.imports.jsActions.test_fn = mockFn;
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
type: 'JsAction',
response: 'response',
index: 0,
response: 'js_fn',
type: 'Custom',
},
b: {
response: {
a: {
index: 0,
response: 'response',
type: 'Custom',
},
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
startTimestamp: { date: 0 },
endTimestamp: { date: 0 },
});
expect(mockFn).toHaveBeenCalledTimes(1);
});
test('JsAction with no args, async fn', async () => {
test('getActions params is a', async () => {
const rootBlock = {
blockId: 'root',
meta: {
@ -106,6 +127,7 @@ test('JsAction with no args, async fn', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -116,10 +138,13 @@ test('JsAction with no args, async fn', async () => {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 'test_fn',
},
type: 'Custom',
params: true,
},
{
id: 'b',
type: 'Action',
params: 'a',
},
],
},
@ -132,38 +157,36 @@ test('JsAction with no args, async fn', async () => {
lowdefy,
rootBlock,
});
const timeout = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fn = async () => {
await timeout(300);
return 'js_fn';
};
const mockFn = jest.fn().mockImplementation(fn);
context.lowdefy.imports.jsActions.test_fn = mockFn;
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
type: 'JsAction',
response: 'response',
index: 0,
response: 'js_fn',
type: 'Custom',
},
b: {
response: {
index: 0,
response: 'response',
type: 'Custom',
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
startTimestamp: { date: 0 },
endTimestamp: { date: 0 },
});
expect(mockFn).toHaveBeenCalledTimes(1);
});
test('JsAction with args, synchronous fn', async () => {
test('getActions params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
@ -173,6 +196,7 @@ test('JsAction with args, synchronous fn', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -183,11 +207,7 @@ test('JsAction with args, synchronous fn', async () => {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 'test_fn',
args: [1, '2', new Date()],
},
type: 'Action',
},
],
},
@ -200,138 +220,255 @@ test('JsAction with args, synchronous fn', async () => {
lowdefy,
rootBlock,
});
const mockFn = jest.fn((...args) => args);
context.lowdefy.imports.jsActions.test_fn = mockFn;
const { button } = context.RootBlocks.map;
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toMatchInlineSnapshot(`
Object {
"blockId": "button",
"bounced": false,
"endTimestamp": Object {
"date": 0,
},
"event": undefined,
"eventName": "onClick",
"responses": Object {
"a": Object {
"index": 0,
"response": Array [
Object {
"actions": Object {
"CallMethod": [Function],
"JsAction": [Function],
"Link": [Function],
"Login": [Function],
"Logout": [Function],
"Message": [Function],
"Request": [Function],
"Reset": [Function],
"ResetValidation": [Function],
"ScrollTo": [Function],
"SetGlobal": [Function],
"SetState": [Function],
"Throw": [Function],
"Validate": [Function],
"Wait": [Function],
},
"contextId": "test",
"input": Object {},
"pageId": "root",
"requests": Object {},
"state": Object {},
"urlQuery": Object {},
},
1,
"2",
Object {
"date": 0,
},
],
"type": "JsAction",
},
},
"startTimestamp": Object {
"date": 0,
},
"success": true,
}
`);
expect(mockFn).toHaveBeenCalledTimes(1);
});
test('JsAction name not a string', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 1,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getActions params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getActions params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getActions params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Custom',
params: true,
},
{
id: 'b',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'response',
index: 0,
type: 'Custom',
},
b: {
response: 'defaulto',
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getActions params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Custom',
params: true,
},
{
id: 'b',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'response',
index: 0,
type: 'Custom',
},
b: {
response: {
a: {
index: 0,
response: 'response',
type: 'Custom',
},
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getActions params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
name: 1,
key: {},
},
type: 'JsAction',
type: 'Action',
},
error: {
error: new Error(`JsAction requires a string for 'params.name'.`),
error: new Error(
'Method Error: getActions params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'JsAction',
type: 'Action',
},
},
responses: {
a: {
type: 'JsAction',
error: new Error(
'Method Error: getActions params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
error: new Error(`JsAction requires a string for 'params.name'.`),
type: 'Action',
},
},
success: false,
startTimestamp: { date: 0 },
endTimestamp: { date: 0 },
success: false,
});
});
test('JsAction args not an array', async () => {
test('getActions params.key is a', async () => {
const rootBlock = {
blockId: 'root',
meta: {
@ -341,6 +478,7 @@ test('JsAction args not an array', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -351,10 +489,14 @@ test('JsAction args not an array', async () => {
onClick: [
{
id: 'a',
type: 'JsAction',
type: 'Custom',
params: true,
},
{
id: 'b',
type: 'Action',
params: {
name: 'js_fn',
args: { a: 1 },
key: 'a',
},
},
],
@ -368,160 +510,31 @@ test('JsAction args not an array', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
args: {
a: 1,
},
name: 'js_fn',
},
type: 'JsAction',
},
error: {
error: new Error(`JsAction requires a array for 'params.args'.`),
index: 0,
type: 'JsAction',
},
},
responses: {
a: {
type: 'JsAction',
response: 'response',
index: 0,
error: new Error(`JsAction requires a array for 'params.args'.`),
type: 'Custom',
},
},
success: false,
startTimestamp: { date: 0 },
endTimestamp: { date: 0 },
});
});
test('JsAction args not a function', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 'js_not_fn',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
name: 'js_not_fn',
b: {
response: {
index: 0,
response: 'response',
type: 'Custom',
},
type: 'JsAction',
},
error: {
error: new Error(`JsAction js_not_fn is not a function.`),
index: 0,
type: 'JsAction',
index: 1,
type: 'Action',
},
},
responses: {
a: {
type: 'JsAction',
index: 0,
error: new Error(`JsAction js_not_fn is not a function.`),
},
},
success: false,
startTimestamp: { date: 0 },
endTimestamp: { date: 0 },
success: true,
});
});
test('JsAction can use Lowdefy actions', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'JsAction',
params: {
name: 'test_fn',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const fn = async ({ actions }) => {
actions.SetState({ answer: 42 });
return actions;
};
const mockFn = jest.fn().mockImplementation(fn);
context.lowdefy.imports.jsActions.test_fn = mockFn;
const { button } = context.RootBlocks.map;
const res = await button.triggerEvent({ name: 'onClick' });
expect(context.state).toEqual({ answer: 42 });
expect(Object.keys(res.responses.a.response)).toEqual(Object.keys(actionFns));
});

View File

@ -0,0 +1,23 @@
/*
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 createGetBlockId({ blockId }) {
return function getBlockId() {
return blockId;
};
}
export default createGetBlockId;

View File

@ -0,0 +1,100 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getBlockId }, params }) => {
return getBlockId(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getBlockId no params', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'button',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetEvent({ arrayIndices, blockId, event }) {
return function getEvent(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: event,
method: 'getEvent',
params,
});
};
}
export default createGetEvent;

View File

@ -0,0 +1,471 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getEvent }, params }) => {
return getEvent(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getEvent params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: {
some: 'data',
},
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getEvent params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: { some: 'data' },
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getEvent params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getEvent params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: { some: 'data' },
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getEvent params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getEvent params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: { some: 'data' },
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getEvent params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: { some: 'data' },
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getEvent params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: { some: 'data' },
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getEvent params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getEvent params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getEvent params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick', event: { some: 'data' } });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: { some: 'data' },
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetGlobal({ arrayIndices, blockId, context }) {
return function getGlobal(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context._internal.lowdefy.lowdefyGlobal,
method: 'getGlobal',
params,
});
};
}
export default createGetGlobal;

View File

@ -0,0 +1,472 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getGlobal }, params }) => {
return getGlobal(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
lowdefyGlobal: {
some: 'data',
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getGlobal params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getGlobal params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getGlobal params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getGlobal params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getGlobal params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getGlobal params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getGlobal params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getGlobal params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getGlobal params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getGlobal params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getGlobal params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetInput({ arrayIndices, blockId, context }) {
return function getInput(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context._internal.lowdefy.inputs[context.id],
method: 'getInput',
params,
});
};
}
export default createGetInput;

View File

@ -0,0 +1,472 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getInput }, params }) => {
return getInput(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
inputs: {
test: { some: 'data' },
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getInput params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getInput params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getInput params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getInput params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getInput params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getInput params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getInput params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getInput params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getInput params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getInput params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getInput params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,23 @@
/*
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 createGetPageId({ context }) {
return function getPageId() {
return context.pageId;
};
}
export default createGetPageId;

View File

@ -0,0 +1,100 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getPageId }, params }) => {
return getPageId(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getPageId no params', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'root',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
const createGetRequestDetails = ({ arrayIndices, blockId, context }) => {
return function getRequestDetails(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context.requests,
method: 'getRequestDetails',
params,
});
};
};
export default createGetRequestDetails;

View File

@ -0,0 +1,595 @@
/*
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 '../../test/testContext.js';
// Mock apollo client
const mockReqResponses = {
req_one: {
id: 'req_one',
success: true,
response: 1,
},
req_error: new Error('Request error'),
};
const mockCallRequest = jest.fn();
const mockCallRequestImp = ({ requestId }) => {
return new Promise((resolve, reject) => {
if (requestId === 'req_error') {
reject(mockReqResponses[requestId]);
}
resolve(mockReqResponses[requestId]);
});
};
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getRequestDetails }, params }) => {
return getRequestDetails(params);
},
Request: ({ methods: { request }, params }) => {
return request(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
callRequest: mockCallRequest,
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeAll(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
beforeEach(() => {
mockCallRequest.mockReset();
mockCallRequest.mockImplementation(mockCallRequestImp);
});
test('getRequestDetails params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
response: {
req_one: {
error: [],
loading: false,
response: 1,
},
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getRequestDetails params is req_one', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: 'req_one',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
response: {
error: [],
loading: false,
response: 1,
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getRequestDetails params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'b', type: 'Action' },
error: {
error: new Error(
'Method Error: getRequestDetails params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 1,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
error: new Error(
'Method Error: getRequestDetails params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getRequestDetails params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
response: 'defaulto',
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getRequestDetails params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
response: {
req_one: {
error: [],
loading: false,
response: 1,
},
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getRequestDetails params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'b',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getRequestDetails params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 1,
type: 'Action',
},
},
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
error: new Error(
'Method Error: getRequestDetails params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getRequestDetails params.key is req_one', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
requests: [
{
requestId: 'req_one',
},
],
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{ id: 'a', type: 'Request', params: ['req_one'] },
{
id: 'b',
type: 'Action',
params: {
key: 'req_one',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: [1],
type: 'Request',
},
b: {
response: {
error: [],
loading: false,
response: 1,
},
index: 1,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetState({ arrayIndices, blockId, context }) {
return function getState(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context.state,
method: 'getState',
params,
});
};
}
export default createGetState;

View File

@ -0,0 +1,476 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getState }, params }) => {
return getState(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getState params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getState params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getState params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getState params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getState params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getState params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getState params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getState params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getState params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getState params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getState params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
initState: { some: 'data' },
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetUrlQuery({ arrayIndices, blockId, context }) {
return function getUrlQuery(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context._internal.lowdefy.urlQuery,
method: 'getUrlQuery',
params,
});
};
}
export default createGetUrlQuery;

View File

@ -0,0 +1,472 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getUrlQuery }, params }) => {
return getUrlQuery(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
urlQuery: {
some: 'data',
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getUrlQuery params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUrlQuery params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUrlQuery params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getUrlQuery params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getUrlQuery params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getUrlQuery params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUrlQuery params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUrlQuery params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getUrlQuery params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getUrlQuery params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getUrlQuery params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,31 @@
/*
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 getFromObject from './getFromObject.js';
function createGetUser({ arrayIndices, blockId, context }) {
return function getUser(params) {
return getFromObject({
arrayIndices,
location: blockId,
object: context._internal.lowdefy.user,
method: 'getUser',
params,
});
};
}
export default createGetUser;

View File

@ -0,0 +1,472 @@
/*
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 '../../test/testContext.js';
const lowdefy = {
_internal: {
actions: {
Action: ({ methods: { getUser }, params }) => {
return getUser(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
},
user: {
some: 'data',
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('getUser params is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: true,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUser params is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: 'some',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
index: 0,
response: 'data',
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUser params is none', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: { id: 'a', type: 'Action' },
error: {
error: new Error(
'Method Error: getUser params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error(
'Method Error: getUser params must be of type string, integer, boolean or object. Received: undefined at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getUser params.key is null', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: null,
default: 'defaulto',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'defaulto',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUser params.all is true', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
all: true,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: { some: 'data' },
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});
test('getUser params.key is not string or int', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: {},
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
params: {
key: {},
},
type: 'Action',
},
error: {
error: new Error(
'Method Error: getUser params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
responses: {
a: {
error: new Error(
'Method Error: getUser params.key must be of type string or integer. Received: {"key":{}} at button.'
),
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('getUser params.key is some', async () => {
const rootBlock = {
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
valueType: 'string',
},
events: {
onClick: [
{
id: 'a',
type: 'Action',
params: {
key: 'some',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: 'data',
index: 0,
type: 'Action',
},
},
startTimestamp: { date: 0 },
success: true,
});
});

View File

@ -0,0 +1,23 @@
/*
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 createLink({ context }) {
return function link(params) {
context._internal.lowdefy._internal.link(params);
};
}
export default createLink;

View File

@ -14,10 +14,32 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import { type } from '@lowdefy/helpers';
import testContext from '../../test/testContext.js';
const lowdefy = {
_internal: { link: jest.fn() },
_internal: {
actions: {
Link: ({ methods: { link }, params }) => {
const linkParams = type.isString(params) ? { pageId: params } : params;
try {
link(linkParams);
} catch (error) {
console.log(error);
throw new Error(
`Invalid Link, check action params. Received "${JSON.stringify(params)}".`
);
}
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
List: { meta: { category: 'list' } },
TextInput: { meta: { category: 'input' } },
},
link: jest.fn(),
},
};
const RealDate = Date;
@ -48,7 +70,7 @@ test('Link with string pageId params', async () => {
content: {
blocks: [
{
id: 'block:root:root:0',
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
@ -69,13 +91,7 @@ test('Link with string pageId params', async () => {
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(lowdefy._internal.link.mock.calls).toEqual([
[
{
pageId: 'pageId',
},
],
]);
expect(lowdefy._internal.link.mock.calls).toEqual([[{ pageId: 'pageId' }]]);
expect(res.success).toBe(true);
});

View File

@ -0,0 +1,23 @@
/*
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 createLogin({ context }) {
return function login(params) {
return context._internal.lowdefy._internal.auth.login(params);
};
}
export default createLogin;

View File

@ -14,15 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
const pageId = 'one';
import testContext from '../../test/testContext.js';
const lowdefy = {
auth: {
login: jest.fn(),
_internal: {
actions: {
Login: ({ methods: { login }, params }) => {
return login(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
auth: {
login: jest.fn(),
},
},
pageId,
};
const RealDate = Date;
@ -31,7 +38,7 @@ mockDate.now = jest.fn(() => 0);
beforeEach(() => {
global.Date = mockDate;
lowdefy.auth.login.mockReset();
lowdefy._internal.auth.login.mockReset();
});
afterAll(() => {
@ -48,6 +55,7 @@ test('Login', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -72,9 +80,9 @@ test('Login', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(lowdefy.auth.login.mock.calls).toEqual([
expect(lowdefy._internal.auth.login.mock.calls).toEqual([
[
{
input: { i: true },

View File

@ -0,0 +1,23 @@
/*
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 createLogout({ context }) {
return function logout() {
return context._internal.lowdefy._internal.auth.logout();
};
}
export default createLogout;

View File

@ -14,15 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
const pageId = 'one';
import testContext from '../../test/testContext.js';
const lowdefy = {
auth: {
logout: jest.fn(),
_internal: {
actions: {
Logout: ({ methods: { logout } }) => {
return logout();
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
auth: {
logout: jest.fn(),
},
},
pageId,
};
const RealDate = Date;
@ -31,14 +38,14 @@ mockDate.now = jest.fn(() => 0);
beforeEach(() => {
global.Date = mockDate;
lowdefy.auth.logout.mockReset();
lowdefy._internal.auth.logout.mockReset();
});
afterAll(() => {
global.Date = RealDate;
});
test('Login', async () => {
test('Logout', async () => {
const rootBlock = {
blockId: 'root',
meta: {
@ -48,6 +55,7 @@ test('Login', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -71,8 +79,8 @@ test('Login', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(lowdefy.auth.logout.mock.calls).toEqual([[]]);
expect(lowdefy._internal.auth.logout.mock.calls).toEqual([[]]);
expect(res.success).toBe(true);
});

View File

@ -0,0 +1,23 @@
/*
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 createRequest({ actions, arrayIndices, context, event }) {
return async function request(params) {
return await context._internal.Requests.callRequests({ actions, arrayIndices, event, params });
};
}
export default createRequest;

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from '../../test/testContext.js';
// Mock apollo client
const mockReqResponses = {
@ -33,6 +33,7 @@ const mockReqResponses = {
const mockCallRequest = jest.fn();
const mockCallRequestImp = ({ requestId }) => {
if (requestId === 'req_error') throw mockReqResponses['req_error'];
return new Promise((resolve, reject) => {
if (requestId === 'req_error') {
reject(mockReqResponses[requestId]);
@ -41,10 +42,18 @@ const mockCallRequestImp = ({ requestId }) => {
});
};
const pageId = 'one';
const lowdefy = {
callRequest: mockCallRequest,
pageId,
_internal: {
actions: {
Request: ({ methods: { request }, params }) => {
return request(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
callRequest: mockCallRequest,
},
};
const RealDate = Date;
@ -83,6 +92,7 @@ test('Request call one request', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -101,7 +111,7 @@ test('Request call one request', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const promise = button.triggerEvent({ name: 'onClick' });
expect(context.requests.req_one).toEqual({
error: [],
@ -109,11 +119,6 @@ test('Request call one request', async () => {
response: null,
});
const res = await promise;
expect(context.requests.req_one).toEqual({
error: [],
loading: false,
response: 1,
});
expect(res).toEqual({
blockId: 'button',
bounced: false,
@ -150,6 +155,7 @@ test('Request call all requests', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -168,7 +174,7 @@ test('Request call all requests', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const promise = button.triggerEvent({ name: 'onClick' });
expect(context.requests).toEqual({
req_one: {
@ -231,6 +237,7 @@ test('Request call array of requests', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -249,7 +256,7 @@ test('Request call array of requests', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const promise = button.triggerEvent({ name: 'onClick' });
expect(context.requests).toEqual({
req_one: {
@ -312,6 +319,7 @@ test('Request pass if params are none', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -330,7 +338,7 @@ test('Request pass if params are none', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
await button.triggerEvent({ name: 'onClick' });
expect(context.requests).toEqual({});
});
@ -350,6 +358,7 @@ test('Request call request error', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -368,9 +377,8 @@ test('Request call request error', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(context.requests.req_error).toEqual({
error: [new Error('Request error')],
loading: false,

View File

@ -0,0 +1,29 @@
/*
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 { serializer } from '@lowdefy/helpers';
function createReset({ context }) {
return function reset() {
context._internal.State.resetState();
context._internal.RootBlocks.reset(
serializer.deserializeFromString(context._internal.State.frozenState)
);
context._internal.update();
};
}
export default createReset;

View File

@ -14,10 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from '../../test/testContext.js';
const pageId = 'one';
const lowdefy = { pageId };
const lowdefy = {
_internal: {
actions: {
Reset: ({ methods: { reset } }) => {
return reset();
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
List: { meta: { category: 'list', valueType: 'array' } },
TextInput: { meta: { category: 'input', valueType: 'string' } },
},
},
};
test('Reset one field', async () => {
const rootBlock = {
@ -29,6 +41,7 @@ test('Reset one field', async () => {
content: {
blocks: [
{
id: 'textInput',
blockId: 'textInput',
type: 'TextInput',
meta: {
@ -37,6 +50,7 @@ test('Reset one field', async () => {
},
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -57,8 +71,8 @@ test('Reset one field', async () => {
initState: { textInput: 'init' },
});
expect(context.state).toEqual({ textInput: 'init' });
const { button, textInput } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const textInput = context._internal.RootBlocks.map['textInput'];
textInput.setValue('1');
expect(context.state).toEqual({ textInput: '1' });
button.triggerEvent({ name: 'onClick' });
@ -75,6 +89,7 @@ test('Reset on primitive array after adding item', async () => {
content: {
blocks: [
{
id: 'list',
blockId: 'list',
type: 'List',
meta: {
@ -85,6 +100,7 @@ test('Reset on primitive array after adding item', async () => {
content: {
blocks: [
{
id: 'list.$',
blockId: 'list.$',
type: 'TextInput',
meta: {
@ -97,6 +113,7 @@ test('Reset on primitive array after adding item', async () => {
},
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -117,8 +134,8 @@ test('Reset on primitive array after adding item', async () => {
initState: { list: ['init'] },
});
expect(context.state).toEqual({ list: ['init'] });
const { button, list } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const list = context._internal.RootBlocks.map['list'];
list.pushItem();
expect(context.state).toEqual({ list: ['init', null] });
button.triggerEvent({ name: 'onClick' });
@ -135,6 +152,7 @@ test('Reset on object array after removing item', async () => {
content: {
blocks: [
{
id: 'list',
blockId: 'list',
type: 'List',
meta: {
@ -145,6 +163,7 @@ test('Reset on object array after removing item', async () => {
content: {
blocks: [
{
id: 'list.$.textInput',
blockId: 'list.$.textInput',
type: 'TextInput',
defaultValue: '123',
@ -158,6 +177,7 @@ test('Reset on object array after removing item', async () => {
},
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -178,7 +198,8 @@ test('Reset on object array after removing item', async () => {
initState: { list: [{ textInput: 'init' }] },
});
const { button, list } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const list = context._internal.RootBlocks.map['list'];
expect(context.state).toEqual({ list: [{ textInput: 'init' }] });
list.removeItem(0);

View File

@ -16,16 +16,10 @@
import getBlockMatcher from '../getBlockMatcher.js';
async function Validate({ context, params }) {
const validationErrors = context.RootBlocks.validate(getBlockMatcher(params));
if (validationErrors.length > 0) {
const error = new Error(
`Your input has ${validationErrors.length} validation error${
validationErrors.length !== 1 ? 's' : ''
}.`
);
throw error;
}
function createResetValidation({ context }) {
return function resetValidation(params) {
context._internal.RootBlocks.resetValidation(getBlockMatcher(params));
};
}
export default Validate;
export default createResetValidation;

View File

@ -14,15 +14,79 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import { get, type } from '@lowdefy/helpers';
const pageId = 'one';
// TODO: issue importing plugin packages with jest due to jest es module resolution #https://github.com/facebook/jest/issues/9771
// import { _not, _type } from '@lowdefy/operators-js/operators/client';
import testContext from '../../test/testContext.js';
const closeLoader = jest.fn();
const displayMessage = jest.fn();
const lowdefy = {
displayMessage,
pageId,
_internal: {
actions: {
ResetValidation: ({ methods: { resetValidation }, params }) => {
return resetValidation(params);
},
Validate: ({ methods: { validate }, params }) => {
return validate(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
TextInput: { meta: { category: 'input', valueType: 'string' } },
},
displayMessage,
operators: {
_not: ({ params }) => {
return !params;
},
_type: ({ location, params, state }) => {
const typeName = type.isObject(params) ? params.type : params;
if (!type.isString(typeName)) {
throw new Error(
`Operator Error: _type.type must be a string. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
const on = Object.prototype.hasOwnProperty.call(params, 'on')
? params.on
: get(state, get(params, 'key', { default: location }));
switch (typeName) {
case 'string':
return type.isString(on);
case 'array':
return type.isArray(on);
case 'date':
return type.isDate(on); // Testing for date is problematic due to stringify
case 'object':
return type.isObject(on);
case 'boolean':
return type.isBoolean(on);
case 'number':
return type.isNumber(on);
case 'integer':
return type.isInt(on);
case 'null':
return type.isNull(on);
case 'undefined':
return type.isUndefined(on);
case 'none':
return type.isNone(on);
case 'primitive':
return type.isPrimitive(on);
default:
throw new Error(
`Operator Error: "${typeName}" is not a valid _type test. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
},
},
},
};
const RealDate = Date;
@ -57,6 +121,7 @@ test('RestValidation after required field', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -66,6 +131,7 @@ test('RestValidation after required field', async () => {
required: true,
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -81,6 +147,7 @@ test('RestValidation after required field', async () => {
},
},
{
id: 'reset',
blockId: 'reset',
type: 'Button',
meta: {
@ -102,8 +169,11 @@ test('RestValidation after required field', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, reset, text1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const reset = context._internal.RootBlocks.map['reset'];
const text1 = context._internal.RootBlocks.map['text1'];
expect(text1.eval.validation).toEqual({
errors: ['This field is required'],
status: null,

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 { applyArrayIndices, set } from '@lowdefy/helpers';
const createSetGlobal = ({ arrayIndices, context }) => {
return function setGlobal(params) {
Object.keys(params).forEach((key) => {
set(
context._internal.lowdefy._internal.lowdefyGlobal,
applyArrayIndices(arrayIndices, key),
params[key]
);
});
context._internal.RootBlocks.reset();
context._internal.update();
};
};
export default createSetGlobal;

View File

@ -14,14 +14,21 @@
limitations under the License.
*/
import testContext from '../testContext.js';
const pageId = 'one';
import testContext from '../../test/testContext.js';
test('SetGlobal data to global', async () => {
const lowdefy = {
lowdefyGlobal: { x: 'old', init: 'init' },
pageId,
_internal: {
actions: {
SetGlobal: ({ methods: { setGlobal }, params }) => {
return setGlobal(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
lowdefyGlobal: { x: 'old', init: 'init' },
},
};
const rootBlock = {
id: 'block:root:root:0',
@ -58,12 +65,10 @@ test('SetGlobal data to global', async () => {
lowdefy,
rootBlock,
});
expect(context._internal.lowdefy.lowdefyGlobal).toEqual({ x: 'old', init: 'init' });
expect(context._internal.lowdefy._internal.lowdefyGlobal).toEqual({ x: 'old', init: 'init' });
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(context._internal.lowdefy.lowdefyGlobal).toEqual({
expect(context._internal.lowdefy._internal.lowdefyGlobal).toEqual({
init: 'init',
str: 'hello',
number: 13,

View File

@ -0,0 +1,29 @@
/*
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 { applyArrayIndices } from '@lowdefy/helpers';
function createSetState({ arrayIndices, context }) {
return function setState(params) {
Object.keys(params).forEach((key) => {
context._internal.State.set(applyArrayIndices(arrayIndices, key), params[key]);
});
context._internal.RootBlocks.reset();
context._internal.update();
};
}
export default createSetState;

View File

@ -14,11 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from '../../test/testContext.js';
const pageId = 'one';
const lowdefy = { pageId };
const lowdefy = {
_internal: {
actions: {
SetState: ({ methods: { setState }, params }) => {
return setState(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
List: { meta: { category: 'list', valueType: 'array' } },
TextInput: { meta: { category: 'input', valueType: 'string' } },
},
},
};
test('SetState data to state', async () => {
const rootBlock = {

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 getBlockMatcher from '../getBlockMatcher.js';
function createValidate({ context }) {
return function validate(params) {
const validationErrors = context._internal.RootBlocks.validate(getBlockMatcher(params));
if (validationErrors.length > 0) {
const error = new Error(
`Your input has ${validationErrors.length} validation error${
validationErrors.length !== 1 ? 's' : ''
}.`
);
throw error;
}
};
}
export default createValidate;

View File

@ -14,15 +14,115 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import { get, type } from '@lowdefy/helpers';
const pageId = 'one';
import testContext from '../../test/testContext.js';
const closeLoader = jest.fn();
const displayMessage = jest.fn();
const lowdefy = {
displayMessage,
pageId,
_internal: {
actions: {
Validate: ({ methods: { validate }, params }) => {
return validate(params);
},
},
blockComponents: {
Button: { meta: { category: 'display' } },
TextInput: { meta: { category: 'input', valueType: 'string' } },
},
displayMessage,
operators: {
_not: ({ params }) => {
return !params;
},
_type: ({ location, params, state }) => {
const typeName = type.isObject(params) ? params.type : params;
if (!type.isString(typeName)) {
throw new Error(
`Operator Error: _type.type must be a string. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
const on = Object.prototype.hasOwnProperty.call(params, 'on')
? params.on
: get(state, get(params, 'key', { default: location }));
switch (typeName) {
case 'string':
return type.isString(on);
case 'array':
return type.isArray(on);
case 'date':
return type.isDate(on); // Testing for date is problematic due to stringify
case 'object':
return type.isObject(on);
case 'boolean':
return type.isBoolean(on);
case 'number':
return type.isNumber(on);
case 'integer':
return type.isInt(on);
case 'null':
return type.isNull(on);
case 'undefined':
return type.isUndefined(on);
case 'none':
return type.isNone(on);
case 'primitive':
return type.isPrimitive(on);
default:
throw new Error(
`Operator Error: "${typeName}" is not a valid _type test. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
},
_regex: ({ location, params, state }) => {
const pattern = type.isObject(params) ? params.pattern : params;
if (!type.isString(pattern)) {
throw new Error(
`Operator Error: _regex.pattern must be a string. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
let on = !type.isUndefined(params.on) ? params.on : get(state, location);
if (!type.isUndefined(params.key)) {
if (!type.isString(params.key)) {
throw new Error(
`Operator Error: _regex.key must be a string. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
on = get(state, params.key);
}
if (type.isNone(on)) {
return false;
}
if (!type.isString(on)) {
throw new Error(
`Operator Error: _regex.on must be a string. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
try {
const re = new RegExp(pattern, params.flags || 'gm');
return re.test(on);
} catch (e) {
// log e to LowdefyError
throw new Error(
`Operator Error: _regex failed to execute RegExp.test. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
},
},
},
};
const RealDate = Date;
@ -57,6 +157,7 @@ test('Validate required field', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -66,6 +167,7 @@ test('Validate required field', async () => {
required: true,
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -87,14 +189,16 @@ test('Validate required field', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['text1'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: ['This field is required'],
status: null,
status: 'error',
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -178,6 +282,7 @@ test('Validate all fields', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -192,6 +297,7 @@ test('Validate all fields', async () => {
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -206,6 +312,7 @@ test('Validate all fields', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -227,8 +334,11 @@ test('Validate all fields', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text1, text2 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['text1'];
const text2 = context._internal.RootBlocks.map['text2'];
expect(text1.eval.validation).toEqual({
errors: ['text1 does not match pattern "text1"'],
status: null,
@ -378,6 +488,7 @@ test('Validate only one field', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -392,6 +503,7 @@ test('Validate only one field', async () => {
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -406,6 +518,7 @@ test('Validate only one field', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -428,14 +541,17 @@ test('Validate only one field', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text1, text2 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['text1'];
const text2 = context._internal.RootBlocks.map['text2'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: ['text1 does not match pattern "text1"'],
status: null,
status: 'error',
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -528,6 +644,7 @@ test('Validate list of fields', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -542,6 +659,7 @@ test('Validate list of fields', async () => {
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -556,6 +674,7 @@ test('Validate list of fields', async () => {
],
},
{
id: 'text3',
blockId: 'text3',
type: 'TextInput',
meta: {
@ -570,6 +689,7 @@ test('Validate list of fields', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -592,17 +712,22 @@ test('Validate list of fields', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text1, text2, text3 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['text1'];
const text2 = context._internal.RootBlocks.map['text2'];
const text3 = context._internal.RootBlocks.map['text3'];
text1.setValue('text1');
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: [],
status: null,
status: 'success',
warnings: [],
});
expect(text2.eval.validation).toEqual({
errors: ['text2 does not match pattern "text2"'],
status: null,
status: 'error',
warnings: [],
});
expect(text3.eval.validation).toEqual({
@ -610,7 +735,6 @@ test('Validate list of fields', async () => {
status: null,
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -703,6 +827,7 @@ test('Invalid Validate params', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -726,7 +851,7 @@ test('Invalid Validate params', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
@ -779,6 +904,7 @@ test('Validate does not fail on warnings', async () => {
content: {
blocks: [
{
id: 'text1',
blockId: 'text1',
type: 'TextInput',
meta: {
@ -794,6 +920,7 @@ test('Validate does not fail on warnings', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -815,14 +942,16 @@ test('Validate does not fail on warnings', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['text1'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: [],
status: null,
status: 'warning',
warnings: ['text1 does not match pattern "text1"'],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -855,6 +984,7 @@ test('Validate on nested objects using params.regex string', async () => {
content: {
blocks: [
{
id: 'obj.text1',
blockId: 'obj.text1',
type: 'TextInput',
meta: {
@ -869,6 +999,7 @@ test('Validate on nested objects using params.regex string', async () => {
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -883,6 +1014,7 @@ test('Validate on nested objects using params.regex string', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -907,14 +1039,16 @@ test('Validate on nested objects using params.regex string', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, 'obj.text1': text1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text1 = context._internal.RootBlocks.map['obj.text1'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: ['text1 does not match pattern "text1"'],
status: null,
status: 'error',
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -951,6 +1085,7 @@ test('Validate on nested objects using params.regex array', async () => {
content: {
blocks: [
{
id: 'obj.text1',
blockId: 'obj.text1',
type: 'TextInput',
meta: {
@ -965,6 +1100,7 @@ test('Validate on nested objects using params.regex array', async () => {
],
},
{
id: 'obj.abc1',
blockId: 'obj.abc1',
type: 'TextInput',
meta: {
@ -979,6 +1115,7 @@ test('Validate on nested objects using params.regex array', async () => {
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -993,6 +1130,7 @@ test('Validate on nested objects using params.regex array', async () => {
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -1017,14 +1155,18 @@ test('Validate on nested objects using params.regex array', async () => {
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text2, 'obj.text1': text1, 'obj.abc1': abc1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text2 = context._internal.RootBlocks.map['text2'];
const text1 = context._internal.RootBlocks.map['obj.text1'];
const abc1 = context._internal.RootBlocks.map['obj.abc1'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: ['text1 does not match pattern "text1"'],
status: null,
status: 'error',
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
@ -1075,6 +1217,7 @@ test('Validate on nested objects using params.regex array and blockIds', async (
content: {
blocks: [
{
id: 'obj.text1',
blockId: 'obj.text1',
type: 'TextInput',
meta: {
@ -1089,6 +1232,7 @@ test('Validate on nested objects using params.regex array and blockIds', async (
],
},
{
id: 'obj.abc1',
blockId: 'obj.abc1',
type: 'TextInput',
meta: {
@ -1103,6 +1247,7 @@ test('Validate on nested objects using params.regex array and blockIds', async (
],
},
{
id: 'text2',
blockId: 'text2',
type: 'TextInput',
meta: {
@ -1117,6 +1262,7 @@ test('Validate on nested objects using params.regex array and blockIds', async (
],
},
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -1142,14 +1288,18 @@ test('Validate on nested objects using params.regex array and blockIds', async (
const context = await testContext({
lowdefy,
rootBlock,
operators: lowdefy._internal.operators,
});
const { button, text2, 'obj.text1': text1, 'obj.abc1': abc1 } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const text2 = context._internal.RootBlocks.map['text2'];
const text1 = context._internal.RootBlocks.map['obj.text1'];
const abc1 = context._internal.RootBlocks.map['obj.abc1'];
await button.triggerEvent({ name: 'onClick' });
expect(text1.eval.validation).toEqual({
errors: ['text1 does not match pattern "text1"'],
status: null,
status: 'error',
warnings: [],
});
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,

View File

@ -0,0 +1,65 @@
/*
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 createCallMethod from './createCallMethod.js';
import createGetActions from './createGetActions.js';
import createGetBlockId from './createGetBlockId.js';
import createGetEvent from './createGetEvent.js';
import createGetGlobal from './createGetGlobal.js';
import createGetInput from './createGetInput.js';
import createGetPageId from './createGetPageId.js';
import createGetRequestDetails from './createGetRequestDetails.js';
import createGetState from './createGetState.js';
import createGetUrlQuery from './createGetUrlQuery.js';
import createGetUser from './createGetUser.js';
import createLink from './createLink.js';
import createLogin from './createLogin.js';
import createLogout from './createLogout.js';
import createDisplayMessage from './createDisplayMessage.js';
import createRequest from './createRequest.js';
import createReset from './createReset.js';
import createResetValidation from './createResetValidation.js';
import createSetGlobal from './createSetGlobal.js';
import createSetState from './createSetState.js';
import createValidate from './createValidate.js';
function getActionMethods(props) {
return {
callMethod: createCallMethod(props),
displayMessage: createDisplayMessage(props),
getActions: createGetActions(props),
getBlockId: createGetBlockId(props),
getEvent: createGetEvent(props),
getGlobal: createGetGlobal(props),
getInput: createGetInput(props),
getPageId: createGetPageId(props),
getRequestDetails: createGetRequestDetails(props),
getState: createGetState(props),
getUrlQuery: createGetUrlQuery(props),
getUser: createGetUser(props),
link: createLink(props),
login: createLogin(props),
logout: createLogout(props),
request: createRequest(props),
reset: createReset(props),
resetValidation: createResetValidation(props),
setGlobal: createSetGlobal(props),
setState: createSetState(props),
validate: createValidate(props),
};
}
export default getActionMethods;

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 { applyArrayIndices, get, serializer, type } from '@lowdefy/helpers';
const getFromObject = ({ arrayIndices, location, method, object, params }) => {
if (params === true) params = { all: true };
if (type.isString(params) || type.isInt(params)) params = { key: params };
if (!type.isObject(params)) {
throw new Error(
`Method Error: ${method} params must be of type string, integer, boolean or object. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
if (params.key === null) return get(params, 'default', { default: null, copy: true });
if (params.all === true) return serializer.copy(object);
if (!type.isString(params.key) && !type.isInt(params.key)) {
throw new Error(
`Method Error: ${method} params.key must be of type string or integer. Received: ${JSON.stringify(
params
)} at ${location}.`
);
}
return get(object, applyArrayIndices(arrayIndices, params.key), {
default: get(params, 'default', { default: null, copy: true }),
copy: true,
});
};
export default getFromObject;

View File

@ -1,49 +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 CallMethod from './CallMethod.js';
import JsAction from './JsAction.js';
import Link from './Link.js';
import Login from './Login.js';
import Logout from './Logout.js';
import Message from './Message.js';
import Request from './Request.js';
import Reset from './Reset.js';
import ResetValidation from './ResetValidation.js';
import ScrollTo from './ScrollTo.js';
import SetGlobal from './SetGlobal.js';
import SetState from './SetState.js';
import Throw from './Throw.js';
import Validate from './Validate.js';
import Wait from './Wait.js';
export default {
CallMethod,
JsAction,
Link,
Login,
Logout,
Message,
Request,
Reset,
ResetValidation,
ScrollTo,
SetGlobal,
SetState,
Throw,
Validate,
Wait,
};

View File

@ -50,7 +50,7 @@ const testContext = async ({ lowdefy, operators, rootBlock, initState = {} }) =>
_internal.Actions = new Actions(ctx);
_internal.Requests = new Requests(ctx);
_internal.RootBlocks = new Blocks({
areas: { root: { blocks: [_internal.rootBlock] } },
areas: _internal.rootBlock.areas,
context: ctx,
});
_internal.RootBlocks.init();

View File

@ -0,0 +1,3 @@
# Lowdefy Actions Core
Core Lowdefy actions

View File

@ -0,0 +1,14 @@
module.exports = {
clearMocks: true,
collectCoverage: true,
collectCoverageFrom: ['src/**/*.js'],
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/src/test', '<rootDir>/src/index.js'],
coverageReporters: [['lcov', { projectRoot: '../../../..' }], 'text', 'clover'],
errorOnDeprecated: true,
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/src/test'],
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest', { configFile: '../../../../.swcrc.test' }],
},
};

View File

@ -0,0 +1,62 @@
{
"name": "@lowdefy/actions-core",
"version": "4.0.0-alpha.6",
"licence": "Apache-2.0",
"description": "",
"homepage": "https://lowdefy.com",
"keywords": [
"lowdefy",
"lowdefy actions",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"
},
"contributors": [
{
"name": "Sam Tolmay",
"url": "https://github.com/SamTolmay"
},
{
"name": "Gerrie van Wyk",
"url": "https://github.com/Gervwyk"
},
{
"name": "Sandile Memela",
"url": "https://github.com/sah-memela"
}
],
"repository": {
"type": "git",
"url": "https://github.com/lowdefy/lowdefy.git"
},
"type": "module",
"exports": {
"./actions": "./dist/actions.js",
"./types": "./dist/types.js"
},
"files": [
"dist/*"
],
"scripts": {
"build": "yarn swc",
"clean": "rm -rf dist",
"prepare": "yarn build",
"swc": "swc src --out-dir dist --config-file ../../../../.swcrc --delete-dir-on-start --copy-files",
"test": "jest --coverage"
},
"dependencies": {
"@lowdefy/helpers": "4.0.0-alpha.6"
},
"devDependencies": {
"@lowdefy/engine": "4.0.0-alpha.6",
"@lowdefy/operators": "4.0.0-alpha.6",
"@swc/cli": "0.1.55",
"@swc/core": "1.2.135",
"@swc/jest": "0.2.17",
"jest": "27.5.1"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,30 @@
/*
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.
*/
export { default as CallMethod } from './actions/CallMethod.js';
export { default as Link } from './actions/Link.js';
export { default as Login } from './actions/Login.js';
export { default as Logout } from './actions/Logout.js';
export { default as DisplayMessage } from './actions/DisplayMessage.js';
export { default as Request } from './actions/Request.js';
export { default as Reset } from './actions/Reset.js';
export { default as ResetValidation } from './actions/ResetValidation.js';
export { default as ScrollTo } from './actions/ScrollTo.js';
export { default as SetGlobal } from './actions/SetGlobal.js';
export { default as SetState } from './actions/SetState.js';
export { default as Throw } from './actions/Throw.js';
export { default as Validate } from './actions/Validate.js';
export { default as Wait } from './actions/Wait.js';

View File

@ -0,0 +1,21 @@
/*
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 CallMethod({ methods: { callMethod }, params }) {
return callMethod(params);
}
export default CallMethod;

View File

@ -0,0 +1,28 @@
/*
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 CallMethod from './CallMethod.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('CallMethod action invocation', async () => {
CallMethod({ methods: { callMethod: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -0,0 +1,38 @@
/*
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 DisplayMessage({ methods: { displayMessage }, params }) {
if (!type.isObject(params) && !type.isNone(params)) {
throw new Error(
`Invalid DisplayMessage, check action params. Params must be an object, received "${JSON.stringify(
params
)}".`
);
}
if (type.isNone(params)) {
displayMessage({ content: 'Success' });
}
if (type.isObject(params)) {
displayMessage({
...params,
content: type.isNone(params.content) ? 'Success' : params.content,
});
}
}
export default DisplayMessage;

View File

@ -0,0 +1,351 @@
/*
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.js';
import DisplayMessage from './DisplayMessage.js';
const displayMessage = jest.fn();
const lowdefy = {
_internal: {
actions: {
DisplayMessage,
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
displayMessage,
},
};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
displayMessage.mockReset();
});
beforeAll(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('DisplayMessage params is not object', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'a',
type: 'DisplayMessage',
params: 1,
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'a',
type: 'DisplayMessage',
params: 1,
},
error: {
error: new Error(
'Invalid DisplayMessage, check action params. Params must be an object, received "1".'
),
index: 0,
type: 'DisplayMessage',
},
},
responses: {
a: {
error: new Error(
'Invalid DisplayMessage, check action params. Params must be an object, received "1".'
),
type: 'DisplayMessage',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: false,
});
});
test('DisplayMessage params is null or undefined', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'a',
type: 'DisplayMessage',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: undefined,
type: 'DisplayMessage',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: true,
});
expect(displayMessage.mock.calls).toEqual([[{ content: 'Success' }]]);
});
test('DisplayMessage params.content is none', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'a',
type: 'DisplayMessage',
params: {
status: 'info',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: undefined,
type: 'DisplayMessage',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: true,
});
expect(displayMessage.mock.calls).toEqual([[{ content: 'Success', status: 'info' }]]);
});
test('DisplayMessage params.content is ""', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'a',
type: 'DisplayMessage',
params: {
content: '',
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: undefined,
type: 'DisplayMessage',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: true,
});
expect(displayMessage.mock.calls).toEqual([[{ content: '' }]]);
});
test('DisplayMessage params.content is falsy', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'block:root:button:0',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'a',
type: 'DisplayMessage',
params: {
content: 0,
},
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
responses: {
a: {
response: undefined,
type: 'DisplayMessage',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: true,
});
expect(displayMessage.mock.calls).toEqual([[{ content: 0 }]]);
});

View File

@ -16,10 +16,10 @@
import { type } from '@lowdefy/helpers';
async function Link({ context, params }) {
function Link({ methods: { link }, params }) {
const linkParams = type.isString(params) ? { pageId: params } : params;
try {
context._internal.lowdefy._internal.link(linkParams);
link(linkParams);
} catch (error) {
console.log(error);
throw new Error(`Invalid Link, check action params. Received "${JSON.stringify(params)}".`);

View File

@ -0,0 +1,163 @@
/*
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.js';
import Link from './Link.js';
const mockActionMethod = jest.fn();
const lowdefy = {
_internal: {
actions: {
Link,
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
link: (params) => {
if (params.pageId === 'error') {
throw new Error('Param error');
} else mockActionMethod(params);
},
},
};
// Comment out to use console
console.log = () => {};
console.error = () => {};
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
beforeEach(() => {
mockActionMethod.mockReset();
});
beforeAll(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('action invocation', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'action',
type: 'Link',
params: 'call',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
await button.triggerEvent({ name: 'onClick' });
expect(mockActionMethod.mock.calls).toEqual([[{ pageId: 'call' }]]);
});
test('error params action invocation', async () => {
const rootBlock = {
id: 'block:root:root:0',
blockId: 'root',
meta: {
category: 'container',
},
areas: {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
category: 'display',
},
events: {
onClick: [
{
id: 'action',
type: 'Link',
params: 'error',
},
],
},
},
],
},
},
};
const context = await testContext({
lowdefy,
rootBlock,
});
const button = context._internal.RootBlocks.map['button'];
await button.triggerEvent({ name: 'onClick' });
expect(button.Events.events.onClick.history[0]).toEqual({
blockId: 'button',
bounced: false,
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'action',
type: 'Link',
params: 'error',
},
error: {
error: new Error('Invalid Link, check action params. Received ""error"".'),
index: 0,
type: 'Link',
},
},
responses: {
action: {
error: new Error('Invalid Link, check action params. Received ""error"".'),
type: 'Link',
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: false,
});
});

View File

@ -14,8 +14,8 @@
limitations under the License.
*/
async function Login({ context, params }) {
return context._internal.lowdefy._internal.auth.login(params);
function Login({ methods: { login }, params }) {
return login(params);
}
export default Login;

View File

@ -0,0 +1,28 @@
/*
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 Login from './Login.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('Login action invocation', async () => {
Login({ methods: { login: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -14,8 +14,8 @@
limitations under the License.
*/
async function Logout({ context }) {
return context._internal.lowdefy._internal.auth.logout();
function Logout({ methods: { logout } }) {
return logout();
}
export default Logout;

View File

@ -0,0 +1,28 @@
/*
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 Logout from './Logout.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('Logout action invocation', async () => {
Logout({ methods: { logout: mockActionMethod } });
expect(mockActionMethod.mock.calls).toEqual([[]]);
});

View File

@ -14,8 +14,8 @@
limitations under the License.
*/
async function Request({ actions, arrayIndices, context, event, params }) {
return context._internal.Requests.callRequests({ actions, arrayIndices, event, params });
async function Request({ methods: { request }, params }) {
return await request(params);
}
export default Request;

View File

@ -0,0 +1,28 @@
/*
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 Request from './Request.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('Request action invocation', async () => {
Request({ methods: { request: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -14,14 +14,8 @@
limitations under the License.
*/
import { serializer } from '@lowdefy/helpers';
function Reset({ context }) {
context._internal.State.resetState();
context._internal.RootBlocks.reset(
serializer.deserializeFromString(context._internal.State.frozenState)
);
context._internal.update();
function Reset({ methods: { reset } }) {
reset();
}
export default Reset;

View File

@ -0,0 +1,28 @@
/*
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 Reset from './Reset.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('Reset action invocation', async () => {
Reset({ methods: { reset: mockActionMethod } });
expect(mockActionMethod.mock.calls).toEqual([[]]);
});

View File

@ -14,10 +14,8 @@
limitations under the License.
*/
import getBlockMatcher from '../getBlockMatcher.js';
async function ResetValidation({ context, params }) {
context._internal.RootBlocks.resetValidation(getBlockMatcher(params));
function ResetValidation({ methods: { resetValidation }, params }) {
resetValidation(params);
}
export default ResetValidation;

View File

@ -0,0 +1,28 @@
/*
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 ResetValidation from './ResetValidation.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('ResetValidation action invocation', async () => {
ResetValidation({ methods: { resetValidation: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -14,14 +14,19 @@
limitations under the License.
*/
async function ScrollTo({ context, params = {} }) {
import { type } from '@lowdefy/helpers';
function ScrollTo({ document, params, window }) {
if (!type.isObject(params)) {
throw new Error(`Invalid ScrollTo, check action params. Received "${JSON.stringify(params)}".`);
}
if (params.blockId) {
const element = context._internal.lowdefy._internal.document.getElementById(params.blockId);
const element = document.getElementById(params.blockId);
if (element) {
element.scrollIntoView(params.options);
}
} else {
context._internal.lowdefy._internal.window.scrollTo(params);
window.scrollTo(params);
}
}

View File

@ -14,7 +14,9 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from './testContext.js';
import ScrollTo from './ScrollTo.js';
// Mock document
const mockDocGetElementById = jest.fn();
@ -38,11 +40,21 @@ const window = {
const lowdefy = {
_internal: {
actions: {
ScrollTo,
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
document,
window,
},
};
// Comment out to use console
console.log = () => {};
console.error = () => {};
beforeEach(() => {
mockWindowOpen.mockReset();
mockWindowFocus.mockReset();
@ -52,6 +64,18 @@ beforeEach(() => {
mockElemScrollIntoView.mockReset();
});
const RealDate = Date;
const mockDate = jest.fn(() => ({ date: 0 }));
mockDate.now = jest.fn(() => 0);
beforeAll(() => {
global.Date = mockDate;
});
afterAll(() => {
global.Date = RealDate;
});
test('ScrollTo with no params', async () => {
const rootBlock = {
id: 'block:root:root:0',
@ -83,8 +107,34 @@ test('ScrollTo with no params', async () => {
rootBlock,
});
const button = context._internal.RootBlocks.map['block:root:button:0'];
button.triggerEvent({ name: 'onClick' });
expect(mockWindowScrollTo.mock.calls).toEqual([[{}]]);
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',
bounced: false,
endTimestamp: { date: 0 },
error: {
action: {
id: 'a',
type: 'ScrollTo',
},
error: {
error: new Error('Invalid ScrollTo, check action params. Received "undefined".'),
index: 0,
type: 'ScrollTo',
},
},
event: undefined,
eventName: 'onClick',
responses: {
a: {
error: new Error('Invalid ScrollTo, check action params. Received "undefined".'),
index: 0,
type: 'ScrollTo',
},
},
startTimestamp: { date: 0 },
success: false,
});
});
test('ScrollTo with no blockId', async () => {

View File

@ -14,14 +14,8 @@
limitations under the License.
*/
import { applyArrayIndices, set } from '@lowdefy/helpers';
async function SetGlobal({ arrayIndices, context, params }) {
Object.keys(params).forEach((key) => {
set(context._internal.lowdefy.lowdefyGlobal, applyArrayIndices(arrayIndices, key), params[key]);
});
context._internal.RootBlocks.reset();
context._internal.update();
function SetGlobal({ methods: { setGlobal }, params }) {
setGlobal(params);
}
export default SetGlobal;

View File

@ -0,0 +1,28 @@
/*
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 SetGlobal from './SetGlobal.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('SetGlobal action invocation', async () => {
SetGlobal({ methods: { setGlobal: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -14,14 +14,8 @@
limitations under the License.
*/
import { applyArrayIndices } from '@lowdefy/helpers';
async function SetState({ arrayIndices, context, params }) {
Object.keys(params).forEach((key) => {
context._internal.State.set(applyArrayIndices(arrayIndices, key), params[key]);
});
context._internal.RootBlocks.reset();
context._internal.update();
function SetState({ methods: { setState }, params }) {
setState(params);
}
export default SetState;

View File

@ -0,0 +1,28 @@
/*
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 SetState from './SetState.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('SetState action invocation', async () => {
SetState({ methods: { setState: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -13,24 +13,29 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
class ThrowActionError extends Error {
constructor(message, { blockId, context, metaData }) {
constructor(message, { blockId, metaData, pageId }) {
super(message);
this.blockId = blockId;
this.metaData = metaData;
this.name = 'ThrowError';
this.pageId = context.pageId;
this.pageId = pageId;
}
}
function Throw({ blockId, context, params = {} }) {
function Throw({ methods: { getBlockId, getPageId }, params }) {
if (params.throw === false || type.isNone(params.throw)) {
return;
}
if (params.throw === true) {
throw new ThrowActionError(params.message, { blockId, context, metaData: params.metaData });
throw new ThrowActionError(params.message, {
blockId: getBlockId(),
metaData: params.metaData,
pageId: getPageId(),
});
}
throw new Error(`Invalid Throw, check action params. Received "${JSON.stringify(params)}".`);
}

View File

@ -14,13 +14,19 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import { ThrowActionError } from '../../src/actions/Throw.js';
import testContext from './testContext.js';
import { Throw, ThrowActionError } from './Throw.js';
const closeLoader = jest.fn();
const displayMessage = jest.fn();
const lowdefy = {
_internal: {
actions: {
Throw,
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
displayMessage,
},
};
@ -88,18 +94,39 @@ test('Throw no params', async () => {
bounced: false,
event: undefined,
eventName: 'onClick',
error: {
action: {
id: 'throw',
type: 'Throw',
},
error: {
error: new TypeError("Cannot read properties of undefined (reading 'throw')"),
index: 0,
type: 'Throw',
},
},
responses: {
throw: {
error: new TypeError("Cannot read properties of undefined (reading 'throw')"),
type: 'Throw',
response: undefined,
index: 0,
},
},
endTimestamp: { date: 0 },
startTimestamp: { date: 0 },
success: true,
success: false,
});
expect(displayMessage.mock.calls).toMatchInlineSnapshot(`Array []`);
expect(displayMessage.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Object {
"content": "Cannot read properties of undefined (reading 'throw')",
"duration": 6,
"status": "error",
},
],
]
`);
});
test('Throw throw true no message or metaData', async () => {

View File

@ -0,0 +1,21 @@
/*
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 Validate({ methods: { validate }, params }) {
return validate(params);
}
export default Validate;

View File

@ -0,0 +1,28 @@
/*
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 Validate from './Validate.js';
const mockActionMethod = jest.fn();
beforeEach(() => {
mockActionMethod.mockReset();
});
test('Validate action invocation', async () => {
Validate({ methods: { validate: mockActionMethod }, params: 'call' });
expect(mockActionMethod.mock.calls).toEqual([['call']]);
});

View File

@ -16,7 +16,7 @@
import { type, wait } from '@lowdefy/helpers';
async function Wait({ params }) {
function Wait({ params }) {
if (!type.isInt(params.ms)) {
throw new Error(`Wait action "ms" param should be an integer.`);
}

View File

@ -14,15 +14,22 @@
limitations under the License.
*/
import testContext from '../testContext.js';
import testContext from './testContext.js';
const pageId = 'one';
import Wait from './Wait.js';
const lowdefy = {
auth: {
login: jest.fn(),
_internal: {
actions: {
Wait,
},
blockComponents: {
Button: { meta: { category: 'display' } },
},
auth: {
login: jest.fn(),
},
},
pageId,
};
const RealDate = Date;
@ -35,7 +42,7 @@ console.error = () => {};
beforeEach(() => {
global.Date = mockDate;
lowdefy.auth.login.mockReset();
lowdefy._internal.auth.login.mockReset();
});
afterAll(() => {
@ -56,6 +63,7 @@ test('Wait', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -80,7 +88,7 @@ test('Wait', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
let resolved = false;
button.triggerEvent({ name: 'onClick' }).then(() => {
resolved = true;
@ -104,6 +112,7 @@ test('Wait ms not a integer', async () => {
content: {
blocks: [
{
id: 'button',
blockId: 'button',
type: 'Button',
meta: {
@ -128,7 +137,7 @@ test('Wait ms not a integer', async () => {
lowdefy,
rootBlock,
});
const { button } = context.RootBlocks.map;
const button = context._internal.RootBlocks.map['button'];
const res = await button.triggerEvent({ name: 'onClick' });
expect(res).toEqual({
blockId: 'button',

View File

@ -0,0 +1,67 @@
/*
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 { Actions, Blocks, Requests, State } from '@lowdefy/engine';
import { WebParser } from '@lowdefy/operators';
const testContext = async ({ lowdefy, operators, rootBlock, initState = {} }) => {
const testLowdefy = {
inputs: { test: {} },
urlQuery: {},
...lowdefy,
_internal: {
displayMessage: () => () => undefined,
updateBlock: () => {},
...lowdefy._internal,
},
};
const ctx = {
id: 'test',
pageId: rootBlock.blockId,
eventLog: [],
requests: {},
state: {},
_internal: {
lowdefy: testLowdefy,
rootBlock,
},
};
const _internal = ctx._internal;
_internal.parser = new WebParser({ context: ctx, contexts: {}, operators: operators || {} });
await _internal.parser.init();
_internal.State = new State(ctx);
_internal.Actions = new Actions(ctx);
_internal.Requests = new Requests(ctx);
_internal.RootBlocks = new Blocks({
areas: _internal.rootBlock.areas,
context: ctx,
});
_internal.RootBlocks.init();
_internal.update = () => {
_internal.RootBlocks.update();
};
if (initState) {
Object.keys(initState).forEach((key) => {
_internal.State.set(key, initState[key]);
});
_internal.RootBlocks.reset();
}
_internal.update();
_internal.State.freezeState();
return ctx;
};
export default testContext;

View File

@ -0,0 +1,20 @@
/*
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 * as actions from './actions.js';
export default {
actions: Object.keys(actions),
};

View File

@ -8,7 +8,8 @@
"lowdefy",
"lowdefy blocks",
"antd",
"ant design"
"ant design",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -6,7 +6,8 @@
"homepage": "https://lowdefy.com",
"keywords": [
"lowdefy",
"lowdefy blocks"
"lowdefy blocks",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -8,7 +8,8 @@
"lowdefy",
"lowdefy blocks",
"color picker",
"react-color"
"react-color",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -8,7 +8,8 @@
"lowdefy",
"lowdefy blocks",
"echarts",
"charts"
"charts",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -6,7 +6,8 @@
"homepage": "https://lowdefy.com",
"keywords": [
"lowdefy",
"lowdefy blocks"
"lowdefy blocks",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -8,7 +8,8 @@
"lowdefy",
"lowdefy blocks",
"markdown",
"react-markdown"
"react-markdown",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -6,7 +6,8 @@
"homepage": "https://lowdefy.com",
"keywords": [
"lowdefy",
"lowdefy connection"
"lowdefy connection",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

View File

@ -6,7 +6,8 @@
"homepage": "https://lowdefy.com",
"keywords": [
"lowdefy",
"lowdefy connection"
"lowdefy connection",
"lowdefy plugin"
],
"bugs": {
"url": "https://github.com/lowdefy/lowdefy/issues"

Some files were not shown because too many files have changed in this diff Show More