Merge pull request #14 from cmath10/proposal-refactor#input
Refactored plugin install method; code style; typehints
This commit is contained in:
commit
95ebddc161
130
README.md
130
README.md
@ -1,86 +1,90 @@
|
||||
## What is Vue Formulario?
|
||||
|
||||
Vue Formulario is a library, based on <a href="https://vueformulate.com">Vue Formulate</a>, that handles the core logic for working with forms and gives full control on the form presentation.
|
||||
Vue Formulario is a library, based on <a href="https://vueformulate.com">Vue Formulate</a>, that handles the core logic
|
||||
for working with forms and gives full control on the form presentation.
|
||||
|
||||
## Examples
|
||||
|
||||
Every form control have to rendered inside FormularioInput component. This component provides `id` and `context` in v-slot props. Control should use `context.model` as v-model and `context.blurHandler` as handler for `blur` event (it is necessary for validation when property `errorBehavior` is `blur`). Errors object list for field can be accessed through `context.allErrors`. Each error is an object with fields message (translated message), rule (rule name) and context.
|
||||
Every form control have to rendered inside FormularioInput component. This component provides `id` and `context` in
|
||||
v-slot props. Control should use `context.model` as v-model and `context.runValidation` as handler for `blur` event
|
||||
(it is necessary for validation when property `validationBehavior` is `demand`). Errors list for a field can be
|
||||
accessed through `context.allErrors`.
|
||||
|
||||
The example below creates the authorization form from data:
|
||||
```json
|
||||
{
|
||||
"username": "",
|
||||
"password": "",
|
||||
"options": {
|
||||
"anonym": false,
|
||||
"tags": ["test"]
|
||||
},
|
||||
{
|
||||
"username": "",
|
||||
"password": "",
|
||||
"options": {
|
||||
"anonymous": false,
|
||||
"tags": ["test"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<FormularioForm
|
||||
v-model="formData"
|
||||
name="formName"
|
||||
<FormularioForm
|
||||
v-model="formData"
|
||||
name="formName"
|
||||
>
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
name="username"
|
||||
validation="required|email"
|
||||
validation-behavior="live"
|
||||
>
|
||||
<FormularioInput
|
||||
v-slot="vSlot"
|
||||
name="username"
|
||||
validation="required|email"
|
||||
error-behavior="live"
|
||||
<input
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
@blur="context.runValidation"
|
||||
>
|
||||
<input
|
||||
v-model="vSlot.context.model"
|
||||
type="text"
|
||||
@blur="vSlot.context.blurHandler"
|
||||
<div v-if="context.allErrors.length > 0">
|
||||
<span
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
>
|
||||
<div v-if="vSlot.context.showValidationErrors">
|
||||
<span
|
||||
v-for="(error, index) in vSlot.context.allErrors"
|
||||
:key="index"
|
||||
{{ error }}
|
||||
</span>
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
name="password"
|
||||
validation="required|min:4,length"
|
||||
>
|
||||
<input
|
||||
v-model="context.model"
|
||||
type="password"
|
||||
>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioGrouping name="options">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
name="anonymous"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
id="options-anonymous"
|
||||
v-model="context.model"
|
||||
type="checkbox"
|
||||
>
|
||||
{{ error.message }}
|
||||
</span>
|
||||
<label for="options-anonymous">As anonymous</label>
|
||||
</div>
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="vSlot"
|
||||
name="password"
|
||||
validation="required|min:4,length"
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
name="options.tags[0]"
|
||||
>
|
||||
<input
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
>
|
||||
<input
|
||||
v-model="vSlot.context.model"
|
||||
type="password"
|
||||
>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioGrouping name="options">
|
||||
<FormularioInput
|
||||
v-slot="vSlot"
|
||||
name="anonym"
|
||||
>
|
||||
<div>
|
||||
<input
|
||||
:id="vSlot.id"
|
||||
v-model="vSlot.context.model"
|
||||
type="checkbox"
|
||||
>
|
||||
<label :for="vSlot.id">As anonym</label>
|
||||
</div>
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="vSlot"
|
||||
name="options.tags[0]"
|
||||
>
|
||||
<input
|
||||
v-model="vSlot.context.model"
|
||||
type="text"
|
||||
>
|
||||
</FormularioInput>
|
||||
</FormularioForm>
|
||||
</FormularioInput>
|
||||
</FormularioForm>
|
||||
```
|
||||
|
||||
## License
|
||||
|
@ -1,45 +1,29 @@
|
||||
import { VueConstructor } from 'vue'
|
||||
|
||||
import merge from '@/utils/merge'
|
||||
import validationRules from '@/validation/rules'
|
||||
import validationMessages from '@/validation/messages'
|
||||
|
||||
import FormularioForm from '@/FormularioForm.vue'
|
||||
import FormularioGrouping from '@/FormularioGrouping.vue'
|
||||
import FormularioInput from '@/FormularioInput.vue'
|
||||
|
||||
import {
|
||||
ValidationContext,
|
||||
CheckRuleFn,
|
||||
CreateMessageFn,
|
||||
ValidationRuleFn,
|
||||
ValidationMessageFn,
|
||||
ValidationMessageI18NFn,
|
||||
} from '@/validation/validator'
|
||||
|
||||
interface FormularioOptions {
|
||||
validationRules?: any;
|
||||
validationMessages?: Record<string, Function>;
|
||||
export interface FormularioOptions {
|
||||
validationRules?: Record<string, ValidationRuleFn>;
|
||||
validationMessages?: Record<string, ValidationMessageI18NFn|string>;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
/**
|
||||
* The base formulario library.
|
||||
*/
|
||||
export default class Formulario {
|
||||
public validationRules: Record<string, CheckRuleFn> = {}
|
||||
public validationMessages: Record<string, Function> = {}
|
||||
public validationRules: Record<string, ValidationRuleFn> = {}
|
||||
public validationMessages: Record<string, ValidationMessageI18NFn|string> = {}
|
||||
|
||||
constructor () {
|
||||
constructor (options?: FormularioOptions) {
|
||||
this.validationRules = validationRules
|
||||
this.validationMessages = validationMessages
|
||||
}
|
||||
|
||||
/**
|
||||
* Install vue formulario, and register it’s components.
|
||||
*/
|
||||
install (Vue: VueConstructor, options?: FormularioOptions): void {
|
||||
Vue.prototype.$formulario = this
|
||||
Vue.component('FormularioForm', FormularioForm)
|
||||
Vue.component('FormularioGrouping', FormularioGrouping)
|
||||
Vue.component('FormularioInput', FormularioInput)
|
||||
|
||||
this.extend(options || {})
|
||||
}
|
||||
@ -59,16 +43,16 @@ export default class Formulario {
|
||||
/**
|
||||
* Get validation rules by merging any passed in with global rules.
|
||||
*/
|
||||
getRules (extendWith: Record<string, CheckRuleFn> = {}): Record<string, CheckRuleFn> {
|
||||
getRules (extendWith: Record<string, ValidationRuleFn> = {}): Record<string, ValidationRuleFn> {
|
||||
return merge(this.validationRules, extendWith)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validation messages by merging any passed in with global messages.
|
||||
*/
|
||||
getMessages (vm: Vue, extendWith: Record<string, Function>): Record<string, CreateMessageFn> {
|
||||
getMessages (vm: Vue, extendWith: Record<string, ValidationMessageI18NFn|string>): Record<string, ValidationMessageFn> {
|
||||
const raw = merge(this.validationMessages || {}, extendWith)
|
||||
const messages: Record<string, CreateMessageFn> = {}
|
||||
const messages: Record<string, ValidationMessageFn> = {}
|
||||
|
||||
for (const name in raw) {
|
||||
messages[name] = (context: ValidationContext, ...args: any[]): string => {
|
||||
|
@ -126,7 +126,7 @@ export default class FormularioForm extends Vue {
|
||||
|
||||
@Provide('formularioRegister')
|
||||
register (field: string, component: FormularioInput): void {
|
||||
this.registry.register(field, component)
|
||||
this.registry.add(field, component)
|
||||
}
|
||||
|
||||
@Provide('formularioDeregister')
|
||||
@ -154,7 +154,7 @@ export default class FormularioForm extends Vue {
|
||||
const newValue = getNested(values, registryKey)
|
||||
|
||||
if (!shallowEqualObjects(newValue, oldValue)) {
|
||||
this.setFieldValue(registryKey, newValue, false)
|
||||
this.setFieldValue(registryKey, newValue)
|
||||
proxyHasChanges = true
|
||||
}
|
||||
|
||||
@ -171,8 +171,7 @@ export default class FormularioForm extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
@Provide('formularioSetter')
|
||||
setFieldValue (field: string, value: any, emit = true): void {
|
||||
setFieldValue (field: string, value: any): void {
|
||||
if (value === undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { [field]: value, ...proxy } = this.proxy
|
||||
@ -180,10 +179,12 @@ export default class FormularioForm extends Vue {
|
||||
} else {
|
||||
setNested(this.proxy, field, value)
|
||||
}
|
||||
}
|
||||
|
||||
if (emit) {
|
||||
this.$emit('input', Object.assign({}, this.proxy))
|
||||
}
|
||||
@Provide('formularioSetter')
|
||||
setFieldValueAndEmit (field: string, value: any): void {
|
||||
this.setFieldValue(field, value)
|
||||
this.$emit('input', { ...this.proxy })
|
||||
}
|
||||
|
||||
setErrors ({ formErrors, inputErrors }: { formErrors?: string[]; inputErrors?: Record<string, string[]> }): void {
|
||||
|
@ -15,8 +15,8 @@ import {
|
||||
} from 'vue-property-decorator'
|
||||
import { arrayify, has, shallowEqualObjects, snakeToCamel } from './utils'
|
||||
import {
|
||||
CheckRuleFn,
|
||||
CreateMessageFn,
|
||||
ValidationRuleFn,
|
||||
ValidationMessageI18NFn,
|
||||
processConstraints,
|
||||
validate,
|
||||
Violation,
|
||||
@ -47,8 +47,8 @@ export default class FormularioInput extends Vue {
|
||||
}) name!: string
|
||||
|
||||
@Prop({ default: '' }) validation!: string|any[]
|
||||
@Prop({ default: () => ({}) }) validationRules!: Record<string, CheckRuleFn>
|
||||
@Prop({ default: () => ({}) }) validationMessages!: Record<string, CreateMessageFn|string>
|
||||
@Prop({ default: () => ({}) }) validationRules!: Record<string, ValidationRuleFn>
|
||||
@Prop({ default: () => ({}) }) validationMessages!: Record<string, ValidationMessageI18NFn|string>
|
||||
@Prop({
|
||||
default: VALIDATION_BEHAVIOR.DEMAND,
|
||||
validator: behavior => Object.values(VALIDATION_BEHAVIOR).includes(behavior)
|
||||
@ -69,10 +69,7 @@ export default class FormularioInput extends Vue {
|
||||
|
||||
get model (): any {
|
||||
const model = this.hasModel ? 'value' : 'proxy'
|
||||
if (this[model] === undefined) {
|
||||
return ''
|
||||
}
|
||||
return this[model]
|
||||
return this[model] !== undefined ? this[model] : ''
|
||||
}
|
||||
|
||||
set model (value: any) {
|
||||
@ -102,17 +99,17 @@ export default class FormularioInput extends Vue {
|
||||
})
|
||||
}
|
||||
|
||||
get normalizedValidationRules (): Record<string, CheckRuleFn> {
|
||||
const rules: Record<string, CheckRuleFn> = {}
|
||||
get normalizedValidationRules (): Record<string, ValidationRuleFn> {
|
||||
const rules: Record<string, ValidationRuleFn> = {}
|
||||
Object.keys(this.validationRules).forEach(key => {
|
||||
rules[snakeToCamel(key)] = this.validationRules[key]
|
||||
})
|
||||
return rules
|
||||
}
|
||||
|
||||
get normalizedValidationMessages (): Record<string, any> {
|
||||
const messages: Record<string, any> = {}
|
||||
Object.keys(this.validationMessages).forEach((key) => {
|
||||
get normalizedValidationMessages (): Record<string, ValidationMessageI18NFn|string> {
|
||||
const messages: Record<string, ValidationMessageI18NFn|string> = {}
|
||||
Object.keys(this.validationMessages).forEach(key => {
|
||||
messages[snakeToCamel(key)] = this.validationMessages[key]
|
||||
})
|
||||
return messages
|
||||
|
@ -20,10 +20,34 @@ export default class Registry {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the registry.
|
||||
* Fully register a component.
|
||||
* @param {string} field name of the field.
|
||||
* @param {FormularioForm} component the actual component instance.
|
||||
*/
|
||||
add (name: string, component: FormularioInput): void {
|
||||
this.registry.set(name, component)
|
||||
add (field: string, component: FormularioInput): void {
|
||||
if (this.registry.has(field)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.registry.set(field, component)
|
||||
|
||||
// @ts-ignore
|
||||
const value = getNested(this.ctx.initialValues, field)
|
||||
const hasModel = has(component.$options.propsData || {}, 'value')
|
||||
|
||||
// @ts-ignore
|
||||
if (!hasModel && this.ctx.hasInitialValue && value !== undefined) {
|
||||
// In the case that the form is carrying an initial value and the
|
||||
// element is not, set it directly.
|
||||
// @ts-ignore
|
||||
component.context.model = value
|
||||
// @ts-ignore
|
||||
} else if (hasModel && !shallowEqualObjects(component.proxy, value)) {
|
||||
// In this case, the field is v-modeled or has an initial value and the
|
||||
// form has no value or a different value, so use the field value
|
||||
// @ts-ignore
|
||||
this.ctx.setFieldValueAndEmit(field, component.proxy)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,40 +127,6 @@ export default class Registry {
|
||||
return Array.from(this.registry.keys())
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully register a component.
|
||||
* @param {string} field name of the field.
|
||||
* @param {FormularioForm} component the actual component instance.
|
||||
*/
|
||||
register (field: string, component: FormularioInput): void {
|
||||
if (this.registry.has(field)) {
|
||||
return
|
||||
}
|
||||
this.registry.set(field, component)
|
||||
const hasModel = has(component.$options.propsData || {}, 'value')
|
||||
if (
|
||||
!hasModel &&
|
||||
// @ts-ignore
|
||||
this.ctx.hasInitialValue &&
|
||||
// @ts-ignore
|
||||
getNested(this.ctx.initialValues, field) !== undefined
|
||||
) {
|
||||
// In the case that the form is carrying an initial value and the
|
||||
// element is not, set it directly.
|
||||
// @ts-ignore
|
||||
component.context.model = getNested(this.ctx.initialValues, field)
|
||||
} else if (
|
||||
hasModel &&
|
||||
// @ts-ignore
|
||||
!shallowEqualObjects(component.proxy, getNested(this.ctx.initialValues, field))
|
||||
) {
|
||||
// In this case, the field is v-modeled or has an initial value and the
|
||||
// form has no value or a different value, so use the field value
|
||||
// @ts-ignore
|
||||
this.ctx.setFieldValue(field, component.proxy)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the registry.
|
||||
* @param {function} callback
|
||||
|
28
src/index.ts
28
src/index.ts
@ -1,3 +1,27 @@
|
||||
import Formulario from '@/Formulario.ts'
|
||||
import Formulario, { FormularioOptions } from '@/Formulario.ts'
|
||||
import { VueConstructor } from 'vue'
|
||||
import FormularioForm from '@/FormularioForm.vue'
|
||||
import FormularioGrouping from '@/FormularioGrouping.vue'
|
||||
import FormularioInput from '@/FormularioInput.vue'
|
||||
|
||||
export default new Formulario()
|
||||
export default {
|
||||
install (Vue: VueConstructor, options?: FormularioOptions): void {
|
||||
Vue.component('FormularioForm', FormularioForm)
|
||||
Vue.component('FormularioGrouping', FormularioGrouping)
|
||||
Vue.component('FormularioInput', FormularioInput)
|
||||
|
||||
Vue.mixin({
|
||||
beforeCreate () {
|
||||
const o = this.$options as Record<string, any>
|
||||
|
||||
if (typeof o.formulario === 'function') {
|
||||
this.$formulario = o.formulario()
|
||||
} else if (o.parent && o.parent.$formulario) {
|
||||
this.$formulario = o.parent.$formulario
|
||||
} else {
|
||||
this.$formulario = new Formulario(options)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
@ -14,7 +14,13 @@ export default function clone (value: any): any {
|
||||
|
||||
for (const key in value) {
|
||||
if (has(value, key)) {
|
||||
copy[key] = isScalar(value[key]) ? value[key] : clone(value[key])
|
||||
if (isScalar(value[key])) {
|
||||
copy[key] = value[key]
|
||||
} else if (value instanceof Date) {
|
||||
copy[key] = new Date(copy[key])
|
||||
} else {
|
||||
copy[key] = clone(value[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { ValidationContext } from '@/validation/validator'
|
||||
import {
|
||||
ValidationContext,
|
||||
ValidationMessageI18NFn,
|
||||
} from '@/validation/validator'
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Message builders, names match rules names, see @/validation/rules
|
||||
*/
|
||||
const messages: Record<string, ValidationMessageI18NFn> = {
|
||||
/**
|
||||
* The default render method for error messages.
|
||||
* Fallback for rules without message builder
|
||||
* @param vm
|
||||
* @param context
|
||||
*/
|
||||
default (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Valid accepted value.
|
||||
*/
|
||||
accepted (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.accepted', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The date is not after.
|
||||
*/
|
||||
after (vm: Vue, context: ValidationContext, compare: string | false = false): string {
|
||||
if (typeof compare === 'string' && compare.length) {
|
||||
return vm.$t('validation.after.compare', context)
|
||||
@ -26,23 +28,14 @@ export default {
|
||||
return vm.$t('validation.after.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The value is not a letter.
|
||||
*/
|
||||
alpha (vm: Vue, context: Record<string, any>): string {
|
||||
alpha (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.alpha', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is alpha numeric
|
||||
*/
|
||||
alphanumeric (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.alphanumeric', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The date is not before.
|
||||
*/
|
||||
before (vm: Vue, context: ValidationContext, compare: string|false = false): string {
|
||||
if (typeof compare === 'string' && compare.length) {
|
||||
return vm.$t('validation.before.compare', context)
|
||||
@ -51,9 +44,6 @@ export default {
|
||||
return vm.$t('validation.before.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The value is not between two numbers or lengths
|
||||
*/
|
||||
between (vm: Vue, context: ValidationContext, from: number|any = 0, to: number|any = 10, force?: string): string {
|
||||
const data = { ...context, from, to }
|
||||
|
||||
@ -64,16 +54,10 @@ export default {
|
||||
return vm.$t('validation.between.default', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* The confirmation field does not match
|
||||
*/
|
||||
confirm (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.confirm', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Is not a valid date.
|
||||
*/
|
||||
date (vm: Vue, context: ValidationContext, format: string | false = false): string {
|
||||
if (typeof format === 'string' && format.length) {
|
||||
return vm.$t('validation.date.format', context)
|
||||
@ -82,24 +66,15 @@ export default {
|
||||
return vm.$t('validation.date.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Is not a valid email address.
|
||||
*/
|
||||
email (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.email.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Ends with specified value
|
||||
*/
|
||||
endsWith (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.endsWith.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Value is an allowed value.
|
||||
*/
|
||||
in: function (vm: Vue, context: ValidationContext): string {
|
||||
in (vm: Vue, context: ValidationContext): string {
|
||||
if (typeof context.value === 'string' && context.value) {
|
||||
return vm.$t('validation.in.string', context)
|
||||
}
|
||||
@ -107,16 +82,10 @@ export default {
|
||||
return vm.$t('validation.in.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Value is not a match.
|
||||
*/
|
||||
matches (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.matches.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
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)
|
||||
@ -129,9 +98,6 @@ export default {
|
||||
return vm.$tc('validation.max.default', maximum, context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
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)
|
||||
@ -144,38 +110,25 @@ export default {
|
||||
return vm.$tc('validation.min.default', minimum, context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The field is not an allowed value
|
||||
*/
|
||||
not (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.not.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* The field is not a number
|
||||
*/
|
||||
number (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.number.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Required field.
|
||||
*/
|
||||
required (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.required.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts with specified value
|
||||
*/
|
||||
startsWith (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.startsWith.default', context)
|
||||
},
|
||||
|
||||
/**
|
||||
* Value is not a url.
|
||||
*/
|
||||
url (vm: Vue, context: Record<string, any>): string {
|
||||
url (vm: Vue, context: ValidationContext): string {
|
||||
return vm.$t('validation.url.default', context)
|
||||
}
|
||||
}
|
||||
|
||||
export default messages
|
||||
|
@ -1,264 +1,264 @@
|
||||
import isUrl from 'is-url'
|
||||
import { shallowEqualObjects, regexForFormat, has } from '@/utils'
|
||||
import { ValidationContext } from '@/validation/validator'
|
||||
import { has, regexForFormat, shallowEqualObjects } from '@/utils'
|
||||
import {
|
||||
ValidationContext,
|
||||
ValidationRuleFn,
|
||||
} from '@/validation/validator'
|
||||
|
||||
interface DateValidationContext extends ValidationContext {
|
||||
value: Date|string;
|
||||
}
|
||||
|
||||
export default {
|
||||
const rules: Record<string, ValidationRuleFn> = {
|
||||
/**
|
||||
* Rule: the value must be "yes", "on", "1", or true
|
||||
*/
|
||||
accepted ({ value }: ValidationContext): Promise<boolean> {
|
||||
return Promise.resolve(['yes', 'on', '1', 1, true, 'true'].includes(value))
|
||||
accepted ({ value }: ValidationContext): boolean {
|
||||
return ['yes', 'on', '1', 1, true, 'true'].includes(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
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))
|
||||
after ({ value }: ValidationContext, compare: string | false = false): boolean {
|
||||
const compareTimestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const valueTimestamp = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
|
||||
return isNaN(valueTimestamp) ? false : (valueTimestamp > compareTimestamp)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is only alpha
|
||||
*/
|
||||
alpha ({ value }: { value: string }, set = 'default'): Promise<boolean> {
|
||||
alpha ({ value }: ValidationContext, set = 'default'): boolean {
|
||||
const sets: Record<string, RegExp> = {
|
||||
default: /^[a-zA-ZÀ-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z]+$/
|
||||
latin: /^[a-zA-Z]+$/,
|
||||
}
|
||||
const selectedSet = has(sets, set) ? set : 'default'
|
||||
|
||||
return Promise.resolve(sets[selectedSet].test(value))
|
||||
return typeof value === 'string' && sets[has(sets, set) ? set : 'default'].test(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is alpha numeric
|
||||
*/
|
||||
alphanumeric ({ value }: { value: string }, set = 'default'): Promise<boolean> {
|
||||
alphanumeric ({ value }: ValidationContext, set = 'default'): boolean {
|
||||
const sets: Record<string, RegExp> = {
|
||||
default: /^[a-zA-Z0-9À-ÖØ-öø-ÿ]+$/,
|
||||
latin: /^[a-zA-Z0-9]+$/
|
||||
}
|
||||
const selectedSet = has(sets, set) ? set : 'default'
|
||||
|
||||
return Promise.resolve(sets[selectedSet].test(value))
|
||||
return typeof value === 'string' && sets[has(sets, set) ? set : 'default'].test(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a value is after a given date. Defaults to current time
|
||||
*/
|
||||
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))
|
||||
before ({ value }: ValidationContext, compare: string|false = false): boolean {
|
||||
const compareTimestamp = compare !== false ? Date.parse(compare) : Date.now()
|
||||
const valueTimestamp = value instanceof Date ? value.getTime() : Date.parse(value)
|
||||
|
||||
return isNaN(valueTimestamp) ? false : (valueTimestamp < compareTimestamp)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is between two other values
|
||||
*/
|
||||
between ({ value }: { value: string|number }, from: number|any = 0, to: number|any = 10, force?: string): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
if (from === null || to === null || isNaN(from) || isNaN(to)) {
|
||||
return false
|
||||
}
|
||||
if ((!isNaN(Number(value)) && force !== 'length') || force === 'value') {
|
||||
value = Number(value)
|
||||
from = Number(from)
|
||||
to = Number(to)
|
||||
return (value > from && value < to)
|
||||
}
|
||||
if (typeof value === 'string' || force === 'length') {
|
||||
value = (!isNaN(Number(value)) ? value.toString() : value) as string
|
||||
return value.length > from && value.length < to
|
||||
}
|
||||
between ({ value }: ValidationContext, from: number|any = 0, to: number|any = 10, force?: string): boolean {
|
||||
if (from === null || to === null || isNaN(from) || isNaN(to)) {
|
||||
return false
|
||||
})())
|
||||
}
|
||||
|
||||
if ((!isNaN(Number(value)) && force !== 'length') || force === 'value') {
|
||||
value = Number(value)
|
||||
return (value > Number(from) && value < Number(to))
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || force === 'length') {
|
||||
value = (!isNaN(Number(value)) ? value.toString() : value) as string
|
||||
return value.length > from && value.length < to
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm that the value of one field is the same as another, mostly used
|
||||
* for password confirmations.
|
||||
*/
|
||||
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 formValues[confirmationFieldName] === value
|
||||
})())
|
||||
confirm ({ value, formValues, name }: ValidationContext, field?: string): boolean {
|
||||
let confirmationFieldName = field
|
||||
if (!confirmationFieldName) {
|
||||
confirmationFieldName = /_confirm$/.test(name) ? name.substr(0, name.length - 8) : `${name}_confirm`
|
||||
}
|
||||
return formValues[confirmationFieldName] === value
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: ensures the value is a date according to Date.parse(), or a format
|
||||
* regex.
|
||||
*/
|
||||
date ({ value }: { value: string }, format: string | false = false): Promise<boolean> {
|
||||
return Promise.resolve(format ? regexForFormat(format).test(value) : !isNaN(Date.parse(value)))
|
||||
date ({ value }: ValidationContext, format: string | false = false): boolean {
|
||||
return format ? regexForFormat(format).test(value) : !isNaN(Date.parse(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: tests
|
||||
*/
|
||||
email ({ value }: { value: string }): Promise<boolean> {
|
||||
email ({ value }: ValidationContext): boolean {
|
||||
if (!value) {
|
||||
return Promise.resolve(true)
|
||||
return true
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
const isEmail = /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
|
||||
return Promise.resolve(isEmail.test(value))
|
||||
return isEmail.test(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value ends with one of the given Strings
|
||||
*/
|
||||
endsWith ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
endsWith ({ value }: ValidationContext, ...stack: any[]): boolean {
|
||||
if (!value) {
|
||||
return Promise.resolve(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return Promise.resolve(stack.length === 0 || stack.some(str => value.endsWith(str)))
|
||||
return stack.length === 0 || stack.some(str => value.endsWith(str))
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value is in an array (stack).
|
||||
*/
|
||||
in ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(stack.some(item => {
|
||||
return typeof item === 'object' ? shallowEqualObjects(item, value) : item === value
|
||||
}))
|
||||
in ({ value }: ValidationContext, ...stack: any[]): boolean {
|
||||
return stack.some(item => typeof item === 'object' ? shallowEqualObjects(item, value) : item === value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Match the value against a (stack) of patterns or strings
|
||||
*/
|
||||
matches ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(!!stack.find(pattern => {
|
||||
matches ({ value }: ValidationContext, ...stack: any[]): boolean {
|
||||
return !!stack.find(pattern => {
|
||||
if (typeof pattern === 'string' && pattern.substr(0, 1) === '/' && pattern.substr(-1) === '/') {
|
||||
pattern = new RegExp(pattern.substr(1, pattern.length - 2))
|
||||
}
|
||||
|
||||
if (pattern instanceof RegExp) {
|
||||
return pattern.test(value)
|
||||
}
|
||||
|
||||
return pattern === value
|
||||
}))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the maximum value of a particular.
|
||||
*/
|
||||
max ({ value }: { value: any }, maximum: string | number = 10, force?: string): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
if (Array.isArray(value)) {
|
||||
maximum = !isNaN(Number(maximum)) ? Number(maximum) : maximum
|
||||
return value.length <= maximum
|
||||
}
|
||||
if ((!isNaN(value) && force !== 'length') || force === 'value') {
|
||||
value = !isNaN(value) ? Number(value) : value
|
||||
return value <= maximum
|
||||
}
|
||||
if (typeof value === 'string' || (force === 'length')) {
|
||||
value = !isNaN(value) ? value.toString() : value
|
||||
return value.length <= maximum
|
||||
}
|
||||
return false
|
||||
})())
|
||||
max ({ value }: ValidationContext, maximum: string | number = 10, force?: string): boolean {
|
||||
if (Array.isArray(value)) {
|
||||
maximum = !isNaN(Number(maximum)) ? Number(maximum) : maximum
|
||||
return value.length <= maximum
|
||||
}
|
||||
|
||||
if ((!isNaN(value) && force !== 'length') || force === 'value') {
|
||||
value = !isNaN(value) ? Number(value) : value
|
||||
return value <= maximum
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || (force === 'length')) {
|
||||
value = !isNaN(value) ? value.toString() : value
|
||||
return value.length <= maximum
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
})())
|
||||
min ({ value }: ValidationContext, minimum: number | any = 1, force?: string): 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.
|
||||
*/
|
||||
not ({ value }: { value: any }, ...stack: any[]): Promise<boolean> {
|
||||
return Promise.resolve(!stack.some(item => {
|
||||
return typeof item === 'object' ? shallowEqualObjects(item, value) : item === value
|
||||
}))
|
||||
not ({ value }: ValidationContext, ...stack: any[]): boolean {
|
||||
return !stack.some(item => typeof item === 'object' ? shallowEqualObjects(item, value) : item === value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if the value is only alpha numeric
|
||||
*/
|
||||
number ({ value }: { value: any }): Promise<boolean> {
|
||||
return Promise.resolve(String(value).length > 0 && !isNaN(Number(value)))
|
||||
number ({ value }: ValidationContext): boolean {
|
||||
return String(value).length > 0 && !isNaN(Number(value))
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: must be a value
|
||||
*/
|
||||
required ({ value }: { value: any }, isRequired: string|boolean = true): Promise<boolean> {
|
||||
return Promise.resolve(((): boolean => {
|
||||
if (!isRequired || ['no', 'false'].includes(isRequired as string)) {
|
||||
return true
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return !!value.length
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return !!value
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return (!value) ? false : !!Object.keys(value).length
|
||||
}
|
||||
required ({ value }: ValidationContext, isRequired: string|boolean = true): boolean {
|
||||
if (!isRequired || ['no', 'false'].includes(isRequired as string)) {
|
||||
return true
|
||||
})())
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return !!value.length
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return !!value
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return (!value) ? false : !!Object.keys(value).length
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: Value starts with one of the given Strings
|
||||
*/
|
||||
startsWith ({ value }: { value: any }, ...stack: string[]): Promise<boolean> {
|
||||
startsWith ({ value }: ValidationContext, ...stack: string[]): boolean {
|
||||
if (!value) {
|
||||
return Promise.resolve(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return Promise.resolve(stack.length === 0 || stack.some(str => value.startsWith(str)))
|
||||
return stack.length === 0 || stack.some(str => value.startsWith(str))
|
||||
}
|
||||
|
||||
return Promise.resolve(false)
|
||||
return false
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: checks if a string is a valid url
|
||||
*/
|
||||
url ({ value }: { value: string }): Promise<boolean> {
|
||||
return Promise.resolve(isUrl(value))
|
||||
url ({ value }: ValidationContext): boolean {
|
||||
return isUrl(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Rule: not a true rule — more like a compiler flag.
|
||||
*/
|
||||
bail (): Promise<boolean> {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
bail (): boolean {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
export default rules
|
||||
|
@ -11,14 +11,18 @@ export interface Violation {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface CheckRuleFn {
|
||||
export interface ValidationRuleFn {
|
||||
(context: ValidationContext, ...args: any[]): Promise<boolean>|boolean;
|
||||
}
|
||||
|
||||
export interface CreateMessageFn {
|
||||
export interface ValidationMessageFn {
|
||||
(context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationMessageI18NFn {
|
||||
(vm: Vue, context: ValidationContext, ...args: any[]): string;
|
||||
}
|
||||
|
||||
export interface ValidationContext {
|
||||
// The value of the field (do not mutate!),
|
||||
value: any;
|
||||
@ -34,21 +38,20 @@ export type ValidatorGroup = {
|
||||
}
|
||||
|
||||
export function createValidator (
|
||||
ruleFn: CheckRuleFn,
|
||||
ruleFn: ValidationRuleFn,
|
||||
ruleName: string|null,
|
||||
ruleArgs: any[],
|
||||
messageFn: CreateMessageFn
|
||||
messageFn: ValidationMessageFn
|
||||
): 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
|
||||
})
|
||||
return Promise.resolve(ruleFn(context, ...ruleArgs)).then(valid => {
|
||||
return !valid ? {
|
||||
rule: ruleName,
|
||||
args: ruleArgs,
|
||||
context,
|
||||
message: messageFn(context, ...ruleArgs),
|
||||
} : null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +64,8 @@ export function parseModifier (ruleName: string): [string, string|null] {
|
||||
|
||||
export function processSingleArrayConstraint (
|
||||
constraint: any[],
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
rules: Record<string, ValidationRuleFn>,
|
||||
messages: Record<string, ValidationMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
const args = constraint.slice()
|
||||
const first = args.shift()
|
||||
@ -95,8 +98,8 @@ export function processSingleArrayConstraint (
|
||||
|
||||
export function processSingleStringConstraint (
|
||||
constraint: string,
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
rules: Record<string, ValidationRuleFn>,
|
||||
messages: Record<string, ValidationMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
const args = constraint.split(':')
|
||||
const [name, modifier] = parseModifier(args.shift() || '')
|
||||
@ -118,9 +121,9 @@ export function processSingleStringConstraint (
|
||||
}
|
||||
|
||||
export function processSingleConstraint (
|
||||
constraint: string|Validator|[Validator|string, ...any[]],
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
constraint: Validator|string|[Validator|string, ...any[]],
|
||||
rules: Record<string, ValidationRuleFn>,
|
||||
messages: Record<string, ValidationMessageFn>
|
||||
): [Validator, string|null, string|null] {
|
||||
if (typeof constraint === 'function') {
|
||||
return [constraint, null, null]
|
||||
@ -139,8 +142,8 @@ export function processSingleConstraint (
|
||||
|
||||
export function processConstraints (
|
||||
constraints: string|any[],
|
||||
rules: Record<string, CheckRuleFn>,
|
||||
messages: Record<string, CreateMessageFn>
|
||||
rules: Record<string, ValidationRuleFn>,
|
||||
messages: Record<string, ValidationMessageFn>
|
||||
): [Validator, string|null, string|null][] {
|
||||
if (typeof constraints === 'string') {
|
||||
return processConstraints(constraints.split('|').filter(f => f.length), rules, messages)
|
||||
|
122
storybook/stories/ExampleAddressList.tale.vue
Normal file
122
storybook/stories/ExampleAddressList.tale.vue
Normal file
@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<FormularioForm v-model="values">
|
||||
<h1>Address list</h1>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="addressList"
|
||||
name="addressList"
|
||||
>
|
||||
<FormularioGrouping name="addressList">
|
||||
<FormularioGrouping
|
||||
v-for="(address, addressIndex) in addressList.context.model"
|
||||
:key="'address-' + addressIndex"
|
||||
:name="addressIndex"
|
||||
:is-array-item="true"
|
||||
class="row mx-n2"
|
||||
>
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
class="col col-auto px-2 mb-3"
|
||||
name="street"
|
||||
validation="required"
|
||||
>
|
||||
<label for="address-street">Street <span class="text-danger">*</span></label>
|
||||
<input
|
||||
id="address-street"
|
||||
v-model="context.model"
|
||||
class="field form-control"
|
||||
type="text"
|
||||
@blur="context.runValidation"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
class="col col-auto px-2 mb-3"
|
||||
name="building"
|
||||
validation="^required|number"
|
||||
>
|
||||
<label for="address-building">Building <span class="text-danger">*</span></label>
|
||||
<input
|
||||
id="address-building"
|
||||
v-model="context.model"
|
||||
class="field form-control"
|
||||
type="text"
|
||||
@blur="context.runValidation"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
</FormularioGrouping>
|
||||
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
@click="addAddressBlank"
|
||||
>
|
||||
Add address
|
||||
</button>
|
||||
</FormularioInput>
|
||||
</FormularioForm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormularioForm from '@/FormularioForm'
|
||||
import FormularioGrouping from '@/FormularioGrouping'
|
||||
import FormularioInput from '@/FormularioInput'
|
||||
|
||||
export default {
|
||||
name: 'ExampleAddressListTale',
|
||||
|
||||
components: {
|
||||
FormularioForm,
|
||||
FormularioGrouping,
|
||||
FormularioInput,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
values: {
|
||||
addressList: [],
|
||||
},
|
||||
}),
|
||||
|
||||
created () {
|
||||
this.$formulario.extend({
|
||||
validationMessages: {
|
||||
number: () => 'Value is not a number',
|
||||
required: () => 'Value is required',
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
addAddressBlank () {
|
||||
this.values.addressList.push({
|
||||
street: '',
|
||||
building: '',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.field {
|
||||
max-width: 250px;
|
||||
}
|
||||
</style>
|
@ -1,41 +1,106 @@
|
||||
<template>
|
||||
<FormularioForm v-model="values">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
class="mb-3"
|
||||
name="groups"
|
||||
validation="min:1"
|
||||
validation-behavior="live"
|
||||
<div>
|
||||
<div
|
||||
v-for="(item, groupIndex) in groups"
|
||||
:key="groupIndex"
|
||||
>
|
||||
<FormularioGroupingGroupTale v-model="context.model" />
|
||||
<FormularioGrouping :name="`groups[${groupIndex}]`">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
class="mb-3"
|
||||
name="text"
|
||||
validation="number|required"
|
||||
validation-behavior="live"
|
||||
>
|
||||
<label for="text-field">Text field (number|required)</label>
|
||||
<input
|
||||
id="text-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<div>{{ values }}</div>
|
||||
</FormularioForm>
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
:validation-messages="{ in: 'The value was different than expected' }"
|
||||
class="mb-3"
|
||||
name="abcdef-field"
|
||||
validation="in:abcdef"
|
||||
validation-behavior="live"
|
||||
>
|
||||
<label for="abcdef-field">Text field (in:abcdef)</label>
|
||||
<input
|
||||
id="abcdef-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
|
||||
<button @click="onRemoveGroup(groupIndex)">
|
||||
Remove Group
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button @click="onAddGroup">
|
||||
Add Group
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormularioForm from '@/FormularioForm'
|
||||
import FormularioGroupingGroupTale from './FormularioGroupingGroup.tale.vue'
|
||||
import FormularioGrouping from '@/FormularioGrouping'
|
||||
import FormularioInput from '@/FormularioInput'
|
||||
|
||||
export default {
|
||||
name: 'FormularioGroupingTale',
|
||||
name: 'FormularioGroupingGroupTale',
|
||||
|
||||
components: {
|
||||
FormularioForm,
|
||||
FormularioGroupingGroupTale,
|
||||
FormularioGrouping,
|
||||
FormularioInput,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
values: { groups: [] },
|
||||
}),
|
||||
model: {
|
||||
prop: 'groups',
|
||||
event: 'change'
|
||||
},
|
||||
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAddGroup () {
|
||||
this.$emit('change', this.groups.concat([{}]))
|
||||
},
|
||||
onRemoveGroup (removedIndex) {
|
||||
this.$emit('change', this.groups.filter((item, index) => {
|
||||
return index !== removedIndex
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,106 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-for="(item, groupIndex) in groups"
|
||||
:key="groupIndex"
|
||||
>
|
||||
<FormularioGrouping :name="`groups[${groupIndex}]`">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
class="mb-3"
|
||||
name="text"
|
||||
validation="number|required"
|
||||
validation-behavior="live"
|
||||
>
|
||||
<label for="text-field">Text field (number|required)</label>
|
||||
<input
|
||||
id="text-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
:validation-messages="{ in: 'The value was different than expected' }"
|
||||
class="mb-3"
|
||||
name="abcdef-field"
|
||||
validation="in:abcdef"
|
||||
validation-behavior="live"
|
||||
>
|
||||
<label for="abcdef-field">Text field (in:abcdef)</label>
|
||||
<input
|
||||
id="abcdef-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
</FormularioGrouping>
|
||||
|
||||
<button @click="onRemoveGroup(groupIndex)">
|
||||
Remove Group
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button @click="onAddGroup">
|
||||
Add Group
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormularioGrouping from '@/FormularioGrouping'
|
||||
import FormularioInput from '@/FormularioInput'
|
||||
|
||||
export default {
|
||||
name: 'FormularioGroupingGroupTale',
|
||||
|
||||
components: {
|
||||
FormularioGrouping,
|
||||
FormularioInput,
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'groups',
|
||||
event: 'change'
|
||||
},
|
||||
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAddGroup () {
|
||||
this.$emit('change', this.groups.concat([{}]))
|
||||
},
|
||||
onRemoveGroup (removedIndex) {
|
||||
this.$emit('change', this.groups.filter((item, index) => {
|
||||
return index !== removedIndex
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<FormularioForm v-model="values">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
:validation-messages="{
|
||||
number: 'The value is not a number',
|
||||
required: 'Value required',
|
||||
}"
|
||||
class="mb-3"
|
||||
name="number"
|
||||
validation="required"
|
||||
>
|
||||
<label for="text-field">Text field (required)</label>
|
||||
<input
|
||||
id="text-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
@blur="context.runValidation()"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
:validation-messages="{ in: 'The value is not in range (abc,def)' }"
|
||||
class="mb-3"
|
||||
name="abcdef-field"
|
||||
validation="in:abc,def"
|
||||
>
|
||||
<label for="abcdef-field">Text field (in:abc,def)</label>
|
||||
<input
|
||||
id="abcdef-field"
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 250px;"
|
||||
@blur="context.runValidation()"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(error, index) in context.allErrors"
|
||||
:key="index"
|
||||
class="text-danger"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
</FormularioInput>
|
||||
|
||||
<div>{{ values }}</div>
|
||||
</FormularioForm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormularioForm from '@/FormularioForm'
|
||||
import FormularioInput from '@/FormularioInput'
|
||||
|
||||
export default {
|
||||
name: 'FormularioInputTale',
|
||||
|
||||
components: {
|
||||
FormularioForm,
|
||||
FormularioInput,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
values: {},
|
||||
}),
|
||||
}
|
||||
</script>
|
@ -5,8 +5,8 @@ import { storiesOf } from '@storybook/vue'
|
||||
import Vue from 'vue'
|
||||
import VueFormulario from '../../dist/formulario.esm'
|
||||
|
||||
import FormularioGroupingTale from './FormularioGrouping.tale'
|
||||
import FormularioInputTale from './FormularioInput.tale'
|
||||
import ExampleAddressList from './ExampleAddressList.tale'
|
||||
import FormularioGrouping from './FormularioGrouping.tale'
|
||||
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
@ -20,6 +20,6 @@ Vue.mixin({
|
||||
})
|
||||
Vue.use(VueFormulario)
|
||||
|
||||
storiesOf('FormularioInput', module)
|
||||
.add('Default', () => FormularioInputTale)
|
||||
.add('Grouping', () => FormularioGroupingTale)
|
||||
storiesOf('Examples', module)
|
||||
.add('Address list', () => ExampleAddressList)
|
||||
.add('FormularioGrouping', () => FormularioGrouping)
|
||||
|
@ -1,18 +1,62 @@
|
||||
import Formulario from '@/index.ts'
|
||||
import { createLocalVue, mount } from '@vue/test-utils'
|
||||
import Formulario from '@/Formulario.ts'
|
||||
import plugin from '@/index.ts'
|
||||
|
||||
describe('Formulario', () => {
|
||||
it('Installs on vue instance', () => {
|
||||
const registry = []
|
||||
function Vue () {}
|
||||
Vue.component = function (name, instance) {
|
||||
registry.push(name)
|
||||
}
|
||||
Formulario.install(Vue)
|
||||
expect(Vue.prototype.$formulario).toBe(Formulario)
|
||||
expect(registry).toEqual([
|
||||
'FormularioForm',
|
||||
'FormularioGrouping',
|
||||
'FormularioInput',
|
||||
])
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(plugin)
|
||||
|
||||
expect(localVue.component('FormularioForm')).toBeTruthy()
|
||||
expect(localVue.component('FormularioGrouping')).toBeTruthy()
|
||||
expect(localVue.component('FormularioInput')).toBeTruthy()
|
||||
|
||||
const wrapper = mount({ template: '<div />', }, { localVue })
|
||||
|
||||
expect(wrapper.vm.$formulario).toBeInstanceOf(Formulario)
|
||||
})
|
||||
|
||||
it ('Pushes Formulario instance to child a component', () => {
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(plugin)
|
||||
localVue.component('TestComponent', {
|
||||
render (h) {
|
||||
return h('div')
|
||||
}
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
render (h) {
|
||||
return h('div', [h('TestComponent', { ref: 'test' })])
|
||||
},
|
||||
}, { localVue })
|
||||
|
||||
expect(wrapper.vm.$formulario === wrapper.vm.$refs.test.$formulario).toBe(true)
|
||||
})
|
||||
|
||||
it ('Does not pushes Formulario instance to a child component, if it has its own', () => {
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(plugin)
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
localVue.component('TestComponent', {
|
||||
formulario () {
|
||||
return new Formulario()
|
||||
},
|
||||
|
||||
render (h) {
|
||||
return h('div')
|
||||
},
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
render (h) {
|
||||
return h('div', [h('TestComponent', { ref: 'test' })])
|
||||
},
|
||||
}, { localVue })
|
||||
|
||||
expect(wrapper.vm.$formulario === wrapper.vm.$refs.test.$formulario).toBe(false)
|
||||
})
|
||||
})
|
||||
|
@ -48,7 +48,7 @@ describe('FormularioForm', () => {
|
||||
`
|
||||
}
|
||||
})
|
||||
expect(wrapper.vm.registry.keys()).toEqual(['sub1', 'sub2'])
|
||||
expect(wrapper.vm['registry'].keys()).toEqual(['sub1', 'sub2'])
|
||||
})
|
||||
|
||||
it('Removes subcomponents from the registry', async () => {
|
||||
@ -62,10 +62,10 @@ describe('FormularioForm', () => {
|
||||
`
|
||||
})
|
||||
await flushPromises()
|
||||
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['sub1', 'sub2'])
|
||||
expect(wrapper.findComponent(FormularioForm).vm['registry'].keys()).toEqual(['sub1', 'sub2'])
|
||||
wrapper.setData({ active: false })
|
||||
await flushPromises()
|
||||
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['sub2'])
|
||||
expect(wrapper.findComponent(FormularioForm).vm['registry'].keys()).toEqual(['sub2'])
|
||||
})
|
||||
|
||||
it('Getting nested fields from registry', async () => {
|
||||
@ -347,10 +347,21 @@ describe('FormularioForm', () => {
|
||||
it('Emits correct validation event on entry', async () => {
|
||||
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.runValidation()">
|
||||
<FormularioInput
|
||||
v-slot="{ context }"
|
||||
name="firstField"
|
||||
validation="required|in:foo"
|
||||
>
|
||||
<input
|
||||
v-model="context.model"
|
||||
type="text"
|
||||
@blur="context.runValidation()"
|
||||
>
|
||||
</FormularioInput>
|
||||
<FormularioInput name="bar" validation="required" />
|
||||
<FormularioInput
|
||||
name="secondField"
|
||||
validation="required"
|
||||
/>
|
||||
` }
|
||||
})
|
||||
wrapper.find('input[type="text"]').setValue('bar')
|
||||
@ -361,37 +372,21 @@ describe('FormularioForm', () => {
|
||||
expect(wrapper.emitted('validation')).toBeTruthy()
|
||||
expect(wrapper.emitted('validation').length).toBe(1)
|
||||
expect(wrapper.emitted('validation')[0][0]).toEqual({
|
||||
name: 'foo',
|
||||
violations: [ expect.any(Object) ], // @TODO: Check object structure
|
||||
name: 'firstField',
|
||||
violations: [ {
|
||||
rule: expect.any(String),
|
||||
args: ['foo'],
|
||||
context: {
|
||||
value: 'bar',
|
||||
formValues: expect.any(Object),
|
||||
name: 'firstField',
|
||||
},
|
||||
message: expect.any(String),
|
||||
} ],
|
||||
})
|
||||
})
|
||||
|
||||
return
|
||||
|
||||
it('Removes field data when that field is de-registered', async () => {
|
||||
const wrapper = mount({
|
||||
data: () => ({ values: {} }),
|
||||
template: `
|
||||
<FormularioForm v-model="values">
|
||||
<FormularioInput v-slot="{ context }" name="foo">
|
||||
<input v-model="context.model" type="text" value="abc123">
|
||||
</FormularioInput>
|
||||
<FormularioInput v-if="values.foo !== 'bar'" name="bar" value="1" />
|
||||
</FormularioForm>
|
||||
`,
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
wrapper.find('input[type="text"]').setValue('bar')
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.findComponent(FormularioForm).vm.proxy).toEqual({ foo: 'bar' })
|
||||
expect(wrapper.vm['values']).toEqual({ foo: 'bar' })
|
||||
})
|
||||
|
||||
it('Allows resetting a form, hiding validation and clearing inputs.', async () => {
|
||||
it('Allows resetting a form, wiping validation.', async () => {
|
||||
const wrapper = mount({
|
||||
data: () => ({ values: {} }),
|
||||
template: `
|
||||
@ -411,6 +406,7 @@ describe('FormularioForm', () => {
|
||||
})
|
||||
|
||||
const password = wrapper.find('input[type="password"]')
|
||||
|
||||
password.setValue('foo')
|
||||
password.trigger('blur')
|
||||
|
||||
@ -422,10 +418,9 @@ describe('FormularioForm', () => {
|
||||
// First make sure we caught the errors
|
||||
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(1)
|
||||
wrapper.vm.$refs.form.resetValidation()
|
||||
wrapper.vm.$refs.form.setValues({ })
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(0)
|
||||
expect(wrapper.vm['values']).toEqual({})
|
||||
})
|
||||
})
|
||||
|
@ -1,473 +1,428 @@
|
||||
import rules from '@/validation/rules.ts'
|
||||
|
||||
/**
|
||||
* Accepted rule
|
||||
*/
|
||||
const today = new Date()
|
||||
const tomorrow = new Date()
|
||||
const yesterday = new Date()
|
||||
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
yesterday.setDate(today.getDate() - 1)
|
||||
|
||||
describe('accepted', () => {
|
||||
it('passes with true', async () => expect(await rules.accepted({ value: 'yes' })).toBe(true))
|
||||
const validate = value => rules.accepted({ value, name: '', formValues: {} })
|
||||
const expectPass = value => expect(validate(value)).toBe(true)
|
||||
const expectFail = value => expect(validate(value)).toBe(false)
|
||||
|
||||
it('passes with on', async () => expect(await rules.accepted({ value: 'on' })).toBe(true))
|
||||
|
||||
it('passes with 1', async () => expect(await rules.accepted({ value: '1' })).toBe(true))
|
||||
|
||||
it('passes with number 1', async () => expect(await rules.accepted({ value: 1 })).toBe(true))
|
||||
|
||||
it('passes with boolean true', async () => expect(await rules.accepted({ value: true })).toBe(true))
|
||||
|
||||
it('fail with boolean false', async () => expect(await rules.accepted({ value: false })).toBe(false))
|
||||
|
||||
it('fail with "false"', async () => expect(await rules.accepted({ value: 'false' })).toBe(false))
|
||||
it('passes with true', () => expectPass('yes'))
|
||||
it('passes with on', () => expectPass('on'))
|
||||
it('passes with 1', () => expectPass('1'))
|
||||
it('passes with number 1', () => expectPass(1))
|
||||
it('passes with boolean true', () => expectPass(true))
|
||||
it('fail with boolean false', () => expectFail(false))
|
||||
it('fail with "false"', () => expectFail('false'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a date is after another date
|
||||
*/
|
||||
describe('after', () => {
|
||||
const today = new Date()
|
||||
const tomorrow = new Date()
|
||||
const yesterday = new Date()
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
yesterday.setDate(today.getDate() - 1)
|
||||
const validate = (value, compare = false) => rules.after({ value, name: '', formValues: {} }, compare)
|
||||
const expectPass = (value, compare = false) => expect(validate(value, compare)).toBe(true)
|
||||
const expectFail = (value, compare = false) => expect(validate(value, compare)).toBe(false)
|
||||
|
||||
it('passes with tomorrow’s date object', async () => expect(await rules.after({ value: tomorrow })).toBe(true))
|
||||
|
||||
it('passes with future date', async () => expect(await rules.after({ value: 'January 15, 2999' })).toBe(true))
|
||||
|
||||
it('passes with long past date', async () => expect(await rules.after({ value: yesterday }, 'Jan 15, 2000')).toBe(true))
|
||||
|
||||
it('fails with yesterday’s date', async () => expect(await rules.after({ value: yesterday })).toBe(false))
|
||||
|
||||
it('fails with old date string', async () => expect(await rules.after({ value: 'January, 2000' })).toBe(false))
|
||||
|
||||
it('fails with invalid value', async () => expect(await rules.after({ value: '' })).toBe(false))
|
||||
it('passes with tomorrow’s date object', () => expectPass(tomorrow))
|
||||
it('passes with future date', () => expectPass('January 15, 2999'))
|
||||
it('passes with long past date', () => expectPass(yesterday, 'Jan 15, 2000'))
|
||||
it('fails with yesterday’s date', () => expectFail(yesterday))
|
||||
it('fails with old date string', () => expectFail('January, 2000'))
|
||||
it('fails with invalid value', () => expectFail(''))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a date is after another date
|
||||
*/
|
||||
describe('alpha', () => {
|
||||
it('passes with simple string', async () => expect(await rules.alpha({ value: 'abc' })).toBe(true))
|
||||
const validate = (value, set = 'default') => rules.alpha({ value, name: '', formValues: {} }, set)
|
||||
|
||||
it('passes with long string', async () => expect(await rules.alpha({ value: 'lkashdflaosuihdfaisudgflakjsdbflasidufg' })).toBe(true))
|
||||
it('passes with simple string', () => {
|
||||
expect(validate('abc')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with single character', async () => expect(await rules.alpha({ value: 'z' })).toBe(true))
|
||||
it('passes with long string', () => {
|
||||
expect(validate('lkashdflaosuihdfaisudgflakjsdbflasidufg')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with accented character', async () => expect(await rules.alpha({ value: 'jüstin' })).toBe(true))
|
||||
it('passes with single character', () => {
|
||||
expect(validate('z')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with lots of accented characters', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' })).toBe(true))
|
||||
it('passes with accented character', () => {
|
||||
expect(validate('jüstin')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' }, 'russian')).toBe(true))
|
||||
it('passes with lots of accented characters', () => {
|
||||
expect(validate('àáâäïíôöÆ')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails with lots of accented characters if latin', async () => expect(await rules.alpha({ value: 'àáâäïíôöÆ' }, 'latin')).toBe(false))
|
||||
it('passes with lots of accented characters if invalid set', () => {
|
||||
expect(validate('àáâäïíôöÆ', 'russian')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails with numbers', async () => expect(await rules.alpha({ value: 'justin83' })).toBe(false))
|
||||
it('fails with lots of accented characters if latin', () => {
|
||||
expect(validate('àáâäïíôöÆ', 'latin')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails with symbols', async () => expect(await rules.alpha({ value: '-justin' })).toBe(false))
|
||||
it('fails with numbers', () => {
|
||||
expect(validate('justin83')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails with symbols', () => {
|
||||
expect(validate('-justin')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a date alpha and numeric
|
||||
*/
|
||||
describe('alphanumeric', () => {
|
||||
it('passes with simple string', async () => expect(await rules.alphanumeric({ value: '567abc' })).toBe(true))
|
||||
const validate = (value, set = 'default') => rules.alphanumeric({ value, name: '', formValues: {} }, set)
|
||||
|
||||
it('passes with long string', async () => expect(await rules.alphanumeric({ value: 'lkashdfla234osuihdfaisudgflakjsdbfla567sidufg' })).toBe(true))
|
||||
it('passes with simple string', () => {
|
||||
expect(validate('567abc')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with single character', async () => expect(await rules.alphanumeric({ value: 'z' })).toBe(true))
|
||||
it('passes with long string', () => {
|
||||
expect(validate('lkashdfla234osuihdfaisudgflakjsdbfla567sidufg')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with accented character', async () => expect(await rules.alphanumeric({ value: 'jüst56in' })).toBe(true))
|
||||
it('passes with single character', () => {
|
||||
expect(validate('z')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with lots of accented characters', async () => expect(await rules.alphanumeric({ value: 'àáâ7567567äïíôöÆ' })).toBe(true))
|
||||
it('passes with accented character', () => {
|
||||
expect(validate('jüst56in')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes with lots of accented characters if invalid set', async () => expect(await rules.alphanumeric({ value: '123123àáâäï67íôöÆ' }, 'russian')).toBe(true))
|
||||
it('passes with lots of accented characters', () => {
|
||||
expect(validate('àáâ7567567äïíôöÆ')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails with lots of accented characters if latin', async () => expect(await rules.alphanumeric({ value: 'àáâäï123123íôöÆ' }, 'latin')).toBe(false))
|
||||
it('passes with lots of accented characters if invalid set', () => {
|
||||
expect(validate('123123àáâäï67íôöÆ', 'russian')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails with decimals in', async () => expect(await rules.alphanumeric({ value: 'abcABC99.123' })).toBe(false))
|
||||
it('fails with lots of accented characters if latin', () => {
|
||||
expect(validate('àáâäï123123íôöÆ', 'latin')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails with decimals in', () => {
|
||||
expect(validate('abcABC99.123')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a date is after another date
|
||||
*/
|
||||
describe('before', () => {
|
||||
const today = new Date()
|
||||
const tomorrow = new Date()
|
||||
const yesterday = new Date()
|
||||
tomorrow.setDate(today.getDate() + 1)
|
||||
yesterday.setDate(today.getDate() - 1)
|
||||
const validate = (value, compare = false) => rules.before({ value, name: '', formValues: {} }, compare)
|
||||
const expectPass = (value, compare = false) => expect(validate(value, compare)).toBe(true)
|
||||
const expectFail = (value, compare = false) => expect(validate(value, compare)).toBe(false)
|
||||
|
||||
it('fails with tomorrow’s date object', async () => expect(await rules.before({ value: tomorrow })).toBe(false))
|
||||
|
||||
it('fails with future date', async () => expect(await rules.before({ value: 'January 15, 2999' })).toBe(false))
|
||||
|
||||
it('fails with long past date', async () => expect(await rules.before({ value: yesterday }, 'Jan 15, 2000')).toBe(false))
|
||||
|
||||
it('passes with yesterday’s date', async () => expect(await rules.before({ value: yesterday })).toBe(true))
|
||||
|
||||
it('passes with old date string', async () => expect(await rules.before({ value: 'January, 2000' })).toBe(true))
|
||||
|
||||
it('fails with invalid value', async () => expect(await rules.after({ value: '' })).toBe(false))
|
||||
it('fails with tomorrow’s date object', () => expectFail(tomorrow))
|
||||
it('fails with future date', () => expectFail('January 15, 2999'))
|
||||
it('fails with long past date', () => expectFail(yesterday, 'Jan 15, 2000'))
|
||||
it('passes with yesterday’s date', () => expectPass(yesterday))
|
||||
it('passes with old date string', () => expectPass('January, 2000'))
|
||||
it('fails with invalid value', () => expectFail(''))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if between
|
||||
*/
|
||||
describe('between', () => {
|
||||
it('passes with simple number', async () => expect(await rules.between({ value: 5 }, 0, 10)).toBe(true))
|
||||
const validate = (value, from, to, force = undefined) => {
|
||||
return rules.between({value, name: '', formValues: {}}, from, to, force)
|
||||
}
|
||||
|
||||
it('passes with simple number string', async () => expect(await rules.between({ value: '5' }, '0', '10')).toBe(true))
|
||||
const expectPass = (value, from, to, force = undefined) => expect(validate(value, from, to, force)).toBe(true)
|
||||
const expectFail = (value, from, to, force = undefined) => expect(validate(value, from, to, force)).toBe(false)
|
||||
|
||||
it('passes with decimal number string', async () => expect(await rules.between({ value: '0.5' }, '0', '1')).toBe(true))
|
||||
|
||||
it('passes with string length', async () => expect(await rules.between({ value: 'abc' }, 2, 4)).toBe(true))
|
||||
|
||||
it('fails with string length too long', async () => expect(await rules.between({ value: 'abcdef' }, 2, 4)).toBe(false))
|
||||
|
||||
it('fails with string length too short', async () => expect(await rules.between({ value: 'abc' }, 3, 10)).toBe(false))
|
||||
|
||||
it('fails with number to small', async () => expect(await rules.between({ value: 0 }, 3, 10)).toBe(false))
|
||||
|
||||
it('fails with number to large', async () => expect(await rules.between({ value: 15 }, 3, 10)).toBe(false))
|
||||
|
||||
it('passes when forced to value', async () => expect(await rules.between({ value: '4' }, 3, 10, 'value')).toBe(true))
|
||||
|
||||
it('fails when forced to value', async () => expect(await rules.between({ value: 442 }, 3, 10, 'value')).toBe(false))
|
||||
|
||||
it('passes when forced to length', async () => expect(await rules.between({ value: 7442 }, 3, 10, 'length')).toBe(true))
|
||||
|
||||
it('fails when forced to length', async () => expect(await rules.between({ value: 6 }, 3, 10, 'length')).toBe(false))
|
||||
it('passes with simple number', () => expectPass(5, 0, 10))
|
||||
it('passes with simple number string', () => expectPass('5', '0', '10'))
|
||||
it('passes with decimal number string', () => expectPass('0.5', '0', '1'))
|
||||
it('passes with string length', () => expectPass('abc', 2, 4))
|
||||
it('fails with string length too long', () => expectFail('abcdef', 2, 4))
|
||||
it('fails with string length too short', () => expectFail('abc', 3, 10))
|
||||
it('fails with number too small', () => expectFail(0, 3, 10))
|
||||
it('fails with number too large', () => expectFail(15, 3, 10))
|
||||
it('passes when forced to value', () => expectPass('4', 3, 10, 'value'))
|
||||
it('fails when forced to value', () => expectFail(442, 3, 10, 'value'))
|
||||
it('passes when forced to length', () => expectPass(7442, 3, 10, 'length'))
|
||||
it('fails when forced to length', () => expectFail(6, 3, 10, 'length'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Confirm
|
||||
*/
|
||||
describe('confirm', () => {
|
||||
it('Passes when the values are the same strings', async () => expect(await rules.confirm(
|
||||
{ value: 'abc', name: 'password', formValues: { password_confirm: 'abc' } }
|
||||
)).toBe(true))
|
||||
const validate = (context, field = undefined) => rules.confirm(context, field)
|
||||
const expectPass = (context, field = undefined) => expect(validate(context, field)).toBe(true)
|
||||
const expectFail = (context, field = undefined) => expect(validate(context, field)).toBe(false)
|
||||
|
||||
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 the values are the same strings', () => expectPass({
|
||||
value: 'abc',
|
||||
name: 'password',
|
||||
formValues: { password_confirm: 'abc' }
|
||||
}))
|
||||
|
||||
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 the values are the same integers', () => expectPass({
|
||||
value: 4422132,
|
||||
name: 'xyz',
|
||||
formValues: { xyz_confirm: 4422132 }
|
||||
}))
|
||||
|
||||
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('Passes when using a custom field', () => expectPass({
|
||||
value: 4422132,
|
||||
name: 'name',
|
||||
formValues: { other_field: 4422132 }
|
||||
}, 'other_field'))
|
||||
|
||||
it('Fails when using different strings', async () => expect(await rules.confirm(
|
||||
{ value: 'Justin', name: 'name', formValues: { name_confirm: 'Daniel' } },
|
||||
)).toBe(false))
|
||||
it('Passes when using a field ends in _confirm', () => expectPass({
|
||||
value: '$ecret',
|
||||
name: 'password_confirm',
|
||||
formValues: { password: '$ecret' }
|
||||
}))
|
||||
|
||||
it('Fails when the types are different', async () => expect(await rules.confirm(
|
||||
{ value: '1234', name: 'num', formValues: { num_confirm: 1234 } },
|
||||
)).toBe(false))
|
||||
it('Fails when using different strings', () => expectFail({
|
||||
value: 'Justin',
|
||||
name: 'name',
|
||||
formValues: { name_confirm: 'Daniel' }
|
||||
}))
|
||||
|
||||
it('Fails when the types are different', () => expectFail({
|
||||
value: '1234',
|
||||
name: 'num',
|
||||
formValues: { num_confirm: 1234 }
|
||||
}))
|
||||
})
|
||||
|
||||
/**
|
||||
* Determines if the string is a date
|
||||
*/
|
||||
describe('date', () => {
|
||||
it('passes with month day year', async () => expect(await rules.date({ value: 'December 17, 2020' })).toBe(true))
|
||||
|
||||
it('passes with month day', async () => expect(await rules.date({ value: 'December 17' })).toBe(true))
|
||||
|
||||
it('passes with short month day', async () => expect(await rules.date({ value: 'Dec 17' })).toBe(true))
|
||||
|
||||
it('passes with short month day', async () => expect(await rules.date({ value: 'Dec 17 12:34:15' })).toBe(true))
|
||||
|
||||
it('passes with out of bounds number', async () => expect(await rules.date({ value: 'January 77' })).toBe(true))
|
||||
|
||||
it('passes with only month', async () => expect(await rules.date({ value: 'January' })).toBe(false))
|
||||
|
||||
it('passes with valid date format', async () => expect(await rules.date({ value: '12/17/1987' }, 'MM/DD/YYYY')).toBe(true))
|
||||
|
||||
it('fails with simple number and date format', async () => expect(await rules.date({ value: '1234' }, 'MM/DD/YYYY')).toBe(false))
|
||||
|
||||
it('fails with only day of week', async () => expect(await rules.date({ value: 'saturday' })).toBe(false))
|
||||
|
||||
it('fails with random string', async () => expect(await rules.date({ value: 'Pepsi 17' })).toBe(false))
|
||||
|
||||
it('fails with random number', async () => expect(await rules.date({ value: '1872301237' })).toBe(false))
|
||||
const validate = (value, format = false) => rules.date({ value, name: '', formValues: {} }, format)
|
||||
const expectPass = (value, compare = false) => expect(validate(value, compare)).toBe(true)
|
||||
const expectFail = (value, compare = false) => expect(validate(value, compare)).toBe(false)
|
||||
|
||||
it('passes with month day year', () => expectPass('December 17, 2020'))
|
||||
it('passes with month day', () => expectPass('December 17'))
|
||||
it('passes with short month day', () => expectPass('Dec 17'))
|
||||
it('passes with short month day and time', () => expectPass('Dec 17 12:34:15'))
|
||||
it('passes with out of bounds number', () => expectPass('January 77'))
|
||||
it('fails with only month', () => expectFail('January'))
|
||||
it('passes with valid date format', () => expectPass('12/17/1987', 'MM/DD/YYYY'))
|
||||
it('fails with simple number and date format', () => expectFail('1234', 'MM/DD/YYYY'))
|
||||
it('fails with only day of week', () => expectFail('saturday'))
|
||||
it('fails with random string', () => expectFail('Pepsi 17'))
|
||||
it('fails with random number', () => expectFail('1872301237'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if email.
|
||||
*
|
||||
* Note: testing is light, regular expression used is here: http://jsfiddle.net/ghvj4gy9/embedded/result,js/
|
||||
*/
|
||||
describe('email', () => {
|
||||
it('passes normal email', async () => expect(await rules.email({ value: 'dev+123@wearebraid.com' })).toBe(true))
|
||||
const validate = value => rules.email({ value, name: '', formValues: {} })
|
||||
const expectPass = value => expect(validate(value)).toBe(true)
|
||||
const expectFail = value => expect(validate(value)).toBe(false)
|
||||
|
||||
it('passes numeric email', async () => expect(await rules.email({ value: '12345@google.com' })).toBe(true))
|
||||
|
||||
it('passes unicode email', async () => expect(await rules.email({ value: 'àlphä@❤️.ly' })).toBe(true))
|
||||
|
||||
it('passes numeric with new tld', async () => expect(await rules.email({ value: '12345@google.photography' })).toBe(true))
|
||||
|
||||
it('fails string without tld', async () => expect(await rules.email({ value: '12345@localhost' })).toBe(false))
|
||||
|
||||
it('fails string without invalid name', async () => expect(await rules.email({ value: '1*(123)2345@localhost' })).toBe(false))
|
||||
it('passes normal email', () => expectPass('dev+123@wearebraid.com'))
|
||||
it('passes numeric email', () => expectPass('12345@google.com'))
|
||||
it('passes unicode email', () => expectPass('àlphä@❤️.ly'))
|
||||
it('passes numeric with new tld', () => expectPass('12345@google.photography'))
|
||||
it('fails string without tld', () => expectFail('12345@localhost'))
|
||||
it('fails string without invalid name', () => expectFail('1*(123)2345@localhost'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if value ends with a one of the specified Strings.
|
||||
*/
|
||||
describe('endsWith', () => {
|
||||
it('fails when value ending is not in stack of single value', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com' }, '@gmail.com')).toBe(false)
|
||||
})
|
||||
const validate = (value, ...stack) => rules.endsWith({ value, name: '', formValues: {} }, ...stack)
|
||||
const expectPass = (value, ...stack) => expect(validate(value, ...stack)).toBe(true)
|
||||
const expectFail = (value, ...stack) => expect(validate(value, ...stack)).toBe(false)
|
||||
|
||||
it('fails when value ending is not in stack of multiple values', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com' }, '@gmail.com', '@yahoo.com')).toBe(false)
|
||||
})
|
||||
it('fails when value ending is not in stack of single value', () => expectFail(
|
||||
'andrew@wearebraid.com',
|
||||
'@gmail.com'
|
||||
))
|
||||
|
||||
it('fails when passed value is not a string', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com'}, ['@gmail.com', '@wearebraid.com'])).toBe(false)
|
||||
})
|
||||
it('fails when value ending is not in stack of multiple values', () => expectFail(
|
||||
'andrew@wearebraid.com',
|
||||
'@gmail.com', '@yahoo.com'
|
||||
))
|
||||
|
||||
it('fails when passed value is not a string', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com'}, {value: '@wearebraid.com'})).toBe(false)
|
||||
})
|
||||
it('fails when passed value is not a string', () => expectFail(
|
||||
'andrew@wearebraid.com',
|
||||
['@gmail.com', '@wearebraid.com']
|
||||
))
|
||||
|
||||
it('passes when a string value is present and matched even if non-string values also exist as arguments', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com'}, {value: 'bad data'}, ['no bueno'], '@wearebraid.com')).toBe(true)
|
||||
})
|
||||
it('fails when passed value is not a string', () => expectFail(
|
||||
'andrew@wearebraid.com',
|
||||
{ value: '@wearebraid.com' }
|
||||
))
|
||||
|
||||
it('passes when stack consists of zero values', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com' })).toBe(true)
|
||||
})
|
||||
it('passes when a string value is present and matched even if non-string values also exist as arguments', () => {
|
||||
expectPass('andrew@wearebraid.com', { value: 'bad data' }, ['no bueno'], '@wearebraid.com')
|
||||
})
|
||||
|
||||
it('passes when value ending is in stack of single value', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com' }, '@wearebraid.com')).toBe(true)
|
||||
})
|
||||
it('passes when stack consists of zero values', () => expectPass('andrew@wearebraid.com'))
|
||||
|
||||
it('passes when value ending is in stack of multiple values', async () => {
|
||||
expect(await rules.endsWith({ value: 'andrew@wearebraid.com' }, '@yahoo.com', '@wearebraid.com', '@gmail.com')).toBe(true)
|
||||
})
|
||||
it('passes when value ending is in stack of single value', () => expectPass(
|
||||
'andrew@wearebraid.com',
|
||||
'@wearebraid.com'
|
||||
))
|
||||
|
||||
it('passes when value ending is in stack of multiple values', () => expectPass(
|
||||
'andrew@wearebraid.com',
|
||||
'@yahoo.com', '@wearebraid.com', '@gmail.com'
|
||||
))
|
||||
})
|
||||
|
||||
/**
|
||||
* In rule
|
||||
*/
|
||||
describe('in', () => {
|
||||
it('fails when not in stack', async () => {
|
||||
expect(await rules.in({ value: 'third' }, 'first', 'second')).toBe(false)
|
||||
})
|
||||
const validate = (value, ...stack) => rules.in({ value, name: '', formValues: {} }, ...stack)
|
||||
const expectPass = (value, ...stack) => expect(validate(value, ...stack)).toBe(true)
|
||||
const expectFail = (value, ...stack) => expect(validate(value, ...stack)).toBe(false)
|
||||
|
||||
it('fails when case sensitive mismatch is in stack', async () => {
|
||||
expect(await rules.in({ value: 'third' }, 'first', 'second', 'Third')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails comparing dissimilar objects', async () => {
|
||||
expect(await rules.in({ value: {f: 'abc'} }, {a: 'cdf'}, {b: 'abc'})).toBe(false)
|
||||
})
|
||||
|
||||
it('passes when case sensitive match is in stack', async () => {
|
||||
expect(await rules.in({ value: 'third' }, 'first', 'second', 'third')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes a shallow array compare', async () => {
|
||||
expect(await rules.in({ value: ['abc'] }, ['cdf'], ['abc'])).toBe(true)
|
||||
})
|
||||
|
||||
it('passes a shallow object compare', async () => {
|
||||
expect(await rules.in({ value: {f: 'abc'} }, {a: 'cdf'}, {f: 'abc'},)).toBe(true)
|
||||
})
|
||||
it('fails when not in stack', () => expectFail('third', 'first', 'second'))
|
||||
it('fails when case sensitive mismatch is in stack', () => expectFail(
|
||||
'third',
|
||||
'first', 'second', 'Third'
|
||||
))
|
||||
it('fails comparing dissimilar objects', () => expectFail(
|
||||
{ f: 'abc' },
|
||||
{ a: 'cdf' }, { b: 'abc' }
|
||||
))
|
||||
it('passes when case sensitive match is in stack', () => expectPass(
|
||||
'third',
|
||||
'first', 'second', 'third'
|
||||
))
|
||||
it('passes a shallow array compare', () => expectPass(['abc'], ['cdf'], ['abc']))
|
||||
it('passes a shallow object compare', () => expectPass(
|
||||
{ f: 'abc' },
|
||||
{ a: 'cdf' }, { f: 'abc' }
|
||||
))
|
||||
})
|
||||
|
||||
/**
|
||||
* Matches rule
|
||||
*/
|
||||
describe('matches', () => {
|
||||
it('simple strings fail if they aren’t equal', async () => {
|
||||
expect(await rules.matches({ value: 'third' }, 'first')).toBe(false)
|
||||
})
|
||||
const validate = (value, ...stack) => rules.matches({ value, name: '', formValues: {} }, ...stack)
|
||||
const expectPass = (value, ...stack) => expect(validate(value, ...stack)).toBe(true)
|
||||
const expectFail = (value, ...stack) => expect(validate(value, ...stack)).toBe(false)
|
||||
|
||||
it('fails on non matching regex', async () => {
|
||||
expect(await rules.matches({ value: 'third' }, /^thirds/)).toBe(false)
|
||||
})
|
||||
|
||||
it('passes if simple strings match', async () => {
|
||||
expect(await rules.matches({ value: 'second' }, 'third', 'second')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes on matching regex', async () => {
|
||||
expect(await rules.matches({ value: 'third' }, /^third/)).toBe(true)
|
||||
})
|
||||
|
||||
it('passes on matching mixed regex and string', async () => {
|
||||
expect(await rules.matches({ value: 'first-fourth' }, 'second', /^third/, /fourth$/)).toBe(true)
|
||||
})
|
||||
|
||||
it('fails on a regular expression encoded as a string', async () => {
|
||||
expect(await rules.matches({ value: 'mypassword' }, '/[0-9]/')).toBe(false)
|
||||
})
|
||||
|
||||
it('passes on a regular expression encoded as a string', async () => {
|
||||
expect(await rules.matches({ value: 'mypa55word' }, '/[0-9]/')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes on a regular expression containing slashes', async () => {
|
||||
expect(await rules.matches({ value: 'https://' }, '/https?:///')).toBe(true)
|
||||
})
|
||||
it('simple strings fail if they aren’t equal', () => expectFail('third', 'first'))
|
||||
it('fails on non matching regex', () => expectFail('third', /^thirds/))
|
||||
it('passes if simple strings match', () => expectPass('second', 'third', 'second'))
|
||||
it('passes on matching regex', () => expectPass('third', /^third/))
|
||||
it('passes on matching mixed regex and string', () => expectPass(
|
||||
'first-fourth',
|
||||
'second', /^third/, /fourth$/
|
||||
))
|
||||
it('fails on a regular expression encoded as a string', () => expectFail('mypassword', '/[0-9]/'))
|
||||
it('passes on a regular expression encoded as a string', () => expectPass('mypa55word', '/[0-9]/'))
|
||||
it('passes on a regular expression containing slashes', () => expectPass(
|
||||
'https://',
|
||||
'/https?:///'
|
||||
))
|
||||
})
|
||||
|
||||
/**
|
||||
* Minimum.
|
||||
*/
|
||||
describe('min', () => {
|
||||
it('passes when a number string', async () => expect(await rules.min({ value: '5' }, '5')).toBe(true))
|
||||
|
||||
it('passes when a number', async () => expect(await rules.min({ value: 6 }, 5)).toBe(true))
|
||||
|
||||
it('passes when a string length', async () => expect(await rules.min({ value: 'foobar' }, '6')).toBe(true))
|
||||
|
||||
it('passes when a array length', async () => expect(await rules.min({ value: Array(6) }, '6')).toBe(true))
|
||||
|
||||
it('passes when string is forced to value', async () => expect(await rules.min({ value: 'bcd' }, 'aaa', 'value')).toBe(true))
|
||||
|
||||
it('fails when string is forced to lesser value', async () => expect(await rules.min({ value: 'a' }, 'b', 'value')).toBe(false))
|
||||
|
||||
it('passes when a number is forced to length', async () => expect(await rules.min({ value: '000' }, 3, 'length')).toBe(true))
|
||||
|
||||
it('fails when a number is forced to length', async () => expect(await rules.min({ value: '44' }, 3, 'length')).toBe(false))
|
||||
|
||||
it('fails when a array length', async () => expect(await rules.min({ value: Array(6) }, '7')).toBe(false))
|
||||
|
||||
it('fails when a string length', async () => expect(await rules.min({ value: 'bar' }, 4)).toBe(false))
|
||||
|
||||
it('fails when a number', async () => expect(await rules.min({ value: 3 }, '7')).toBe(false))
|
||||
|
||||
})
|
||||
|
||||
/**
|
||||
* Maximum.
|
||||
*/
|
||||
describe('max', () => {
|
||||
it('passes when a number string', async () => expect(await rules.max({ value: '5' }, '5')).toBe(true))
|
||||
const validate = (value, max, force = undefined) => rules.max({value, name: '', formValues: {}}, max, force)
|
||||
const expectPass = (v, max, force = undefined) => expect(validate(v, max, force)).toBe(true)
|
||||
const expectFail = (v, max, force = undefined) => expect(validate(v, max, force)).toBe(false)
|
||||
|
||||
it('passes when a number', async () => expect(await rules.max({ value: 5 }, 6)).toBe(true))
|
||||
|
||||
it('passes when a string length', async () => expect(await rules.max({ value: 'foobar' }, '6')).toBe(true))
|
||||
|
||||
it('passes when a array length', async () => expect(await rules.max({ value: Array(6) }, '6')).toBe(true))
|
||||
|
||||
it('passes when forced to validate on length', async () => expect(await rules.max({ value: 10 }, 3, 'length')).toBe(true))
|
||||
|
||||
it('passes when forced to validate string on value', async () => expect(await rules.max({ value: 'b' }, 'e', 'value')).toBe(true))
|
||||
|
||||
it('fails when a array length', async () => expect(await rules.max({ value: Array(6) }, '5')).toBe(false))
|
||||
|
||||
it('fails when a string length', async () => expect(await rules.max({ value: 'bar' }, 2)).toBe(false))
|
||||
|
||||
it('fails when a number', async () => expect(await rules.max({ value: 10 }, '7')).toBe(false))
|
||||
|
||||
it('fails when a number', async () => expect(await rules.max({ value: 10 }, '7')).toBe(false))
|
||||
|
||||
it('fails when forced to validate on length', async () => expect(await rules.max({ value: -10 }, '1', 'length')).toBe(false))
|
||||
it('passes when a number string', () => expectPass('5', '5'))
|
||||
it('passes when a number', () => expectPass(5, 6))
|
||||
it('passes when a string length', () => expectPass('foobar', '6'))
|
||||
it('passes when a array length', () => expectPass(Array(6), '6'))
|
||||
it('passes when forced to validate on length', () => expectPass(10, 3, 'length'))
|
||||
it('passes when forced to validate string on value', () => expectPass('b', 'e', 'value'))
|
||||
it('fails when a array length', () => expectFail(Array(6), '5'))
|
||||
it('fails when a string length', () => expectFail('bar', 2))
|
||||
it('fails when a number', () => expectFail(10, '7'))
|
||||
it('fails when forced to validate on length', () => expectFail(-10, '1', 'length'))
|
||||
})
|
||||
|
||||
describe('min', () => {
|
||||
const validate = (value, min, force = undefined) => rules.min({value, name: '', formValues: {}}, min, force)
|
||||
const expectPass = (v, min, force = undefined) => expect(validate(v, min, force)).toBe(true)
|
||||
const expectFail = (v, min, force = undefined) => expect(validate(v, min, force)).toBe(false)
|
||||
|
||||
it('passes when a number string', () => expectPass('5', '5'))
|
||||
it('passes when a number', () => expectPass(6, 5))
|
||||
it('passes when a string length', () => expectPass('foobar', '6'))
|
||||
it('passes when a array length', () => expectPass(Array(6), '6'))
|
||||
it('passes when string is forced to value', () => expectPass('bcd', 'aaa', 'value'))
|
||||
it('fails when string is forced to lesser value', () => expectFail('a', 'b', 'value'))
|
||||
it('passes when a number is forced to length', () => expectPass('000', 3, 'length'))
|
||||
it('fails when a number is forced to length', () => expectFail('44', 3, 'length'))
|
||||
it('fails when a array length', () => expectFail(Array(6), '7'))
|
||||
it('fails when a string length', () => expectFail('bar', 4))
|
||||
it('fails when a number', () => expectFail(3, '7'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Maximum.
|
||||
*/
|
||||
describe('not', () => {
|
||||
it('passes when a number string', async () => expect(await rules.not({ value: '5' }, '6')).toBe(true))
|
||||
const validate = (value, ...stack) => rules.not({ value, name: '', formValues: {} }, ...stack)
|
||||
const expectPass = (value, ...stack) => expect(validate(value, ...stack)).toBe(true)
|
||||
const expectFail = (value, ...stack) => expect(validate(value, ...stack)).toBe(false)
|
||||
|
||||
it('passes when a number', async () => expect(await rules.not({ value: 1 }, 30)).toBe(true))
|
||||
|
||||
it('passes when a string', async () => expect(await rules.not({ value: 'abc' }, 'def')).toBe(true))
|
||||
|
||||
it('fails when a shallow equal array', async () => expect(await rules.not({ value: ['abc'] }, ['abc'])).toBe(false))
|
||||
|
||||
it('fails when a shallow equal object', async () => expect(await rules.not({ value: {a: 'abc'} }, ['123'], {a: 'abc'})).toBe(false))
|
||||
|
||||
it('fails when string is in stack', async () => expect(await rules.not({ value: 'a' }, 'b', 'c', 'd', 'a', 'f')).toBe(false))
|
||||
it('passes when a number string', () => expectPass('5', '6'))
|
||||
it('passes when a number', () => expectPass(1, 30))
|
||||
it('passes when a string', () => expectPass('abc', 'def'))
|
||||
it('fails when a shallow equal array', () => expectFail(['abc'], ['abc']))
|
||||
it('fails when a shallow equal object', () => expectFail({a: 'abc'}, ['123'], {a: 'abc'}))
|
||||
it('fails when string is in stack', () => expectFail('a', 'b', 'c', 'd', 'a', 'f'))
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a date is after another date
|
||||
*/
|
||||
describe('number', () => {
|
||||
it('passes with simple number string', async () => expect(await rules.number({ value: '123' })).toBe(true))
|
||||
const validate = value => rules.number({ value, name: '', formValues: {} })
|
||||
const expectPass = value => expect(validate(value)).toBe(true)
|
||||
const expectFail = value => expect(validate(value)).toBe(false)
|
||||
|
||||
it('passes with simple number', async () => expect(await rules.number({ value: 19832461234 })).toBe(true))
|
||||
|
||||
it('passes with float', async () => expect(await rules.number({ value: 198.32464 })).toBe(true))
|
||||
|
||||
it('passes with decimal in string', async () => expect(await rules.number({ value: '567.23' })).toBe(true))
|
||||
|
||||
it('fails with comma in number string', async () => expect(await rules.number({ value: '123,456' })).toBe(false))
|
||||
|
||||
it('fails with alpha', async () => expect(await rules.number({ value: '123sdf' })).toBe(false))
|
||||
it('passes with simple number string', () => expectPass('123'))
|
||||
it('passes with simple number', () => expectPass(19832461234))
|
||||
it('passes with float', () => expectPass(198.32464))
|
||||
it('passes with decimal in string', () => expectPass('567.23'))
|
||||
it('fails with comma in number string', () => expectFail('123,456'))
|
||||
it('fails with alpha', () => expectFail('123sdf'))
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Required rule
|
||||
*/
|
||||
describe('required', () => {
|
||||
it('fails on empty string', async () => expect(await rules.required({ value: '' })).toBe(false))
|
||||
const validate = (value, isRequired = true) => rules.required({ value, name: '', formValues: {} }, isRequired)
|
||||
const expectPass = (value, isRequired = true) => expect(validate(value, isRequired)).toBe(true)
|
||||
const expectFail = (value, isRequired = true) => expect(validate(value, isRequired)).toBe(false)
|
||||
|
||||
it('fails on empty array', async () => expect(await rules.required({ value: [] })).toBe(false))
|
||||
|
||||
it('fails on empty object', async () => expect(await rules.required({ value: {} })).toBe(false))
|
||||
|
||||
it('fails on null', async () => expect(await rules.required({ value: null })).toBe(false))
|
||||
|
||||
it('passes with the number zero', async () => expect(await rules.required({ value: 0 })).toBe(true))
|
||||
|
||||
it('passes with the boolean false', async () => expect(await rules.required({ value: false })).toBe(true))
|
||||
|
||||
it('passes with a non empty array', async () => expect(await rules.required({ value: ['123'] })).toBe(true))
|
||||
|
||||
it('passes with a non empty object', async () => expect(await rules.required({ value: {a: 'b'} })).toBe(true))
|
||||
|
||||
it('passes with empty value if second argument is false', async () => expect(await rules.required({ value: '' }, false)).toBe(true))
|
||||
|
||||
it('passes with empty value if second argument is false string', async () => expect(await rules.required({ value: '' }, 'false')).toBe(true))
|
||||
it('fails on empty string', () => expectFail(''))
|
||||
it('fails on empty array', () => expectFail([]))
|
||||
it('fails on empty object', () => expectFail({}))
|
||||
it('fails on null', () => expectFail(null))
|
||||
it('passes with the number zero', () => expectPass(0))
|
||||
it('passes with the boolean false', () => expectPass(false))
|
||||
it('passes with a non empty array', () => expectPass(['123']))
|
||||
it('passes with a non empty object', () => expectPass({ a: 'b' }))
|
||||
it('passes with empty value if second argument is false', () => expectPass('', false))
|
||||
it('passes with empty value if second argument is false string', () => {
|
||||
expectPass('', 'false')
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if value starts with a one of the specified Strings.
|
||||
*/
|
||||
describe('startsWith', () => {
|
||||
it('fails when value starting is not in stack of single value', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday' }, 'pizza')).toBe(false)
|
||||
})
|
||||
const validate = (value, ...args) => rules.startsWith({ value, name: '', formValues: {} }, ...args)
|
||||
|
||||
it('fails when value starting is not in stack of multiple values', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday' }, 'pizza', 'coffee')).toBe(false)
|
||||
})
|
||||
it('fails when value starting is not in stack of single value', () => {
|
||||
expect(validate('taco tuesday', 'pizza')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails when passed value is not a string', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday'}, ['taco', 'pizza'])).toBe(false)
|
||||
})
|
||||
it('fails when value starting is not in stack of multiple values', () => {
|
||||
expect(validate('taco tuesday', 'pizza', 'coffee')).toBe(false)
|
||||
})
|
||||
|
||||
it('fails when passed value is not a string', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday'}, {value: 'taco'})).toBe(false)
|
||||
})
|
||||
it('fails when passed value is not a string', () => {
|
||||
expect(validate('taco tuesday', ['taco', 'pizza'])).toBe(false)
|
||||
})
|
||||
|
||||
it('passes when a string value is present and matched even if non-string values also exist as arguments', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday'}, {value: 'taco'}, ['taco'], 'taco')).toBe(true)
|
||||
})
|
||||
it('fails when passed value is not a string', () => {
|
||||
expect(validate('taco tuesday', {value: 'taco'})).toBe(false)
|
||||
})
|
||||
|
||||
it('passes when stack consists of zero values', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday' })).toBe(true)
|
||||
})
|
||||
it('passes when a string value is present and matched even if non-string values also exist as arguments', () => {
|
||||
expect(validate('taco tuesday', {value: 'taco'}, ['taco'], 'taco')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes when value starting is in stack of single value', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday' }, 'taco')).toBe(true)
|
||||
})
|
||||
it('passes when stack consists of zero values', () => {
|
||||
expect(validate('taco tuesday')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes when value starting is in stack of multiple values', async () => {
|
||||
expect(await rules.startsWith({ value: 'taco tuesday' }, 'pizza', 'taco', 'coffee')).toBe(true)
|
||||
})
|
||||
it('passes when value starting is in stack of single value', () => {
|
||||
expect(validate('taco tuesday', 'taco')).toBe(true)
|
||||
})
|
||||
|
||||
it('passes when value starting is in stack of multiple values', () => {
|
||||
expect(validate('taco tuesday', 'pizza', 'taco', 'coffee')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
@ -477,7 +432,11 @@ describe('startsWith', () => {
|
||||
* well tested: https://github.com/segmentio/is-url/blob/master/test/index.js
|
||||
*/
|
||||
describe('url', () => {
|
||||
it('passes with http://google.com', async () => expect(await rules.url({ value: 'http://google.com' })).toBe(true))
|
||||
const validate = value => rules.url({ value, name: '', formValues: {} })
|
||||
const expectPass = value => expect(validate(value)).toBe(true)
|
||||
const expectFail = value => expect(validate(value)).toBe(false)
|
||||
|
||||
it('fails with google.com', async () => expect(await rules.url({ value: 'google.com' })).toBe(false))
|
||||
it('passes with http://google.com', () => expectPass('http://google.com'))
|
||||
it('passes with http://scholar.google.com', () => expectPass('http://scholar.google.com'))
|
||||
it('fails with google.com', () => expectFail('google.com'))
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user