diff --git a/packages/engine/src/getContext.js b/packages/engine/src/getContext.js index 414d78624..7774b55af 100644 --- a/packages/engine/src/getContext.js +++ b/packages/engine/src/getContext.js @@ -30,6 +30,7 @@ const blockData = ({ id, layout, meta, + operators, pageId, properties, requests, @@ -47,6 +48,7 @@ const blockData = ({ id, layout, meta, + operators, pageId, properties, requests, @@ -69,12 +71,13 @@ const getContext = async ({ block, contextId, lowdefy }) => { if (!lowdefy.inputs[contextId]) { lowdefy.inputs[contextId] = {}; } + const operatorsSet = new Set([...block.operators, '_not', '_type']); lowdefy.contexts[contextId] = { id: contextId, blockId: block.blockId, eventLog: [], requests: {}, - operators: block.operators || [], + operators: [...operatorsSet], lowdefy, pageId: lowdefy.pageId, rootBlock: blockData(block), // filter block to prevent circular structure diff --git a/packages/engine/test/Actions/Validate.test.js b/packages/engine/test/Actions/Validate.test.js index 724f72a97..7a812281b 100644 --- a/packages/engine/test/Actions/Validate.test.js +++ b/packages/engine/test/Actions/Validate.test.js @@ -46,6 +46,116 @@ afterAll(() => { global.Date = RealDate; }); +test('Validate required field', async () => { + const rootBlock = { + blockId: 'root', + meta: { + category: 'context', + }, + areas: { + content: { + blocks: [ + { + blockId: 'text1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + required: true, + }, + { + blockId: 'button', + type: 'Button', + meta: { + category: 'display', + }, + events: { + onClick: [ + { + id: 'validate', + type: 'Validate', + }, + ], + }, + }, + ], + }, + }, + }; + const context = await testContext({ + lowdefy, + rootBlock, + }); + const { button, text1 } = context.RootBlocks.map; + expect(text1.validationEval.output).toEqual({ + errors: ['This field is required'], + status: null, + warnings: [], + }); + await button.triggerEvent({ name: 'onClick' }); + expect(button.Events.events.onClick.history[0]).toEqual({ + blockId: 'button', + event: undefined, + eventName: 'onClick', + responses: [ + { + actionId: 'validate', + actionType: 'Validate', + error: new Error('Your input has 1 validation error.'), + }, + ], + success: false, + timestamp: { + date: 0, + }, + }); + expect(text1.validationEval.output).toEqual({ + errors: ['This field is required'], + status: 'error', + warnings: [], + }); + expect(displayMessage.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "content": "Your input has 1 validation error.", + "duration": 6, + "status": "error", + }, + ], + ] + `); + displayMessage.mockReset(); + displayMessage.mockImplementation(() => closeLoader); + text1.setValue('text1'); + await button.triggerEvent({ name: 'onClick' }); + expect(button.Events.events.onClick.history[0]).toEqual({ + blockId: 'button', + event: undefined, + eventName: 'onClick', + responses: [ + { + actionId: 'validate', + actionType: 'Validate', + response: undefined, + }, + ], + success: true, + timestamp: { + date: 0, + }, + }); + expect(text1.validationEval.output).toEqual({ + errors: [], + status: 'success', + warnings: [], + }); + expect(displayMessage.mock.calls).toEqual([]); + displayMessage.mockReset(); + displayMessage.mockImplementation(() => closeLoader); +}); + test('Validate all fields', async () => { const rootBlock = { blockId: 'root', diff --git a/packages/engine/test/getContext.test.js b/packages/engine/test/getContext.test.js index 30dc1cb5d..5ca401964 100644 --- a/packages/engine/test/getContext.test.js +++ b/packages/engine/test/getContext.test.js @@ -46,6 +46,7 @@ test('memoize context', async () => { meta: { type: 'context', }, + operators: [], }; const c1 = await getContext({ block, contextId: 'c1', lowdefy }); const c2 = await getContext({ block, contextId: 'c1', lowdefy }); @@ -71,6 +72,7 @@ test('create context', async () => { meta: { type: 'context', }, + operators: [], }; const context = await getContext({ block, contextId: 'contextId', lowdefy }); expect(context.Actions).toBeDefined(); @@ -81,6 +83,7 @@ test('create context', async () => { expect(context.lowdefy).toEqual(lowdefy); expect(context.eventLog).toEqual([]); expect(context.id).toEqual('contextId'); + expect(context.operators).toBeInstanceOf(Array); expect(context.lowdefy.pageId).toEqual('pageId'); expect(context.parser).toBeDefined(); expect(context.requests).toEqual({}); @@ -111,6 +114,7 @@ test('create context, initialize input', async () => { meta: { type: 'context', }, + operators: [], }; const context = await getContext({ block, contextId: 'contextId', lowdefy }); expect(context.lowdefy.inputs.contextId).toEqual({}); @@ -129,12 +133,14 @@ test('call update for listening contexts', async () => { meta: { type: 'context', }, + operators: [], }; const block2 = { blockId: 'block2', meta: { type: 'context', }, + operators: [], }; const mockUpdate = jest.fn(); const c1 = await getContext({ block: block1, contextId: 'c1', lowdefy }); @@ -158,6 +164,7 @@ test('remove contextId from updateListeners if not found', async () => { meta: { type: 'context', }, + operators: [], }; const c1 = await getContext({ block, contextId: 'c1', lowdefy }); @@ -180,6 +187,7 @@ test('remove contextId from updateListeners if equal to own contextId', async () meta: { type: 'context', }, + operators: [], }; const c1 = await getContext({ block, contextId: 'c1', lowdefy }); @@ -202,6 +210,7 @@ test('update memoized context', async () => { meta: { type: 'context', }, + operators: [], }; const mockUpdate = jest.fn(); const c1 = await getContext({ block, contextId: 'c1', lowdefy }); @@ -223,6 +232,7 @@ test('call update for nested contexts and prevent circular loop structure', asyn meta: { type: 'context', }, + operators: [], }; const block1 = { blockId: 'block1', @@ -234,6 +244,7 @@ test('call update for nested contexts and prevent circular loop structure', asyn blocks: block2, }, }, + operators: [], }; const c1 = await getContext({ block: block1, contextId: 'c1', lowdefy }); const getC2 = () => @@ -244,3 +255,22 @@ test('call update for nested contexts and prevent circular loop structure', asyn }); await expect(getC2()).resolves.not.toThrow(); }); + +test('Add operators for required validation', async () => { + const lowdefy = { + client, + contexts: {}, + inputs: {}, + pageId, + updateBlock, + }; + const block = { + blockId: 'blockId', + meta: { + type: 'context', + }, + operators: [], + }; + const context = await getContext({ block, contextId: 'contextId', lowdefy }); + expect(context.operators).toEqual(expect.arrayContaining(['_not', '_type'])); +});