Adds support for validation and error-visibility events
* Adds validation events Adds validation events on FormulateInput and FormulateForm — also refactors some of the context object to get ensure errors are available in context (for future scoped slot work that is relevant to this). Co-authored-by: Christoph Wagner <christoph@cwagner.me>
This commit is contained in:
parent
01cb68d728
commit
486e477ebb
2
dist/formulate.esm.js
vendored
2
dist/formulate.esm.js
vendored
File diff suppressed because one or more lines are too long
6
dist/formulate.min.js
vendored
6
dist/formulate.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/formulate.umd.js
vendored
2
dist/formulate.umd.js
vendored
File diff suppressed because one or more lines are too long
3
dist/snow.css
vendored
3
dist/snow.css
vendored
@ -1,5 +1,6 @@
|
|||||||
.formulate-input {
|
.formulate-input {
|
||||||
margin-bottom: 1.5em; }
|
margin-bottom: 1.5em;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; }
|
||||||
.formulate-input .formulate-input-label {
|
.formulate-input .formulate-input-label {
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
2
dist/snow.min.css
vendored
2
dist/snow.min.css
vendored
File diff suppressed because one or more lines are too long
@ -150,8 +150,13 @@ class Formulate {
|
|||||||
* Attempt to get the vue-i18n configured locale.
|
* Attempt to get the vue-i18n configured locale.
|
||||||
*/
|
*/
|
||||||
i18n (vm) {
|
i18n (vm) {
|
||||||
if (vm.$i18n && vm.$i18n.locale) {
|
if (vm.$i18n) {
|
||||||
return vm.$i18n.locale
|
switch (typeof vm.$i18n.locale) {
|
||||||
|
case 'string':
|
||||||
|
return vm.$i18n.locale
|
||||||
|
case 'function':
|
||||||
|
return vm.$i18n.locale()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -25,29 +25,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
showValidationErrors: {
|
context: {
|
||||||
type: Boolean,
|
type: Object,
|
||||||
default: false
|
default: () => ({})
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
type: [Array, Boolean],
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
validationErrors: {
|
|
||||||
type: [Array],
|
|
||||||
default: () => ([])
|
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'form'
|
default: 'form'
|
||||||
},
|
|
||||||
preventRegistration: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
fieldName: {
|
|
||||||
type: [String, Boolean],
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
@ -57,20 +41,28 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
visibleValidationErrors () {
|
||||||
|
return Array.isArray(this.context.visibleValidationErrors) ? this.context.visibleValidationErrors : []
|
||||||
|
},
|
||||||
|
errors () {
|
||||||
|
return Array.isArray(this.context.errors) ? this.context.errors : []
|
||||||
|
},
|
||||||
mergedErrors () {
|
mergedErrors () {
|
||||||
return arrayify(this.errors).concat(this.localErrors)
|
return this.errors.concat(this.localErrors)
|
||||||
},
|
},
|
||||||
visibleErrors () {
|
visibleErrors () {
|
||||||
return Array.from(new Set(this.mergedErrors.concat(this.showValidationErrors ? this.validationErrors : [])))
|
return Array.from(new Set(this.mergedErrors.concat(this.visibleValidationErrors)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
if (!this.preventRegistration && typeof this.observeErrors === 'function' && (this.type === 'form' || this.fieldName)) {
|
// This registration is for <FormulateErrors /> that are used for displaying
|
||||||
this.observeErrors({ callback: this.boundSetErrors, type: this.type, field: this.fieldName })
|
// Form errors in an override position.
|
||||||
|
if (this.type === 'form' && typeof this.observeErrors === 'function' && !Array.isArray(this.context.errors)) {
|
||||||
|
this.observeErrors({ callback: this.boundSetErrors, type: this.type })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
if (!this.preventRegistration && typeof this.removeErrorObserver === 'function') {
|
if (this.type === 'form' && typeof this.removeErrorObserver === 'function' && !Array.isArray(this.context.errors)) {
|
||||||
this.removeErrorObserver(this.boundSetErrors)
|
this.removeErrorObserver(this.boundSetErrors)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -5,9 +5,7 @@
|
|||||||
>
|
>
|
||||||
<FormulateErrors
|
<FormulateErrors
|
||||||
v-if="!hasFormErrorObservers"
|
v-if="!hasFormErrorObservers"
|
||||||
type="form"
|
:context="formContext"
|
||||||
:errors="mergedFormErrors"
|
|
||||||
:prevent-registration="true"
|
|
||||||
/>
|
/>
|
||||||
<slot />
|
<slot />
|
||||||
</form>
|
</form>
|
||||||
@ -24,7 +22,8 @@ export default {
|
|||||||
formulateFormRegister: this.register,
|
formulateFormRegister: this.register,
|
||||||
getFormValues: this.getFormValues,
|
getFormValues: this.getFormValues,
|
||||||
observeErrors: this.addErrorObserver,
|
observeErrors: this.addErrorObserver,
|
||||||
removeErrorObserver: this.removeErrorObserver
|
removeErrorObserver: this.removeErrorObserver,
|
||||||
|
formulateFieldValidation: this.formulateFieldValidation
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
name: 'FormulateForm',
|
name: 'FormulateForm',
|
||||||
@ -65,6 +64,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
/**
|
||||||
|
* @todo in 2.3.0 this will expand and be extracted to a separate module to
|
||||||
|
* support better scoped slot interoperability.
|
||||||
|
*/
|
||||||
|
formContext () {
|
||||||
|
return {
|
||||||
|
errors: this.mergedFormErrors
|
||||||
|
}
|
||||||
|
},
|
||||||
hasInitialValue () {
|
hasInitialValue () {
|
||||||
return (
|
return (
|
||||||
(this.formulateValue && typeof this.formulateValue === 'object') ||
|
(this.formulateValue && typeof this.formulateValue === 'object') ||
|
||||||
@ -245,14 +253,19 @@ export default {
|
|||||||
getFormValues () {
|
getFormValues () {
|
||||||
return this.internalFormModelProxy
|
return this.internalFormModelProxy
|
||||||
},
|
},
|
||||||
|
formulateFieldValidation (errorObject) {
|
||||||
|
this.$emit('validation', errorObject)
|
||||||
|
},
|
||||||
hasValidationErrors () {
|
hasValidationErrors () {
|
||||||
const resolvers = []
|
const resolvers = []
|
||||||
for (const fieldName in this.registry) {
|
for (const fieldName in this.registry) {
|
||||||
if (typeof this.registry[fieldName].hasValidationErrors === 'function') {
|
if (typeof this.registry[fieldName].getValidationErrors === 'function') {
|
||||||
resolvers.push(this.registry[fieldName].hasValidationErrors())
|
resolvers.push(this.registry[fieldName].getValidationErrors())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(resolvers).then(fields => !!fields.find(hasErrors => hasErrors))
|
return Promise.all(resolvers).then((errorObjects) => {
|
||||||
|
return errorObjects.some(item => item.hasErrors)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,17 +49,14 @@
|
|||||||
<FormulateErrors
|
<FormulateErrors
|
||||||
v-if="!disableErrors"
|
v-if="!disableErrors"
|
||||||
:type="`input`"
|
:type="`input`"
|
||||||
:errors="explicitErrors"
|
:context="context"
|
||||||
:field-name="nameOrFallback"
|
|
||||||
:validation-errors="validationErrors"
|
|
||||||
:show-validation-errors="showValidationErrors"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import context from './libs/context'
|
import context from './libs/context'
|
||||||
import { shallowEqualObjects, parseRules, snakeToCamel } from './libs/utils'
|
import { shallowEqualObjects, parseRules, snakeToCamel, arrayify } from './libs/utils'
|
||||||
import nanoid from 'nanoid/non-secure'
|
import nanoid from 'nanoid/non-secure'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -67,8 +64,11 @@ export default {
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
inject: {
|
inject: {
|
||||||
formulateFormSetter: { default: undefined },
|
formulateFormSetter: { default: undefined },
|
||||||
|
formulateFieldValidation: { default: () => () => ({}) },
|
||||||
formulateFormRegister: { default: undefined },
|
formulateFormRegister: { default: undefined },
|
||||||
getFormValues: { default: () => () => ({}) }
|
getFormValues: { default: () => () => ({}) },
|
||||||
|
observeErrors: { default: undefined },
|
||||||
|
removeErrorObserver: { default: undefined }
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
prop: 'formulateValue',
|
prop: 'formulateValue',
|
||||||
@ -191,6 +191,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
defaultId: nanoid(9),
|
defaultId: nanoid(9),
|
||||||
localAttributes: {},
|
localAttributes: {},
|
||||||
|
localErrors: [],
|
||||||
internalModelProxy: this.getInitialValue(),
|
internalModelProxy: this.getInitialValue(),
|
||||||
behavioralErrorVisibility: (this.errorBehavior === 'live'),
|
behavioralErrorVisibility: (this.errorBehavior === 'live'),
|
||||||
formShouldShowErrors: false,
|
formShouldShowErrors: false,
|
||||||
@ -239,6 +240,12 @@ export default {
|
|||||||
if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
|
if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) {
|
||||||
this.context.model = newValue
|
this.context.model = newValue
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
showValidationErrors: {
|
||||||
|
handler (val) {
|
||||||
|
this.$emit('error-visibility', val)
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@ -246,9 +253,17 @@ export default {
|
|||||||
if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') {
|
if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') {
|
||||||
this.formulateFormRegister(this.nameOrFallback, this)
|
this.formulateFormRegister(this.nameOrFallback, this)
|
||||||
}
|
}
|
||||||
|
if (!this.disableErrors && typeof this.observeErrors === 'function') {
|
||||||
|
this.observeErrors({ callback: this.setErrors, type: 'input', field: this.nameOrFallback })
|
||||||
|
}
|
||||||
this.updateLocalAttributes(this.$attrs)
|
this.updateLocalAttributes(this.$attrs)
|
||||||
this.performValidation()
|
this.performValidation()
|
||||||
},
|
},
|
||||||
|
destroyed () {
|
||||||
|
if (!this.disableErrors && typeof this.removeErrorObserver === 'function') {
|
||||||
|
this.removeErrorObserver(this.setErrors)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getInitialValue () {
|
getInitialValue () {
|
||||||
// Manually request classification, pre-computed props
|
// Manually request classification, pre-computed props
|
||||||
@ -293,9 +308,20 @@ export default {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then(result => result.filter(result => result))
|
.then(result => result.filter(result => result))
|
||||||
.then(errorMessages => { this.validationErrors = errorMessages })
|
.then(messages => this.didValidate(messages))
|
||||||
return this.pendingValidation
|
return this.pendingValidation
|
||||||
},
|
},
|
||||||
|
didValidate (messages) {
|
||||||
|
const validationChanged = !shallowEqualObjects(messages, this.validationErrors)
|
||||||
|
this.validationErrors = messages
|
||||||
|
if (validationChanged) {
|
||||||
|
const errorObject = this.getErrorObject()
|
||||||
|
this.$emit('validation', errorObject)
|
||||||
|
if (this.formulateFieldValidation && typeof this.formulateFieldValidation === 'function') {
|
||||||
|
this.formulateFieldValidation(errorObject)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
getMessage (ruleName, args) {
|
getMessage (ruleName, args) {
|
||||||
return this.getMessageFunc(ruleName)({
|
return this.getMessageFunc(ruleName)({
|
||||||
args,
|
args,
|
||||||
@ -323,6 +349,23 @@ export default {
|
|||||||
this.pendingValidation.then(() => resolve(!!this.validationErrors.length))
|
this.pendingValidation.then(() => resolve(!!this.validationErrors.length))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
getValidationErrors () {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.pendingValidation.then(() => resolve(this.getErrorObject()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getErrorObject () {
|
||||||
|
return {
|
||||||
|
name: this.context.nameOrFallback || this.context.name,
|
||||||
|
errors: this.validationErrors,
|
||||||
|
hasErrors: !!this.validationErrors.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setErrors (errors) {
|
||||||
|
this.localErrors = arrayify(errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,10 @@ export default {
|
|||||||
blurHandler,
|
blurHandler,
|
||||||
performValidation,
|
performValidation,
|
||||||
hasValidationErrors,
|
hasValidationErrors,
|
||||||
|
getValidationErrors,
|
||||||
|
validationErrors,
|
||||||
|
setErrors,
|
||||||
|
visibleValidationErrors,
|
||||||
component,
|
component,
|
||||||
hasLabel,
|
hasLabel,
|
||||||
...context
|
...context
|
||||||
|
@ -27,6 +27,12 @@ export default {
|
|||||||
uploadBehavior: this.uploadBehavior,
|
uploadBehavior: this.uploadBehavior,
|
||||||
preventWindowDrops: this.preventWindowDrops,
|
preventWindowDrops: this.preventWindowDrops,
|
||||||
hasValidationErrors: this.hasValidationErrors,
|
hasValidationErrors: this.hasValidationErrors,
|
||||||
|
getValidationErrors: this.getValidationErrors.bind(this),
|
||||||
|
validationErrors: this.validationErrors,
|
||||||
|
errors: this.explicitErrors,
|
||||||
|
setErrors: this.setErrors.bind(this),
|
||||||
|
showValidationErrors: this.showValidationErrors,
|
||||||
|
visibleValidationErrors: this.visibleValidationErrors,
|
||||||
...this.typeContext
|
...this.typeContext
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -44,7 +50,8 @@ export default {
|
|||||||
allErrors,
|
allErrors,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
hasVisibleErrors,
|
hasVisibleErrors,
|
||||||
showValidationErrors
|
showValidationErrors,
|
||||||
|
visibleValidationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,12 +141,20 @@ function showValidationErrors () {
|
|||||||
if (this.showErrors || this.formShouldShowErrors) {
|
if (this.showErrors || this.formShouldShowErrors) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (this.classification === 'file' && this.uploadBehavior === 'live' && this.context.model) {
|
if (this.classification === 'file' && this.uploadBehavior === 'live' && modelGetter.call(this)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return this.behavioralErrorVisibility
|
return this.behavioralErrorVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of the currently visible validation errors (does not include error handling)
|
||||||
|
* @return {array}
|
||||||
|
*/
|
||||||
|
function visibleValidationErrors () {
|
||||||
|
return (this.showValidationErrors && this.validationErrors.length) ? this.validationErrors : []
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the element’s name, or select a fallback.
|
* Return the element’s name, or select a fallback.
|
||||||
*/
|
*/
|
||||||
@ -188,6 +203,7 @@ function createOptionList (options) {
|
|||||||
*/
|
*/
|
||||||
function explicitErrors () {
|
function explicitErrors () {
|
||||||
return arrayify(this.errors)
|
return arrayify(this.errors)
|
||||||
|
.concat(this.localErrors)
|
||||||
.concat(arrayify(this.error))
|
.concat(arrayify(this.error))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +223,7 @@ function hasErrors () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if form has actively visible errors.
|
* Returns if form has actively visible errors (of any kind)
|
||||||
*/
|
*/
|
||||||
function hasVisibleErrors () {
|
function hasVisibleErrors () {
|
||||||
return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length)
|
return ((this.validationErrors && this.showValidationErrors) || !!this.explicitErrors.length)
|
||||||
|
@ -153,7 +153,7 @@ describe('Formulate', () => {
|
|||||||
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('nl-BE')
|
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('nl-BE')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can select a matching locale using i18n', () => {
|
it('can select a matching locale using i18n locale string', () => {
|
||||||
Formulate.selectedLocale = false // reset the memoization
|
Formulate.selectedLocale = false // reset the memoization
|
||||||
function Vue () {}
|
function Vue () {}
|
||||||
Vue.component = function (name, instance) {}
|
Vue.component = function (name, instance) {}
|
||||||
@ -166,4 +166,19 @@ describe('Formulate', () => {
|
|||||||
})
|
})
|
||||||
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('cn')
|
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('cn')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can select a matching locale using i18n locale function', () => {
|
||||||
|
Formulate.selectedLocale = false // reset the memoization
|
||||||
|
function Vue () {}
|
||||||
|
Vue.component = function (name, instance) {}
|
||||||
|
const vm = { $i18n: {locale: () => 'en-US' } }
|
||||||
|
Formulate.install(Vue, {
|
||||||
|
locales: {
|
||||||
|
cn: {},
|
||||||
|
em: {},
|
||||||
|
en: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('en')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -23,7 +23,6 @@ describe('FormulateForm', () => {
|
|||||||
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
|
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it('intercepts submit event', () => {
|
it('intercepts submit event', () => {
|
||||||
const formSubmitted = jest.fn()
|
const formSubmitted = jest.fn()
|
||||||
const wrapper = mount(FormulateForm, {
|
const wrapper = mount(FormulateForm, {
|
||||||
@ -389,4 +388,50 @@ describe('FormulateForm', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.find(FormulateForm).vm.errorObservers.length).toBe(0)
|
expect(wrapper.find(FormulateForm).vm.errorObservers.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('emits correct validation event on entry', async () => {
|
||||||
|
const wrapper = mount(FormulateForm, {
|
||||||
|
slots: { default: `
|
||||||
|
<div>
|
||||||
|
<FormulateInput type="text" validation="required|in:bar" name="testinput" />
|
||||||
|
<FormulateInput type="radio" validation="required" name="bar" />
|
||||||
|
</div>
|
||||||
|
` }
|
||||||
|
})
|
||||||
|
wrapper.find('input[type="text"]').setValue('foo')
|
||||||
|
await flushPromises()
|
||||||
|
const errorObjects = wrapper.emitted('validation')
|
||||||
|
// There should be 3 events, both inputs mounting, and the value being set removing required on testinput
|
||||||
|
expect(errorObjects.length).toBe(3)
|
||||||
|
// this should be the event from the setValue()
|
||||||
|
const errorObject = errorObjects[2][0]
|
||||||
|
expect(errorObject).toEqual({
|
||||||
|
name: 'testinput',
|
||||||
|
errors: [
|
||||||
|
expect.any(String)
|
||||||
|
],
|
||||||
|
hasErrors: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits correct validation event when no errors', async () => {
|
||||||
|
const wrapper = mount(FormulateForm, {
|
||||||
|
slots: { default: `
|
||||||
|
<div>
|
||||||
|
<FormulateInput type="text" validation="required|in:bar" name="testinput" />
|
||||||
|
<FormulateInput type="radio" validation="required" name="bar" />
|
||||||
|
</div>
|
||||||
|
` }
|
||||||
|
})
|
||||||
|
wrapper.find('input[type="text"]').setValue('bar')
|
||||||
|
await flushPromises()
|
||||||
|
const errorObjects = wrapper.emitted('validation')
|
||||||
|
expect(errorObjects.length).toBe(3)
|
||||||
|
const errorObject = errorObjects[2][0]
|
||||||
|
expect(errorObject).toEqual({
|
||||||
|
name: 'testinput',
|
||||||
|
errors: [],
|
||||||
|
hasErrors: false
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -70,7 +70,6 @@ describe('FormulateInput', () => {
|
|||||||
validationRules: {
|
validationRules: {
|
||||||
foobar: async ({ value }) => value === 'foo'
|
foobar: async ({ value }) => value === 'foo'
|
||||||
},
|
},
|
||||||
validation: 'required|foobar',
|
|
||||||
errorBehavior: 'live',
|
errorBehavior: 'live',
|
||||||
value: 'bar'
|
value: 'bar'
|
||||||
} })
|
} })
|
||||||
@ -88,7 +87,6 @@ describe('FormulateInput', () => {
|
|||||||
validationRules: {
|
validationRules: {
|
||||||
foobar: ({ value }) => value === 'foo'
|
foobar: ({ value }) => value === 'foo'
|
||||||
},
|
},
|
||||||
validation: 'required|foobar',
|
|
||||||
errorBehavior: 'live',
|
errorBehavior: 'live',
|
||||||
value: 'bar'
|
value: 'bar'
|
||||||
} })
|
} })
|
||||||
@ -117,4 +115,66 @@ describe('FormulateInput', () => {
|
|||||||
await flushPromises()
|
await flushPromises()
|
||||||
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
expect(wrapper.contains(FormulateInputBox)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('emits correct validation event', async () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: {
|
||||||
|
type: 'text',
|
||||||
|
validation: 'required',
|
||||||
|
errorBehavior: 'live',
|
||||||
|
value: '',
|
||||||
|
name: 'testinput',
|
||||||
|
} })
|
||||||
|
await flushPromises()
|
||||||
|
const errorObject = wrapper.emitted('validation')[0][0]
|
||||||
|
expect(errorObject).toEqual({
|
||||||
|
name: 'testinput',
|
||||||
|
errors: [
|
||||||
|
expect.any(String)
|
||||||
|
],
|
||||||
|
hasErrors: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits a error-visibility event on blur', async () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: {
|
||||||
|
type: 'text',
|
||||||
|
validation: 'required',
|
||||||
|
errorBehavior: 'blur',
|
||||||
|
value: '',
|
||||||
|
name: 'testinput',
|
||||||
|
} })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.emitted('error-visibility')[0][0]).toBe(false)
|
||||||
|
wrapper.find('input[type="text"]').trigger('blur')
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.emitted('error-visibility')[1][0]).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits error-visibility event immediately when live', async () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: {
|
||||||
|
type: 'text',
|
||||||
|
validation: 'required',
|
||||||
|
errorBehavior: 'live',
|
||||||
|
value: '',
|
||||||
|
name: 'testinput',
|
||||||
|
} })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.emitted('error-visibility').length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Does not emit an error-visibility event if visibility did not change', async () => {
|
||||||
|
const wrapper = mount(FormulateInput, { propsData: {
|
||||||
|
type: 'text',
|
||||||
|
validation: 'in:xyz',
|
||||||
|
errorBehavior: 'live',
|
||||||
|
value: 'bar',
|
||||||
|
name: 'testinput',
|
||||||
|
} })
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.emitted('error-visibility').length).toBe(1)
|
||||||
|
wrapper.find('input[type="text"]').setValue('bar')
|
||||||
|
await flushPromises()
|
||||||
|
expect(wrapper.emitted('error-visibility').length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
.formulate-input {
|
.formulate-input {
|
||||||
margin-bottom: 1.5em;
|
margin-bottom: 1.5em;
|
||||||
|
font-family: $formulate-font-stack;
|
||||||
|
|
||||||
.formulate-input-label {
|
.formulate-input-label {
|
||||||
display: block;
|
display: block;
|
||||||
|
Loading…
Reference in New Issue
Block a user