diff --git a/src/process/validation/i18n/en.ts b/src/process/validation/i18n/en.ts index ede84533..3362078d 100644 --- a/src/process/validation/i18n/en.ts +++ b/src/process/validation/i18n/en.ts @@ -34,6 +34,7 @@ export const EN_ERRORS = { captchaTokenValidation: 'ReCAPTCHA: Token validation error', captchaTokenNotSpecified: 'ReCAPTCHA: Token is not specified in submission', captchaFailure: 'ReCaptcha: Response token not found', - time: '{{field}} is not a valid time', + time: '{{field}} is not a valid time.', invalidDate: '{{field}} is not a valid date', + number: '{{field}} is not a valid number.' }; diff --git a/src/process/validation/rules/__tests__/validateNumber.test.ts b/src/process/validation/rules/__tests__/validateNumber.test.ts new file mode 100644 index 00000000..6ca89ccb --- /dev/null +++ b/src/process/validation/rules/__tests__/validateNumber.test.ts @@ -0,0 +1,27 @@ +import { expect } from 'chai'; + +import { FieldError } from 'error'; +import { simpleNumberField } from './fixtures/components'; +import { generateProcessorContext } from './fixtures/util'; +import { validateNumber } from '../validateNumber'; + +it('Validating a valid number will return null', async () => { + const component = simpleNumberField; + const data = { + component: 45, + }; + const context = generateProcessorContext(component, data); + const result = await validateNumber(context); + expect(result).to.equal(null); +}); + +it('Validating an invalid number will return a FieldError', async () => { + const component = simpleNumberField; + const data = { + component: 'text', + }; + const context = generateProcessorContext(component, data); + const result = await validateNumber(context); + expect(result).to.be.instanceOf(FieldError); + expect(result?.errorKeyOrMessage).to.contain('number'); +}); diff --git a/src/process/validation/rules/clientRules.ts b/src/process/validation/rules/clientRules.ts index 065b9dec..cb6b1c35 100644 --- a/src/process/validation/rules/clientRules.ts +++ b/src/process/validation/rules/clientRules.ts @@ -23,6 +23,7 @@ import { validateRequiredDayInfo } from "./validateRequiredDay"; import { validateTimeInfo } from "./validateTime"; import { validateUrlInfo } from "./validateUrl"; import { validateValuePropertyInfo } from "./validateValueProperty"; +import { validateNumberInfo } from "./validateNumber"; // These are the validations that are performed in the client. export const clientRules: ValidationRuleInfo[] = [ @@ -49,5 +50,6 @@ export const clientRules: ValidationRuleInfo[] = [ validateRequiredDayInfo, validateTimeInfo, validateUrlInfo, - validateValuePropertyInfo + validateValuePropertyInfo, + validateNumberInfo ]; diff --git a/src/process/validation/rules/validateNumber.ts b/src/process/validation/rules/validateNumber.ts new file mode 100644 index 00000000..283bb9ce --- /dev/null +++ b/src/process/validation/rules/validateNumber.ts @@ -0,0 +1,39 @@ +import { FieldError } from '../../../error/FieldError'; +import { NumberComponent, RuleFn, RuleFnSync, ValidationContext } from '../../../types/index'; +import { ProcessorInfo } from 'types/process/ProcessorInfo'; + +const isValidatableNumberComponent = (component: any): component is NumberComponent => { + return component && component.type === 'number'; +}; + +export const shouldValidate = (context: ValidationContext) => { + const { component, value } = context; + if (!value) { + return false; + } + if (!isValidatableNumberComponent(component)) { + return false; + } + return true; +}; + +export const validateNumber: RuleFn = async (context: ValidationContext) => { + return validateNumberSync(context); +}; + +export const validateNumberSync: RuleFnSync = (context: ValidationContext) => { + const error = new FieldError('number', context); + const { value } = context; + + if (typeof value !== 'number') { + return error; + } + return null; +}; + +export const validateNumberInfo: ProcessorInfo = { + name: 'validateNumber', + process: validateNumber, + processSync: validateNumberSync, + shouldProcess: shouldValidate, +}; \ No newline at end of file