Tests actualized
This commit is contained in:
parent
051dd3e20c
commit
348f3aa017
95
test/unit/Formulario.test.js
Normal file
95
test/unit/Formulario.test.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
527
test/unit/FormularioForm.test.js
Normal file
527
test/unit/FormularioForm.test.js
Normal 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 field’s 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({})
|
||||||
|
})
|
||||||
|
})
|
91
test/unit/FormularioGrouping.test.js
Normal file
91
test/unit/FormularioGrouping.test.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
259
test/unit/FormularioInput.test.js
Normal file
259
test/unit/FormularioInput.test.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
@ -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')
|
|
||||||
})
|
|
||||||
})
|
|
@ -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 field’s 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({})
|
|
||||||
})
|
|
||||||
})
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
@ -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);
|
|
||||||
})
|
|
||||||
})
|
|
@ -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"])
|
|
||||||
})
|
|
@ -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 () => {
|
|
||||||
|
|
||||||
// })
|
|
||||||
})
|
|
@ -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([])
|
|
||||||
})
|
|
||||||
})
|
|
@ -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"])
|
|
||||||
})
|
|
||||||
})
|
|
@ -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"])
|
|
||||||
})
|
|
||||||
})
|
|
@ -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('doesn’t 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('doesn’t 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('doesn’t 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('doesn’t 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)
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in New Issue
Block a user