diff --git a/src/process/validation/rules/__tests__/validateAvailableItems.test.ts b/src/process/validation/rules/__tests__/validateAvailableItems.test.ts index 9643ece2..0d8dfcb1 100644 --- a/src/process/validation/rules/__tests__/validateAvailableItems.test.ts +++ b/src/process/validation/rules/__tests__/validateAvailableItems.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { FieldError } from 'error'; -import { RadioComponent, SelectComponent } from 'types'; +import { RadioComponent, SelectBoxesComponent, SelectComponent } from 'types'; import { simpleRadioField, simpleSelectBoxes, @@ -577,4 +577,106 @@ describe('validateAvailableItems', function () { expect(result).to.be.instanceOf(FieldError); expect(result?.errorKeyOrMessage).to.equal('invalidOption'); }); + + it('Validating a simple static values select boxes component with the available items validation parameter will return null if the selected item is valid', async function () { + const component: SelectBoxesComponent = { + ...simpleSelectBoxes, + validate: { onlyAvailableItems: true }, + }; + const data = { + component: { + foo: true, + bar: false, + baz: true, + biz: false, + }, + }; + const context = generateProcessorContext(component, data); + const result = await validateAvailableItems(context); + expect(result).to.equal(null); + }); + + it('Validating a simple static values select boxes component with the available items validation parameter will return FieldError if the selected item is invalid', async function () { + const component: SelectBoxesComponent = { + ...simpleSelectBoxes, + validate: { onlyAvailableItems: true }, + }; + const data = { + component: { + foo: true, + bar: false, + baz: true, + biz: false, + new: true, + test: false + }, + }; + const context = generateProcessorContext(component, data); + const result = await validateAvailableItems(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.equal('invalidOption'); + }); + + it('Validating a select boxes component with url data source with the available items validation parameter will return null if the selected item is valid', async function () { + const component: SelectBoxesComponent = { + ...simpleSelectBoxes, + dataSrc: 'url', + data: { + url: 'http://localhost:8080/numbers', + headers: [], + }, + validate: { onlyAvailableItems: true }, + }; + const data = { + component: { + one: true, + two: false, + three: true + }, + }; + const context = generateProcessorContext(component, data); + + context.fetch = () => { + return Promise.resolve({ + ok: true, + json: () => + Promise.resolve(['one', 'two', 'three']), + }); + }; + const result = await validateAvailableItems(context); + expect(result).to.equal(null); + }); + + it('Validating a select boxes component with url data source with the available items validation parameter will return FieldError if the selected item is invalid', async function () { + const component: SelectBoxesComponent = { + ...simpleSelectBoxes, + dataSrc: 'url', + data: { + url: 'http://localhost:8080/numbers', + headers: [], + }, + validate: { onlyAvailableItems: true }, + }; + const data = { + component: { + one: true, + two: false, + three: true, + four: true, + five: false + }, + }; + const context = generateProcessorContext(component, data); + + context.fetch = () => { + return Promise.resolve({ + ok: true, + json: () => + Promise.resolve(['one', 'two', 'three']), + }); + }; + const result = await validateAvailableItems(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.equal('invalidOption'); + }); }); diff --git a/src/process/validation/rules/validateAvailableItems.ts b/src/process/validation/rules/validateAvailableItems.ts index 07abe967..f6523fff 100644 --- a/src/process/validation/rules/validateAvailableItems.ts +++ b/src/process/validation/rules/validateAvailableItems.ts @@ -1,4 +1,4 @@ -import { isEmpty, isUndefined } from 'lodash'; +import { isEmpty, isUndefined, difference } from 'lodash'; import { FieldError, ProcessorError } from 'error'; import { Evaluator } from 'utils'; import { @@ -31,8 +31,7 @@ function isValidateableSelectBoxesComponent(component: any): component is Select return ( component && !!component.validate?.onlyAvailableItems && - component.type === 'selectboxes' && - component.dataSrc === 'url' + component.type === 'selectboxes' ); } @@ -273,19 +272,18 @@ export const validateAvailableItems: RuleFn = async (context: ValidationContext) return values.find((optionValue) => optionValue === value) !== undefined ? null : error; } } else if (isValidateableSelectBoxesComponent(component)) { - if (value == null || isEmpty(value)) { + if (value == null || isEmpty(value) || !isObject(value)) { return null; } - const values = await getAvailableDynamicValues(component, context); - if (values) { - if (isObject(value)) { - return values.find((optionValue) => compareComplexValues(optionValue, value, context)) !== - undefined - ? null - : error; - } - return values.find((optionValue) => optionValue === value) !== undefined ? null : error; + const values = + component.dataSrc === 'url' + ? await getAvailableDynamicValues(component, context) + : component.values.map(val => val.value); + if (values) { + return difference(Object.keys(value), values).length + ? error + : null; } } } catch (err: any) { @@ -337,6 +335,17 @@ export const validateAvailableItemsSync: RuleFnSync = (context: ValidationContex } return values.find((optionValue) => optionValue === value) !== undefined ? null : error; } + } else if (isValidateableSelectBoxesComponent(component) && component.dataSrc !== 'url') { + if (value == null || isEmpty(value) || !isObject(value)) { + return null; + } + + const values = component.values.map(val => val.value); + if (values) { + return difference(Object.keys(value), values).length + ? error + : null; + } } } catch (err: any) { throw new ProcessorError(err.message || err, context, 'validate:validateAvailableItems');