Skip to content

Commit

Permalink
Merge pull request #130 from formio/fio-8632-multiple-value-component…
Browse files Browse the repository at this point in the history
…s-required-validation

FIO-8632: Fixes an issue where required validation is not triggered for multiple value components like Select if it has no values added
  • Loading branch information
brendanbond authored Oct 11, 2024
2 parents 5aea7d1 + 29d1695 commit 1a100f3
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 19 deletions.
81 changes: 81 additions & 0 deletions src/process/__tests__/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4191,6 +4191,87 @@ it('Should not unset values for conditionally visible fields with different form
expect((context.scope as ValidationScope).errors).to.have.length(1);
});

it('Should validate required an empty array for multiple select', async () => {
const form = {
_id: '66f4141e34ac6c4049cc5144',
title: 'required multiple',
name: 'requiredMultiple',
path: 'requiredmultiple',
type: 'form',
display: 'form',
owner: '637b2e6b48c1227e60b1f910',
components: [
{
label: 'Select',
widget: 'choicesjs',
tableView: true,
multiple: true,
data: {
values: [
{
label: 'a',
value: 'a',
},
{
label: 'b',
value: 'b',
},
{
label: 'c',
value: 'c',
},
],
},
validate: {
required: true,
},
validateWhenHidden: false,
key: 'select',
type: 'select',
input: true,
},
{
type: 'button',
label: 'Submit',
key: 'submit',
disableOnInvalid: true,
input: true,
tableView: false,
},
],
project: '66f26afae0c7ef9920ae59f6',
};

const submission = {
data: { select: [] },
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, 1);
assert.equal(context.scope.errors[0].ruleName, 'required');
});

describe('For EditGrid:', () => {
const components = [
{
Expand Down
74 changes: 64 additions & 10 deletions src/process/validation/__tests__/multiple.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { expect } from 'chai';
import { ValidationContext } from 'types';
import { validationRules } from '..';
import { rules, serverRules } from '../rules';
import { shouldValidate as shouldValidateRegexPattern } from '../rules/validateRegexPattern';
import { shouldValidate as shouldValidateRequired } from '../rules/validateRequired';

const allRules = [...rules, ...serverRules];

const component = {
const textFieldComponent = {
type: 'textfield',
key: 'multiple_textfield',
label: 'Multiple Textfield',
Expand All @@ -17,24 +20,75 @@ const component = {
pattern: '^[0-9]+$',
}
};
const selectComponent = {
type: 'select',
key: 'multiple_select',
label: 'Multiple Select',
widget: 'choicesjs',
input: true,
multiple: true,
data: {
values: [
{
label: 'A',
value: 'a',
},
{
label: 'B',
value: 'b',
},
{
label: 'C',
value: 'c',
},
],
},
validate: {
required: true,
}
};

const context = {
component,
const context: ValidationContext = {
component: textFieldComponent,
value: [],
path: 'multiple_textfield',
data: {multiple_textfield: []},
row: {multiple_textfield: []},
scope: {errors: []},
};

const contextWithSelectComponent: ValidationContext = {
component: selectComponent,
value: [],
path: 'multiple_select',
data: {multiple_select: []},
row: {multiple_select: []},
scope: {errors: []},
};

it('Validating required rule will work for multiple values component with no rows', async () => {
const fullValueRules = allRules.filter((rule) => rule.fullValue);
const rulesToValidate = validationRules(context, fullValueRules, undefined);
expect(rulesToValidate).to.not.have.length(0);
// TextField
const shouldValidateRequiredTextField = shouldValidateRequired(context);
expect(shouldValidateRequiredTextField).to.be.equal(true);
// Select
const shouldValidateRequiredSelect = shouldValidateRequired(contextWithSelectComponent);
expect(shouldValidateRequiredSelect).to.be.equal(true);
});

it('Validati olther rules will skip for multiple values component with no rows', async () => {
const otherRules = allRules.filter((rule) => !rule.fullValue);
const rulesToValidate = validationRules(context, otherRules, undefined);
expect(rulesToValidate).to.have.length(0);
it('Validate RegexPattern rule won\'t execute for multiple values component with no rows', async () => {
const shouldValidateRegexRule = shouldValidateRegexPattern(context);
expect(shouldValidateRegexRule).to.be.equal(false);

const contextWithValues = {
...context,
value: 'a',
data: {
multiple_textfield: ['a'],
},
row: {
multiple_textfield: ['a'],
},
};
const shouldValidateRegexRuleWithValues = shouldValidateRegexPattern(contextWithValues);
expect(shouldValidateRegexRuleWithValues).to.be.equal(true);
});
8 changes: 0 additions & 8 deletions src/process/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@ export function validationRules(
}
const validationRules: ValidationRuleInfo[] = [];
return rules.reduce((acc, rule: ValidationRuleInfo) => {
if (context.component.multiple &&
Array.isArray(context.value) &&
context.value?.length === 0 &&
!rule.fullValue
) {
return acc;
}

if (rule.shouldProcess && rule.shouldProcess(context)) {
acc.push(rule);
}
Expand Down
2 changes: 1 addition & 1 deletion src/process/validation/rules/validateRegexPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const shouldValidate = (context: ValidationContext) => {
}

const pattern = component.validate?.pattern;
if (!pattern) {
if (!pattern || !value || typeof value !== 'string') {
return false;
}
return true;
Expand Down

0 comments on commit 1a100f3

Please sign in to comment.