From 6824b07127f86ed19d0239ba903f88ddb4287932 Mon Sep 17 00:00:00 2001 From: Gervwyk Date: Mon, 16 Aug 2021 22:10:35 +0200 Subject: [PATCH] feat(engine): showValidation on block level and params.regex for Validate. --- packages/engine/src/Blocks.js | 40 +- packages/engine/src/actions/Validate.js | 48 +- packages/engine/test/Actions/Validate.test.js | 417 ++++++++++++++++-- packages/engine/test/Block/validate.test.js | 127 +++--- 4 files changed, 502 insertions(+), 130 deletions(-) diff --git a/packages/engine/src/Blocks.js b/packages/engine/src/Blocks.js index 12eaee89b..744bbe76e 100644 --- a/packages/engine/src/Blocks.js +++ b/packages/engine/src/Blocks.js @@ -217,6 +217,7 @@ class Blocks { const initState = serializer.copy(initWithState || this.context.state); this.loopBlocks((block) => { block.update = true; + block.showValidation = false; if (get(block, 'meta.category') === 'input' || get(block, 'meta.category') === 'list') { let blockValue = get(initState, block.field); if (type.isUndefined(blockValue)) { @@ -368,13 +369,13 @@ class Blocks { } } }); - if (this.context.showValidationErrors && validation.length > 0) { + if (validation.length > 0) { block.validationEval.output.status = 'success'; } if (validationWarning) { block.validationEval.output.status = 'warning'; } - if (this.context.showValidationErrors && validationError) { + if (validationError) { block.validationEval.output.status = 'error'; } @@ -497,18 +498,26 @@ class Blocks { }); } - getValidateRec(result) { + getValidateRec(params, match, result) { this.loopBlocks((block) => { - if (block.visibleEval.output && block.validationEval.output.status === 'error') { - result.push({ - blockId: block.blockId, - validation: block.validationEval.output, - }); + if (match(block.blockId, params)) { + block.showValidation = true; + block.update = true; + if ( + block.visibleEval.output !== false && + block.validationEval.output && + block.validationEval.output.status === 'error' + ) { + result.push({ + blockId: block.blockId, + validation: block.validationEval.output, + }); + } } }); Object.keys(this.subBlocks).forEach((subKey) => { this.subBlocks[subKey].forEach((subBlock) => { - subBlock.getValidateRec(result); + subBlock.getValidateRec(params, match, result); }); }); return result; @@ -536,9 +545,11 @@ class Blocks { }); } - validate() { - this.update(); // update to recalculate validationEval with showValidationErrors set to raise block errors - return this.getValidateRec([]); + validate(params, match) { + this.updateStateFromRoot(); // update to recalculate validationEval to raise block errors + const validationErrors = this.getValidateRec(params, match, []); // get all relevant raised block errors and set showValidation + this.setBlocksCache(); // update cache to render + return validationErrors; } update() { @@ -562,7 +573,10 @@ class Blocks { required: block.requiredEval.output, layout: block.layoutEval.output, style: block.styleEval.output, - validation: block.validationEval.output, + validation: { + ...(block.validationEval.output || {}), + status: block.showValidation ? (block.validationEval.output || {}).status : null, + }, value: type.isNone(block.value) ? null : block.value, visible: block.visibleEval.output, }; diff --git a/packages/engine/src/actions/Validate.js b/packages/engine/src/actions/Validate.js index e675cd994..219604501 100644 --- a/packages/engine/src/actions/Validate.js +++ b/packages/engine/src/actions/Validate.js @@ -16,23 +16,47 @@ import { type } from '@lowdefy/helpers'; +const getMatch = (params) => (id) => { + if (params.blockIds === true || (type.isArray(params.blockIds) && params.blockIds.includes(id))) { + return true; + } + if (type.isArray(params.regex)) { + for (const regex of params.regex) { + if (regex.test(id)) { + return true; + } + } + } + return false; +}; + async function Validate({ context, params }) { - if (!type.isNone(params) && !type.isString(params) && !type.isArray(params)) { + let testParams = params; + if (type.isNone(testParams)) { + testParams = { blockIds: true }; + } + if (type.isString(testParams)) { + testParams = { blockIds: [testParams] }; + } + if (type.isArray(testParams)) { + testParams = { blockIds: testParams }; + } + if (!type.isObject(testParams)) { throw new Error('Invalid validate params.'); } - context.showValidationErrors = true; - let validationErrors = context.RootBlocks.validate(); - if (params) { - const blockIds = type.isString(params) ? [params] : params; - validationErrors = validationErrors.filter((block) => { - return blockIds.includes(block.blockId); - }); + if (type.isString(testParams.regex)) { + testParams.regex = [testParams.regex]; } + if (type.isArray(testParams.regex)) { + testParams.regex = testParams.regex.map((regex) => new RegExp(regex)); + } + const validationErrors = context.RootBlocks.validate(testParams, getMatch(testParams)); if (validationErrors.length > 0) { - const message = `Your input has ${validationErrors.length} validation error${ - validationErrors.length !== 1 ? 's' : '' - }.`; - const error = new Error(message); + const error = new Error( + `Your input has ${validationErrors.length} validation error${ + validationErrors.length !== 1 ? 's' : '' + }.` + ); throw error; } } diff --git a/packages/engine/test/Actions/Validate.test.js b/packages/engine/test/Actions/Validate.test.js index 514610e27..7e81c8343 100644 --- a/packages/engine/test/Actions/Validate.test.js +++ b/packages/engine/test/Actions/Validate.test.js @@ -29,8 +29,9 @@ const RealDate = Date; const mockDate = jest.fn(() => ({ date: 0 })); mockDate.now = jest.fn(() => 0); -// Comment out to use console.log +// Comment out to use console console.log = () => {}; +console.error = () => {}; beforeEach(() => { displayMessage.mockReset(); @@ -88,7 +89,7 @@ test('Validate required field', async () => { rootBlock, }); const { button, text1 } = context.RootBlocks.map; - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['This field is required'], status: null, warnings: [], @@ -120,7 +121,7 @@ test('Validate required field', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['This field is required'], status: 'error', warnings: [], @@ -155,7 +156,7 @@ test('Validate required field', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], @@ -226,7 +227,7 @@ test('Validate all fields', async () => { rootBlock, }); const { button, text1, text2 } = context.RootBlocks.map; - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['text1 does not match pattern "text1"'], status: null, warnings: [], @@ -258,12 +259,12 @@ test('Validate all fields', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['text1 does not match pattern "text1"'], status: 'error', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: ['text2 does not match pattern "text2"'], status: 'error', warnings: [], @@ -309,12 +310,12 @@ test('Validate all fields', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: ['text2 does not match pattern "text2"'], status: 'error', warnings: [], @@ -349,12 +350,12 @@ test('Validate all fields', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], @@ -424,7 +425,7 @@ test('Validate only one field', async () => { rootBlock, }); const { button, text1, text2 } = context.RootBlocks.map; - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['text1 does not match pattern "text1"'], status: null, warnings: [], @@ -457,14 +458,14 @@ test('Validate only one field', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: ['text1 does not match pattern "text1"'], status: 'error', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: ['text2 does not match pattern "text2"'], - status: 'error', + status: null, warnings: [], }); expect(displayMessage.mock.calls).toMatchInlineSnapshot(` @@ -497,14 +498,14 @@ test('Validate only one field', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: ['text2 does not match pattern "text2"'], - status: 'error', + status: null, warnings: [], }); expect(displayMessage.mock.calls).toMatchInlineSnapshot(`Array []`); @@ -587,17 +588,17 @@ test('Validate list of fields', async () => { }); const { button, text1, text2, text3 } = context.RootBlocks.map; text1.setValue('text1'); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: null, warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: ['text2 does not match pattern "text2"'], status: null, warnings: [], }); - expect(text3.validationEval.output).toEqual({ + expect(text3.eval.validation).toEqual({ errors: ['text3 does not match pattern "text3"'], status: null, warnings: [], @@ -644,19 +645,19 @@ test('Validate list of fields', async () => { displayMessage.mockReset(); displayMessage.mockImplementation(() => closeLoader); text2.setValue('text2'); - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], }); - expect(text2.validationEval.output).toEqual({ + expect(text2.eval.validation).toEqual({ errors: [], status: 'success', warnings: [], }); - expect(text3.validationEval.output).toEqual({ + expect(text3.eval.validation).toEqual({ errors: ['text3 does not match pattern "text3"'], - status: 'error', + status: null, warnings: [], }); await button.triggerEvent({ name: 'onClick' }); @@ -675,9 +676,9 @@ test('Validate list of fields', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); - expect(text3.validationEval.output).toEqual({ + expect(text3.eval.validation).toEqual({ errors: ['text3 does not match pattern "text3"'], - status: 'error', + status: null, warnings: [], }); expect(displayMessage.mock.calls).toMatchInlineSnapshot(`Array []`); @@ -703,7 +704,7 @@ test('Invalid Validate params', async () => { { id: 'validate', type: 'Validate', - params: { invalid: true }, + params: 1, }, ], }, @@ -725,9 +726,7 @@ test('Invalid Validate params', async () => { error: { action: { id: 'validate', - params: { - invalid: true, - }, + params: 1, type: 'Validate', }, error: { @@ -808,9 +807,9 @@ test('Validate does not fail on warnings', async () => { rootBlock, }); const { button, text1 } = context.RootBlocks.map; - expect(text1.validationEval.output).toEqual({ + expect(text1.eval.validation).toEqual({ errors: [], - status: 'warning', + status: null, warnings: ['text1 does not match pattern "text1"'], }); await button.triggerEvent({ name: 'onClick' }); @@ -828,4 +827,356 @@ test('Validate does not fail on warnings', async () => { startTimestamp: { date: 0 }, endTimestamp: { date: 0 }, }); + expect(text1.eval.validation).toEqual({ + errors: [], + status: 'warning', + warnings: ['text1 does not match pattern "text1"'], + }); +}); + +test('Validate on nested objects using params.regex string', async () => { + const rootBlock = { + blockId: 'root', + meta: { + category: 'context', + }, + areas: { + content: { + blocks: [ + { + blockId: 'obj.text1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text1', key: 'text1' } }, + message: 'text1 does not match pattern "text1"', + }, + ], + }, + { + blockId: 'text2', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text2', key: 'text2' } }, + message: 'text2 does not match pattern "text2"', + }, + ], + }, + { + blockId: 'button', + type: 'Button', + meta: { + category: 'display', + }, + events: { + onClick: [ + { + id: 'validate', + type: 'Validate', + params: { + regex: '^obj.*1$', + }, + }, + ], + }, + }, + ], + }, + }, + }; + const context = await testContext({ + lowdefy, + rootBlock, + }); + const { button, 'obj.text1': text1 } = context.RootBlocks.map; + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: null, + warnings: [], + }); + await button.triggerEvent({ name: 'onClick' }); + expect(button.Events.events.onClick.history[0]).toEqual({ + blockId: 'button', + error: { + error: { type: 'Validate', error: new Error('Your input has 1 validation error.'), index: 0 }, + action: { id: 'validate', type: 'Validate', params: { regex: '^obj.*1$' } }, + }, + eventName: 'onClick', + responses: { + validate: { + type: 'Validate', + error: new Error('Your input has 1 validation error.'), + index: 0, + }, + }, + endTimestamp: { date: 0 }, + startTimestamp: { date: 0 }, + success: false, + }); + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: 'error', + warnings: [], + }); +}); + +test('Validate on nested objects using params.regex array', async () => { + const rootBlock = { + blockId: 'root', + meta: { + category: 'context', + }, + areas: { + content: { + blocks: [ + { + blockId: 'obj.text1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text1', key: 'text1' } }, + message: 'text1 does not match pattern "text1"', + }, + ], + }, + { + blockId: 'obj.abc1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'abc1', key: 'abc1' } }, + message: 'abc1 does not match pattern "abc1"', + }, + ], + }, + { + blockId: 'text2', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text2', key: 'text2' } }, + message: 'text2 does not match pattern "text2"', + }, + ], + }, + { + blockId: 'button', + type: 'Button', + meta: { + category: 'display', + }, + events: { + onClick: [ + { + id: 'validate', + type: 'Validate', + params: { + regex: ['^obj.*1$'], + }, + }, + ], + }, + }, + ], + }, + }, + }; + const context = await testContext({ + lowdefy, + rootBlock, + }); + const { button, text2, 'obj.text1': text1, 'obj.abc1': abc1 } = context.RootBlocks.map; + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: null, + warnings: [], + }); + await button.triggerEvent({ name: 'onClick' }); + expect(button.Events.events.onClick.history[0]).toEqual({ + blockId: 'button', + error: { + error: { + type: 'Validate', + error: new Error('Your input has 2 validation errors.'), + index: 0, + }, + action: { id: 'validate', type: 'Validate', params: { regex: ['^obj.*1$'] } }, + }, + eventName: 'onClick', + responses: { + validate: { + type: 'Validate', + error: new Error('Your input has 2 validation errors.'), + index: 0, + }, + }, + endTimestamp: { date: 0 }, + startTimestamp: { date: 0 }, + success: false, + }); + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: 'error', + warnings: [], + }); + expect(abc1.eval.validation).toEqual({ + errors: ['abc1 does not match pattern "abc1"'], + status: 'error', + warnings: [], + }); + expect(text2.eval.validation).toEqual({ + errors: ['text2 does not match pattern "text2"'], + status: null, + warnings: [], + }); +}); + +test('Validate on nested objects using params.regex array and blockIds', async () => { + const rootBlock = { + blockId: 'root', + meta: { + category: 'context', + }, + areas: { + content: { + blocks: [ + { + blockId: 'obj.text1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text1', key: 'text1' } }, + message: 'text1 does not match pattern "text1"', + }, + ], + }, + { + blockId: 'obj.abc1', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'abc1', key: 'abc1' } }, + message: 'abc1 does not match pattern "abc1"', + }, + ], + }, + { + blockId: 'text2', + type: 'TextInput', + meta: { + category: 'input', + valueType: 'string', + }, + validate: [ + { + pass: { _regex: { pattern: 'text2', key: 'text2' } }, + message: 'text2 does not match pattern "text2"', + }, + ], + }, + { + blockId: 'button', + type: 'Button', + meta: { + category: 'display', + }, + events: { + onClick: [ + { + id: 'validate', + type: 'Validate', + params: { + regex: ['^obj.*t1$'], + blockIds: ['text2'], + }, + }, + ], + }, + }, + ], + }, + }, + }; + const context = await testContext({ + lowdefy, + rootBlock, + }); + const { button, text2, 'obj.text1': text1, 'obj.abc1': abc1 } = context.RootBlocks.map; + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: null, + warnings: [], + }); + await button.triggerEvent({ name: 'onClick' }); + expect(button.Events.events.onClick.history[0]).toEqual({ + blockId: 'button', + error: { + error: { + type: 'Validate', + error: new Error('Your input has 2 validation errors.'), + index: 0, + }, + action: { + id: 'validate', + type: 'Validate', + params: { regex: ['^obj.*t1$'], blockIds: ['text2'] }, + }, + }, + event: undefined, + eventName: 'onClick', + responses: { + validate: { + type: 'Validate', + error: new Error('Your input has 2 validation errors.'), + index: 0, + }, + }, + endTimestamp: { date: 0 }, + startTimestamp: { date: 0 }, + success: false, + }); + expect(text1.eval.validation).toEqual({ + errors: ['text1 does not match pattern "text1"'], + status: 'error', + warnings: [], + }); + expect(text2.eval.validation).toEqual({ + errors: ['text2 does not match pattern "text2"'], + status: 'error', + warnings: [], + }); + expect(abc1.eval.validation).toEqual({ + errors: ['abc1 does not match pattern "abc1"'], + status: null, + warnings: [], + }); }); diff --git a/packages/engine/test/Block/validate.test.js b/packages/engine/test/Block/validate.test.js index 5b96af099..bfab35997 100644 --- a/packages/engine/test/Block/validate.test.js +++ b/packages/engine/test/Block/validate.test.js @@ -1,27 +1,27 @@ /* - Copyright 2020-2021 Lowdefy, Inc + 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 + 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 + 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. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. */ import testContext from '../testContext'; const pageId = 'one'; +const match = () => true; const lowdefy = { pageId }; -// Comment out to use console.log +// Comment out to use console console.log = () => {}; -// Comment out to use console.log console.error = () => {}; test('parse validate on fields', async () => { @@ -63,25 +63,24 @@ test('parse validate on fields', async () => { const { text } = context.RootBlocks.map; expect(context.state).toEqual({ text: 'a' }); - expect(text.validationEval.output).toEqual({ errors: ["Not 'c'"], status: null, warnings: [] }); + expect(text.eval.validation).toEqual({ errors: ["Not 'c'"], status: null, warnings: [] }); - context.showValidationErrors = true; - context.update(); - expect(text.validationEval.output).toEqual({ + context.RootBlocks.validate(true, match); + expect(text.eval.validation).toEqual({ errors: ["Not 'c'"], status: 'error', warnings: [], }); text.setValue('c'); - expect(text.validationEval.output).toEqual({ + expect(text.eval.validation).toEqual({ errors: ["Not 'a'"], status: 'error', warnings: [], }); text.setValue('b'); - expect(text.validationEval.output).toEqual({ + expect(text.eval.validation).toEqual({ errors: ["Not 'a'", "Not 'c'"], status: 'error', warnings: [], @@ -121,9 +120,8 @@ test('validate should fail if parser has errors', async () => { }); const { text } = context.RootBlocks.map; - context.showValidationErrors = true; - context.update(); - expect(text.validationEval.output).toEqual({ + context.RootBlocks.validate(true, match); + expect(text.eval.validation).toEqual({ errors: ['Parser failed'], status: 'error', warnings: [], @@ -168,9 +166,8 @@ test('validate, only test where parser failed should fail', async () => { }); const { text } = context.RootBlocks.map; - context.showValidationErrors = true; - context.update(); - expect(text.validationEval.output).toEqual({ + context.RootBlocks.validate(true, match); + expect(text.eval.validation).toEqual({ errors: ['Parser failed'], status: 'error', warnings: [], @@ -211,20 +208,19 @@ test('parse validate, validate an object not an array', async () => { }); const { text } = context.RootBlocks.map; expect(context.state).toEqual({ text: 'a' }); - expect(text.validationEval.output).toEqual({ errors: ["Not 'c'"], status: null, warnings: [] }); + expect(text.eval.validation).toEqual({ errors: ["Not 'c'"], status: null, warnings: [] }); - context.showValidationErrors = true; - context.update(); - expect(text.validationEval.output).toEqual({ + context.RootBlocks.validate(true, match); + expect(text.eval.validation).toEqual({ errors: ["Not 'c'"], status: 'error', warnings: [], }); text.setValue('c'); - expect(text.validationEval.output).toEqual({ errors: [], status: 'success', warnings: [] }); + expect(text.eval.validation).toEqual({ errors: [], status: 'success', warnings: [] }); }); -test('RootBlock.validate() to ignore errors where field not visible', async () => { +test('RootBlock.validate(true, match) to ignore errors where field not visible', async () => { const rootBlock = { blockId: 'root', meta: { @@ -288,14 +284,10 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = rootBlock, }); const { text, list } = context.RootBlocks.map; - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue('1'); - expect(context.RootBlocks.validate()).toEqual([]); - - context.showValidationErrors = true; - context.RootBlocks.update(); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list', validation: { errors: ['Error 123'], status: 'error', warnings: [] }, @@ -303,7 +295,7 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = ]); text.setValue('12'); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list', validation: { errors: ['Error 123'], status: 'error', warnings: [] }, @@ -311,11 +303,11 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = ]); text.setValue('123'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue('12'); list.pushItem(); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list', validation: { errors: ['Error 123'], status: 'error', warnings: [] } }, { blockId: 'list.0.innerText', @@ -325,7 +317,7 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = text.setValue('123'); list.pushItem(); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list.0.innerText', validation: { errors: ['Error 1234'], status: 'error', warnings: [] }, @@ -337,13 +329,13 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = ]); text.setValue('1234'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue('0'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue('12'); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list', validation: { errors: ['Error 123'], status: 'error', warnings: [] } }, { blockId: 'list.0.innerText', @@ -356,7 +348,7 @@ test('RootBlock.validate() to ignore errors where field not visible', async () = ]); }); -test('required on input to return validation error on RootBlock.validate()', async () => { +test('required on input to return validation error on RootBlock.validate(true, match)', async () => { const rootBlock = { blockId: 'root', meta: { @@ -386,18 +378,16 @@ test('required on input to return validation error on RootBlock.validate()', asy expect(context.state).toEqual({ text: null, }); - expect(context.RootBlocks.validate()).toEqual([]); - context.showValidationErrors = true; - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'text', validation: { errors: ['This field is required'], status: 'error', warnings: [] }, }, ]); text.setValue('a'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue(''); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'text', validation: { errors: ['This field is required'], status: 'error', warnings: [] }, @@ -405,7 +395,7 @@ test('required on input to return validation error on RootBlock.validate()', asy ]); }); -test('required on input to return validation error with priority over validation errors on RootBlock.validate()', async () => { +test('required on input to return validation error with priority over validation errors on RootBlock.validate(true, match)', async () => { const rootBlock = { blockId: 'root', meta: { @@ -442,9 +432,7 @@ test('required on input to return validation error with priority over validation expect(context.state).toEqual({ text: null, }); - expect(context.RootBlocks.validate()).toEqual([]); - context.showValidationErrors = true; - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'text', validation: { @@ -455,7 +443,7 @@ test('required on input to return validation error with priority over validation }, ]); text.setValue('a'); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'text', validation: { @@ -466,9 +454,9 @@ test('required on input to return validation error with priority over validation }, ]); text.setValue('1234'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue(''); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'text', validation: { @@ -480,7 +468,7 @@ test('required on input to return validation error with priority over validation ]); }); -test('nested arrays with validate, and RootBlock.validate() returns all validation errors', async () => { +test('nested arrays with validate, and RootBlock.validate(true, match) returns all validation errors', async () => { const rootBlock = { blockId: 'root', meta: { @@ -609,11 +597,7 @@ test('nested arrays with validate, and RootBlock.validate() returns all validati { innerList: [], swtch: true }, ], }); - expect(context.RootBlocks.validate()).toEqual([]); - - context.showValidationErrors = true; - context.RootBlocks.update(); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list.0.swtch', validation: { @@ -672,7 +656,7 @@ test('nested arrays with validate, and RootBlock.validate() returns all validati }, ]); text.setValue('1'); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list.0.swtch', validation: { @@ -715,9 +699,9 @@ test('nested arrays with validate, and RootBlock.validate() returns all validati }, ]); text.setValue('12'); - expect(context.RootBlocks.validate()).toEqual([]); + expect(context.RootBlocks.validate(true, match)).toEqual([]); text.setValue('0'); - expect(context.RootBlocks.validate()).toEqual([ + expect(context.RootBlocks.validate(true, match)).toEqual([ { blockId: 'list.0.swtch', validation: { @@ -818,29 +802,28 @@ test('validation warnings', async () => { const { text } = context.RootBlocks.map; expect(context.state).toEqual({ text: 'a' }); - expect(text.validationEval.output).toEqual({ + expect(text.eval.validation).toEqual({ errors: [], - status: 'warning', + status: null, warnings: ["Not 'c'"], }); - context.showValidationErrors = true; - context.update(); - expect(text.validationEval.output).toEqual({ + context.RootBlocks.validate(true, match); + expect(text.eval.validation).toEqual({ errors: [], status: 'warning', warnings: ["Not 'c'"], }); text.setValue('c'); - expect(text.validationEval.output).toEqual({ + expect(text.eval.validation).toEqual({ errors: [], status: 'warning', warnings: ["Not 'a'"], }); text.setValue('b'); - expect(text.validationEval.output).toEqual({ + expect(text.eval.validation).toEqual({ errors: [], status: 'warning', warnings: ["Not 'a'", "Not 'c'"],