feat!: Remove blur error behavior & blurHandler, validation logic refactor & cleanup
This commit is contained in:
parent
3d31c461e6
commit
ea93863a05
@ -1,6 +1,5 @@
|
||||
import { VueConstructor } from 'vue'
|
||||
|
||||
import { has } from '@/libs/utils'
|
||||
import rules from '@/validation/rules'
|
||||
import messages from '@/validation/messages'
|
||||
import merge from '@/utils/merge'
|
||||
@ -11,12 +10,13 @@ import FormularioGrouping from '@/FormularioGrouping.vue'
|
||||
|
||||
import {
|
||||
ValidationContext,
|
||||
ValidationRule,
|
||||
} from '@/validation/types'
|
||||
CheckRuleFn,
|
||||
CreateMessageFn,
|
||||
} from '@/validation/validator'
|
||||
|
||||
interface FormularioOptions {
|
||||
rules?: any;
|
||||
validationMessages?: any;
|
||||
validationMessages?: Record<string, Function>;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
@ -24,15 +24,12 @@ interface FormularioOptions {
|
||||
* The base formulario library.
|
||||
*/
|
||||
export default class Formulario {
|
||||
public options: FormularioOptions
|
||||
public idRegistry: { [name: string]: number }
|
||||
public rules: Record<string, CheckRuleFn> = {}
|
||||
public messages: Record<string, Function> = {}
|
||||
|
||||
constructor () {
|
||||
this.options = {
|
||||
rules,
|
||||
validationMessages: messages,
|
||||
}
|
||||
this.idRegistry = {}
|
||||
this.rules = rules
|
||||
this.messages = messages
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,47 +44,35 @@ export default class Formulario {
|
||||
this.extend(options || {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a deterministically generated id based on the sequence by which it
|
||||
* was requested. This should be *theoretically* the same SSR as client side.
|
||||
* However, SSR and deterministic ids can be very challenging, so this
|
||||
* implementation is open to community review.
|
||||
*/
|
||||
nextId (vm: Vue): string {
|
||||
const path = vm.$route && vm.$route.path ? vm.$route.path : false
|
||||
const pathPrefix = path ? vm.$route.path.replace(/[/\\.\s]/g, '-') : 'global'
|
||||
if (!has(this.idRegistry, pathPrefix)) {
|
||||
this.idRegistry[pathPrefix] = 0
|
||||
}
|
||||
return `formulario-${pathPrefix}-${++this.idRegistry[pathPrefix]}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of options, apply them to the pre-existing options.
|
||||
*/
|
||||
extend (extendWith: FormularioOptions): Formulario {
|
||||
if (typeof extendWith === 'object') {
|
||||
this.options = merge(this.options, extendWith)
|
||||
this.rules = merge(this.rules, extendWith.rules || {})
|
||||
this.messages = merge(this.messages, extendWith.validationMessages || {})
|
||||
return this
|
||||
}
|
||||
throw new Error(`VueFormulario extend() should be passed an object (was ${typeof extendWith})`)
|
||||
throw new Error(`[Formulario]: Formulario.extend() should be passed an object (was ${typeof extendWith})`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation rules by merging any passed in with global rules.
|
||||
*/
|
||||
rules (rules: Record<string, ValidationRule> = {}): () => Record<string, ValidationRule> {
|
||||
return { ...this.options.rules, ...rules }
|
||||
getRules (extendWith: Record<string, CheckRuleFn> = {}): Record<string, CheckRuleFn> {
|
||||
return merge(this.rules, extendWith)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation message for a particular error.
|
||||
*/
|
||||
validationMessage (rule: string, context: ValidationContext, vm: Vue): string {
|
||||
if (has(this.options.validationMessages, rule)) {
|
||||
return this.options.validationMessages[rule](vm, context)
|
||||
} else {
|
||||
return this.options.validationMessages.default(vm, context)
|
||||
getMessages (vm: Vue, extendWith: Record<string, Function>): Record<string, CreateMessageFn> {
|
||||
const raw = merge(this.messages || {}, extendWith)
|
||||
const messages: Record<string, CreateMessageFn> = {}
|
||||
|
||||
for (const name in raw) {
|
||||
messages[name] = (context: ValidationContext, ...args: any[]): string => {
|
||||
return typeof raw[name] === 'string' ? raw[name] : raw[name](vm, context, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
ErrorObserverRegistry,
|
||||
} from '@/validation/ErrorObserver'
|
||||
|
||||
import { ValidationErrorBag } from '@/validation/types'
|
||||
import { Violation } from '@/validation/validator'
|
||||
|
||||
@Component({ name: 'FormularioForm' })
|
||||
export default class FormularioForm extends Vue {
|
||||
@ -112,8 +112,8 @@ export default class FormularioForm extends Vue {
|
||||
}
|
||||
|
||||
@Provide()
|
||||
onFormularioFieldValidation (errorBag: ValidationErrorBag): void {
|
||||
this.$emit('validation', errorBag)
|
||||
onFormularioFieldValidation (payload: { name: string; violations: Violation[]}): void {
|
||||
this.$emit('validation', payload)
|
||||
}
|
||||
|
||||
@Provide()
|
||||
|
@ -13,23 +13,18 @@ import {
|
||||
Prop,
|
||||
Watch,
|
||||
} from 'vue-property-decorator'
|
||||
import { arrayify, has, parseRules, shallowEqualObjects, snakeToCamel } from './libs/utils'
|
||||
import { arrayify, has, shallowEqualObjects, snakeToCamel } from './libs/utils'
|
||||
import {
|
||||
ValidationContext,
|
||||
ValidationError,
|
||||
ValidationRule,
|
||||
} from '@/validation/types'
|
||||
import {
|
||||
createValidatorGroups,
|
||||
CheckRuleFn,
|
||||
CreateMessageFn,
|
||||
processConstraints,
|
||||
validate,
|
||||
Validator,
|
||||
ValidatorGroup,
|
||||
Violation,
|
||||
} from '@/validation/validator'
|
||||
|
||||
const ERROR_BEHAVIOR = {
|
||||
BLUR: 'blur',
|
||||
const VALIDATION_BEHAVIOR = {
|
||||
DEMAND: 'demand',
|
||||
LIVE: 'live',
|
||||
NONE: 'none',
|
||||
SUBMIT: 'submit',
|
||||
}
|
||||
|
||||
@ -52,26 +47,25 @@ export default class FormularioInput extends Vue {
|
||||
}) name!: string
|
||||
|
||||
@Prop({ default: '' }) validation!: string|any[]
|
||||
@Prop({ default: () => ({}) }) validationRules!: Record<string, ValidationRule>
|
||||
@Prop({ default: () => ({}) }) validationMessages!: Record<string, any>
|
||||
@Prop({ default: () => ({}) }) validationRules!: Record<string, CheckRuleFn>
|
||||
@Prop({ default: () => ({}) }) validationMessages!: Record<string, CreateMessageFn|string>
|
||||
@Prop({ default: () => [] }) errors!: string[]
|
||||
@Prop({
|
||||
default: ERROR_BEHAVIOR.BLUR,
|
||||
validator: behavior => [
|
||||
ERROR_BEHAVIOR.BLUR,
|
||||
ERROR_BEHAVIOR.LIVE,
|
||||
ERROR_BEHAVIOR.NONE,
|
||||
ERROR_BEHAVIOR.SUBMIT,
|
||||
].includes(behavior)
|
||||
default: VALIDATION_BEHAVIOR.DEMAND,
|
||||
validator: behavior => Object.values(VALIDATION_BEHAVIOR).includes(behavior)
|
||||
}) errorBehavior!: string
|
||||
|
||||
@Prop({ default: false }) errorsDisabled!: boolean
|
||||
|
||||
proxy: any = this.getInitialValue()
|
||||
localErrors: string[] = []
|
||||
violations: ValidationError[] = []
|
||||
violations: Violation[] = []
|
||||
pendingValidation: Promise<any> = Promise.resolve()
|
||||
|
||||
get fullQualifiedName (): string {
|
||||
return this.path !== '' ? `${this.path}.${this.name}` : this.name
|
||||
}
|
||||
|
||||
get model (): any {
|
||||
const model = this.hasModel ? 'value' : 'proxy'
|
||||
if (this[model] === undefined) {
|
||||
@ -98,13 +92,11 @@ export default class FormularioInput extends Vue {
|
||||
validate: this.performValidation.bind(this),
|
||||
violations: this.violations,
|
||||
errors: this.mergedErrors,
|
||||
// @TODO: Deprecated
|
||||
// @TODO: Deprecated, will be removed in next versions, use context.violations & context.errors separately
|
||||
allErrors: [
|
||||
...this.mergedErrors.map(message => ({ message })),
|
||||
...this.mergedErrors.map(message => ({ rule: null, args: [], context: null, message })),
|
||||
...arrayify(this.violations)
|
||||
],
|
||||
blurHandler: this.blurHandler.bind(this),
|
||||
performValidation: this.performValidation.bind(this),
|
||||
}, 'model', {
|
||||
get: () => this.model,
|
||||
set: (value: any) => {
|
||||
@ -113,15 +105,15 @@ export default class FormularioInput extends Vue {
|
||||
})
|
||||
}
|
||||
|
||||
get parsedValidationRules (): Record<string, ValidationRule> {
|
||||
const rules: Record<string, ValidationRule> = {}
|
||||
get normalizedValidationRules (): Record<string, CheckRuleFn> {
|
||||
const rules: Record<string, CheckRuleFn> = {}
|
||||
Object.keys(this.validationRules).forEach(key => {
|
||||
rules[snakeToCamel(key)] = this.validationRules[key]
|
||||
})
|
||||
return rules
|
||||
}
|
||||
|
||||
get messages (): Record<string, any> {
|
||||
get normalizedValidationMessages (): Record<string, any> {
|
||||
const messages: Record<string, any> = {}
|
||||
Object.keys(this.validationMessages).forEach((key) => {
|
||||
messages[snakeToCamel(key)] = this.validationMessages[key]
|
||||
@ -129,13 +121,6 @@ export default class FormularioInput extends Vue {
|
||||
return messages
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the element’s name, or select a fallback.
|
||||
*/
|
||||
get fullQualifiedName (): string {
|
||||
return this.path !== '' ? `${this.path}.${this.name}` : this.name
|
||||
}
|
||||
|
||||
/**
|
||||
* These are errors we that have been explicitly passed to us.
|
||||
*/
|
||||
@ -155,7 +140,7 @@ export default class FormularioInput extends Vue {
|
||||
if (!this.hasModel && !shallowEqualObjects(newValue, oldValue)) {
|
||||
this.context.model = newValue
|
||||
}
|
||||
if (this.errorBehavior === ERROR_BEHAVIOR.LIVE) {
|
||||
if (this.errorBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
||||
this.performValidation()
|
||||
} else {
|
||||
this.violations = []
|
||||
@ -177,7 +162,7 @@ export default class FormularioInput extends Vue {
|
||||
if (typeof this.addErrorObserver === 'function' && !this.errorsDisabled) {
|
||||
this.addErrorObserver({ callback: this.setErrors, type: 'input', field: this.fullQualifiedName })
|
||||
}
|
||||
if (this.errorBehavior === ERROR_BEHAVIOR.LIVE) {
|
||||
if (this.errorBehavior === VALIDATION_BEHAVIOR.LIVE) {
|
||||
this.performValidation()
|
||||
}
|
||||
}
|
||||
@ -192,16 +177,6 @@ export default class FormularioInput extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bound into the context object.
|
||||
*/
|
||||
blurHandler (): void {
|
||||
this.$emit('blur')
|
||||
if (this.errorBehavior === ERROR_BEHAVIOR.BLUR) {
|
||||
this.performValidation()
|
||||
}
|
||||
}
|
||||
|
||||
getInitialValue (): any {
|
||||
return has(this.$options.propsData || {}, 'value') ? this.value : ''
|
||||
}
|
||||
@ -215,91 +190,37 @@ export default class FormularioInput extends Vue {
|
||||
}
|
||||
|
||||
performValidation (): Promise<void> {
|
||||
this.pendingValidation = this.validate().then(errors => {
|
||||
this.didValidate(errors)
|
||||
this.pendingValidation = this.validate().then(violations => {
|
||||
const validationChanged = !shallowEqualObjects(violations, this.violations)
|
||||
this.violations = violations
|
||||
if (validationChanged) {
|
||||
const payload = {
|
||||
name: this.context.name,
|
||||
violations: this.violations,
|
||||
}
|
||||
this.$emit('validation', payload)
|
||||
if (typeof this.onFormularioFieldValidation === 'function') {
|
||||
this.onFormularioFieldValidation(payload)
|
||||
}
|
||||
}
|
||||
|
||||
return this.violations
|
||||
})
|
||||
return this.pendingValidation
|
||||
}
|
||||
|
||||
applyValidator (validator: Validator): Promise<ValidationError|false> {
|
||||
return validate(validator, {
|
||||
value: this.context.model,
|
||||
name: this.context.name,
|
||||
getFormValues: this.getFormValues.bind(this),
|
||||
}).then(valid => valid ? false : this.getMessageObject(validator.name, validator.args))
|
||||
}
|
||||
|
||||
applyValidatorGroup (group: ValidatorGroup): Promise<ValidationError[]> {
|
||||
return Promise.all(group.validators.map(this.applyValidator))
|
||||
.then(violations => (violations.filter(v => v !== false) as ValidationError[]))
|
||||
}
|
||||
|
||||
validate (): Promise<ValidationError[]> {
|
||||
return new Promise(resolve => {
|
||||
const resolveGroups = (groups: ValidatorGroup[], all: ValidationError[] = []): void => {
|
||||
if (groups.length) {
|
||||
const current = groups.shift() as ValidatorGroup
|
||||
|
||||
this.applyValidatorGroup(current).then(violations => {
|
||||
// The rule passed or its a non-bailing group, and there are additional groups to check, continue
|
||||
if ((violations.length === 0 || !current.bail) && groups.length) {
|
||||
return resolveGroups(groups, all.concat(violations))
|
||||
}
|
||||
return resolve(all.concat(violations))
|
||||
})
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
}
|
||||
resolveGroups(createValidatorGroups(
|
||||
parseRules(this.validation, this.$formulario.rules(this.parsedValidationRules))
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
didValidate (violations: ValidationError[]): void {
|
||||
const validationChanged = !shallowEqualObjects(violations, this.violations)
|
||||
this.violations = violations
|
||||
if (validationChanged) {
|
||||
const errorBag = {
|
||||
validate (): Promise<Violation[]> {
|
||||
return validate(
|
||||
processConstraints(
|
||||
this.validation,
|
||||
this.$formulario.getRules(this.normalizedValidationRules),
|
||||
this.$formulario.getMessages(this, this.normalizedValidationMessages),
|
||||
), {
|
||||
value: this.context.model,
|
||||
name: this.context.name,
|
||||
errors: this.violations,
|
||||
formValues: this.getFormValues(),
|
||||
}
|
||||
this.$emit('validation', errorBag)
|
||||
if (this.onFormularioFieldValidation && typeof this.onFormularioFieldValidation === 'function') {
|
||||
this.onFormularioFieldValidation(errorBag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getMessageObject (ruleName: string | undefined, args: any[]): ValidationError {
|
||||
const context = {
|
||||
args,
|
||||
name: this.name,
|
||||
value: this.context.model,
|
||||
formValues: this.getFormValues(),
|
||||
}
|
||||
const message = this.getMessageFunc(ruleName || '')(context)
|
||||
|
||||
return {
|
||||
rule: ruleName,
|
||||
context,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
getMessageFunc (ruleName: string): Function {
|
||||
ruleName = snakeToCamel(ruleName)
|
||||
if (this.messages && typeof this.messages[ruleName] !== 'undefined') {
|
||||
switch (typeof this.messages[ruleName]) {
|
||||
case 'function':
|
||||
return this.messages[ruleName]
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
return (): string => this.messages[ruleName]
|
||||
}
|
||||
}
|
||||
return (context: ValidationContext): string => this.$formulario.validationMessage(ruleName, context, this)
|
||||
)
|
||||
}
|
||||
|
||||
hasValidationErrors (): Promise<boolean> {
|
||||
|
@ -48,16 +48,6 @@ export function snakeToCamel (string: string | any): string | any {
|
||||
return string
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the rule name with the applicable modifier as an array.
|
||||
*/
|
||||
function parseModifier (ruleName: any): [string|any, string|null] {
|
||||
if (typeof ruleName === 'string' && /^[\^]/.test(ruleName.charAt(0))) {
|
||||
return [snakeToCamel(ruleName.substr(1)), ruleName.charAt(0)]
|
||||
}
|
||||
return [snakeToCamel(ruleName), null]
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts to array.
|
||||
* If given parameter is not string, object ot array, result will be an empty array.
|
||||
@ -79,58 +69,6 @@ export function arrayify (item: any): any[] {
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string or function, parse it and return an array in the format
|
||||
* [fn, [...arguments]]
|
||||
*/
|
||||
function parseRule (rule: any, rules: Record<string, any>) {
|
||||
if (typeof rule === 'function') {
|
||||
return [rule, []]
|
||||
}
|
||||
|
||||
if (Array.isArray(rule) && rule.length) {
|
||||
rule = rule.slice() // light clone
|
||||
const [ruleName, modifier] = parseModifier(rule.shift())
|
||||
if (typeof ruleName === 'string' && Object.prototype.hasOwnProperty.call(rules, ruleName)) {
|
||||
return [rules[ruleName], rule, ruleName, modifier]
|
||||
}
|
||||
if (typeof ruleName === 'function') {
|
||||
return [ruleName, rule, ruleName, modifier]
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof rule === 'string') {
|
||||
const segments = rule.split(':')
|
||||
const [ruleName, modifier] = parseModifier(segments.shift())
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(rules, ruleName)) {
|
||||
return [rules[ruleName], segments.length ? segments.join(':').split(',') : [], ruleName, modifier]
|
||||
} else {
|
||||
throw new Error(`Unknown validation rule ${rule}`)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array or string return an array of callables.
|
||||
* @param {array|string} validation
|
||||
* @param {array} rules and array of functions
|
||||
* @return {array} an array of functions
|
||||
*/
|
||||
export function parseRules (validation: any[]|string, rules: any): any[] {
|
||||
if (typeof validation === 'string') {
|
||||
return parseRules(validation.split('|').filter(f => f.length), rules)
|
||||
}
|
||||
if (!Array.isArray(validation)) {
|
||||
return []
|
||||
}
|
||||
return validation.map(rule => {
|
||||
return parseRule(rule, rules)
|
||||
}).filter(f => !!f)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a string for use in regular expressions.
|
||||
*/
|
||||
@ -198,20 +136,6 @@ export function cloneDeep (value: any): any {
|
||||
return copy
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a locale string, parse the options.
|
||||
* @param {string} locale
|
||||
*/
|
||||
export function parseLocale (locale: string): string[] {
|
||||
const segments = locale.split('-')
|
||||
return segments.reduce((options: string[], segment: string) => {
|
||||
if (options.length) {
|
||||
options.unshift(`${options[0]}-${segment}`)
|
||||
}
|
||||
return options.length ? options : [segment]
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for Object.prototype.hasOwnProperty.call (space saving)
|
||||
*/
|
||||
|
@ -1,19 +1,5 @@
|
||||
import { ValidationContext } from '@/validation/types'
|
||||
import { ValidationContext } from '@/validation/validator'
|
||||
|
||||
/**
|
||||
* This is an object of functions that each produce valid responses. There's no
|
||||
* need for these to be 1-1 with english, feel free to change the wording or
|
||||
* use/not use any of the variables available in the object or the
|
||||
* arguments for the message to make the most sense in your language and culture.
|
||||
*
|
||||
* The validation context object includes the following properties:
|
||||
* {
|
||||
* args // Array of rule arguments: between:5,10 (args are ['5', '10'])
|
||||
* name: // The validation name to be used
|
||||
* value: // The value of the field (do not mutate!),
|
||||
* formValues: // If wrapped in a FormulateForm, the value of other form fields.
|
||||
* }
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* The default render method for error messages.
|
||||
@ -32,8 +18,8 @@ export default {
|
||||
/**
|
||||
* The date is not after.
|
||||
*/
|
||||
after (vm: Vue, context: ValidationContext): string {
|
||||
if (Array.isArray(context.args) && context.args.length) {
|
||||
after (vm: Vue, context: ValidationContext, compare: string | false = false): string {
|
||||
if (typeof compare === 'string' && compare.length) {
|
||||
return vm.$t('validation.after.compare', context)
|
||||
}
|
||||
|
||||
@ -50,15 +36,15 @@ export default {
|
||||
/**
|
||||
* Rule: checks if the value is alpha numeric
|
||||
*/
|
||||
alphanumeric (vm: Vue, context: Record<string, any>): string {
|
||||
alphanumeric (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.alphanumeric', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The date is not before.
|
||||
*/
|
||||
before (vm: Vue, context: ValidationContext): string {
|
||||
if (Array.isArray(context.args) && context.args.length) {
|
||||
before (vm: Vue, context: ValidationContext, compare: string|false = false): string {
|
||||
if (typeof compare === 'string' && compare.length) {
|
||||
return vm.$t('validation.before.compare', context)
|
||||
}
|
||||
|
||||
@ -68,14 +54,14 @@ export default {
|
||||
/**
|
||||
* The value is not between two numbers or lengths
|
||||
*/
|
||||
between (vm: Vue, context: ValidationContext): string {
|
||||
const force = Array.isArray(context.args) && context.args[2] ? context.args[2] : false
|
||||
between (vm: Vue, context: ValidationContext, from: number|any = 0, to: number|any = 10, force?: string): string {
|
||||
const data = { ...context, from, to }
|
||||
|
||||
if ((!isNaN(context.value) && force !== 'length') || force === 'value') {
|
||||
return vm.$t('validation.between.force', context)
|
||||
return vm.$t('validation.between.force', data)
|
||||
}
|
||||
|
||||
return vm.$t('validation.between.default', context)
|
||||
return vm.$t('validation.between.default', data)
|
||||
},
|
||||
|
||||
/**
|
||||
@ -88,8 +74,8 @@ export default {
|
||||
/**
|
||||
* Is not a valid date.
|
||||
*/
|
||||
date (vm: Vue, context: ValidationContext): string {
|
||||
if (Array.isArray(context.args) && context.args.length) {
|
||||
date (vm: Vue, context: ValidationContext, format: string | false = false): string {
|
||||
if (typeof format === 'string' && format.length) {
|
||||
return vm.$t('validation.date.format', context)
|
||||
}
|
||||
|
||||
@ -131,45 +117,30 @@ export default {
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
max (vm: Vue, context: ValidationContext): string {
|
||||
const maximum = context.args[0] as number
|
||||
|
||||
max (vm: Vue, context: ValidationContext, maximum: string | number = 10, force?: string): string {
|
||||
if (Array.isArray(context.value)) {
|
||||
return vm.$tc('validation.max.array', maximum, context)
|
||||
}
|
||||
const force = Array.isArray(context.args) && context.args[1] ? context.args[1] : false
|
||||
|
||||
if ((!isNaN(context.value) && force !== 'length') || force === 'value') {
|
||||
return vm.$tc('validation.max.force', maximum, context)
|
||||
}
|
||||
|
||||
return vm.$tc('validation.max.default', maximum, context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The (field-level) error message for mime errors.
|
||||
*/
|
||||
mime (vm: Vue, context: ValidationContext): string {
|
||||
const types = context.args[0]
|
||||
|
||||
if (types) {
|
||||
return vm.$t('validation.mime.default', context)
|
||||
} else {
|
||||
return vm.$t('validation.mime.no_formats_allowed', context)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
min (vm: Vue, context: ValidationContext): string {
|
||||
const minimum = context.args[0] as number
|
||||
|
||||
min (vm: Vue, context: ValidationContext, minimum: number | any = 1, force?: string): string {
|
||||
if (Array.isArray(context.value)) {
|
||||
return vm.$tc('validation.min.array', minimum, context)
|
||||
}
|
||||
const force = Array.isArray(context.args) && context.args[1] ? context.args[1] : false
|
||||
|
||||
if ((!isNaN(context.value) && force !== 'length') || force === 'value') {
|
||||
return vm.$tc('validation.min.force', minimum, context)
|
||||
}
|
||||
|
||||
return vm.$tc('validation.min.default', minimum, context)
|
||||
},
|
||||
|
||||
|
@ -1,19 +1,23 @@
|
||||
import isUrl from 'is-url'
|
||||
import { shallowEqualObjects, regexForFormat, has } from '@/libs/utils'
|
||||
import { ValidatableData } from '@/validation/types'
|
||||
import { ValidationContext } from '@/validation/validator'
|
||||
|
||||
interface DateValidationContext extends ValidationContext {
|
||||
value: Date|string;
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Rule: the value must be "yes", "on", "1", or true
|
||||
*/
|
||||
accepted ({ value }: ValidatableData): Promise<boolean> {
|
||||
accepted ({ value }: ValidationContext): Promise<boolean> {
|
||||
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
after ({ value }: { value: Date|string }, compare: string | false = false): Promise<boolean> {
|
||||
after ({ value }: DateValidationContext, compare: string | false = false): Promise<boolean> {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const fieldValue = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue > timestamp))
|
||||
@ -23,12 +27,12 @@ export default {
|
||||
* Rule: checks if the value is only alpha
|
||||
*/
|
||||
alpha ({ value }: { value: string }, set = 'default'): Promise<boolean> {
|
||||
const sets = {
|
||||
const sets: Record<string, RegExp> = {
|
||||
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z]+$/
|
||||
}
|
||||
const selectedSet = has(sets, set) ? set : 'default'
|
||||
// @ts-ignore
|
||||
|
||||
return Promise.resolve(sets[selectedSet].test(value))
|
||||
},
|
||||
|
||||
@ -36,19 +40,19 @@ export default {
|
||||
* Rule: checks if the value is alpha numeric
|
||||
*/
|
||||
alphanumeric ({ value }: { value: string }, set = 'default'): Promise<boolean> {
|
||||
const sets = {
|
||||
const sets: Record<string, RegExp> = {
|
||||
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z0-9]+$/
|
||||
}
|
||||
const selectedSet = has(sets, set) ? set : 'default'
|
||||
// @ts-ignore
|
||||
|
||||
return Promise.resolve(sets[selectedSet].test(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
before ({ value }: { value: Date|string }, compare: string|false = false): Promise<boolean> {
|
||||
before ({ value }: DateValidationContext, compare: string|false = false): Promise<boolean> {
|
||||
const timestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const fieldValue = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
return Promise.resolve(isNaN(fieldValue) ? false : (fieldValue < timestamp))
|
||||
@ -80,13 +84,13 @@ export default {
|
||||
* Confirm that the value of one field is the same as another, mostly used
|
||||
* for password confirmations.
|
||||
*/
|
||||
confirm ({ value, getFormValues, name }: ValidatableData, field?: string): Promise<boolean> {
|
||||
confirm ({ value, formValues, name }: ValidationContext, field?: string): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
let confirmationFieldName = field
|
||||
if (!confirmationFieldName) {
|
||||
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : `${name}_confirm`
|
||||
}
|
||||
return getFormValues()[confirmationFieldName] === value
|
||||
return formValues[confirmationFieldName] === value
|
||||
})())
|
||||
},
|
||||
|
||||
@ -150,27 +154,6 @@ export default {
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the minimum value of a particular.
|
||||
*/
|
||||
min ({ value }: { value: any }, minimum: number | any = 1, force?: string): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
if (Array.isArray(value)) {
|
||||
minimum = !isNaN(minimum) ? Number(minimum) : minimum
|
||||
return value.length >= minimum
|
||||
}
|
||||
if ((!isNaN(value) && force !== 'length') || force === 'value') {
|
||||
value = !isNaN(value) ? Number(value) : value
|
||||
return value >= minimum
|
||||
}
|
||||
if (typeof value === 'string' || (force === 'length')) {
|
||||
value = !isNaN(value) ? value.toString() : value
|
||||
return value.length >= minimum
|
||||
}
|
||||
return false
|
||||
})())
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the maximum value of a particular.
|
||||
*/
|
||||
@ -192,6 +175,27 @@ export default {
|
||||
})())
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the minimum value of a particular.
|
||||
*/
|
||||
min ({ value }: { value: any }, minimum: number | any = 1, force?: string): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
if (Array.isArray(value)) {
|
||||
minimum = !isNaN(minimum) ? Number(minimum) : minimum
|
||||
return value.length >= minimum
|
||||
}
|
||||
if ((!isNaN(value) && force !== 'length') || force === 'value') {
|
||||
value = !isNaN(value) ? Number(value) : value
|
||||
return value >= minimum
|
||||
}
|
||||
if (typeof value === 'string' || (force === 'length')) {
|
||||
value = !isNaN(value) ? value.toString() : value
|
||||
return value.length >= minimum
|
||||
}
|
||||
return false
|
||||
})())
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value is not in stack.
|
||||
*/
|
||||
|
@ -1,34 +0,0 @@
|
||||
export interface ValidatableData {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
// If wrapped in a FormulateForm, the value of other form fields.
|
||||
getFormValues(): Record<string, any>;
|
||||
// The validation name to be used
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ValidationContext {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
// If wrapped in a FormulateForm, the value of other form fields.
|
||||
formValues: Record<string, any>;
|
||||
// The validation name to be used
|
||||
name: string;
|
||||
// Array of rule arguments: between:5,10 (args are ['5', '10'])
|
||||
args: any[];
|
||||
}
|
||||
|
||||
export interface ValidationRule {
|
||||
(context: ValidatableData, ...args: any[]): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
rule?: string;
|
||||
context?: ValidationContext;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ValidationErrorBag {
|
||||
name: string;
|
||||
errors: ValidationError[];
|
||||
}
|
@ -1,12 +1,31 @@
|
||||
import {
|
||||
ValidatableData,
|
||||
ValidationRule,
|
||||
} from '@/validation/types'
|
||||
import { has, snakeToCamel } from '@/libs/utils'
|
||||
|
||||
export type Validator = {
|
||||
name?: string;
|
||||
rule: ValidationRule;
|
||||
export interface Validator {
|
||||
(context: ValidationContext): Promise<Violation|null>;
|
||||
}
|
||||
|
||||
export interface Violation {
|
||||
rule: string|null;
|
||||
args: any[];
|
||||
context: ValidationContext|null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface CheckRuleFn {
|
||||
(context: ValidationContext, ...args: any[]): Promise<boolean>|boolean;
|
||||
}
|
||||
|
||||
export interface CreateMessageFn {
|
||||
(context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationContext {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
// If wrapped in a FormulateForm, the value of other form fields.
|
||||
formValues: Record<string, any>;
|
||||
// The validation name to be used
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type ValidatorGroup = {
|
||||
@ -14,6 +33,128 @@ export type ValidatorGroup = {
|
||||
bail: boolean;
|
||||
}
|
||||
|
||||
export function createValidator (
|
||||
ruleFn: CheckRuleFn,
|
||||
ruleName: string|null,
|
||||
ruleArgs: any[],
|
||||
messageFn: CreateMessageFn
|
||||
): Validator {
|
||||
return (context: ValidationContext): Promise<Violation|null> => {
|
||||
return Promise.resolve(ruleFn(context, ...ruleArgs))
|
||||
.then(valid => {
|
||||
return !valid ? {
|
||||
rule: ruleName,
|
||||
args: ruleArgs,
|
||||
context,
|
||||
message: messageFn(context, ...ruleArgs),
|
||||
} : null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function parseModifier (ruleName: string): [string, string|null] {
|
||||
if (/^[\^]/.test(ruleName.charAt(0))) {
|
||||
return [snakeToCamel(ruleName.substr(1)), ruleName.charAt(0)]
|
||||
}
|
||||
return [snakeToCamel(ruleName), null]
|
||||
}
|
||||
|
||||
export function processArrayConstraint (
|
||||
constraint: any[],
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
const args = constraint.slice()
|
||||
const first = args.shift()
|
||||
|
||||
if (typeof first === 'function') {
|
||||
return [first, null, null]
|
||||
}
|
||||
|
||||
if (typeof first !== 'string') {
|
||||
throw new Error('[Formulario]: For array constraint first element must be rule name or Validator function')
|
||||
}
|
||||
|
||||
const [name, modifier] = parseModifier(first)
|
||||
|
||||
if (has(rules, name)) {
|
||||
return [
|
||||
createValidator(
|
||||
rules[name],
|
||||
name,
|
||||
args,
|
||||
messages[name] || messages.default
|
||||
),
|
||||
name,
|
||||
modifier,
|
||||
]
|
||||
}
|
||||
|
||||
throw new Error(`[Formulario] Can't create validator for constraint: ${JSON.stringify(constraint)}`)
|
||||
}
|
||||
|
||||
export function processStringConstraint (
|
||||
constraint: string,
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
const args = constraint.split(':')
|
||||
const [name, modifier] = parseModifier(args.shift() || '')
|
||||
|
||||
if (has(rules, name)) {
|
||||
return [
|
||||
createValidator(
|
||||
rules[name],
|
||||
name,
|
||||
args.length ? args.join(':').split(',') : [],
|
||||
messages[name] || messages.default
|
||||
),
|
||||
name,
|
||||
modifier,
|
||||
]
|
||||
}
|
||||
|
||||
throw new Error(`[Formulario] Can't create validator for constraint: ${constraint}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string or function, parse it and return an array in the format
|
||||
* [fn, [...arguments]]
|
||||
*/
|
||||
export function processConstraint (
|
||||
constraint: any,
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
if (typeof constraint === 'function') {
|
||||
return [constraint, null, null]
|
||||
}
|
||||
|
||||
if (Array.isArray(constraint) && constraint.length) {
|
||||
return processArrayConstraint(constraint, rules, messages)
|
||||
}
|
||||
|
||||
if (typeof constraint === 'string') {
|
||||
return processStringConstraint(constraint, rules, messages)
|
||||
}
|
||||
|
||||
return [(): Promise<Violation|null> => Promise.resolve(null), null, null]
|
||||
}
|
||||
|
||||
export function processConstraints (
|
||||
constraints: string|any[],
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
): [Validator, string|null, string|null][] {
|
||||
if (typeof constraints === 'string') {
|
||||
return processConstraints(constraints.split('|').filter(f => f.length), rules, messages)
|
||||
}
|
||||
if (!Array.isArray(constraints)) {
|
||||
return []
|
||||
}
|
||||
return constraints.map(constraint => processConstraint(constraint, rules, messages))
|
||||
}
|
||||
|
||||
export function enlarge (groups: ValidatorGroup[]): ValidatorGroup[] {
|
||||
const enlarged: ValidatorGroup[] = []
|
||||
|
||||
@ -46,25 +187,20 @@ export function enlarge (groups: ValidatorGroup[]): ValidatorGroup[] {
|
||||
* [[required, min, max]]
|
||||
* @param {array} rules
|
||||
*/
|
||||
export function createValidatorGroups (rules: [ValidationRule, any[], string, string|null][]): ValidatorGroup[] {
|
||||
const mapper = ([
|
||||
rule,
|
||||
args,
|
||||
name,
|
||||
modifier
|
||||
]: [ValidationRule, any[], string, any]): ValidatorGroup => ({
|
||||
validators: [{ name, rule, args }],
|
||||
export function createValidatorGroups (rules: [Validator, string|null, string|null][]): ValidatorGroup[] {
|
||||
const mapper = ([validator, /** name */, modifier]: [Validator, string|null, string|null]): ValidatorGroup => ({
|
||||
validators: [validator],
|
||||
bail: modifier === '^',
|
||||
})
|
||||
|
||||
const groups: ValidatorGroup[] = []
|
||||
|
||||
const bailIndex = rules.findIndex(([,, rule]) => rule.toLowerCase() === 'bail')
|
||||
const bailIndex = rules.findIndex(([, name]) => name && name.toLowerCase() === 'bail')
|
||||
|
||||
if (bailIndex >= 0) {
|
||||
groups.push(...enlarge(rules.splice(0, bailIndex + 1).slice(0, -1).map(mapper)))
|
||||
groups.push(...rules.map(([rule, args, name]) => ({
|
||||
validators: [{ rule, args, name }],
|
||||
groups.push(...rules.map(([validator]) => ({
|
||||
validators: [validator],
|
||||
bail: true,
|
||||
})))
|
||||
} else {
|
||||
@ -74,6 +210,33 @@ export function createValidatorGroups (rules: [ValidationRule, any[], string, st
|
||||
return groups
|
||||
}
|
||||
|
||||
export function validate (validator: Validator, data: ValidatableData): Promise<boolean> {
|
||||
return Promise.resolve(validator.rule(data, ...validator.args))
|
||||
function validateByGroup (group: ValidatorGroup, context: ValidationContext): Promise<Violation[]> {
|
||||
return Promise.all(
|
||||
group.validators.map(validate => validate(context))
|
||||
)
|
||||
.then(violations => (violations.filter(v => v !== null) as Violation[]))
|
||||
}
|
||||
|
||||
export function validate (
|
||||
validators: [Validator, string|null, string|null][],
|
||||
context: ValidationContext
|
||||
): Promise<Violation[]> {
|
||||
return new Promise(resolve => {
|
||||
const resolveGroups = (groups: ValidatorGroup[], all: Violation[] = []): void => {
|
||||
if (groups.length) {
|
||||
const current = groups.shift() as ValidatorGroup
|
||||
|
||||
validateByGroup(current, context).then(violations => {
|
||||
// The rule passed or its a non-bailing group, and there are additional groups to check, continue
|
||||
if ((violations.length === 0 || !current.bail) && groups.length) {
|
||||
return resolveGroups(groups, all.concat(violations))
|
||||
}
|
||||
return resolve(all.concat(violations))
|
||||
})
|
||||
} else {
|
||||
resolve([])
|
||||
}
|
||||
}
|
||||
resolveGroups(createValidatorGroups(validators))
|
||||
})
|
||||
}
|
||||
|
@ -298,7 +298,7 @@ describe('FormularioForm', () => {
|
||||
slots: {
|
||||
default: `
|
||||
<FormularioInput v-slot="{ context }" name="foo" validation="required|in:foo">
|
||||
<input v-model="context.model" type="text" @blur="context.blurHandler">
|
||||
<input v-model="context.model" type="text" @blur="context.validate()">
|
||||
</FormularioInput>
|
||||
<FormularioInput name="bar" validation="required" />
|
||||
`,
|
||||
@ -313,7 +313,7 @@ describe('FormularioForm', () => {
|
||||
expect(wrapper.emitted('validation').length).toBe(1)
|
||||
expect(wrapper.emitted('validation')[0][0]).toEqual({
|
||||
name: 'foo',
|
||||
errors: [],
|
||||
violations: [],
|
||||
})
|
||||
})
|
||||
|
||||
@ -321,7 +321,7 @@ describe('FormularioForm', () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
slots: { default: `
|
||||
<FormularioInput v-slot="{ context }" name="foo" validation="required|in:foo">
|
||||
<input v-model="context.model" type="text" @blur="context.blurHandler">
|
||||
<input v-model="context.model" type="text" @blur="context.validate()">
|
||||
</FormularioInput>
|
||||
<FormularioInput name="bar" validation="required" />
|
||||
` }
|
||||
@ -335,7 +335,7 @@ describe('FormularioForm', () => {
|
||||
expect(wrapper.emitted('validation').length).toBe(1)
|
||||
expect(wrapper.emitted('validation')[0][0]).toEqual({
|
||||
name: 'foo',
|
||||
errors: [ expect.any(Object) ], // @TODO: Check object structure
|
||||
violations: [ expect.any(Object) ], // @TODO: Check object structure
|
||||
})
|
||||
})
|
||||
|
||||
@ -399,6 +399,6 @@ describe('FormularioForm', () => {
|
||||
|
||||
await flushPromises()
|
||||
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(0)
|
||||
expect(wrapper.vm.values).toEqual({})
|
||||
expect(wrapper.vm['values']).toEqual({})
|
||||
})
|
||||
})
|
||||
|
@ -178,15 +178,17 @@ describe('FormularioInput', () => {
|
||||
validation: 'required',
|
||||
errorBehavior: 'live',
|
||||
value: '',
|
||||
name: 'testinput',
|
||||
name: 'fieldName',
|
||||
}
|
||||
})
|
||||
await flushPromises()
|
||||
const errorObject = wrapper.emitted('validation')[0][0]
|
||||
expect(errorObject).toEqual({
|
||||
name: 'testinput',
|
||||
errors: [{
|
||||
|
||||
expect(wrapper.emitted('validation')).toBeTruthy()
|
||||
expect(wrapper.emitted('validation')[0][0]).toEqual({
|
||||
name: 'fieldName',
|
||||
violations: [{
|
||||
rule: expect.stringContaining('required'),
|
||||
args: expect.any(Array),
|
||||
context: expect.any(Object),
|
||||
message: expect.any(String),
|
||||
}],
|
||||
@ -243,7 +245,7 @@ describe('FormularioInput', () => {
|
||||
scopedSlots: {
|
||||
default: `
|
||||
<div>
|
||||
<input v-model="props.context.model" @blur="props.context.blurHandler">
|
||||
<input v-model="props.context.model" @blur="props.context.validate()">
|
||||
<span v-if="props.context.formShouldShowErrors" v-for="error in props.context.allErrors">{{ error.message }}</span>
|
||||
</div>
|
||||
`
|
||||
|
@ -1,62 +1,4 @@
|
||||
import { cloneDeep, isScalar, parseRules, regexForFormat, snakeToCamel } from '@/libs/utils'
|
||||
import rules from '@/validation/rules.ts'
|
||||
|
||||
describe('parseRules', () => {
|
||||
it('parses single string rules, returning empty arguments array', () => {
|
||||
expect(parseRules('required', rules)).toEqual([
|
||||
[rules.required, [], 'required', null]
|
||||
])
|
||||
})
|
||||
|
||||
it('throws errors for invalid validation rules', () => {
|
||||
expect(() => {
|
||||
parseRules('required|notarule', rules, null)
|
||||
}).toThrow()
|
||||
})
|
||||
|
||||
it('parses arguments for a rule', () => {
|
||||
expect(parseRules('in:foo,bar', rules)).toEqual([
|
||||
[rules.in, ['foo', 'bar'], 'in', null]
|
||||
])
|
||||
})
|
||||
|
||||
it('parses multiple string rules and arguments', () => {
|
||||
expect(parseRules('required|in:foo,bar', rules)).toEqual([
|
||||
[rules.required, [], 'required', null],
|
||||
[rules.in, ['foo', 'bar'], 'in', null]
|
||||
])
|
||||
})
|
||||
|
||||
it('parses multiple array rules and arguments', () => {
|
||||
expect(parseRules(['required', 'in:foo,bar'], rules)).toEqual([
|
||||
[rules.required, [], 'required', null],
|
||||
[rules.in, ['foo', 'bar'], 'in', null]
|
||||
])
|
||||
})
|
||||
|
||||
it('parses array rules with expression arguments', () => {
|
||||
expect(parseRules([
|
||||
['matches', /^abc/, '1234']
|
||||
], rules)).toEqual([
|
||||
[rules.matches, [/^abc/, '1234'], 'matches', null]
|
||||
])
|
||||
})
|
||||
|
||||
it('parses string rules with caret modifier', () => {
|
||||
expect(parseRules('^required|min:10', rules)).toEqual([
|
||||
[rules.required, [], 'required', '^'],
|
||||
[rules.min, ['10'], 'min', null],
|
||||
])
|
||||
})
|
||||
|
||||
it('parses array rule with caret modifier', () => {
|
||||
expect(parseRules([['required'], ['^max', '10']], rules)).toEqual([
|
||||
[rules.required, [], 'required', null],
|
||||
[rules.max, ['10'], 'max', '^'],
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
import { cloneDeep, isScalar, regexForFormat, snakeToCamel } from '@/libs/utils'
|
||||
|
||||
describe('regexForFormat', () => {
|
||||
it('allows MM format with other characters', () => expect(regexForFormat('abc/MM').test('abc/01')).toBe(true))
|
||||
|
@ -143,29 +143,29 @@ describe('between', () => {
|
||||
* Confirm
|
||||
*/
|
||||
describe('confirm', () => {
|
||||
it('passes when the values are the same strings', async () => expect(await rules.confirm(
|
||||
{ value: 'abc', name: 'password', getFormValues: () => ({ password_confirm: 'abc' }) }
|
||||
it('Passes when the values are the same strings', async () => expect(await rules.confirm(
|
||||
{ value: 'abc', name: 'password', formValues: { password_confirm: 'abc' } }
|
||||
)).toBe(true))
|
||||
|
||||
it('passes when the values are the same integers', async () => expect(await rules.confirm(
|
||||
{ value: 4422132, name: 'xyz', getFormValues: () => ({ xyz_confirm: 4422132 }) }
|
||||
it('Passes when the values are the same integers', async () => expect(await rules.confirm(
|
||||
{ value: 4422132, name: 'xyz', formValues: { xyz_confirm: 4422132 } }
|
||||
)).toBe(true))
|
||||
|
||||
it('passes when using a custom field', async () => expect(await rules.confirm(
|
||||
{ value: 4422132, name: 'name', getFormValues: () => ({ other_field: 4422132 }) },
|
||||
it('Passes when using a custom field', async () => expect(await rules.confirm(
|
||||
{ value: 4422132, name: 'name', formValues: { other_field: 4422132 } },
|
||||
'other_field'
|
||||
)).toBe(true))
|
||||
|
||||
it('passes when using a field ends in _confirm', async () => expect(await rules.confirm(
|
||||
{ value: '$ecret', name: 'password_confirm', getFormValues: () => ({ password: '$ecret' }) }
|
||||
it('Passes when using a field ends in _confirm', async () => expect(await rules.confirm(
|
||||
{ value: '$ecret', name: 'password_confirm', formValues: { password: '$ecret' } }
|
||||
)).toBe(true))
|
||||
|
||||
it('fails when using different strings', async () => expect(await rules.confirm(
|
||||
{ value: 'Justin', name: 'name', getFormValues: () => ({ name_confirm: 'Daniel' }) },
|
||||
it('Fails when using different strings', async () => expect(await rules.confirm(
|
||||
{ value: 'Justin', name: 'name', formValues: { name_confirm: 'Daniel' } },
|
||||
)).toBe(false))
|
||||
|
||||
it('fails when the types are different', async () => expect(await rules.confirm(
|
||||
{ value: '1234', name: 'num', getFormValues: () => ({ num_confirm: 1234 }) },
|
||||
it('Fails when the types are different', async () => expect(await rules.confirm(
|
||||
{ value: '1234', name: 'num', formValues: { num_confirm: 1234 } },
|
||||
)).toBe(false))
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user