diff --git a/__tests__/suits/Validator.test.tsx b/__tests__/suits/Validator.test.tsx index afffece..ad50bb0 100644 --- a/__tests__/suits/Validator.test.tsx +++ b/__tests__/suits/Validator.test.tsx @@ -109,7 +109,7 @@ describe('test validator', () => { it('should merge rules', () => { const rules = Validator.mergeRules(['rule_one', 'rule_two'], 'rule_tree|rule_four', ['rule_five|rule_six']); - expect(rules.length).toBe(6); + expect(rules.length).toBe(5); }); it('should throw an error when trying to get area when not in area', () => { diff --git a/__tests__/suits/rules/regex.test.tsx b/__tests__/suits/rules/regex.test.tsx new file mode 100644 index 0000000..8786d5a --- /dev/null +++ b/__tests__/suits/rules/regex.test.tsx @@ -0,0 +1,50 @@ +import { Validator } from '@/Validator'; +import { mount } from 'enzyme'; +import { ValidatorArea, ValidatorAreaProps } from '@/components/ValidatorArea'; +import React from 'react'; +import regex from '@/rules/regex'; +import tick from '../../common/tick'; + +describe('test regex rule', () => { + beforeEach(() => { + Validator.extend('regex', regex); + }); + + it('should always validate inputs and not validate non-inputs', async (): Promise => { + const input = document.createElement('input'); + const canvas = document.createElement('canvas'); + input.value = 'foo,|bar'; + + const validator_input = new Validator([ + input + ], + ['regex:(\\w)+,(\\w)+'], + ''); + + const validator_canvas = new Validator([ + canvas + ], + ['regex:(\\w)+,(\\w)+'], + ''); + + await validator_input.validate(); + expect(validator_input.getErrors().length).toBe(1); + + await validator_canvas.validate(); + expect(validator_canvas.getErrors().length).toBe(0); + }); + + it('should validate select', async () => { + const area = mount( + + + + ); + + area.find('select').simulate('blur'); + await tick(); + expect(area.state().errors.length).toBe(1); + }); +}); diff --git a/src/Validator.ts b/src/Validator.ts index 7c6f09e..db4bade 100644 --- a/src/Validator.ts +++ b/src/Validator.ts @@ -124,23 +124,35 @@ export class Validator { return typeof Validator.rules[rule] === 'function'; } + /** + * Get the rule name and the parameters as tuple + */ + private static getRuleNameAndParameters(rule: string): [string, string[]] { + const [name, ...splittedParameters] = rule.split(':'); + const parameters = splittedParameters.join(':'); + + if (['regex'].indexOf(name) !== -1) { + return [name, [parameters]]; + } + + return [name, parameters.split(',')]; + } + /** * Validate a specific rule */ private async validateRule(rule: string): Promise { - const [ruleName, ruleArgs = ''] = rule.split(':'); + const [ruleName, ruleParameters] = Validator.getRuleNameAndParameters(rule); if (Validator.ruleExists(ruleName)) { const ruleObj: RuleObject = Validator.isRuleFunction(ruleName) ? (Validator.rules[ruleName] as RuleFunction)(this) : Validator.rules[ruleName] as RuleObject; - const ruleArgsArray = ruleArgs.split(','); - - const passed = await ruleObj.passed(this.elements, ...ruleArgsArray); + const passed = await ruleObj.passed(this.elements, ...ruleParameters); if(!passed) { - this.errors.push(this.localize(ruleObj.message(), ...ruleArgsArray)); + this.errors.push(this.localize(ruleObj.message(), ...ruleParameters)); return false; } @@ -215,12 +227,12 @@ export class Validator { * Merges rules from different sources into one array */ public static mergeRules(...rules: RuleOptions[]): string[] { - const mergedRules: string[] = []; + let mergedRules: string[] = []; rules.forEach((rule: string | string[]) => { if (typeof rule === 'string') { rule.split('|').forEach((subRule) => mergedRules.push(subRule)); } else if (Array.isArray(rule) && rule.length) { - Validator.mergeRules(...rule).forEach((subRule) => mergedRules.push(subRule)); + mergedRules = [...mergedRules, ...rule]; } }); diff --git a/src/rules/index.ts b/src/rules/index.ts index 35abf5e..784b227 100644 --- a/src/rules/index.ts +++ b/src/rules/index.ts @@ -2,4 +2,5 @@ export { default as min } from '@/rules/min'; export { default as max } from '@/rules/max'; export { default as required } from '@/rules/required'; export { default as activeUrl } from '@/rules/activeUrl'; +export { default as regex } from '@/rules/regex'; export { IncorrectArgumentTypeError } from '@/rules/IncorrectArgumentTypeError'; diff --git a/src/rules/regex.ts b/src/rules/regex.ts new file mode 100644 index 0000000..51334a8 --- /dev/null +++ b/src/rules/regex.ts @@ -0,0 +1,21 @@ +import { isInputElement, isSelectElement, getValue } from '@/common/dom'; + + +export default { + passed(elements: HTMLElement[], pattern: string): boolean { + return elements.every((element: HTMLElement) => { + const matchesRegex = (value: string) => new RegExp(pattern).test(value); + + if (isInputElement(element) || isSelectElement(element)) { + const values = getValue(element).filter(Boolean); + + return values.every((value) => matchesRegex(value)); + } + + return true; + }) + }, + message(): string { + return '{name} heeft een ongeldig formaat'; + } +}