Skip to content

Commit

Permalink
Merge pull request #126 from formio/fix/FIO-8731_validation_hidden_co…
Browse files Browse the repository at this point in the history
…mp_in_nested_forms
  • Loading branch information
brendanbond authored and lane-formio committed Aug 2, 2024
1 parent 5618762 commit 76c0e72
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 12 deletions.
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [Unreleased: 2.2.0-rc.8]
### Changed
- FIO-8731: Fixes component gets validated when being in a hidden parent

## 2.2.0-rc.7
### Changed
- FIO-8597: fixed an issue with an empty array value for a number component with multiple values enabled
Expand Down
4 changes: 4 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ FormioCore.render(document.getElementById('data-table'), {
```

See https://formio.github.io/core for more examples of how to use this library.

### Debug

[Instructions on how to debug with API Server](https://formio.atlassian.net/wiki/spaces/SD/pages/184025089/Debugging+formio+core)
5 changes: 4 additions & 1 deletion src/process/__tests__/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import clearOnHideWithCustomCondition from './clearOnHideWithCustomCondition.json';
import clearOnHideWithHiddenParent from './clearOnHideWithHiddenParent.json';
import skipValidForConditionallyHiddenComp from './skipValidForConditionallyHiddenComp.json';
import skipValidForLogicallyHiddenComp from './skipValidForLogicallyHiddenComp.json';
import skipValidWithHiddenParentComp from './skipValidWithHiddenParentComp.json';
import data1a from './data1a.json';
import form1 from './form1.json';
import subs from './subs.json';

export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, data1a, form1, subs };
export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForLogicallyHiddenComp, skipValidForConditionallyHiddenComp, skipValidWithHiddenParentComp, data1a, form1, subs };
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"form": {
"name": "conditional",
"path": "conditional",
"type": "form",
"display": "form",
"components": [
{
"label": "Checkbox",
"tableView": false,
"validateWhenHidden": false,
"key": "checkbox",
"type": "checkbox",
"input": true
},
{
"collapsible": false,
"key": "panel",
"conditional": {
"show": false,
"conjunction": "all",
"conditions": [
{
"component": "checkbox",
"operator": "isEqual",
"value": true
}
]
},
"type": "panel",
"label": "Panel",
"input": false,
"tableView": false,
"components": [
{
"label": "Text Field",
"applyMaskOn": "change",
"tableView": true,
"validate": {
"required": true
},
"validateWhenHidden": false,
"key": "textField",
"type": "textfield",
"input": true
}
]
},
{
"type": "button",
"label": "Submit",
"key": "submit",
"disableOnInvalid": true,
"input": true,
"tableView": false
}
],
"created": "2024-08-02T10:28:35.696Z",
"modified": "2024-08-02T10:28:35.704Z"
},
"submission": {
"data": {
"checkbox": true,
"submit": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"form": {
"type": "form",
"display": "form",
"components": [
{
"label": "Checkbox",
"tableView": false,
"validateWhenHidden": false,
"key": "checkbox",
"type": "checkbox",
"input": true
},
{
"collapsible": false,
"key": "panel",
"logic": [
{
"name": "1",
"trigger": {
"type": "simple",
"simple": {
"show": true,
"conjunction": "all",
"conditions": [
{
"component": "checkbox",
"operator": "isEqual",
"value": true
}
]
}
},
"actions": [
{
"name": "12",
"type": "property",
"property": {
"label": "Hidden",
"value": "hidden",
"type": "boolean"
},
"state": true
}
]
}
],
"type": "panel",
"label": "Panel",
"input": false,
"tableView": false,
"components": [
{
"label": "Text Field",
"applyMaskOn": "change",
"tableView": true,
"validate": {
"required": true
},
"validateWhenHidden": false,
"key": "textField",
"type": "textfield",
"input": true
}
]
},
{
"type": "button",
"label": "Submit",
"key": "submit",
"disableOnInvalid": true,
"input": true,
"tableView": false
}
],
"created": "2024-08-02T10:28:35.696Z",
"modified": "2024-08-02T10:28:35.704Z"
},
"submission": {
"data": {
"checkbox": true,
"submit": true
}
}
}

55 changes: 55 additions & 0 deletions src/process/__tests__/fixtures/skipValidWithHiddenParentComp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"form": {
"type": "form",
"display": "form",
"components": [
{
"label": "Checkbox",
"tableView": false,
"validateWhenHidden": false,
"key": "checkbox",
"type": "checkbox",
"input": true
},
{
"collapsible": false,
"hidden": true,
"key": "panel",
"type": "panel",
"label": "Panel",
"input": false,
"tableView": false,
"components": [
{
"label": "Text Field",
"applyMaskOn": "change",
"tableView": true,
"validate": {
"required": true
},
"validateWhenHidden": false,
"key": "textField",
"type": "textfield",
"input": true
}
]
},
{
"type": "button",
"label": "Submit",
"key": "submit",
"disableOnInvalid": true,
"input": true,
"tableView": false
}
],
"created": "2024-08-02T11:13:50.020Z",
"modified": "2024-08-02T11:14:14.155Z"
},
"submission": {
"data": {
"checkbox": true,
"submit": true
}
}
}
64 changes: 62 additions & 2 deletions src/process/__tests__/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { expect } from 'chai';
import assert from 'node:assert'
import type { ContainerComponent, ValidationScope } from 'types';
import { getComponent } from 'utils/formUtil';
import { processSync, ProcessTargets } from '../index';
import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, } from './fixtures'
import { process, processSync, ProcessTargets } from '../index';
import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures'
/*
describe('Process Tests', () => {
it('Should perform the processes using the processReduced method.', async () => {
Expand Down Expand Up @@ -2825,6 +2825,66 @@ describe('Process Tests', () => {
});
});

it('Should skip child validation with conditional', async () => {
const { form, submission } = skipValidForConditionallyHiddenComp;
const context = {
form,
submission,
data: submission.data,
components: form.components,
processors: ProcessTargets.submission,
scope: {},
config: {
server: true,
},
};

processSync(context);
context.processors = ProcessTargets.evaluator;
processSync(context);
expect((context.scope as ValidationScope).errors).to.have.length(0);
});

it('Should skip child validation with hidden parent component', async () => {
const { form, submission } = skipValidWithHiddenParentComp;
const context = {
form,
submission,
data: submission.data,
components: form.components,
processors: ProcessTargets.submission,
scope: {},
config: {
server: true,
},
};

await process(context);
context.processors = ProcessTargets.evaluator;
await process(context);
expect((context.scope as ValidationScope).errors).to.have.length(0);
});

it('Should skip child validation with logic', async () => {
const { form, submission } = skipValidForLogicallyHiddenComp;
const context = {
form,
submission,
data: submission.data,
components: form.components,
processors: ProcessTargets.submission,
scope: {},
config: {
server: true,
},
};

processSync(context as any);
context.processors = ProcessTargets.evaluator;
processSync(context as any);
expect((context.scope as ValidationScope).errors).to.have.length(0);
});

it('Should not return fields from conditionally hidden containers, clearOnHide = false', async () => {
const { form, submission } = clearOnHideWithCustomCondition;
const containerComponent = getComponent(form.components, 'section6') as ContainerComponent;
Expand Down
8 changes: 5 additions & 3 deletions src/process/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { ConditionsScope, ProcessorFn, ProcessorFnSync, ProcessorInfo, Validatio
import { evaluationRules, rules, serverRules } from "./rules";
import find from "lodash/find";
import get from "lodash/get";
import set from "lodash/set";
import pick from "lodash/pick";
import { getComponentAbsolutePath, getComponentPath } from "utils/formUtil";
import { getErrorMessage } from "utils/error";
import { FieldError } from "error";
import { ConditionallyHidden, isConditionallyHidden, isCustomConditionallyHidden, isSimpleConditionallyHidden } from "processes/conditions";
import { validate } from 'fast-json-patch';
import { isParentHidden } from 'utils/isParentHidden';

// Cleans up validation errors to remove unnessesary parts
// and make them transferable to ivm.
Expand Down Expand Up @@ -96,6 +95,7 @@ export function isForcedHidden(context: ValidationContext, isConditionallyHidden

export const _shouldSkipValidation = (context: ValidationContext, isConditionallyHidden: ConditionallyHidden) => {
const { component, scope, path } = context;

if (
(scope as ConditionsScope)?.conditionals &&
find((scope as ConditionsScope).conditionals, {
Expand All @@ -115,9 +115,11 @@ export const _shouldSkipValidation = (context: ValidationContext, isConditionall
() => isValueHidden(context),
// Force valid if component is hidden.
() => isForcedHidden(context, isConditionallyHidden) && !validateWhenHidden,
// Do not validate if 'validateWhenHidden' is disabled and some component parent is hidden
() => !validateWhenHidden && isParentHidden(component),
];

return rules.some(pred => pred());
return rules.some(pred => pred());;
};

export const shouldSkipValidationCustom: SkipValidationFn = (context: ValidationContext) => {
Expand Down
19 changes: 15 additions & 4 deletions src/utils/formUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,14 @@ export const eachComponentDataAsync = async (
await eachComponentDataAsync(component.components, data, fn, componentDataPath(component, path, compPath), index, component, includeAll);
}
return true;
} else {
return false;
}
// This way layout components are added as parents to the child components
if (isComponentModelType(component, 'layout')) {
await eachComponentDataAsync(component.components, data, fn, componentDataPath(component, path, compPath), index, component, includeAll);
return true;
}

return false;
},
true,
path,
Expand Down Expand Up @@ -341,9 +346,15 @@ export const eachComponentData = (
eachComponentData(component.components, data, fn, componentDataPath(component, path, compPath), index, component, includeAll);
}
return true;
} else {
return false;
}

// This way layout components are added as parents to the child components
if (isComponentModelType(component, 'layout')) {
eachComponentData(component.components, data, fn, componentDataPath(component, path, compPath), index, component, includeAll);
return true;
}

return false
},
true,
path,
Expand Down
Loading

0 comments on commit 76c0e72

Please sign in to comment.