1
0
mirror of synced 2024-11-25 14:56:03 +03:00

Tests actualized

This commit is contained in:
1on 2020-05-22 14:22:16 +03:00
parent 051dd3e20c
commit 348f3aa017
14 changed files with 972 additions and 2402 deletions

View File

@ -0,0 +1,95 @@
import Formulario from '@/Formulario.js'
describe('Formulario', () => {
it('can merge simple object', () => {
let a = {
optionA: true,
optionB: '1234'
}
let b = {
optionA: false
}
expect(Formulario.merge(a, b)).toEqual({
optionA: false,
optionB: '1234'
})
})
it('can add to simple array', () => {
let a = {
optionA: true,
optionB: ['first', 'second']
}
let b = {
optionB: ['third']
}
expect(Formulario.merge(a, b, true)).toEqual({
optionA: true,
optionB: ['first', 'second', 'third']
})
})
it('can merge recursively', () => {
let a = {
optionA: true,
optionC: {
first: '123',
third: {
a: 'b'
}
},
optionB: '1234'
}
let b = {
optionB: '567',
optionC: {
first: '1234',
second: '789',
}
}
expect(Formulario.merge(a, b)).toEqual({
optionA: true,
optionC: {
first: '1234',
third: {
a: 'b'
},
second: '789'
},
optionB: '567'
})
})
it('installs on vue instance', () => {
const components = [
'FormularioForm',
'FormularioInput',
'FormularioGrouping',
]
const registry = []
function Vue () {}
Vue.component = function (name, instance) {
registry.push(name)
}
Formulario.install(Vue, { extended: true })
expect(Vue.prototype.$formulario).toBe(Formulario)
expect(registry).toEqual(components)
})
it('can extend instance in a plugin', () => {
function Vue () {}
Vue.component = function (name, instance) {}
const plugin = function (i) {
i.extend({
rules: {
testRule: () => false
}
})
}
Formulario.install(Vue, {
plugins: [ plugin ]
})
expect(typeof Vue.prototype.$formulario.options.rules.testRule).toBe('function')
})
})

View File

@ -0,0 +1,527 @@
import Vue from 'vue'
import { mount, shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulario from '../../src/Formulario.js'
import FormSubmission from '../../src/FormSubmission.js'
import FormularioForm from '@/FormularioForm.vue'
import FormularioInput from '@/FormularioInput.vue'
Vue.use(Formulario)
describe('FormularioForm', () => {
it('render a form DOM element', () => {
const wrapper = mount(FormularioForm)
expect(wrapper.find('form').exists()).toBe(true)
})
it('accepts a default slot', () => {
const wrapper = mount(FormularioForm, {
slots: {
default: '<div class="default-slot-item" />'
}
})
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
})
it('intercepts submit event', () => {
const formSubmitted = jest.fn()
const wrapper = mount(FormularioForm, {
slots: {
default: "<button type='submit' />"
}
})
const spy = jest.spyOn(wrapper.vm, 'formSubmitted')
wrapper.find('form').trigger('submit')
expect(spy).toHaveBeenCalled()
})
it('registers its subcomponents', () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
slots: { default: '<FormularioInput type="text" name="subinput1" /><FormularioInput type="checkbox" name="subinput2" />' }
})
expect(wrapper.vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
})
it('deregisters a subcomponents', async () => {
const wrapper = mount({
data () {
return {
active: true
}
},
template: `
<FormularioForm>
<FormularioInput v-if="active" type="text" name="subinput1" />
<FormularioInput type="checkbox" name="subinput2" />
</FormularioForm>
`
})
await flushPromises()
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
wrapper.setData({ active: false })
await flushPromises()
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['subinput2'])
})
it('can set a fields initial value', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
slots: { default: `
<FormularioInput v-slot="inputProps" validation="required|in:bar" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
` }
})
await flushPromises()
expect(wrapper.find('input').element.value).toBe('has initial value')
})
it('lets individual fields override form initial value', () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
slots: { default: `
<FormularioInput v-slot="inputProps" formulario-value="123" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
` }
})
expect(wrapper.find('input').element.value).toBe('123')
})
it('lets fields set form initial value with value prop', () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `<FormularioForm v-model="formValues">
<FormularioInput name="name" value="123" />
</FormularioForm>`
})
expect(wrapper.vm.formValues).toEqual({ name: '123' })
})
it('receives updates to form model when individual fields are edited', () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
}
}
},
template: `
<FormularioForm v-model="formValues">
<FormularioInput v-slot="inputProps" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
</FormularioForm>
`
})
wrapper.find('input').setValue('edited value')
expect(wrapper.vm.formValues).toEqual({ testinput: 'edited value' })
})
// ===========================================================================
/**
* @todo in vue-test-utils version 1.0.0-beta.29 has some bugs related to
* synchronous updating. Some details are here:
*
* @update this test was re-implemented in version 1.0.0-beta.31 and seems to
* be workign now with flushPromises(). Leaving these docs here for now.
*
* https://github.com/vuejs/vue-test-utils/issues/1130
*
* This test is being commented out until there is a resolution on this issue,
* and instead being replaced with a mock call.
*/
it('updates initial form values when input contains a populated v-model', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
},
fieldValue: '123'
}
},
template: `
<FormularioForm v-model="formValues">
<FormularioInput type="text" name="testinput" v-model="fieldValue" />
</FormularioForm>
`
})
await flushPromises()
expect(wrapper.vm.formValues).toEqual({ testinput: '123' })
})
// ===========================================================================
// Replacement test for the above test - not quite as good of a test.
it('updates calls setFieldValue on form when a field contains a populated v-model on registration', () => {
const wrapper = mount(FormularioForm, {
propsData: {
formularioValue: { testinput: '123' }
},
slots: {
default: '<FormularioInput type="text" name="testinput" formulario-value="override-data" />'
}
})
expect(wrapper.emitted().input[wrapper.emitted().input.length - 1]).toEqual([{ testinput: 'override-data' }])
})
it('updates an inputs value when the form v-model is modified', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: 'abcd',
}
}
},
template: `
<FormularioForm v-model="formValues">
<FormularioInput v-slot="inputProps" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
</FormularioForm>
`
})
await flushPromises()
wrapper.vm.formValues = { testinput: '1234' }
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('1234')
})
it('emits an instance of FormSubmission', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: '<FormularioInput type="text" formulario-value="123" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission)
})
it('resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: '<FormularioInput type="text" validation="required" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
const submission = wrapper.emitted('submit-raw')[0][0]
expect(await submission.hasValidationErrors()).toBe(true)
})
it('resolves submitted form values to an object', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: '<FormularioInput type="text" validation="required" name="testinput" value="Justin" />' }
})
const submission = await wrapper.vm.formSubmitted()
expect(submission).toEqual({testinput: 'Justin'})
})
it('accepts a values prop and uses it to set the initial values', async () => {
const wrapper = mount(FormularioForm, {
propsData: { values: { name: 'Dave Barnett', candy: true } },
slots: { default: `
<FormularioInput v-slot="inputProps" name="name" validation="required" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
` }
})
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('Dave Barnett')
})
it('automatically registers with root plugin', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { box3: [] }, name: 'login' }
})
expect(wrapper.vm.$formulario.registry.has('login')).toBe(true)
expect(wrapper.vm.$formulario.registry.get('login')).toBe(wrapper.vm)
})
it('calls custom error handler with error and name', async () => {
const mockHandler = jest.fn((err, name) => err);
const wrapper = mount({
template: `
<div>
<FormularioForm
name="login"
/>
<FormularioForm
name="register"
/>
</div>
`
})
wrapper.vm.$formulario.extend({ errorHandler: mockHandler })
wrapper.vm.$formulario.handle({ formErrors: ['This is an error message'] }, 'login')
expect(mockHandler.mock.calls.length).toBe(1);
expect(mockHandler.mock.calls[0]).toEqual([{ formErrors: ['This is an error message'] }, 'login']);
})
it('errors are displayed on correctly named components', async () => {
const wrapper = mount({
template: `
<div>
<FormularioForm
name="login"
v-slot="vSlot"
>
<span v-for="error in vSlot.errors">{{ error }}</span>
</FormularioForm>
<FormularioForm
name="register"
v-slot="vSlot"
>
<span v-for="error in vSlot.errors">{{ error }}</span>
</FormularioForm>
</div>
`
})
expect(wrapper.vm.$formulario.registry.has('login') && wrapper.vm.$formulario.registry.has('register')).toBe(true)
wrapper.vm.$formulario.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulario-form').length).toBe(2)
expect(wrapper.find('.formulario-form--login span').exists()).toBe(true)
expect(wrapper.find('.formulario-form--register span').exists()).toBe(false)
})
it('errors are displayed on correctly named components', async () => {
const wrapper = mount({
template: `
<div>
<FormularioForm
name="login"
v-slot="vSlot"
>
<span v-for="error in vSlot.errors">{{ error }}</span>
</FormularioForm>
<FormularioForm
name="register"
v-slot="vSlot"
>
<span v-for="error in vSlot.errors">{{ error }}</span>
</FormularioForm>
</div>
`
})
expect(wrapper.vm.$formulario.registry.has('login') && wrapper.vm.$formulario.registry.has('register')).toBe(true)
wrapper.vm.$formulario.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulario-form').length).toBe(2)
expect(wrapper.find('.formulario-form--login span').exists()).toBe(true)
expect(wrapper.find('.formulario-form--register span').exists()).toBe(false)
})
it('receives a form-errors prop and displays it', async () => {
const wrapper = mount(FormularioForm, {
propsData: { name: 'main', formErrors: ['first', 'second'] },
scopedSlots: {
default: `
<div>
<span v-for="error in props.formErrors">{{ error }}</span>
<FormularioInput name="name" />
</div>
`
}
})
await flushPromises()
expect(wrapper.vm.$formulario.registry.get('main').mergedFormErrors.length).toBe(2)
})
it('it aggregates form-errors prop with form-named errors', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formErrors: ['first', 'second'], name: 'login' }
})
wrapper.vm.$formulario.handle({ formErrors: ['third'] }, 'login')
await flushPromises()
let errors = wrapper.vm.$formulario.registry.get('login').mergedFormErrors
expect(Object.keys(errors).length).toBe(3)
})
it('displays field errors on inputs with errors prop', async () => {
const wrapper = mount(FormularioForm, {
propsData: { errors: { sipple: ['This field has an error'] }},
slots: {
default: `
<FormularioInput v-slot="vSlot" name="sipple">
<span v-for="error in vSlot.context.allErrors">{{ error }}</span>
</FormularioInput>
`
}
})
await wrapper.vm.$nextTick()
expect(wrapper.find('span').exists()).toBe(true)
})
it('is able to display multiple errors on multiple elements', async () => {
const wrapper = mount({
template: `
<FormularioForm
name="register"
:errors="{inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']}"
>
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
<FormularioInput name="inputC" type="checkbox" />
</FormularioForm>
`
})
await wrapper.vm.$nextTick()
let errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(3)
expect(errors.inputA.length).toBe(2)
expect(errors.inputB.length).toBe(1)
expect(errors.inputC.length).toBe(1)
})
it('it can set multiple field errors with handle()', async () => {
const wrapper = mount({
template: `
<FormularioForm name="register">
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
<FormularioInput name="inputC" type="checkbox" />
</FormularioForm>
`
})
let errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(0)
wrapper.vm.$formulario.handle({ inputErrors: {inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']} }, "register")
await wrapper.vm.$nextTick()
await flushPromises()
errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(3)
expect(errors.inputA.length).toBe(2)
expect(errors.inputB.length).toBe(1)
expect(errors.inputC.length).toBe(1)
})
it('emits correct validation event on entry', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: `
<div>
<FormularioInput v-slot="inputProps" validation="required|in:bar" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
<FormularioInput 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(FormularioForm, {
slots: { default: `
<div>
<FormularioInput v-slot="inputProps" validation="required|in:bar" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
<FormularioInput 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
})
})
it('removes field data when that field is de-registered', async () => {
const wrapper = mount({
template: `
<FormularioForm
v-model="formData"
>
<FormularioInput v-slot="inputProps" name="foo">
<input v-model="inputProps.context.model" type="text" value="abc123">
</FormularioInput>
<FormularioInput type="checkbox" name="bar" v-if="formData.foo !== 'bar'" :value="1" />
</FormularioForm>
`,
data () {
return {
formData: {}
}
}
})
await flushPromises()
wrapper.find('input[type="text"]').setValue('bar')
await flushPromises()
expect(wrapper.findComponent(FormularioForm).vm.proxy).toEqual({ foo: 'bar' })
expect(wrapper.vm.formData).toEqual({ foo: 'bar' })
})
it('it allows resetting a form, hiding validation and clearing inputs.', async () => {
const wrapper = mount({
template: `
<FormularioForm
v-model="formData"
name="login"
ref="form"
>
<FormularioInput v-slot="inputProps" name="username" validation="required">
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
<FormularioInput v-slot="inputProps" name="password" validation="required|min:4,length">
<input v-model="inputProps.context.model" type="password">
</FormularioInput>
</FormularioForm>
`,
data () {
return {
formData: {}
}
}
})
const password = wrapper.find('input[type="password"]')
password.setValue('foo')
password.trigger('blur')
wrapper.find('form').trigger('submit')
wrapper.vm.$formulario.handle({
inputErrors: { username: ['Failed'] }
}, 'login')
await flushPromises()
// First make sure we caugth the errors
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(1)
wrapper.vm.$formulario.reset('login')
await flushPromises()
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(0)
expect(wrapper.vm.formData).toEqual({})
})
})

View File

@ -0,0 +1,91 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulario from '@/Formulario.js'
import FormularioInput from '@/FormularioInput.vue'
import FormularioForm from '@/FormularioForm.vue'
import FormularioGrouping from '@/FormularioGrouping.vue'
Vue.use(Formulario)
describe('FormularioGrouping', () => {
it('grouped fields to be setted', async () => {
const wrapper = mount(FormularioForm, {
propsData: { name: 'form', },
slots: {
default: `
<FormularioGrouping name="sub">
<FormularioInput name="text" v-slot="vSlot">
<input type="text" v-model="vSlot.context.model">
</FormularioInput>
</FormularioGrouping>
`
}
})
expect(wrapper.findAll('input[type="text"]').length).toBe(1)
wrapper.find('input[type="text"]').setValue('test')
const submission = await wrapper.vm.formSubmitted()
expect(submission).toEqual({sub: {text: 'test'}})
})
it('grouped fields to be getted', async () => {
const wrapper = mount(FormularioForm, {
propsData: { name: 'form', formularioValue: { sub: {text: 'initial value'}, text: 'simple text' } },
slots: {
default: `
<FormularioGrouping name="sub">
<FormularioInput name="text" v-slot="vSlot">
<input type="text" v-model="vSlot.context.model">
</FormularioInput>
</FormularioGrouping>
`
}
})
expect(wrapper.find('input[type="text"]').element.value).toBe('initial value')
})
it('data reactive with grouped fields', async () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `
<FormularioForm name="form" v-model="formValues">
<FormularioGrouping name="sub">
<FormularioInput name="text" v-slot="vSlot">
<input type="text" v-model="vSlot.context.model">
<span>{{ formValues.sub.text }}</span>
</FormularioInput>
</FormularioGrouping>
</FormularioForm>
`
})
expect(wrapper.find('span').text()).toBe('')
wrapper.find('input[type="text"]').setValue('test')
await flushPromises()
expect(wrapper.find('span').text()).toBe('test')
})
it('errors are setted for grouped fields', async () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `
<FormularioForm name="form" v-model="formValues" :errors="{'sub.text': 'Test error'}">
<FormularioGrouping name="sub">
<FormularioInput name="text" v-slot="vSlot">
<span v-for="error in vSlot.context.allErrors">{{ error }}</span>
</FormularioInput>
</FormularioGrouping>
</FormularioForm>
`
})
expect(wrapper.findAll('span').length).toBe(1)
})
})

View File

@ -0,0 +1,259 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount, createLocalVue } from '@vue/test-utils'
import Formulario from '@/Formulario.js'
import FormularioForm from '@/FormularioForm.vue'
import FormularioInput from '@/FormularioInput.vue'
const globalRule = jest.fn((context) => { return false })
Vue.use(Formulario, {
rules: {
globalRule
}
})
describe('FormularioInput', () => {
it('allows custom field-rule level validation strings', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
name: 'test',
validation: 'required|in:abcdef',
validationMessages: {in: 'the value was different than expected'},
errorBehavior: 'live',
value: 'other value'
},
scopedSlots: {
default: `<div><span v-for="error in props.context.allErrors">{{ error }}</span></div>`
}
})
await flushPromises()
expect(wrapper.find('span').text()).toBe('the value was different than expected')
})
it('allows custom field-rule level validation functions', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
name: 'test',
validation: 'required|in:abcdef',
validationMessages: { in: ({ value }) => `The string ${value} is not correct.` },
errorBehavior: 'live',
value: 'other value'
},
scopedSlots: {
default: `<div><span v-for="error in props.context.allErrors">{{ error }}</span></div>`
}
})
await flushPromises()
expect(wrapper.find('span').text()).toBe('The string other value is not correct.')
})
it('uses custom async validation rules on defined on the field', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
name: 'test',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: async ({ value }) => value === 'foo'
},
errorBehavior: 'live',
value: 'bar'
},
scopedSlots: {
default: `<div><span v-for="error in props.context.allErrors">{{ error }}</span></div>`
}
})
await flushPromises()
expect(wrapper.find('span').text()).toBe('failed the foobar check')
})
it('uses custom sync validation rules on defined on the field', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
name: 'test',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: ({ value }) => value === 'foo'
},
errorBehavior: 'live',
value: 'bar'
},
scopedSlots: {
default: `<div><span v-for="error in props.context.allErrors">{{ error }}</span></div>`
}
})
await flushPromises()
expect(wrapper.find('span').text()).toBe('failed the foobar check')
})
it('uses global custom validation rules', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
name: 'test',
validation: 'required|globalRule',
errorBehavior: 'live',
value: 'bar'
}
})
await flushPromises()
expect(globalRule.mock.calls.length).toBe(1)
})
it('emits correct validation event', async () => {
const wrapper = mount(FormularioInput, { propsData: {
name: 'test',
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(FormularioInput, {
propsData: {
name: 'test',
validation: 'required',
errorBehavior: 'blur',
value: '',
name: 'testinput',
},
scopedSlots: {
default: `<input type="text" v-model="props.context.model" @blur="props.context.blurHandler">`
}
})
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(FormularioInput, { propsData: {
name: 'test',
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(FormularioInput, {
propsData: {
name: 'test',
validation: 'in:xyz',
errorBehavior: 'live',
value: 'bar',
name: 'testinput',
},
scopedSlots: {
default: `<input type="text" v-model="props.context.model">`
}
})
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)
})
it('can bail on validation when encountering the bail rule', async () => {
const wrapper = mount(FormularioInput, {
propsData: { name: 'test', validation: 'bail|required|in:xyz', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(1);
})
it('can show multiple validation errors if they occur before the bail rule', async () => {
const wrapper = mount(FormularioInput, {
propsData: { name: 'test', validation: 'required|in:xyz|bail', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('can avoid bail behavior by using modifier', async () => {
const wrapper = mount(FormularioInput, {
propsData: { name: 'test', validation: '^required|in:xyz|min:10,length', errorBehavior: 'live', value: '123' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('prevents later error messages when modified rule fails', async () => {
const wrapper = mount(FormularioInput, {
propsData: { name: 'test', validation: '^required|in:xyz|min:10,length', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(1);
})
it('can bail in the middle of the rule set with a modifier', async () => {
const wrapper = mount(FormularioInput, {
propsData: { name: 'test', validation: 'required|^in:xyz|min:10,length', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('does not show errors on blur when set error-behavior is submit', async () => {
const wrapper = mount(FormularioInput, {
propsData: {
validation: 'required',
errorBehavior: 'submit',
name: 'test',
},
scopedSlots: {
default: `
<div>
<input v-model="props.context.model" @blur="props.context.blurHandler">
<span v-if="props.context.formShouldShowErrors" v-for="error in props.context.allErrors">{{ error }}</span>
</div>
`
}
})
expect(wrapper.find('span').exists()).toBe(false)
wrapper.find('input').trigger('input')
wrapper.find('input').trigger('blur')
await flushPromises()
expect(wrapper.find('span').exists()).toBe(false)
})
it('displays errors when error-behavior is submit and form is submitted', async () => {
const wrapper = mount(FormularioForm, {
propsData: {name: 'test'},
slots: {
default: `
<FormularioInput v-slot="inputProps" validation="required" name="testinput" error-behavior="submit">
<span v-for="error in inputProps.context.allErrors">{{ error }}</span>
</FormularioInput>
`
}
})
wrapper.trigger('submit')
await flushPromises()
expect(wrapper.find('span').exists()).toBe(true)
})
})

View File

@ -1,192 +0,0 @@
import Formulate from '@/Formulate.js'
describe('Formulate', () => {
it('can merge simple object', () => {
let a = {
optionA: true,
optionB: '1234'
}
let b = {
optionA: false
}
expect(Formulate.merge(a, b)).toEqual({
optionA: false,
optionB: '1234'
})
})
it('can add to simple array', () => {
let a = {
optionA: true,
optionB: ['first', 'second']
}
let b = {
optionB: ['third']
}
expect(Formulate.merge(a, b, true)).toEqual({
optionA: true,
optionB: ['first', 'second', 'third']
})
})
it('can merge recursively', () => {
let a = {
optionA: true,
optionC: {
first: '123',
third: {
a: 'b'
}
},
optionB: '1234'
}
let b = {
optionB: '567',
optionC: {
first: '1234',
second: '789',
}
}
expect(Formulate.merge(a, b)).toEqual({
optionA: true,
optionC: {
first: '1234',
third: {
a: 'b'
},
second: '789'
},
optionB: '567'
})
})
it('installs on vue instance', () => {
const components = [
'FormulateSlot',
'FormulateForm',
'FormulateHelp',
'FormulateLabel',
'FormulateInput',
'FormulateErrors',
'FormulateAddMore',
'FormulateGrouping',
'FormulateInputBox',
'FormulateInputText',
'FormulateInputFile',
'FormulateRepeatable',
'FormulateInputGroup',
'FormulateInputButton',
'FormulateInputSelect',
'FormulateInputSlider',
'FormulateInputTextArea',
'FormulateRepeatableRemove',
'FormulateRepeatableProvider'
]
const registry = []
function Vue () {}
Vue.component = function (name, instance) {
registry.push(name)
}
Formulate.install(Vue, { extended: true })
expect(Vue.prototype.$formulate).toBe(Formulate)
expect(registry).toEqual(components)
})
it('can extend instance in a plugin', () => {
function Vue () {}
Vue.component = function (name, instance) {}
const plugin = function (i) {
i.extend({
rules: {
testRule: () => false
}
})
}
Formulate.install(Vue, {
plugins: [ plugin ]
})
expect(typeof Vue.prototype.$formulate.options.rules.testRule).toBe('function')
})
it('locale default to en', () => {
Formulate.selectedLocale = false // reset the memoization
function Vue () {}
Vue.component = function (name, instance) {}
const vm = {}
Formulate.install(Vue, {
locales: {
de: {},
fr: {},
cn: {},
en: {}
}
})
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('en')
})
it('explicitly selects language', () => {
Formulate.selectedLocale = false // reset the memoization
function Vue () {}
Vue.component = function (name, instance) {}
const vm = {}
Formulate.install(Vue, {
locale: 'fr-CH',
locales: {
de: {},
fr: {},
cn: {},
en: {}
}
})
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('fr')
})
it('can select a specific language tag', () => {
Formulate.selectedLocale = false // reset the memoization
function Vue () {}
Vue.component = function (name, instance) {}
const vm = {}
Formulate.install(Vue, {
locale: 'nl-BE',
locales: {
de: {},
fr: {},
'nl-BE': {},
nl: {},
cn: {},
en: {}
}
})
expect(Vue.prototype.$formulate.getLocale(vm)).toBe('nl-BE')
})
it('can select a matching locale using i18n locale string', () => {
Formulate.selectedLocale = false // reset the memoization
function Vue () {}
Vue.component = function (name, instance) {}
const vm = { $i18n: {locale: 'cn-ET' } }
Formulate.install(Vue, {
locales: {
cn: {},
em: {}
}
})
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')
})
})

View File

@ -1,600 +0,0 @@
import Vue from 'vue'
import { mount, shallowMount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulate from '../../src/Formulate.js'
import FormSubmission from '../../src/FormSubmission.js'
import FormulateForm from '@/FormulateForm.vue'
import FormulateInput from '@/FormulateInput.vue'
Vue.use(Formulate)
describe('FormulateForm', () => {
it('render a form DOM element', () => {
const wrapper = mount(FormulateForm)
expect(wrapper.find('form').exists()).toBe(true)
})
it('accepts a default slot', () => {
const wrapper = mount(FormulateForm, {
slots: {
default: '<div class="default-slot-item" />'
}
})
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
})
it('intercepts submit event', () => {
const formSubmitted = jest.fn()
const wrapper = mount(FormulateForm, {
slots: {
default: "<button type='submit' />"
}
})
const spy = jest.spyOn(wrapper.vm, 'formSubmitted')
wrapper.find('form').trigger('submit')
expect(spy).toHaveBeenCalled()
})
it('registers its subcomponents', () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { testinput: 'has initial value' } },
slots: { default: '<FormulateInput type="text" name="subinput1" /><FormulateInput type="checkbox" name="subinput2" />' }
})
expect(wrapper.vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
})
it('deregisters a subcomponents', async () => {
const wrapper = mount({
data () {
return {
active: true
}
},
template: `
<FormulateForm>
<FormulateInput v-if="active" type="text" name="subinput1" />
<FormulateInput type="checkbox" name="subinput2" />
</FormulateForm>
`
})
await flushPromises()
expect(wrapper.findComponent(FormulateForm).vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
wrapper.setData({ active: false })
await flushPromises()
expect(wrapper.findComponent(FormulateForm).vm.registry.keys()).toEqual(['subinput2'])
})
it('can set a fields initial value', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { testinput: 'has initial value' } },
slots: { default: '<FormulateInput type="text" name="testinput" />' }
})
await flushPromises()
expect(wrapper.find('input').element.value).toBe('has initial value')
})
it('lets individual fields override form initial value', () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { testinput: 'has initial value' } },
slots: { default: '<FormulateInput type="text" formulate-value="123" name="testinput" />' }
})
expect(wrapper.find('input').element.value).toBe('123')
})
it('lets fields set form initial value with value prop', () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `<FormulateForm v-model="formValues">
<FormulateInput name="name" value="123" />
</FormulateForm>`
})
expect(wrapper.vm.formValues).toEqual({ name: '123' })
})
it('can set initial checked attribute on single checkboxes', () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { box1: true } },
slots: { default: '<FormulateInput type="checkbox" name="box1" />' }
})
expect(wrapper.find('input[type="checkbox"]').element.checked).toBeTruthy()
});
it('can set initial unchecked attribute on single checkboxes', () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { box1: false } },
slots: { default: '<FormulateInput type="checkbox" name="box1" />' }
})
expect(wrapper.find('input[type="checkbox"]').element.checked).toBeFalsy()
});
it('can set checkbox initial value with options', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { box2: ['second', 'third'] } },
slots: { default: '<FormulateInput type="checkbox" name="box2" :options="{first: \'First\', second: \'Second\', third: \'Third\'}" />' }
})
await flushPromises()
expect(wrapper.findAll('input').length).toBe(3)
});
it('receives updates to form model when individual fields are edited', () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
}
}
},
template: `
<FormulateForm v-model="formValues">
<FormulateInput type="text" name="testinput" />
</FormulateForm>
`
})
wrapper.find('input').setValue('edited value')
expect(wrapper.vm.formValues).toEqual({ testinput: 'edited value' })
})
// ===========================================================================
/**
* @todo in vue-test-utils version 1.0.0-beta.29 has some bugs related to
* synchronous updating. Some details are here:
*
* @update this test was re-implemented in version 1.0.0-beta.31 and seems to
* be workign now with flushPromises(). Leaving these docs here for now.
*
* https://github.com/vuejs/vue-test-utils/issues/1130
*
* This test is being commented out until there is a resolution on this issue,
* and instead being replaced with a mock call.
*/
it('updates initial form values when input contains a populated v-model', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
},
fieldValue: '123'
}
},
template: `
<FormulateForm v-model="formValues">
<FormulateInput type="text" name="testinput" v-model="fieldValue" />
</FormulateForm>
`
})
await flushPromises()
expect(wrapper.vm.formValues).toEqual({ testinput: '123' })
})
// ===========================================================================
// Replacement test for the above test - not quite as good of a test.
it('updates calls setFieldValue on form when a field contains a populated v-model on registration', () => {
const wrapper = mount(FormulateForm, {
propsData: {
formulateValue: { testinput: '123' }
},
slots: {
default: '<FormulateInput type="text" name="testinput" formulate-value="override-data" />'
}
})
expect(wrapper.emitted().input[wrapper.emitted().input.length - 1]).toEqual([{ testinput: 'override-data' }])
})
it('updates an inputs value when the form v-model is modified', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: 'abcd',
}
}
},
template: `
<FormulateForm v-model="formValues">
<FormulateInput type="text" name="testinput" />
</FormulateForm>
`
})
await flushPromises()
wrapper.vm.formValues = { testinput: '1234' }
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('1234')
})
it('emits an instance of FormSubmission', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" formulate-value="123" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.emitted('submit-raw')[0][0]).toBeInstanceOf(FormSubmission)
})
it('resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" />' }
})
wrapper.find('form').trigger('submit')
await flushPromises()
const submission = wrapper.emitted('submit-raw')[0][0]
expect(await submission.hasValidationErrors()).toBe(true)
})
it('resolves submitted form values to an object', async () => {
const wrapper = mount(FormulateForm, {
slots: { default: '<FormulateInput type="text" validation="required" name="testinput" value="Justin" />' }
})
const submission = await wrapper.vm.formSubmitted()
expect(submission).toEqual({testinput: 'Justin'})
})
it('accepts a values prop and uses it to set the initial values', async () => {
const wrapper = mount(FormulateForm, {
propsData: { values: { name: 'Dave Barnett', candy: true } },
slots: { default: `<FormulateInput type="text" name="name" validation="required" /><FormulateInput type="checkbox" name="candy" />` }
})
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('Dave Barnett')
expect(wrapper.find('input[type="checkbox"]').element.checked).toBe(true)
})
it('shows error messages when it includes a checkbox with options', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { box3: [] } },
slots: { default: '<FormulateInput type="checkbox" name="box3" validation="required" :options="{first: \'First\', second: \'Second\', third: \'Third\'}" />' }
})
wrapper.trigger('submit')
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.find('.formulate-input-error').exists()).toBe(true)
})
it('automatically registers with root plugin', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formulateValue: { box3: [] }, name: 'login' }
})
expect(wrapper.vm.$formulate.registry.has('login')).toBe(true)
expect(wrapper.vm.$formulate.registry.get('login')).toBe(wrapper.vm)
})
it('calls custom error handler with error and name', async () => {
const mockHandler = jest.fn((err, name) => err);
const wrapper = mount({
template: `
<div>
<FormulateForm
name="login"
/>
<FormulateForm
name="register"
/>
</div>
`
})
wrapper.vm.$formulate.extend({ errorHandler: mockHandler })
wrapper.vm.$formulate.handle({ formErrors: ['This is an error message'] }, 'login')
expect(mockHandler.mock.calls.length).toBe(1);
expect(mockHandler.mock.calls[0]).toEqual([{ formErrors: ['This is an error message'] }, 'login']);
})
it('errors are displayed on correctly named components', async () => {
const wrapper = mount({
template: `
<div>
<FormulateForm
name="login"
/>
<FormulateForm
name="register"
/>
</div>
`
})
expect(wrapper.vm.$formulate.registry.has('login') && wrapper.vm.$formulate.registry.has('register')).toBe(true)
wrapper.vm.$formulate.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulate-form').length).toBe(2)
expect(wrapper.find('.formulate-form--login .formulate-form-errors').exists()).toBe(true)
expect(wrapper.find('.formulate-form--register .formulate-form-errors').exists()).toBe(false)
})
it('errors are displayed on correctly named components', async () => {
const wrapper = mount({
template: `
<div>
<FormulateForm
name="login"
/>
<FormulateForm
name="register"
/>
</div>
`
})
expect(wrapper.vm.$formulate.registry.has('login') && wrapper.vm.$formulate.registry.has('register')).toBe(true)
wrapper.vm.$formulate.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulate-form').length).toBe(2)
expect(wrapper.find('.formulate-form--login .formulate-form-errors').exists()).toBe(true)
expect(wrapper.find('.formulate-form--register .formulate-form-errors').exists()).toBe(false)
})
it('hides root FormError if another form error exists and renders in new location', async () => {
const wrapper = mount({
template: `
<FormulateForm
name="login"
>
<h1>Login</h1>
<FormulateErrors />
<FormulateInput name="username" validation="required" error-behavior="live" />
</FormulateForm>
`
})
wrapper.vm.$formulate.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulate-form-errors').length).toBe(1)
// Ensure that we moved the position of the errors
expect(wrapper.find('h1 + *').element.classList.contains('formulate-form-errors')).toBe(true)
})
it('allows rendering multiple locations', async () => {
const wrapper = mount({
template: `
<FormulateForm
name="login"
>
<h1>Login</h1>
<FormulateErrors />
<FormulateInput name="username" validation="required" error-behavior="live" />
<FormulateErrors />
</FormulateForm>
`
})
wrapper.vm.$formulate.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulate-form-errors').length).toBe(2)
})
it('receives a form-errors prop and displays it', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formErrors: ['first', 'second'] },
slots: {
default: '<FormulateInput name="name" />'
}
})
await flushPromises()
expect(wrapper.findAll('.formulate-form-error').length).toBe(2)
})
it('it aggregates form-errors prop with form-named errors', async () => {
const wrapper = mount(FormulateForm, {
propsData: { formErrors: ['first', 'second'], name: 'login' }
})
wrapper.vm.$formulate.handle({ formErrors: ['third'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.formulate-form-error').length).toBe(3)
})
it('displays field errors on inputs with errors prop', async () => {
const wrapper = mount(FormulateForm, {
propsData: { errors: { sipple: ['This field has an error'] }},
slots: {
default: '<FormulateInput name="sipple" />'
}
})
await wrapper.vm.$nextTick()
expect(wrapper.find('.formulate-input .formulate-input-error').exists()).toBe(true)
})
it('is able to display multiple errors on multiple elements', async () => {
const wrapper = mount({
template: `
<FormulateForm
:errors="{inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']}"
>
<FormulateInput name="inputA" />
<FormulateInput name="inputB" type="textarea" />
<FormulateInput name="inputC" type="checkbox" />
</FormulateForm>
`
})
await wrapper.vm.$nextTick()
expect(wrapper.findAll('.formulate-input-error').length).toBe(4)
})
it('it can set multiple field errors with handle()', async () => {
const wrapper = mount({
template: `
<FormulateForm name="register">
<FormulateInput name="inputA" />
<FormulateInput name="inputB" type="textarea" />
<FormulateInput name="inputC" type="checkbox" />
</FormulateForm>
`
})
expect(wrapper.findAll('.formulate-input-error').length).toBe(0)
wrapper.vm.$formulate.handle({ inputErrors: {inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']} }, "register")
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.findAll('.formulate-input-error').length).toBe(4)
})
it('only sets 1 error when used on a FormulateGroup input', async () => {
const wrapper = mount({
template: `
<FormulateForm
name="register"
:errors="{order: 'this didnt work'}"
>
<FormulateInput name="order" type="checkbox" :options="{first: 'First', last: 'Last', middle: 'Middle'}" />
</FormulateForm>
`
})
await wrapper.vm.$nextTick()
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('properly de-registers an observer when removed', async () => {
const wrapper = mount({
data () {
return {
hasField: true
}
},
template: `
<FormulateForm
name="register"
:errors="{order: 'this didnt work'}"
>
<FormulateInput v-if="hasField" name="order" type="checkbox" :options="{first: 'First', last: 'Last', middle: 'Middle'}" />
</FormulateForm>
`
})
await flushPromises()
expect(wrapper.findComponent(FormulateForm).vm.errorObservers.length).toBe(1)
wrapper.setData({ hasField: false })
await flushPromises()
expect(wrapper.findComponent(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
})
})
it('removes field data when that field is de-registered', async () => {
const wrapper = mount({
template: `
<FormulateForm
v-model="formData"
>
<FormulateInput type="text" name="foo" value="abc123" />
<FormulateInput type="checkbox" name="bar" v-if="formData.foo !== 'bar'" :value="1" />
</FormulateForm>
`,
data () {
return {
formData: {}
}
}
})
await flushPromises()
wrapper.find('input[type="text"]').setValue('bar')
await flushPromises()
expect(wrapper.findComponent(FormulateForm).vm.proxy).toEqual({ foo: 'bar' })
expect(wrapper.vm.formData).toEqual({ foo: 'bar' })
})
it('it allows the removal of properties in proxy.', async () => {
const wrapper = mount({
template: `
<FormulateForm
v-model="formData"
name="login"
ref="form"
>
<FormulateInput type="text" name="username" validation="required" v-model="username" />
<FormulateInput type="password" name="password" validation="required|min:4,length" />
</FormulateForm>
`,
data () {
return {
formData: {},
username: undefined
}
}
})
wrapper.find('input[type="text"]').setValue('foo')
await flushPromises()
expect(wrapper.vm.username).toEqual('foo')
expect(wrapper.vm.formData).toEqual({ username: 'foo' })
wrapper.vm.$refs.form.setValues({})
await flushPromises()
expect(wrapper.vm.formData).toEqual({ username: '' })
})
it('it allows resetting a form, hiding validation and clearing inputs.', async () => {
const wrapper = mount({
template: `
<FormulateForm
v-model="formData"
name="login"
>
<FormulateInput type="text" name="username" validation="required" />
<FormulateInput type="password" name="password" validation="required|min:4,length" />
</FormulateForm>
`,
data () {
return {
formData: {}
}
}
})
const password = wrapper.find('input[type="password"]')
password.setValue('foo')
password.trigger('blur')
wrapper.find('form').trigger('submit')
wrapper.vm.$formulate.handle({
inputErrors: { username: ['Failed'] }
}, 'login')
await flushPromises()
// First make sure we showed the errors
expect(wrapper.findAll('.formulate-input-error').length).toBe(3)
wrapper.vm.$formulate.reset('login')
await flushPromises()
expect(wrapper.findAll('.formulate-input-error').length).toBe(0)
expect(wrapper.vm.formData).toEqual({})
})
})

View File

@ -1,319 +0,0 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount, createLocalVue } from '@vue/test-utils'
import Formulate from '@/Formulate.js'
import FormulateForm from '@/FormulateForm.vue'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputBox from '@/inputs/FormulateInputBox.vue'
const globalRule = jest.fn((context) => { return false })
Vue.use(Formulate, {
locales: {
en: {
email: ({ value }) => `Super invalid email: ${value}`
}
},
rules: {
globalRule
},
library: {
special: {
classification: 'box',
component: 'FormulateInputBox'
}
}
})
describe('FormulateInput', () => {
it('allows custom field-rule level validation strings', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|in:abcdef',
validationMessages: {in: 'the value was different than expected'},
errorBehavior: 'live',
value: 'other value'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('the value was different than expected')
})
it('allows custom field-rule level validation functions', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|in:abcdef',
validationMessages: { in: ({ value }) => `The string ${value} is not correct.` },
errorBehavior: 'live',
value: 'other value'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('The string other value is not correct.')
})
it('uses globally overridden validation messages', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|email',
errorBehavior: 'live',
value: 'wrong email'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('Super invalid email: wrong email')
})
it('uses custom async validation rules on defined on the field', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: async ({ value }) => value === 'foo'
},
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('failed the foobar check')
})
it('uses custom sync validation rules on defined on the field', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|foobar',
validationMessages: {
foobar: 'failed the foobar check'
},
validationRules: {
foobar: ({ value }) => value === 'foo'
},
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.find('.formulate-input-error').text()).toBe('failed the foobar check')
})
it('uses global custom validation rules', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required|globalRule',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(globalRule.mock.calls.length).toBe(1)
})
it('can extend its standard library of inputs', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'special',
validation: 'required',
errorBehavior: 'live',
value: 'bar'
} })
await flushPromises()
expect(wrapper.findComponent(FormulateInputBox).exists()).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)
})
it('allows overriding the label default slot component', async () => {
const localVue = createLocalVue()
localVue.component('CustomLabel', {
render: function (h) {
return h('div', { class: 'custom-label' }, [`custom: ${this.context.label}`])
},
props: ['context']
})
localVue.use(Formulate, { slotComponents: { label: 'CustomLabel' } })
const wrapper = mount(FormulateInput, { localVue, propsData: { label: 'My label here' } })
expect(wrapper.find('.custom-label').html()).toBe('<div class="custom-label">custom: My label here</div>')
})
it('allows overriding the help default slot component', async () => {
const localVue = createLocalVue()
localVue.component('CustomHelp', {
render: function (h) {
return h('small', { class: 'custom-help' }, [`custom: ${this.context.help}`])
},
props: ['context']
})
localVue.use(Formulate, { slotComponents: { help: 'CustomHelp' } })
const wrapper = mount(FormulateInput, { localVue, propsData: { help: 'My help here' } })
expect(wrapper.find('.custom-help').html()).toBe('<small class="custom-help">custom: My help here</small>')
})
it('allows overriding the errors component', async () => {
const localVue = createLocalVue()
localVue.component('CustomErrors', {
render: function (h) {
return h('ul', { class: 'my-errors' }, this.context.visibleValidationErrors.map(message => h('li', message)))
},
props: ['context']
})
localVue.use(Formulate, { slotComponents: { errors: 'CustomErrors' } })
const wrapper = mount(FormulateInput, { localVue, propsData: {
help: 'My help here',
errorBehavior: 'live',
validation: 'required'
} })
await flushPromises()
expect(wrapper.find('.my-errors').html())
.toBe(`<ul class="my-errors">\n <li>Text is required.</li>\n</ul>`)
// Clean up after this call — we should probably get rid of the singleton all together....
Formulate.extend({ slotComponents: { errors: 'FormulateErrors' }})
})
it('links help text with `aria-describedby`', async () => {
const wrapper = mount(FormulateInput, {
propsData: {
type: 'text',
validation: 'required',
errorBehavior: 'live',
value: 'bar',
help: 'Some help text'
}
})
await flushPromises()
const id = `${wrapper.vm.context.id}-help`
expect(wrapper.find('input').attributes('aria-describedby')).toBe(id)
expect(wrapper.find('.formulate-input-help').attributes().id).toBe(id)
});
it('it does not use aria-describedby if there is no help text', async () => {
const wrapper = mount(FormulateInput, {
propsData: {
type: 'text',
validation: 'required',
errorBehavior: 'live',
value: 'bar',
}
})
await flushPromises()
expect(wrapper.find('input').attributes('aria-describedby')).toBeFalsy()
});
it('can bail on validation when encountering the bail rule', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', validation: 'bail|required|in:xyz', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(1);
})
it('can show multiple validation errors if they occur before the bail rule', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', validation: 'required|in:xyz|bail', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('can avoid bail behavior by using modifier', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', validation: '^required|in:xyz|min:10,length', errorBehavior: 'live', value: '123' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('prevents later error messages when modified rule fails', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', validation: '^required|in:xyz|min:10,length', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(1);
})
it('can bail in the middle of the rule set with a modifier', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', validation: 'required|^in:xyz|min:10,length', errorBehavior: 'live' }
})
await flushPromises();
expect(wrapper.vm.context.visibleValidationErrors.length).toBe(2);
})
it('does not show errors on blur when set error-behavior is submit', async () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'text',
validation: 'required',
errorBehavior: 'submit',
} })
wrapper.find('input').trigger('input')
wrapper.find('input').trigger('blur')
await flushPromises()
expect(wrapper.find('.formulate-input-errors').exists()).toBe(false)
})
it('displays errors when error-behavior is submit and form is submitted', async () => {
const wrapper = mount(FormulateForm, {
slots: {
default: `<FormulateInput error-behavior="submit" validation="required" />`
}
})
wrapper.trigger('submit')
await flushPromises()
expect(wrapper.find('.formulate-input-errors').exists()).toBe(true)
})
})

View File

@ -1,228 +0,0 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '../../src/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputBox from '@/inputs/FormulateInputBox.vue'
import FormulateInputGroup from '@/inputs/FormulateInputGroup.vue'
Vue.use(Formulate)
describe('FormulateInputBox', () => {
it('renders a box element when type "checkbox" ', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
expect(wrapper.findComponent(FormulateInputBox).exists()).toBe(true)
})
it('renders a box element when type "radio"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio' } })
expect(wrapper.findComponent(FormulateInputBox).exists()).toBe(true)
})
it('passes an explicitly given name prop through to the root radio elements', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', name: 'foo', options: {a: '1', b: '2'} } })
expect(wrapper.findAll('input[name="foo"]')).toHaveLength(2)
})
it('passes an explicitly given name prop through to the root checkbox elements', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', name: 'foo', options: {a: '1', b: '2'} } })
expect(wrapper.findAll('input[name="foo"]')).toHaveLength(2)
})
it('box inputs properly process options object in context library', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
expect(Array.isArray(wrapper.vm.context.options)).toBe(true)
})
it('renders a group when type "checkbox" with options', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
expect(wrapper.findComponent(FormulateInputGroup).exists()).toBe(true)
})
it('renders a group when type "radio" with options', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
expect(wrapper.findComponent(FormulateInputGroup).exists()).toBe(true)
})
it('defaults labelPosition to "after" when type "checkbox"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
expect(wrapper.vm.context.labelPosition).toBe('after')
})
it('labelPosition of defaults to before when type "checkbox" with options', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'}}})
expect(wrapper.vm.context.labelPosition).toBe('before')
})
it('renders multiple inputs with options when type "radio"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
expect(wrapper.findAll('input[type="radio"]').length).toBe(2)
})
it('generates ids if not provided when type "radio"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
expect(wrapper.find('input[type="radio"]').attributes().id).toBeTruthy()
})
it('additional context does not bleed through to attributes with type "radio" and options', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'} } })
expect(Object.keys(wrapper.find('input[type="radio"]').attributes())).toEqual(["type", "id", "value"])
})
it('additional context does not bleed through to attributes with type "checkbox" and options', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2'} } })
expect(Object.keys(wrapper.find('input[type="checkbox"]').attributes())).toEqual(["type", "id", "value"])
})
it('allows external attributes to make it down to the inner box elements', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'}, readonly: 'true' } })
expect(Object.keys(wrapper.find('input[type="radio"]').attributes()).includes('readonly')).toBe(true)
})
it('does not use the value attribute to be checked', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', value: '123' } })
expect(wrapper.find('input').element.checked).toBe(false)
})
it('uses the checked attribute to be checked', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', checked: 'true' } })
await flushPromises()
await wrapper.vm.$nextTick()
expect(wrapper.find('input').element.checked).toBe(true)
})
it('uses the value attribute to select "type" radio when using options', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'radio', options: {a: '1', b: '2'}, value: 'b' } })
await flushPromises()
expect(wrapper.findAll('input:checked').length).toBe(1)
})
it('uses the value attribute to select "type" checkbox when using options', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: {a: '1', b: '2', c: '123'}, value: ['b', 'c'] } })
await flushPromises()
expect(wrapper.findAll('input:checked').length).toBe(2)
})
/**
* it data binding
*/
it('sets array of values via v-model when type "checkbox"', async () => {
const wrapper = mount({
data () {
return {
checkboxValues: [],
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
}
},
template: `
<div>
<FormulateInput type="checkbox" v-model="checkboxValues" :options="options" />
</div>
`
})
const fooInputs = wrapper.findAll('input[value^="foo"]')
expect(fooInputs.length).toBe(2)
fooInputs.at(0).setChecked()
await flushPromises()
fooInputs.at(1).setChecked()
await flushPromises()
expect(wrapper.vm.checkboxValues).toEqual(['foo', 'fooey'])
})
it('does not pre-set internal value when type "radio" with options', async () => {
const wrapper = mount({
data () {
return {
radioValue: '',
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
}
},
template: `
<div>
<FormulateInput type="radio" v-model="radioValue" :options="options" />
</div>
`
})
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.vm.radioValue).toBe('')
})
it('does not pre-set internal value of FormulateForm when type "radio" with options', async () => {
const wrapper = mount({
data () {
return {
radioValue: '',
formValues: {},
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey'}
}
},
template: `
<FormulateForm
v-model="formValues"
>
<FormulateInput type="radio" v-model="radioValue" name="foobar" :options="options" />
</FormulateForm>
`
})
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.vm.formValues.foobar).toBe('')
})
it('does precheck the correct input when radio with options', async () => {
const wrapper = mount({
data () {
return {
radioValue: 'fooey',
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey', gooey: 'Gooey'}
}
},
template: `
<div>
<FormulateInput type="radio" v-model="radioValue" :options="options" />
</div>
`
})
await flushPromises()
const checkboxes = wrapper.findAll('input[type="radio"]:checked')
expect(checkboxes.length).toBe(1)
expect(checkboxes.at(0).element.value).toBe('fooey')
})
it('shows validation errors when blurred', async () => {
const wrapper = mount({
data () {
return {
radioValue: 'fooey',
options: {foo: 'Foo', bar: 'Bar', fooey: 'Fooey', gooey: 'Gooey'}
}
},
template: `
<div>
<FormulateInput type="radio" v-model="radioValue" :options="options" validation="in:gooey" />
</div>
`
})
wrapper.find('input[value="fooey"]').trigger('blur')
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.find('.formulate-input-error').exists()).toBe(true)
})
it('renders no boxes when options array is empty', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', options: [] } })
expect(wrapper.findComponent(FormulateInputGroup).exists()).toBe(true)
expect(wrapper.find('input[type="checkbox"]').exists()).toBe(false)
})
it('renders multiple labels both with correct id', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox', label: 'VueFormulate FTW!'} })
const id = wrapper.find('input[type="checkbox"]').attributes('id')
const labelIds = wrapper.findAll('label').wrappers.map(label => label.attributes('for'));
expect(labelIds.length).toBe(2);
expect(labelIds.filter(labelId => labelId === id).length).toBe(2);
})
})

View File

@ -1,115 +0,0 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '@/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputButton from '@/inputs/FormulateInputButton.vue'
Vue.use(Formulate)
describe('FormulateInputButton', () => {
it('renders a button element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'button' } })
expect(wrapper.findComponent(FormulateInputButton).exists()).toBe(true)
})
it('renders a button element when type submit', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'submit' } })
expect(wrapper.findComponent(FormulateInputButton).exists()).toBe(true)
})
it('uses value as highest priority content', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'submit',
value: 'Value content',
label: 'Label content',
name: 'Name content'
}})
expect(wrapper.find('button').text()).toBe('Value content')
})
it('uses label as second highest priority content', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'submit',
label: 'Label content',
name: 'Name content'
}})
expect(wrapper.find('button').text()).toBe('Label content')
})
it('uses name as lowest priority content', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'submit',
name: 'Name content'
}})
expect(wrapper.find('button').text()).toBe('Name content')
})
it('uses "Submit" as default content', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'submit',
}})
expect(wrapper.find('button').text()).toBe('Submit')
})
it('with label does not render label element', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'button',
label: 'my label'
}})
expect(wrapper.find('label').exists()).toBe(false)
})
it('does not render label element when type "submit"', () => {
const wrapper = mount(FormulateInput, { propsData: {
type: 'button',
label: 'my label'
}})
expect(wrapper.find('label').exists()).toBe(false)
})
it('renders slot inside button when type "button"', () => {
const wrapper = mount(FormulateInput, {
propsData: {
type: 'button',
label: 'my label',
},
slots: {
default: '<span>My custom slot</span>'
}
})
expect(wrapper.find('button > span').html()).toBe('<span>My custom slot</span>')
})
it('emits a click event when the button itself is clicked', async () => {
const handle = jest.fn();
const wrapper = mount({
template: `
<div>
<FormulateInput
type="submit"
@click="handle"
/>
</div>
`,
methods: {
handle
}
})
wrapper.find('button[type="submit"]').trigger('click')
await flushPromises();
expect(handle.mock.calls.length).toBe(1)
})
})
it('passes an explicitly given name prop through to the root element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'button', name: 'foo' } })
expect(wrapper.find('button[name="foo"]').exists()).toBe(true)
})
it('additional context does not bleed through to button input attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'button' } } )
expect(Object.keys(wrapper.find('button').attributes())).toEqual(["type", "id"])
})

View File

@ -1,60 +0,0 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulate from '@/Formulate.js'
import FileUpload from '@/FileUpload.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputFile from '@/inputs/FormulateInputFile.vue'
Vue.use(Formulate)
describe('FormulateInputFile', () => {
it('type "file" renders a file element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'file' } })
expect(wrapper.findComponent(FormulateInputFile).exists()).toBe(true)
})
it('type "image" renders a file element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image' } })
expect(wrapper.findComponent(FormulateInputFile).exists()).toBe(true)
})
it('forces an error-behavior live mode when upload-behavior is live and it has content', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image', validation: 'mime:image/jpeg', value: [{ url: 'img.jpg' }] } })
expect(wrapper.vm.showValidationErrors).toBe(true)
})
it('wont show errors when upload-behavior is live and it is required but empty', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image', validation: 'required|mime:image/jpeg' } })
expect(wrapper.vm.showValidationErrors).toBe(false)
})
it('contains a data-has-preview attribute when showing a preview', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image', value: [ { url: 'https://www.example.com/image.png' } ], validation: 'required|mime:image/jpeg' } })
const file = wrapper.find('[data-has-preview]')
expect(file.exists()).toBe(true)
expect(file.attributes('data-has-preview')).toBe('true')
})
it('passes an explicitly given name prop through to the root element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image', name: 'foo' } })
expect(wrapper.find('input[name="foo"]').exists()).toBe(true)
})
it('additional context does not bleed through to file input attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'image' } } )
expect(Object.keys(wrapper.find('input[type="file"]').attributes())).toEqual(["type", "id"])
})
/**
* ===========================================================================
* Currently there appears to be no way to properly mock upload data in
* vue-test-utils because JSDom doesn't implement DataTransfer:
*
* https://stackoverflow.com/questions/48993134/how-to-test-input-file-with-jest-and-vue-test-utils
*/
// it('type "image" renders a file element', async () => {
// })
})

View File

@ -1,482 +0,0 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulate from '@/Formulate.js'
import FileUpload from '@/FileUpload.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateForm from '@/FormulateForm.vue'
import FormulateGrouping from '@/FormulateGrouping.vue'
import FormulateRepeatableProvider from '@/FormulateRepeatableProvider.vue'
Vue.use(Formulate)
describe('FormulateInputGroup', () => {
it('allows nested fields to be sub-rendered', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group' },
slots: {
default: '<FormulateInput type="text" />'
}
})
expect(wrapper.findAll('.formulate-input-group-repeatable input[type="text"]').length).toBe(1)
})
it('registers sub-fields with grouping', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group' },
slots: {
default: '<FormulateInput type="text" name="persona" />'
}
})
expect(wrapper.findComponent(FormulateRepeatableProvider).vm.registry.has('persona')).toBeTruthy()
})
it('is not repeatable by default', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group' },
slots: {
default: '<FormulateInput type="text" />'
}
})
expect(wrapper.findAll('.formulate-input-group-add-more').length).toBe(0)
})
it('adds an add more button when repeatable', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true },
slots: {
default: '<FormulateInput type="text" />'
}
})
expect(wrapper.findAll('.formulate-input-group-add-more').length).toBe(1)
})
it('repeats the default slot when adding more', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true },
slots: {
default: '<div class="wrap"><FormulateInput type="text" /></div>'
}
})
wrapper.find('.formulate-input-group-add-more button').trigger('click')
await flushPromises();
expect(wrapper.findAll('.wrap').length).toBe(2)
})
it('re-hydrates a repeatable field', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true, value: [{email: 'jon@example.com'}, {email:'jane@example.com'}] },
slots: {
default: '<div class="wrap"><FormulateInput type="text" name="email" /></div>'
}
})
await flushPromises()
const fields = wrapper.findAll('input[type="text"]')
expect(fields.length).toBe(2)
expect(fields.at(0).element.value).toBe('jon@example.com')
expect(fields.at(1).element.value).toBe('jane@example.com')
})
it('v-modeling a subfield changes all values', async () => {
const wrapper = mount({
template: `
<FormulateInput
v-model="users"
type="group"
>
<FormulateInput type="text" v-model="email" name="email" />
<FormulateInput type="text" name="name" />
</FormulateInput>
`,
data () {
return {
users: [{email: 'jon@example.com'}, {email:'jane@example.com'}],
email: 'jim@example.com'
}
}
})
await flushPromises()
const fields = wrapper.findAll('input[type="text"]')
expect(fields.length).toBe(4)
expect(fields.at(0).element.value).toBe('jim@example.com')
expect(fields.at(2).element.value).toBe('jim@example.com')
})
it('v-modeling a subfield updates group v-model value', async () => {
const wrapper = mount({
template: `
<FormulateInput
v-model="users"
type="group"
>
<FormulateInput type="text" v-model="email" name="email" />
<FormulateInput type="text" name="name" />
</FormulateInput>
`,
data () {
return {
users: [{email: 'jon@example.com'}, {email:'jane@example.com'}],
email: 'jim@example.com'
}
}
})
await flushPromises()
expect(wrapper.vm.users).toEqual([{email: 'jim@example.com'}, {email:'jim@example.com'}])
})
it('prevents form submission when children have validation errors', async () => {
const submit = jest.fn()
const wrapper = mount({
template: `
<FormulateForm
@submit="submit"
>
<FormulateInput
type="text"
validation="required"
value="testing123"
name="name"
/>
<FormulateInput
v-model="users"
type="group"
>
<FormulateInput type="text" name="email" />
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`,
data () {
return {
users: [{email: 'jon@example.com'}, {email:'jane@example.com'}],
}
},
methods: {
submit
}
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
expect(submit.mock.calls.length).toBe(0);
})
it('allows form submission with children when there are no validation errors', async () => {
const submit = jest.fn()
const wrapper = mount({
template: `
<FormulateForm
@submit="submit"
>
<FormulateInput
type="text"
validation="required"
value="testing123"
name="name"
/>
<FormulateInput
name="users"
type="group"
>
<FormulateInput type="text" name="email" validation="required|email" value="justin@wearebraid.com" />
<FormulateInput type="text" name="name" validation="required" value="party" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`,
methods: {
submit
}
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
expect(submit.mock.calls.length).toBe(1);
})
it('displays validation errors on group children when form is submitted', async () => {
const wrapper = mount({
template: `
<FormulateForm>
<FormulateInput
name="users"
type="group"
:repeatable="true"
>
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
await flushPromises()
expect(wrapper.find('[data-classification="text"] .formulate-input-error').exists()).toBe(true);
})
it('displays error messages on newly registered fields when formShouldShowErrors is true', async () => {
const wrapper = mount({
template: `
<FormulateForm>
<FormulateInput
name="users"
type="group"
:repeatable="true"
>
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
// Click the add more button
wrapper.find('button[type="button"]').trigger('click')
await flushPromises()
expect(wrapper.findAll('[data-classification="text"] .formulate-input-error').length).toBe(2)
})
it('displays error messages on newly registered fields when formShouldShowErrors is true', async () => {
const wrapper = mount({
template: `
<FormulateForm>
<FormulateInput
name="users"
type="group"
:repeatable="true"
>
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
// Click the add more button
wrapper.find('button[type="button"]').trigger('click')
await flushPromises()
expect(wrapper.findAll('[data-classification="text"] .formulate-input-error').length).toBe(2)
})
it('allows the removal of groups', async () => {
const wrapper = mount({
template: `
<FormulateForm>
<FormulateInput
name="users"
type="group"
:repeatable="true"
v-model="users"
>
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`,
data () {
return {
users: [{name: 'justin'}, {name: 'bill'}]
}
}
})
await flushPromises()
wrapper.find('.formulate-input-group-repeatable-remove').trigger('click')
await flushPromises()
expect(wrapper.vm.users).toEqual([{name: 'bill'}])
})
it('can override the add more text', async () => {
const wrapper = mount(FormulateInput, {
propsData: { addLabel: '+ Add a user', type: 'group', repeatable: true },
slots: {
default: '<div />'
}
})
expect(wrapper.find('button').text()).toEqual('+ Add a user')
})
it('does not allow more than the limit', async () => {
const wrapper = mount(FormulateInput, {
propsData: { addLabel: '+ Add a user', type: 'group', repeatable: true, limit: 2, value: [{}, {}]},
slots: {
default: '<div class="repeated"/>'
}
})
expect(wrapper.findAll('.repeated').length).toBe(2)
expect(wrapper.find('button').exists()).toBeFalsy()
})
it('does not truncate the number of items if value is more than limit', async () => {
const wrapper = mount(FormulateInput, {
propsData: { addLabel: '+ Add a user', type: 'group', repeatable: true, limit: 2, value: [{}, {}, {}, {}]},
slots: {
default: '<div class="repeated"/>'
}
})
expect(wrapper.findAll('.repeated').length).toBe(4)
})
it('allows a slot override of the add button and has addItem prop', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true, addLabel: '+ Name' },
scopedSlots: {
default: '<div class="repeatable" />',
addmore: '<span class="add-name" @click="props.addMore">{{ props.addLabel }}</span>'
}
})
expect(wrapper.find('.formulate-input-group-add-more').exists()).toBeFalsy()
const addButton = wrapper.find('.add-name')
expect(addButton.text()).toBe('+ Name')
addButton.trigger('click')
await flushPromises()
expect(wrapper.findAll('.repeatable').length).toBe(2)
})
it('allows a slot override of the repeatable area', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true, value: [{}, {}]},
scopedSlots: {
repeatable: '<div class="repeat">{{ props.index }}<div class="remove" @click="props.removeItem" /></div>',
}
})
const repeats = wrapper.findAll('.repeat')
expect(repeats.length).toBe(2)
expect(repeats.at(1).text()).toBe("1")
wrapper.find('.remove').trigger('click')
await flushPromises()
expect(wrapper.findAll('.repeat').length).toBe(1)
})
it('allows a slot override of the remove area', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true, value: [{phone: 'iPhone'}, {phone: 'Android'}]},
scopedSlots: {
default: '<FormulateInput type="text" name="phone" />',
remove: '<button @click="props.removeItem" class="remove-this">Get outta here</button>',
}
})
const repeats = wrapper.findAll('.remove-this')
expect(repeats.length).toBe(2)
const button = wrapper.find('.remove-this')
expect(button.text()).toBe('Get outta here')
button.trigger('click')
await flushPromises()
expect(wrapper.findAll('input').wrappers.map(w => w.element.value)).toEqual(['Android'])
})
it('removes the proper item from the group', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'group', repeatable: true },
slots: {
default: '<FormulateInput type="text" name="foo" />'
}
})
wrapper.find('input').setValue('first entry')
wrapper.find('.formulate-input-group-add-more button').trigger('click')
wrapper.find('.formulate-input-group-add-more button').trigger('click')
await flushPromises();
wrapper.findAll('input').at(1).setValue('second entry')
wrapper.findAll('input').at(2).setValue('third entry')
// First verify all the proper entries are where we expect
expect(wrapper.findAll('input').wrappers.map(input => input.element.value)).toEqual(['first entry', 'second entry', 'third entry'])
// Now remove the middle one
wrapper.findAll('.formulate-input-group-repeatable-remove').at(1).trigger('click')
await flushPromises()
expect(wrapper.findAll('input').wrappers.map(input => input.element.value)).toEqual(['first entry', 'third entry'])
})
it('does not show an error message on group input when child has an error', async () => {
const wrapper = mount({
template: `
<FormulateForm>
<FormulateInput
type="text"
validation="required"
value="testing123"
name="name"
/>
<FormulateInput
v-model="users"
type="group"
>
<FormulateInput type="text" name="email" />
<FormulateInput type="text" name="name" validation="required" />
</FormulateInput>
<FormulateInput type="submit" />
</FormulateForm>
`,
data () {
return {
users: [{email: 'jon@example.com'}, {}],
}
}
})
const form = wrapper.findComponent(FormulateForm)
await form.vm.formSubmitted()
expect(wrapper.find('[data-classification="group"] > .formulate-input-errors').exists()).toBe(false)
})
it('exposes the index to the context object on default slot', async () => {
const wrapper = mount({
template: `
<FormulateInput
type="group"
name="test"
#default="{ name, index }"
:value="[{}, {}]"
>
<div class="repeatable">{{ name }}-{{ index }}</div>
</FormulateInput>
`,
})
const repeatables = wrapper.findAll('.repeatable')
expect(repeatables.length).toBe(2)
expect(repeatables.at(0).text()).toBe('test-0')
expect(repeatables.at(1).text()).toBe('test-1')
})
it('forces non-repeatable groups to not initialize with an empty array', async () => {
const wrapper = mount({
template: `
<FormulateInput
type="group"
name="test"
v-model="model"
>
<div class="repeatable" />
</FormulateInput>
`,
data () {
return {
model: []
}
}
})
await flushPromises();
expect(wrapper.findComponent(FormulateGrouping).vm.items).toEqual([{}])
})
it('allows repeatable groups to initialize with an empty array', async () => {
const wrapper = mount({
template: `
<FormulateInput
type="group"
name="test"
:repeatable="true"
v-model="model"
>
<div class="repeatable" />
</FormulateInput>
`,
data () {
return {
model: []
}
}
})
await flushPromises();
expect(wrapper.findComponent(FormulateGrouping).vm.items).toEqual([])
})
})

View File

@ -1,55 +0,0 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulate from '@/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputSelect from '@/inputs/FormulateInputSelect.vue'
Vue.use(Formulate)
describe('FormulateInputSelect', () => {
it('renders select input when type is "select"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select' } })
expect(wrapper.findComponent(FormulateInputSelect).exists()).toBe(true)
})
it('renders select options when options object is passed', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select', options: { first: 'First', second: 'Second' } } })
const option = wrapper.find('option[value="second"]')
expect(option.exists()).toBe(true)
expect(option.text()).toBe('Second')
})
it('renders select options when options array is passed', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select', options: [
{ value: 13, label: 'Jane' },
{ value: 22, label: 'Jon' }
]} })
const option = wrapper.find('option[value="22"]')
expect(option.exists()).toBe(true)
expect(option.text()).toBe('Jon')
})
it('renders select list with no options when empty array is passed.', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select', options: []} })
const option = wrapper.find('option')
expect(option.exists()).toBe(false)
})
it('renders select list placeholder option.', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select', placeholder: 'Select this', options: []} })
const options = wrapper.findAll('option')
expect(options.length).toBe(1)
expect(options.at(0).attributes('disabled')).toBeTruthy()
})
it('passes an explicitly given name prop through to the root element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select', options: [], name: 'foo' } })
expect(wrapper.find('select[name="foo"]').exists()).toBe(true)
})
it('additional context does not bleed through to text select attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'select' } } )
expect(Object.keys(wrapper.find('select').attributes())).toEqual(["id"])
})
})

View File

@ -1,38 +0,0 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import Formulate from '@/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputSlider from '@/inputs/FormulateInputSlider.vue'
Vue.use(Formulate)
/**
* Test each type of slider element
*/
describe('FormulateInputSlider', () => {
it('renders range input when type is "range"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } })
expect(wrapper.findComponent(FormulateInputSlider).exists()).toBe(true)
})
it('does not show value if the show-value prop is not set', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range', value: '15', min: '0', max: '100' } })
expect(wrapper.find('.formulate-input-element-range-value').exists()).toBe(false)
})
it('renders the value when type is "range" and show-value prop is set', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range', showValue: 'true', value: '15', min: '0', max: '100' } })
expect(wrapper.find('.formulate-input-element-range-value').text()).toBe('15')
})
it('passes an explicitly given name prop through to the root element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range', name: 'foo' } })
expect(wrapper.find('input[name="foo"]').exists()).toBe(true)
})
it('additional context does not bleed through to range input attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'range' } } )
expect(Object.keys(wrapper.find('input[type="range"]').attributes())).toEqual(["type", "id"])
})
})

View File

@ -1,313 +0,0 @@
import Vue from 'vue'
import flushPromises from 'flush-promises'
import { mount } from '@vue/test-utils'
import Formulate from '../../src/Formulate.js'
import FormulateInput from '@/FormulateInput.vue'
import FormulateInputText from '@/inputs/FormulateInputText.vue'
import { doesNotReject } from 'assert';
Vue.use(Formulate)
/**
* Test each type of text element
*/
describe('FormulateInputText', () => {
it('renders text input when type is "text"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders search input when type is "search"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'search' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders email input when type is "email"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'email' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders number input when type is "number"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'number' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders color input when type is "color"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'color' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders date input when type is "date"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'date' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders month input when type is "month"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'month' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders password input when type is "password"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'password' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders tel input when type is "tel"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'tel' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders time input when type is "time"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'time' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders url input when type is "url"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'url' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
it('renders week input when type is "week"', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'week' } })
expect(wrapper.findComponent(FormulateInputText).exists()).toBe(true)
})
/**
* Test rendering functionality to text inputs
*/
it('automatically assigns an id', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.vm.context).toHaveProperty('id')
expect(wrapper.find(`input[id="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
it('passes an explicitly given name prop through to the root text element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', name: 'foo' } })
expect(wrapper.find('input[name="foo"]').exists()).toBe(true)
})
it('passes an explicitly given name prop through to the root textarea element', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'textarea', name: 'foo' } })
expect(wrapper.find('textarea[name="foo"]').exists()).toBe(true)
})
it('additional context does not bleed through to text input attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } } )
expect(Object.keys(wrapper.find('input[type="text"]').attributes())).toEqual(["type", "id"])
})
it('additional context does not bleed through to textarea input attributes', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } } )
expect(Object.keys(wrapper.find('textarea').attributes())).toEqual(["id"])
})
it('doesnt automatically add a label', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('label').exists()).toBe(false)
})
it('renders labels', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', label: 'Field label' } })
expect(wrapper.find(`label[for="${wrapper.vm.context.attributes.id}"]`).exists()).toBe(true)
})
it('doesnt automatically render help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(false)
})
it('renders help text', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', help: 'This is some help text' } })
expect(wrapper.find(`.formulate-input-help`).exists()).toBe(true)
})
/**
* Test data binding
*/
it('emits input (vmodel) event with value when edited', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
wrapper.find('input').setValue('Updated Value')
expect(wrapper.emitted().input).toBeTruthy()
expect(wrapper.emitted().input[0]).toEqual(['Updated Value'])
})
it('doesnt re-context itself if there were no changes', async () => {
const wrapper = mount({
data () {
return {
valueA: 'first value',
valueB: 'second value'
}
},
template: `
<div>
<FormulateInput type="text" ref="first" v-model="valueA" :placeholder="valueA" />
<FormulateInput type="text" ref="second" v-model="valueB" :placeholder="valueB" />
</div>
`
})
await flushPromises()
const firstContext = wrapper.findComponent({ref: "first"}).vm.context
const secondContext = wrapper.findComponent({ref: "second"}).vm.context
wrapper.find('input').setValue('new value')
await flushPromises()
expect(firstContext).toBeTruthy()
expect(wrapper.vm.valueA === 'new value').toBe(true)
expect(wrapper.vm.valueB === 'second value').toBe(true)
expect(wrapper.findComponent({ref: "first"}).vm.context === firstContext).toBe(false)
expect(wrapper.findComponent({ref: "second"}).vm.context === secondContext).toBe(true)
})
it('uses the v-model value as the initial value', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val' } })
expect(wrapper.find('input').element.value).toBe('initial val')
})
it('uses the value as the initial value', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', value: 'initial val' } })
expect(wrapper.find('input').element.value).toBe('initial val')
})
it('uses the value prop as the initial value when v-model provided', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', formulateValue: 'initial val', value: 'initial other val' } })
expect(wrapper.find('input').element.value).toBe('initial other val')
})
it('uses a proxy model internally if it doesnt have a v-model', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'textarea' } })
const input = wrapper.find('textarea')
input.setValue('changed value')
expect(wrapper.vm.proxy).toBe('changed value')
})
/**
* Test error handling
*/
it('doesnt automatically render errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('.formulate-input-errors').exists()).toBe(false)
})
it('accepts a single string as an error prop', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', error: 'This is an error' } })
expect(wrapper.find('.formulate-input-errors').exists()).toBe(true)
})
it('accepts an array as errors prop', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'live', errors: ['This is an error', 'this is another'] } })
expect(wrapper.findAll('.formulate-input-error').length).toBe(2)
})
it('removes any duplicate errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'live', errors: ['This is an error', 'This is an error'] } })
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('adds data-has-errors when there are errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'live', errors: ['This is an error', 'This is an error'] } })
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
})
it('Should always show explicitly set errors, but not validation errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', validation: 'required', errorBehavior: 'blur', errors: ['Bad input'] } })
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(true)
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('Should show no errors when there are no errors', () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text' } })
expect(wrapper.find('[data-has-errors]').exists()).toBe(false)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(false)
expect(wrapper.findAll('.formulate-input-error').exists()).toBe(false)
})
it('allows error-behavior blur to be overridden with show-errors', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'blur', showErrors: true, validation: 'required' } })
await flushPromises()
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(true)
expect(wrapper.findAll('.formulate-input-errors').exists()).toBe(true)
expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('shows errors on blur with error-behavior blur', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'text', errorBehavior: 'blur', validation: 'required' } })
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(false)
expect(wrapper.findAll('.formulate-input-error').exists()).toBe(false)
expect(wrapper.vm.showValidationErrors).toBe(false)
wrapper.find('input').trigger('blur')
await flushPromises()
expect(wrapper.vm.showValidationErrors).toBe(true)
expect(wrapper.find('[data-is-showing-errors]').exists()).toBe(true)
// expect(wrapper.findAll('.formulate-input-errors').exists()).toBe(true)
// expect(wrapper.findAll('.formulate-input-error').length).toBe(1)
})
it('continues to show errors if validation fires more than one time', async () => {
const wrapper = mount(FormulateInput, { propsData: { type: 'date', errorBehavior: 'live', validation: [['after', '01/01/2021']] , value: '01/01/1999' } })
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
wrapper.find('input').trigger('input')
await wrapper.vm.$nextTick()
await flushPromises()
expect(wrapper.find('[data-has-errors]').exists()).toBe(true)
})
it('allows label-before override with scoped slot', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', label: 'flavor' },
scopedSlots: {
label: '<label>{{ props.label }} town</label>'
}
})
expect(wrapper.find('label').text()).toBe('flavor town')
})
it('allows label-after override with scoped slot', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', label: 'flavor', labelPosition: 'after' },
scopedSlots: {
label: '<label>{{ props.label }} town</label>'
}
})
expect(wrapper.find('label').text()).toBe('flavor town')
})
it('allows help-before override', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', label: 'flavor', help: 'I love this next field...', helpPosition: 'before' },
})
expect(wrapper.find('label + *').classes('formulate-input-help')).toBe(true)
})
it('Allow help text override with scoped slot', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', name: 'soda', help: 'Do you want some'},
scopedSlots: {
help: '<small>{{ props.help }} {{ props.name }}?</small>'
}
})
expect(wrapper.find('small').text()).toBe('Do you want some soda?')
})
it('Allow errors override with scoped slot', async () => {
const wrapper = mount(FormulateInput, {
propsData: { type: 'text', name: 'soda', validation: 'required|in:foo,bar', errorBehavior: 'live' },
scopedSlots: {
errors: '<ul class="my-errors"><li v-for="error in props.visibleValidationErrors">{{ error }}</li></ul>'
}
})
await flushPromises();
expect(wrapper.findAll('.my-errors li').length).toBe(2)
})
})