From 5eefb5aecc0a11bdac7cc9ff566e5f08552f32b9 Mon Sep 17 00:00:00 2001 From: "ICX\\Tatsiana.Hashtold" Date: Tue, 20 Aug 2024 15:30:46 +0300 Subject: [PATCH 01/12] FIO-8810: fixed an issue where user unables to resubmit (change) the form with several levels of nested form with required fields --- src/process/__tests__/process.test.ts | 97 +++++++++++++++++++++++++++ src/utils/formUtil.ts | 14 ++-- 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 95225136..3a083159 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -2831,6 +2831,103 @@ describe('Process Tests', () => { }); }) + it('Should allow the submission to go through without errors if there is no the subform reference value', async () => { + const form = { + _id: '66bc5cff7ca1729623a182db', + title: 'form2', + name: 'form2', + path: 'form2', + type: 'resource', + display: 'form', + owner: '637b2e6b48c1227e60b1f910', + components: [ + { + label: 'Text Field - form2', + applyMaskOn: 'change', + tableView: true, + validate: { required: true }, + validateWhenHidden: false, + key: 'textFieldForm2', + type: 'textfield', + input: true, + }, + { + label: 'Form', + tableView: true, + form: '66bc5ccd7ca1729623a18063', + useOriginalRevision: false, + key: 'form', + type: 'form', + input: true, + components: [ + { + label: 'Text Field form1', + applyMaskOn: 'change', + tableView: true, + validate: { required: true }, + validateWhenHidden: false, + key: 'textFieldForm1', + type: 'textfield', + input: true, + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + project: '669e1c67a40e327e67e7ce55', + _vid: 0, + esign: {}, + created: '2024-08-14T07:30:07.953Z', + modified: '2024-08-14T10:09:13.201Z', + machineName: 'qzdhayddccjauyr:form2', + __v: 1, + }; + + const submission = { + data: { textFieldForm2: '1', form: { _id: '66c455fc0f00757fd4b0e79b' } }, + owner: '637b2e6b48c1227e60b1f910', + access: [], + _fvid: 0, + state: 'submitted', + _id: '66c455fc0f00757fd4b0e79d', + form: '66bc5cff7ca1729623a182db', + }; + + const errors: any = []; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: { errors }, + config: { + server: true, + }, + }; + processSync(context); + submission.data = context.data; + context.processors = ProcessTargets.evaluator; + processSync(context); + assert.equal(context.scope.errors.length, 0); + assert.deepEqual(context.data.form, { _id: '66c455fc0f00757fd4b0e79b', data: {} }) + }); + describe('Required component validation in nested form in DataGrid/EditGrid', () => { const nestedForm = { key: 'form', diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 7947f7ac..0392012f 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -278,8 +278,11 @@ export const eachComponentDataAsync = async ( return true; } if (isComponentModelType(component, 'dataObject')) { - // No need to bother processing all the children data if there is no data for this form. - if (has(data, component.path)) { + // No need to bother processing all the children data if there is no data for this form or the reference value has not been loaded. + const nestedFormValue: any = get(data, component.path); + const noReferenceAttached = nestedFormValue && nestedFormValue._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); + const shouldProcessNestedFormData = nestedFormValue && nestedFormValue._id ? !noReferenceAttached : has(data, component.path); + if (shouldProcessNestedFormData) { // For nested forms, we need to reset the "data" and "path" objects for all of the children components, and then re-establish the data when it is done. const childPath: string = componentDataPath(component, path, compPath); const childData: any = get(data, childPath, null); @@ -331,8 +334,11 @@ export const eachComponentData = ( return true; } if (isComponentModelType(component, 'dataObject')) { - // No need to bother processing all the children data if there is no data for this form. - if (has(data, component.path)) { + // No need to bother processing all the children data if there is no data for this form or the reference value has not been loaded. + const nestedFormValue: any = get(data, component.path); + const noReferenceAttached = nestedFormValue && nestedFormValue._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); + const shouldProcessNestedFormData = nestedFormValue && nestedFormValue._id ? !noReferenceAttached : has(data, component.path); + if (shouldProcessNestedFormData) { // For nested forms, we need to reset the "data" and "path" objects for all of the children components, and then re-establish the data when it is done. const childPath: string = componentDataPath(component, path, compPath); const childData: any = get(data, childPath, {}); From e037a25d6c659e34adb56b58d704cbe49cab4509 Mon Sep 17 00:00:00 2001 From: brendanjbond Date: Wed, 21 Aug 2024 19:49:18 -0500 Subject: [PATCH 02/12] use strict equality check for conditional component paths rather than Array.prototype.includes --- src/process/clearHidden.ts | 2 +- .../conditions/__tests__/conditions.test.ts | 750 +++++++++++++++--- src/process/conditions/index.ts | 2 +- 3 files changed, 630 insertions(+), 124 deletions(-) diff --git a/src/process/clearHidden.ts b/src/process/clearHidden.ts index 370a844b..03ed8e80 100644 --- a/src/process/clearHidden.ts +++ b/src/process/clearHidden.ts @@ -31,7 +31,7 @@ export const clearHiddenProcess: ProcessorFnSync = (context) = // Check if there's a conditional set for the component and if it's marked as conditionally hidden const isConditionallyHidden = (scope as ConditionsScope).conditionals?.find((cond) => { - return path.includes(cond.path) && cond.conditionallyHidden; + return path === cond.path && cond.conditionallyHidden; }); const shouldClearValueWhenHidden = !component.hasOwnProperty('clearOnHide') || component.clearOnHide; diff --git a/src/process/conditions/__tests__/conditions.test.ts b/src/process/conditions/__tests__/conditions.test.ts index eb84c890..6aa7106c 100644 --- a/src/process/conditions/__tests__/conditions.test.ts +++ b/src/process/conditions/__tests__/conditions.test.ts @@ -1,142 +1,648 @@ -import { expect } from 'chai'; -import { processSync } from '../../process' -import { conditionProcessInfo } from '../index'; -import { ConditionsScope, ProcessContext } from 'types'; -import { get } from 'lodash'; +import { expect } from "chai"; +import { processSync } from "../../process"; +import { conditionProcessInfo } from "../index"; +import { ConditionsScope, ProcessContext } from "types"; +import { get } from "lodash"; const processForm = (form: any, submission: any) => { - const context: ProcessContext = { - processors: [conditionProcessInfo], - components: form.components, - data: submission.data, - scope: {} - }; - processSync(context); - return context; + const context: ProcessContext = { + processors: [conditionProcessInfo], + components: form.components, + data: submission.data, + scope: {}, + }; + processSync(context); + return context; }; -describe('Condition processor', () => { - it('Should modify component\'s "hidden" property when conditionally visible is false', async () => { - const form = { - components: [ - { - type: 'textfield', - key: 'a', - input: true - }, - { - type: 'textfield', - key: 'b', - input: true, - conditional: { - show: false, - conjunction: 'all', - conditions: [ - { - component: 'a', - operator: 'isEmpty' - } - ] - }, - } - ] - }; - - const submission = { - data: { - a: '', - } - }; - - const context: ProcessContext = processForm(form, submission); - expect(context.components[1]).to.haveOwnProperty('hidden'); - expect(context.components[1].hidden).to.be.true; - }); - - it('Should not define a conditional component (that condition is based on selectBoxes value) as hidden', async () => { - const form1 = { - components: [ - { - label: 'Select Boxes', - optionsLabelPosition: 'right', - tableView: false, - defaultValue: { - '1': false, - '2': false, - '3': false, - test3: false, - }, - values: [ +describe("Condition processor", () => { + it('Should modify component\'s "hidden" property when conditionally visible is false', async () => { + const form = { + components: [ + { + type: "textfield", + key: "a", + input: true, + }, + { + type: "textfield", + key: "b", + input: true, + conditional: { + show: false, + conjunction: "all", + conditions: [ { - label: '1', - value: '1', - shortcut: '', + component: "a", + operator: "isEmpty", }, + ], + }, + }, + ], + }; + + const submission = { + data: { + a: "", + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.components[1]).to.haveOwnProperty("hidden"); + expect(context.components[1].hidden).to.be.true; + }); + + it("Should not define a conditional component (that condition is based on selectBoxes value) as hidden", async () => { + const form1 = { + components: [ + { + label: "Select Boxes", + optionsLabelPosition: "right", + tableView: false, + defaultValue: { + "1": false, + "2": false, + "3": false, + test3: false, + }, + values: [ + { + label: "1", + value: "1", + shortcut: "", + }, + { + label: "2", + value: "2", + shortcut: "", + }, + { + label: "3", + value: "3", + shortcut: "", + }, + ], + validateWhenHidden: false, + key: "selectBoxes", + type: "selectboxes", + input: true, + inputType: "checkbox", + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ { - label: '2', - value: '2', - shortcut: '', + component: "selectBoxes", + operator: "isEqual", + value: "3", }, + ], + }, + type: "textfield", + input: true, + }, + { + type: "button", + label: "Submit", + key: "submit", + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], + }; + + const submission1 = { + data: { + selectBoxes: { + "1": false, + "2": false, + "3": true, + }, + textField: "test", + submit: true, + }, + }; + + const context: ProcessContext = processForm( + form1, + submission1, + ); + + expect(get(context, "scope.conditionals[0].conditionallyHidden")).to.be + .false; + }); + + it("Should always add components keyed by absolute path to conditional scope (simple components)", async () => { + const form = { + components: [ + { + type: "textfield", + key: "a", + input: true, + }, + { + type: "textfield", + key: "b", + input: true, + conditional: { + show: false, + conjunction: "all", + conditions: [ { - label: '3', - value: '3', - shortcut: '', + component: "a", + operator: "isEmpty", }, ], - validateWhenHidden: false, - key: 'selectBoxes', - type: 'selectboxes', - input: true, - inputType: 'checkbox', }, - { - label: 'Text Field', - applyMaskOn: 'change', - tableView: true, - validateWhenHidden: false, - key: 'textField', - conditional: { - show: true, - conjunction: 'all', - conditions: [ + }, + ], + }; + + const submission = { + data: { + a: "", + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal("b"); + }); + + it("Should always add components keyed by absolute data path to conditional scope (data grid components)", async () => { + const form = { + components: [ + { + label: "Check Me", + tableView: false, + validateWhenHidden: false, + key: "checkMe", + type: "checkbox", + input: true, + defaultValue: false, + }, + { + label: "Data Grid", + reorder: false, + addAnotherPosition: "bottom", + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [{}], + validateWhenHidden: false, + key: "dataGrid", + type: "datagrid", + input: true, + components: [ + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ + { + component: "checkMe", + operator: "isEqual", + value: true, + }, + ], + }, + type: "textfield", + input: true, + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField1", + type: "textfield", + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + dataGrid: [{ textField: "test" }, { textField: "test1" }], + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(2); + expect(context.scope.conditionals?.[0].path).to.equal( + "dataGrid[0].textField", + ); + expect(context.scope.conditionals?.[1].path).to.equal( + "dataGrid[1].textField", + ); + }); + + it("Should always add components keyed by absolute data path to conditional scope (edit grid components)", async () => { + const form = { + components: [ + { + label: "Check Me", + tableView: false, + validateWhenHidden: false, + key: "checkMe", + type: "checkbox", + input: true, + defaultValue: false, + }, + { + label: "Edit Grid", + reorder: false, + addAnotherPosition: "bottom", + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [{}], + validateWhenHidden: false, + key: "editGrid", + type: "editgrid", + input: true, + components: [ + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ + { + component: "checkMe", + operator: "isEqual", + value: true, + }, + ], + }, + type: "textfield", + input: true, + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField1", + type: "textfield", + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + editGrid: [{ textField: "test" }, { textField: "test1" }], + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(2); + expect(context.scope.conditionals?.[0].path).to.equal( + "editGrid[0].textField", + ); + expect(context.scope.conditionals?.[1].path).to.equal( + "editGrid[1].textField", + ); + }); + + it("Should always add components keyed by absolute data path to conditional scope (container components)", async () => { + const form = { + components: [ + { + label: "Check Me", + tableView: false, + validateWhenHidden: false, + key: "checkMe", + type: "checkbox", + input: true, + defaultValue: false, + }, + { + label: "Container", + tableView: false, + key: "container", + type: "container", + input: true, + components: [ + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ + { + component: "checkMe", + operator: "isEqual", + value: true, + }, + ], + }, + type: "textfield", + input: true, + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField1", + type: "textfield", + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + container: { textField: "test" }, + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal( + "container.textField", + ); + }); + + it("Should always add components keyed by absolute data path to conditional scope (layout components)", async () => { + const form = { + components: [ + { + label: "Check Me", + tableView: false, + validateWhenHidden: false, + key: "checkMe", + type: "checkbox", + input: true, + defaultValue: false, + }, + { + label: "Panel", + tableView: false, + key: "panel", + type: "panel", + input: true, + components: [ + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ + { + component: "checkMe", + operator: "isEqual", + value: true, + }, + ], + }, + type: "textfield", + input: true, + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField1", + type: "textfield", + input: true, + }, + ], + }, + ], + }; + + const submission = { + data: { + checkMe: false, + panel: { textField: "test" }, + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(1); + // Panel components are layout components, so are not pathed + expect(context.scope.conditionals?.[0].path).to.equal("textField"); + }); + + it("Should always add components keyed by absolute data path to conditional scope (deeply nested components)", async () => { + const form = { + components: [ + { + label: "Tabs", + components: [ + { + label: "Tab 1", + key: "tab1", + components: [ { - component: 'selectBoxes', - operator: 'isEqual', - value: '3', + label: "Check Me", + tableView: false, + validateWhenHidden: false, + key: "checkMe", + type: "checkbox", + input: true, + defaultValue: false, }, ], }, - type: 'textfield', - input: true, - }, + { + label: "Tab 2", + key: "tab2", + components: [ + { + label: "Outer Data Grid", + reorder: false, + addAnotherPosition: "bottom", + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [ + { + outerContainer: { + innerContainer: { + dataGrid: [ + { + textField1: "", + }, + ], + }, + }, + }, + ], + validateWhenHidden: false, + key: "outerDataGrid", + type: "datagrid", + input: true, + components: [ + { + label: "Outer Container", + tableView: false, + validateWhenHidden: false, + key: "outerContainer", + type: "container", + input: true, + components: [ + { + label: "Inner Container", + tableView: false, + validateWhenHidden: false, + key: "innerContainer", + type: "container", + input: true, + components: [ + { + label: "Inner Data Grid", + reorder: false, + addAnotherPosition: "bottom", + layoutFixed: false, + enableRowGroups: false, + initEmpty: false, + tableView: false, + defaultValue: [ + { + textField1: "", + }, + ], + validateWhenHidden: false, + key: "innerDataGrid", + type: "datagrid", + input: true, + components: [ + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField", + conditional: { + show: true, + conjunction: "all", + conditions: [ + { + component: "checkMe", + operator: "isEqual", + value: true, + }, + ], + }, + type: "textfield", + input: true, + }, + { + label: "Text Field", + applyMaskOn: "change", + tableView: true, + validateWhenHidden: false, + key: "textField1", + type: "textfield", + input: true, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + key: "tabs", + type: "tabs", + input: false, + tableView: false, + }, + ], + }; + + const submission = { + data: { + checkMe: false, + outerDataGrid: [ { - type: 'button', - label: 'Submit', - key: 'submit', - disableOnInvalid: true, - input: true, - tableView: false, + outerContainer: { + innerContainer: { + innerDataGrid: [ + { + textField1: "test", + }, + ], + }, + }, }, ], - }; - - const submission1 = { - data: { - selectBoxes: { - '1': false, - '2': false, - '3': true, - }, - textField: 'test', - submit: true, - }, - }; - - const context: ProcessContext = processForm( - form1, - submission1 - ); - - expect(get(context, 'scope.conditionals[0].conditionallyHidden')).to.be.false; - }); + submit: true, + }, + }; + + const context: ProcessContext = processForm( + form, + submission, + ); + expect(context.scope.conditionals).to.have.length(1); + expect(context.scope.conditionals?.[0].path).to.equal( + "outerDataGrid[0].outerContainer.innerContainer.innerDataGrid[0].textField", + ); + }); }); diff --git a/src/process/conditions/index.ts b/src/process/conditions/index.ts index 211a702e..874a6b79 100644 --- a/src/process/conditions/index.ts +++ b/src/process/conditions/index.ts @@ -79,7 +79,7 @@ export const isConditionallyHidden = (context: ConditionsContext): boolean => { export type ConditionallyHidden = (context: ConditionsContext) => boolean; export const conditionalProcess = (context: ConditionsContext, isHidden: ConditionallyHidden) => { - const { component, row, scope, path } = context; + const { component, scope, path } = context; if (!hasConditions(context)) { return; } From 6d147dbb2b0e4d0e7b601b47aaa06caa23022d79 Mon Sep 17 00:00:00 2001 From: Roman Letsuk Date: Thu, 22 Aug 2024 20:15:01 +0300 Subject: [PATCH 03/12] FIO-8901: fixed incorrect handling of excessive rows in nested array model --- .../base/array/__tests__/ArrayComponent.test.ts | 13 +++++++++++-- src/experimental/model/NestedArrayModel.ts | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/experimental/base/array/__tests__/ArrayComponent.test.ts b/src/experimental/base/array/__tests__/ArrayComponent.test.ts index 6a1ae32c..dcc5fb40 100644 --- a/src/experimental/base/array/__tests__/ArrayComponent.test.ts +++ b/src/experimental/base/array/__tests__/ArrayComponent.test.ts @@ -52,23 +52,32 @@ describe('ArrayComponent', () => { { firstName: 'Sally', lastName: 'Thompson' + }, + { + firstName: 'Jane', + lastName: 'Doe' } ]; arrComp.dataValue = employees; assert.deepEqual(data, {employees: employees}); assert.deepEqual(arrComp.dataValue, employees); - assert.equal(arrComp.rows.length, 2); + assert.equal(arrComp.rows.length, 3); assert.equal(arrComp.rows[0].length, 2); assert.equal(arrComp.rows[1].length, 2); - assert.equal(arrComp.components.length, 4); + assert.equal(arrComp.rows[2].length, 2); + assert.equal(arrComp.components.length, 6); assert.equal(arrComp.components[0].dataValue, 'Joe'); assert.equal(arrComp.components[1].dataValue, 'Smith'); assert.strictEqual(arrComp.rows[0][0], arrComp.components[0]); assert.strictEqual(arrComp.rows[0][1], arrComp.components[1]); assert.strictEqual(arrComp.rows[1][0], arrComp.components[2]); assert.strictEqual(arrComp.rows[1][1], arrComp.components[3]); + assert.strictEqual(arrComp.rows[2][0], arrComp.components[4]); + assert.strictEqual(arrComp.rows[2][1], arrComp.components[5]); assert.equal(arrComp.components[2].dataValue, 'Sally'); assert.equal(arrComp.components[3].dataValue, 'Thompson'); + assert.equal(arrComp.components[4].dataValue, 'Jane'); + assert.equal(arrComp.components[5].dataValue, 'Doe'); // Ensure it removes rows. employees = [ diff --git a/src/experimental/model/NestedArrayModel.ts b/src/experimental/model/NestedArrayModel.ts index 17a1486f..cac582d3 100644 --- a/src/experimental/model/NestedArrayModel.ts +++ b/src/experimental/model/NestedArrayModel.ts @@ -152,7 +152,7 @@ export function NestedArrayModel(props: any = {}) : ModelDecoratorInterface { // Remove superfluous rows. if (dataValue.length > value.length) { - for (let i = value.length; i < dataValue.length; i++) { + for (let i = dataValue.length - 1; i >= value.length; i--) { this.removeRow(i); } } @@ -192,4 +192,4 @@ export function NestedArrayModel(props: any = {}) : ModelDecoratorInterface { } } } -}; \ No newline at end of file +}; From c0018fbdcf28ed84be4e7c4177d1de28387528d3 Mon Sep 17 00:00:00 2001 From: TanyaGashtold <61136841+TanyaGashtold@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:31:13 +0300 Subject: [PATCH 04/12] Update src/utils/formUtil.ts Co-authored-by: John Teague <164385719+johnformio@users.noreply.github.com> --- src/utils/formUtil.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 0392012f..dbdd1b59 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -280,8 +280,8 @@ export const eachComponentDataAsync = async ( if (isComponentModelType(component, 'dataObject')) { // No need to bother processing all the children data if there is no data for this form or the reference value has not been loaded. const nestedFormValue: any = get(data, component.path); - const noReferenceAttached = nestedFormValue && nestedFormValue._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); - const shouldProcessNestedFormData = nestedFormValue && nestedFormValue._id ? !noReferenceAttached : has(data, component.path); + const noReferenceAttached = nestedFormValue?._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); + const shouldProcessNestedFormData = nestedFormValue?._id ? !noReferenceAttached : has(data, component.path); if (shouldProcessNestedFormData) { // For nested forms, we need to reset the "data" and "path" objects for all of the children components, and then re-establish the data when it is done. const childPath: string = componentDataPath(component, path, compPath); From a497b67f69d94af000defc3b338156b2f4374746 Mon Sep 17 00:00:00 2001 From: TanyaGashtold <61136841+TanyaGashtold@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:31:56 +0300 Subject: [PATCH 05/12] Update src/utils/formUtil.ts Co-authored-by: John Teague <164385719+johnformio@users.noreply.github.com> --- src/utils/formUtil.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index dbdd1b59..179b216e 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -336,8 +336,8 @@ export const eachComponentData = ( if (isComponentModelType(component, 'dataObject')) { // No need to bother processing all the children data if there is no data for this form or the reference value has not been loaded. const nestedFormValue: any = get(data, component.path); - const noReferenceAttached = nestedFormValue && nestedFormValue._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); - const shouldProcessNestedFormData = nestedFormValue && nestedFormValue._id ? !noReferenceAttached : has(data, component.path); + const noReferenceAttached =nestedFormValue?._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); + const shouldProcessNestedFormData = nestedFormValue?._id ? !noReferenceAttached : has(data, component.path); if (shouldProcessNestedFormData) { // For nested forms, we need to reset the "data" and "path" objects for all of the children components, and then re-establish the data when it is done. const childPath: string = componentDataPath(component, path, compPath); From db3957702bb0f82c1be2f17c6bdc90c3bd242c7f Mon Sep 17 00:00:00 2001 From: TanyaGashtold <61136841+TanyaGashtold@users.noreply.github.com> Date: Fri, 23 Aug 2024 11:33:16 +0300 Subject: [PATCH 06/12] Update formUtil.ts --- src/utils/formUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 179b216e..94925034 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -336,7 +336,7 @@ export const eachComponentData = ( if (isComponentModelType(component, 'dataObject')) { // No need to bother processing all the children data if there is no data for this form or the reference value has not been loaded. const nestedFormValue: any = get(data, component.path); - const noReferenceAttached =nestedFormValue?._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); + const noReferenceAttached = nestedFormValue?._id && isEmpty(nestedFormValue.data) && !has(nestedFormValue, 'form'); const shouldProcessNestedFormData = nestedFormValue?._id ? !noReferenceAttached : has(data, component.path); if (shouldProcessNestedFormData) { // For nested forms, we need to reset the "data" and "path" objects for all of the children components, and then re-establish the data when it is done. From 0b7b56ce6a977d67b1296ac39917dff328a32552 Mon Sep 17 00:00:00 2001 From: brendanjbond Date: Fri, 23 Aug 2024 11:59:02 -0500 Subject: [PATCH 07/12] update hideChildren with same logic --- src/process/hideChildren.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/process/hideChildren.ts b/src/process/hideChildren.ts index 2d2c81de..ec90a3e7 100644 --- a/src/process/hideChildren.ts +++ b/src/process/hideChildren.ts @@ -17,7 +17,7 @@ export const hideChildrenProcessor: ProcessorFnSync = (context) const { component, path, row, scope } = context; // Check if there's a conditional set for the component and if it's marked as conditionally hidden const isConditionallyHidden = scope.conditionals?.find((cond) => { - return path.includes(cond.path) && cond.conditionallyHidden; + return path === cond.path && cond.conditionallyHidden; }); if (component.hidden && isConditionallyHidden) { const info = componentInfo(component); From e0fcbf1d748abcbe53999076ceec7ed3fbee9436 Mon Sep 17 00:00:00 2001 From: brendanjbond Date: Fri, 23 Aug 2024 11:59:53 -0500 Subject: [PATCH 08/12] change formatting --- .../conditions/__tests__/conditions.test.ts | 382 +++++++++--------- 1 file changed, 191 insertions(+), 191 deletions(-) diff --git a/src/process/conditions/__tests__/conditions.test.ts b/src/process/conditions/__tests__/conditions.test.ts index 6aa7106c..4ede936e 100644 --- a/src/process/conditions/__tests__/conditions.test.ts +++ b/src/process/conditions/__tests__/conditions.test.ts @@ -1,8 +1,8 @@ -import { expect } from "chai"; -import { processSync } from "../../process"; -import { conditionProcessInfo } from "../index"; -import { ConditionsScope, ProcessContext } from "types"; -import { get } from "lodash"; +import { expect } from 'chai'; +import { processSync } from '../../process'; +import { conditionProcessInfo } from '../index'; +import { ConditionsScope, ProcessContext } from 'types'; +import { get } from 'lodash'; const processForm = (form: any, submission: any) => { const context: ProcessContext = { @@ -15,26 +15,26 @@ const processForm = (form: any, submission: any) => { return context; }; -describe("Condition processor", () => { +describe('Condition processor', () => { it('Should modify component\'s "hidden" property when conditionally visible is false', async () => { const form = { components: [ { - type: "textfield", - key: "a", + type: 'textfield', + key: 'a', input: true, }, { - type: "textfield", - key: "b", + type: 'textfield', + key: 'b', input: true, conditional: { show: false, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "a", - operator: "isEmpty", + component: 'a', + operator: 'isEmpty', }, ], }, @@ -44,78 +44,78 @@ describe("Condition processor", () => { const submission = { data: { - a: "", + a: '', }, }; const context: ProcessContext = processForm( form, - submission, + submission ); - expect(context.components[1]).to.haveOwnProperty("hidden"); + expect(context.components[1]).to.haveOwnProperty('hidden'); expect(context.components[1].hidden).to.be.true; }); - it("Should not define a conditional component (that condition is based on selectBoxes value) as hidden", async () => { + it('Should not define a conditional component (that condition is based on selectBoxes value) as hidden', async () => { const form1 = { components: [ { - label: "Select Boxes", - optionsLabelPosition: "right", + label: 'Select Boxes', + optionsLabelPosition: 'right', tableView: false, defaultValue: { - "1": false, - "2": false, - "3": false, + '1': false, + '2': false, + '3': false, test3: false, }, values: [ { - label: "1", - value: "1", - shortcut: "", + label: '1', + value: '1', + shortcut: '', }, { - label: "2", - value: "2", - shortcut: "", + label: '2', + value: '2', + shortcut: '', }, { - label: "3", - value: "3", - shortcut: "", + label: '3', + value: '3', + shortcut: '', }, ], validateWhenHidden: false, - key: "selectBoxes", - type: "selectboxes", + key: 'selectBoxes', + type: 'selectboxes', input: true, - inputType: "checkbox", + inputType: 'checkbox', }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "selectBoxes", - operator: "isEqual", - value: "3", + component: 'selectBoxes', + operator: 'isEqual', + value: '3', }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - type: "button", - label: "Submit", - key: "submit", + type: 'button', + label: 'Submit', + key: 'submit', disableOnInvalid: true, input: true, tableView: false, @@ -126,43 +126,43 @@ describe("Condition processor", () => { const submission1 = { data: { selectBoxes: { - "1": false, - "2": false, - "3": true, + '1': false, + '2': false, + '3': true, }, - textField: "test", + textField: 'test', submit: true, }, }; const context: ProcessContext = processForm( form1, - submission1, + submission1 ); - expect(get(context, "scope.conditionals[0].conditionallyHidden")).to.be + expect(get(context, 'scope.conditionals[0].conditionallyHidden')).to.be .false; }); - it("Should always add components keyed by absolute path to conditional scope (simple components)", async () => { + it('Should always add components keyed by absolute path to conditional scope (simple components)', async () => { const form = { components: [ { - type: "textfield", - key: "a", + type: 'textfield', + key: 'a', input: true, }, { - type: "textfield", - key: "b", + type: 'textfield', + key: 'b', input: true, conditional: { show: false, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "a", - operator: "isEmpty", + component: 'a', + operator: 'isEmpty', }, ], }, @@ -172,71 +172,71 @@ describe("Condition processor", () => { const submission = { data: { - a: "", + a: '', }, }; const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(1); - expect(context.scope.conditionals?.[0].path).to.equal("b"); + expect(context.scope.conditionals?.[0].path).to.equal('b'); }); - it("Should always add components keyed by absolute data path to conditional scope (data grid components)", async () => { + it('Should always add components keyed by absolute data path to conditional scope (data grid components)', async () => { const form = { components: [ { - label: "Check Me", + label: 'Check Me', tableView: false, validateWhenHidden: false, - key: "checkMe", - type: "checkbox", + key: 'checkMe', + type: 'checkbox', input: true, defaultValue: false, }, { - label: "Data Grid", + label: 'Data Grid', reorder: false, - addAnotherPosition: "bottom", + addAnotherPosition: 'bottom', layoutFixed: false, enableRowGroups: false, initEmpty: false, tableView: false, defaultValue: [{}], validateWhenHidden: false, - key: "dataGrid", - type: "datagrid", + key: 'dataGrid', + type: 'datagrid', input: true, components: [ { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "checkMe", - operator: "isEqual", + component: 'checkMe', + operator: 'isEqual', value: true, }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField1", - type: "textfield", + key: 'textField1', + type: 'textfield', input: true, }, ], @@ -247,76 +247,76 @@ describe("Condition processor", () => { const submission = { data: { checkMe: false, - dataGrid: [{ textField: "test" }, { textField: "test1" }], + dataGrid: [{ textField: 'test' }, { textField: 'test1' }], }, }; const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(2); expect(context.scope.conditionals?.[0].path).to.equal( - "dataGrid[0].textField", + 'dataGrid[0].textField' ); expect(context.scope.conditionals?.[1].path).to.equal( - "dataGrid[1].textField", + 'dataGrid[1].textField' ); }); - it("Should always add components keyed by absolute data path to conditional scope (edit grid components)", async () => { + it('Should always add components keyed by absolute data path to conditional scope (edit grid components)', async () => { const form = { components: [ { - label: "Check Me", + label: 'Check Me', tableView: false, validateWhenHidden: false, - key: "checkMe", - type: "checkbox", + key: 'checkMe', + type: 'checkbox', input: true, defaultValue: false, }, { - label: "Edit Grid", + label: 'Edit Grid', reorder: false, - addAnotherPosition: "bottom", + addAnotherPosition: 'bottom', layoutFixed: false, enableRowGroups: false, initEmpty: false, tableView: false, defaultValue: [{}], validateWhenHidden: false, - key: "editGrid", - type: "editgrid", + key: 'editGrid', + type: 'editgrid', input: true, components: [ { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "checkMe", - operator: "isEqual", + component: 'checkMe', + operator: 'isEqual', value: true, }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField1", - type: "textfield", + key: 'textField1', + type: 'textfield', input: true, }, ], @@ -327,69 +327,69 @@ describe("Condition processor", () => { const submission = { data: { checkMe: false, - editGrid: [{ textField: "test" }, { textField: "test1" }], + editGrid: [{ textField: 'test' }, { textField: 'test1' }], }, }; const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(2); expect(context.scope.conditionals?.[0].path).to.equal( - "editGrid[0].textField", + 'editGrid[0].textField' ); expect(context.scope.conditionals?.[1].path).to.equal( - "editGrid[1].textField", + 'editGrid[1].textField' ); }); - it("Should always add components keyed by absolute data path to conditional scope (container components)", async () => { + it('Should always add components keyed by absolute data path to conditional scope (container components)', async () => { const form = { components: [ { - label: "Check Me", + label: 'Check Me', tableView: false, validateWhenHidden: false, - key: "checkMe", - type: "checkbox", + key: 'checkMe', + type: 'checkbox', input: true, defaultValue: false, }, { - label: "Container", + label: 'Container', tableView: false, - key: "container", - type: "container", + key: 'container', + type: 'container', input: true, components: [ { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "checkMe", - operator: "isEqual", + component: 'checkMe', + operator: 'isEqual', value: true, }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField1", - type: "textfield", + key: 'textField1', + type: 'textfield', input: true, }, ], @@ -400,66 +400,66 @@ describe("Condition processor", () => { const submission = { data: { checkMe: false, - container: { textField: "test" }, + container: { textField: 'test' }, }, }; const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(1); expect(context.scope.conditionals?.[0].path).to.equal( - "container.textField", + 'container.textField' ); }); - it("Should always add components keyed by absolute data path to conditional scope (layout components)", async () => { + it('Should always add components keyed by absolute data path to conditional scope (layout components)', async () => { const form = { components: [ { - label: "Check Me", + label: 'Check Me', tableView: false, validateWhenHidden: false, - key: "checkMe", - type: "checkbox", + key: 'checkMe', + type: 'checkbox', input: true, defaultValue: false, }, { - label: "Panel", + label: 'Panel', tableView: false, - key: "panel", - type: "panel", + key: 'panel', + type: 'panel', input: true, components: [ { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "checkMe", - operator: "isEqual", + component: 'checkMe', + operator: 'isEqual', value: true, }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField1", - type: "textfield", + key: 'textField1', + type: 'textfield', input: true, }, ], @@ -470,48 +470,48 @@ describe("Condition processor", () => { const submission = { data: { checkMe: false, - panel: { textField: "test" }, + panel: { textField: 'test' }, }, }; const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(1); // Panel components are layout components, so are not pathed - expect(context.scope.conditionals?.[0].path).to.equal("textField"); + expect(context.scope.conditionals?.[0].path).to.equal('textField'); }); - it("Should always add components keyed by absolute data path to conditional scope (deeply nested components)", async () => { + it('Should always add components keyed by absolute data path to conditional scope (deeply nested components)', async () => { const form = { components: [ { - label: "Tabs", + label: 'Tabs', components: [ { - label: "Tab 1", - key: "tab1", + label: 'Tab 1', + key: 'tab1', components: [ { - label: "Check Me", + label: 'Check Me', tableView: false, validateWhenHidden: false, - key: "checkMe", - type: "checkbox", + key: 'checkMe', + type: 'checkbox', input: true, defaultValue: false, }, ], }, { - label: "Tab 2", - key: "tab2", + label: 'Tab 2', + key: 'tab2', components: [ { - label: "Outer Data Grid", + label: 'Outer Data Grid', reorder: false, - addAnotherPosition: "bottom", + addAnotherPosition: 'bottom', layoutFixed: false, enableRowGroups: false, initEmpty: false, @@ -522,7 +522,7 @@ describe("Condition processor", () => { innerContainer: { dataGrid: [ { - textField1: "", + textField1: '', }, ], }, @@ -530,71 +530,71 @@ describe("Condition processor", () => { }, ], validateWhenHidden: false, - key: "outerDataGrid", - type: "datagrid", + key: 'outerDataGrid', + type: 'datagrid', input: true, components: [ { - label: "Outer Container", + label: 'Outer Container', tableView: false, validateWhenHidden: false, - key: "outerContainer", - type: "container", + key: 'outerContainer', + type: 'container', input: true, components: [ { - label: "Inner Container", + label: 'Inner Container', tableView: false, validateWhenHidden: false, - key: "innerContainer", - type: "container", + key: 'innerContainer', + type: 'container', input: true, components: [ { - label: "Inner Data Grid", + label: 'Inner Data Grid', reorder: false, - addAnotherPosition: "bottom", + addAnotherPosition: 'bottom', layoutFixed: false, enableRowGroups: false, initEmpty: false, tableView: false, defaultValue: [ { - textField1: "", + textField1: '', }, ], validateWhenHidden: false, - key: "innerDataGrid", - type: "datagrid", + key: 'innerDataGrid', + type: 'datagrid', input: true, components: [ { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField", + key: 'textField', conditional: { show: true, - conjunction: "all", + conjunction: 'all', conditions: [ { - component: "checkMe", - operator: "isEqual", + component: 'checkMe', + operator: 'isEqual', value: true, }, ], }, - type: "textfield", + type: 'textfield', input: true, }, { - label: "Text Field", - applyMaskOn: "change", + label: 'Text Field', + applyMaskOn: 'change', tableView: true, validateWhenHidden: false, - key: "textField1", - type: "textfield", + key: 'textField1', + type: 'textfield', input: true, }, ], @@ -608,8 +608,8 @@ describe("Condition processor", () => { ], }, ], - key: "tabs", - type: "tabs", + key: 'tabs', + type: 'tabs', input: false, tableView: false, }, @@ -625,7 +625,7 @@ describe("Condition processor", () => { innerContainer: { innerDataGrid: [ { - textField1: "test", + textField1: 'test', }, ], }, @@ -638,11 +638,11 @@ describe("Condition processor", () => { const context: ProcessContext = processForm( form, - submission, + submission ); expect(context.scope.conditionals).to.have.length(1); expect(context.scope.conditionals?.[0].path).to.equal( - "outerDataGrid[0].outerContainer.innerContainer.innerDataGrid[0].textField", + 'outerDataGrid[0].outerContainer.innerContainer.innerDataGrid[0].textField' ); }); }); From 630bc968bb38551355dfa9bfcb2ec0c1cb5f6ffa Mon Sep 17 00:00:00 2001 From: Maria-Golomb <76998395+Maria-Golomb@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:04:32 +0200 Subject: [PATCH 09/12] FIO-8798: updated day component validation (#140) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FIO-8798: updated day component validation * FIO-8798: add isDayComponent type guard --- .../rules/__tests__/validateDay.test.ts | 70 +++++++++++++++++++ src/process/validation/rules/validateDay.ts | 39 +++++++++-- 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/src/process/validation/rules/__tests__/validateDay.test.ts b/src/process/validation/rules/__tests__/validateDay.test.ts index 52abde36..a35f4865 100644 --- a/src/process/validation/rules/__tests__/validateDay.test.ts +++ b/src/process/validation/rules/__tests__/validateDay.test.ts @@ -57,3 +57,73 @@ it('Validating a day component with a valid Date object will return a field erro expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidDay'); }); + +it('Validating a day component with hidden day field with an valid date string value will return null', async () => { + const component = simpleDayField; + component.fields.day.hide = true; + const data = { + component: '03/2023', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.equal(null); +}); + +it('Validating a day component with hidden day field with invalid date will return a field error', async () => { + const component = simpleDayField; + component.fields.day.hide = true; + const data = { + component: '13/2023', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.equal('invalidDay'); +}); + +it('Validating a day component with hidden month field with an valid date string value will return null', async () => { + const component = simpleDayField; + component.fields.month.hide = true; + const data = { + component: '23/2023', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.equal(null); +}); + +it('Validating a day component with hidden month field with invalid date will return a field error', async () => { + const component = simpleDayField; + component.fields.month.hide = true; + const data = { + component: '130/2023', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.equal('invalidDay'); +}); + +it('Validating a day component with hidden year field with an valid date string value will return null', async () => { + const component = simpleDayField; + component.fields.year.hide = true; + const data = { + component: '01/23', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.equal(null); +}); + +it('Validating a day component with hidden year field with invalid date will return a field error', async () => { + const component = simpleDayField; + component.fields.year.hide = true; + const data = { + component: '13/23', + }; + const context = generateProcessorContext(component, data); + const result = await validateDay(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.equal('invalidDay'); +}); + diff --git a/src/process/validation/rules/validateDay.ts b/src/process/validation/rules/validateDay.ts index 73ec54bb..d42d63aa 100644 --- a/src/process/validation/rules/validateDay.ts +++ b/src/process/validation/rules/validateDay.ts @@ -50,20 +50,45 @@ export const validateDay: RuleFn = async (context: ValidationContext) => { export const validateDaySync: RuleFnSync = (context: ValidationContext) => { const { component, value } = context; - if (!shouldValidate(context)) { + if (!shouldValidate(context) || !isDayComponent(component)) { return null; } const error = new FieldError('invalidDay', context, 'day'); if (typeof value !== 'string') { return error; } - const [DAY, MONTH, YEAR] = (component as DayComponent).dayFirst ? [0, 1, 2] : [1, 0, 2]; - const values = value.split('/').map((x) => parseInt(x, 10)), - day = values[DAY], - month = values[MONTH], - year = values[YEAR], - maxDay = getDaysInMonthCount(month, year); + let [DAY, MONTH, YEAR] = component.dayFirst ? [0, 1, 2] : [1, 0, 2]; + + const values = value.split('/').map((x) => parseInt(x, 10)); + let day = values[DAY]; + let month = values[MONTH]; + let year = values[YEAR]; + if (values.length !== 3) { + if (component.fields.day.hide) { + MONTH = MONTH === 0 ? 0 : MONTH - 1; + YEAR = YEAR - 1; + day = 0; + month = values[MONTH]; + year = values[YEAR]; + + }; + if (component.fields.month.hide) { + DAY = DAY === 0 ? 0 : DAY - 1; + YEAR = YEAR - 1; + day = values[DAY]; + month = 0; + year = values[YEAR]; + }; + if (component.fields.year.hide) { + day = values[DAY]; + month = values[MONTH]; + year = 0; + }; + } + + const maxDay = getDaysInMonthCount(month, year); + if (isNaN(day) || day < 0 || day > maxDay) { return error; } From 7e9678d50bf800276ab787981ee522ba2c295920 Mon Sep 17 00:00:00 2001 From: TanyaGashtold <61136841+TanyaGashtold@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:05:38 +0300 Subject: [PATCH 10/12] Update Changelog.md --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 132f54d4..9822401e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,7 @@ - FIO-8645: added tests and translations for validateRequiredDay - FIO-8537: Fixing the filter processor to handle nested component data properly - FIO-8597: fixed an issue with an empty array value for a number component with multiple values enabled + - FIO-8798: updated day component validation ## 2.3.0-rc.1 ### Changed From 5774a517113e24e3e1ced58219c76dbe3b6a58f5 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Tue, 27 Aug 2024 08:47:11 +0000 Subject: [PATCH 11/12] fix: upgrade core-js from 3.37.1 to 3.38.0 Snyk has created this PR to upgrade core-js from 3.37.1 to 3.38.0. See this package in yarn: core-js See this project in Snyk: https://app.snyk.io/org/travist-ulg/project/fe64f696-cd3c-4d0f-a76e-c996c95f8d29?utm_source=github&utm_medium=referral&page=upgrade-pr --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 83f09bda..9d23a41e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "dependencies": { "@types/json-logic-js": "^2.0.7", "browser-cookies": "^1.2.0", - "core-js": "^3.37.1", + "core-js": "^3.38.0", "dayjs": "^1.11.12", "dompurify": "^3.1.6", "eventemitter3": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index cbaa9632..ad52440c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1523,10 +1523,10 @@ core-js@^3.30.2: resolved "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc" integrity sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg== -core-js@^3.37.1: - version "3.37.1" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9" - integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw== +core-js@^3.38.0: + version "3.38.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.1.tgz#aa375b79a286a670388a1a363363d53677c0383e" + integrity sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw== core-util-is@1.0.2: version "1.0.2" From c2f37805a7affef6c27fd08b16228cd7f8d018d3 Mon Sep 17 00:00:00 2001 From: lane-formio Date: Tue, 27 Aug 2024 15:24:35 -0500 Subject: [PATCH 12/12] Update Changelog.md --- Changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog.md b/Changelog.md index 9822401e..05b428e5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,11 @@ - FIO-8537: Fixing the filter processor to handle nested component data properly - FIO-8597: fixed an issue with an empty array value for a number component with multiple values enabled - FIO-8798: updated day component validation + - FIO-8885 & FIO-8886: use strict equality check for conditional component paths rather than Array.prototype.includes + - FIO-8810: fixed an issue where user unables to resubmit (change) the form with several levels of nested forms with required fields + - FIO-8848 fixed validation for TextArea with Save as Json + - FIO-8769 added check for the simpleConditional properties state + - FIO-8901: Fixed incorrect handling of excessive rows in nested array model ## 2.3.0-rc.1 ### Changed