Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIO-8414: Fix required validation not working in Data Grid #105

Merged
merged 8 commits into from
Sep 7, 2024
91 changes: 91 additions & 0 deletions src/process/__tests__/fixtures/forDataGridRequired.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"form": {
"name": "dsf",
"path": "dsf",
"type": "form",
"display": "form",
"components": [
{
"label": "Data Grid",
"reorder": false,
"addAnotherPosition": "bottom",
"layoutFixed": false,
"enableRowGroups": false,
"initEmpty": false,
"tableView": false,
"defaultValue": [
{}
],
"validate": {
"required": true
},
"validateWhenHidden": false,
"key": "dataGrid",
"type": "datagrid",
"input": true,
"components": [
{
"label": "Columns",
"columns": [
{
"components": [
{
"label": "Text Field",
"applyMaskOn": "change",
"tableView": true,
"validateWhenHidden": false,
"key": "textField",
"type": "textfield",
"input": true
}
],
"width": 6,
"offset": 0,
"push": 0,
"pull": 0,
"size": "md",
"currentWidth": 6
},
{
"components": [],
"width": 6,
"offset": 0,
"push": 0,
"pull": 0,
"size": "md",
"currentWidth": 6
}
],
"key": "columns",
"type": "columns",
"input": false,
"tableView": false
}
]
},
{
"type": "button",
"label": "Submit",
"key": "submit",
"disableOnInvalid": true,
"input": true,
"tableView": false
}
],
"created": "2024-08-07T08:41:53.926Z",
"modified": "2024-08-07T08:41:53.932Z",
"machineName": "tbtzzegecytgzpi:dsf"
},
"submission": {
"data": {

"dataGrid": [
{
"textField": ""
}
],
"submit": true
}

}
}
3 changes: 2 additions & 1 deletion src/process/__tests__/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import clearOnHideWithHiddenParent from './clearOnHideWithHiddenParent.json';
import skipValidForConditionallyHiddenComp from './skipValidForConditionallyHiddenComp.json';
import skipValidForLogicallyHiddenComp from './skipValidForLogicallyHiddenComp.json';
import skipValidWithHiddenParentComp from './skipValidWithHiddenParentComp.json';
import forDataGridRequired from './forDataGridRequired.json';
import data1a from './data1a.json';
import form1 from './form1.json';
import subs from './subs.json';

export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForLogicallyHiddenComp, skipValidForConditionallyHiddenComp, skipValidWithHiddenParentComp, data1a, form1, subs };
export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForLogicallyHiddenComp, skipValidForConditionallyHiddenComp, skipValidWithHiddenParentComp, forDataGridRequired, data1a, form1, subs };
23 changes: 22 additions & 1 deletion src/process/__tests__/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from 'node:assert'
import type { ContainerComponent, ValidationScope } from 'types';
import { getComponent } from 'utils/formUtil';
import { process, processSync, ProcessTargets } from '../index';
import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures'
import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, forDataGridRequired, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures'
/*
describe('Process Tests', () => {
it('Should perform the processes using the processReduced method.', async () => {
Expand Down Expand Up @@ -3040,6 +3040,27 @@ describe('Process Tests', () => {
});
});

it('Should validate when all child components are empty in required Data Grid', async () => {
const { form, submission } = forDataGridRequired;
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(1);
});

describe('For EditGrid:', () => {
const components = [
{
Expand Down
6 changes: 5 additions & 1 deletion src/process/validation/rules/validateRequired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
AddressComponent,
DayComponent
} from 'types';
import { isEmptyObject } from '../util';
import { isEmptyObject, doesArrayDataHaveValue } from '../util';
import { isComponentNestedDataType } from 'utils/formUtil';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

Expand Down Expand Up @@ -47,6 +47,10 @@ const valueIsPresent = (value: any, considerFalseTruthy: boolean, isNestedDataty
else if (typeof value === 'object' && !isNestedDatatype) {
return Object.values(value).some((val) => valueIsPresent(val, considerFalseTruthy, isNestedDatatype));
}
// If value is an array, check it's children have value
else if (Array.isArray(value) && value.length) {
return doesArrayDataHaveValue(value);
}
return true;
}

Expand Down
29 changes: 29 additions & 0 deletions src/process/validation/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { FieldError } from 'error';
import { Component, ValidationContext } from 'types';
import { Evaluator, unescapeHTML } from 'utils';
import { VALIDATION_ERRORS } from './i18n';
import _isEmpty from 'lodash/isEmpty';
import _isObject from 'lodash/isObject';
import _isPlainObject from 'lodash/isPlainObject';

export function isComponentPersistent(component: Component) {
return component.persistent ? component.persistent : true;
Expand Down Expand Up @@ -92,3 +95,29 @@ export const interpolateErrors = (errors: FieldError[], lang: string = 'en') =>
};
});
};

export const hasValue = (value: any) => {
if (_isObject(value)) {
return !_isEmpty(value);
}

return (typeof value === 'number' && !Number.isNaN(value)) || !!value;
}

export const doesArrayDataHaveValue = (dataValue: any[] = []): boolean => {
if (!Array.isArray(dataValue)) {
return !!dataValue;
}

if (!dataValue.length) {
return false;
}

const isArrayDataComponent = dataValue.every(_isPlainObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use the existing getModelType family of functions here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brendanbond would be problematic to use the mentioned function here, since a couple of upper functions recieve only 'value' from context, but in order to use this func, we need a 'component'.
Do you think it's worth changing upper functions to recieve component/whole context object?


if (isArrayDataComponent) {
return dataValue.some(value => Object.values(value).some(hasValue));
}

return dataValue.some(hasValue);
};
4 changes: 2 additions & 2 deletions src/utils/operators/IsEmptyValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export default class IsEmptyValue extends ConditionOperator {
const isEmptyValue = isEmpty(value);

if (instance && instance.root) {
const conditionTriggerComponent = instance.root.getComponent(conditionComponentPath);
return conditionTriggerComponent ? conditionTriggerComponent.isEmpty() : isEmptyValue;
const conditionTriggerComponent = instance.root.getComponent?.(conditionComponentPath);
return conditionTriggerComponent?.isEmpty ? conditionTriggerComponent.isEmpty() : isEmptyValue;
}

return isEmptyValue;
Expand Down
Loading