diff --git a/__tests__/_resources/utils.js b/__tests__/_resources/utils.js new file mode 100644 index 0000000..2eafe42 --- /dev/null +++ b/__tests__/_resources/utils.js @@ -0,0 +1,61 @@ +import { mount } from '@vue/test-utils' + +import FormGenerator from '@/FormGenerator.vue' + +/** + * Mount the form generator component + * @param schema - schema object to pass as prop + * @param {Object} model - model object to pass as prop + * @returns {VueWrapper} + */ +export function mountFormGenerator (schema, model) { + return mount(FormGenerator, { props: { schema, model } }) +} + +/** + * Generate a form schema for a single field component + * @param {String} name - name of the field + * @param {String} model - model key of the field + * @param {String} type - field type + * @param {String} inputType - field input type, required if `type === 'input'` + * @param {String} label - field label + * @param {any} initialValue - initial model value + * @param {Object} extraFieldProperties - extra field properties to add + * @returns {{schema: {fields: [{name, model, inputType, label, type}]}, model: {}}} + */ +export function generateSchemaSingleField ( + name, + model, + type, + inputType, + label, + initialValue, + extraFieldProperties +) { + return { + model: { + [model]: initialValue + }, + schema: { + fields: [ + { + name, model, type, inputType, label, ...extraFieldProperties + } + ] + } + } +} + +/** + * Generate props for a single field component + * @param {Object} formSchema - entire form schema object + * @returns {{field: *, model, id: string, formGenerator: {}}} + */ +export function generatePropsSingleField (formSchema) { + return { + id: formSchema.name + '_test_id', + formGenerator: {}, + field: { ...formSchema.schema.fields[0] }, + model: { ...formSchema.model } + } +} diff --git a/__tests__/components/fields/FieldButton.spec.js b/__tests__/components/fields/FieldButton.spec.js new file mode 100644 index 0000000..6c1c7b3 --- /dev/null +++ b/__tests__/components/fields/FieldButton.spec.js @@ -0,0 +1,78 @@ +import { mountFormGenerator, generatePropsSingleField } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldButton from '@/fields/buttons/FieldButton.vue' +import FieldPassword from '@/fields/input/FieldPassword.vue' +import FieldCheckbox from '@/fields/input/FieldCheckbox.vue' + +const form = { + model: { + password: '', + checkboxTestModel: false + }, + schema: { + fields: [ + { + type: 'button', + buttonText: 'Reset password field', + onClick: (model, _field) => { + model.password = '' + } + }, + { + name: 'checkboxTestName', + model: 'checkboxTestModel', + label: 'Checkbox Test', + type: 'checkbox' + }, + { + name: 'passwordTest', + model: 'password', + label: 'Password', + type: 'input', + inputType: 'password' + } + ] + } +} + +const props = generatePropsSingleField(form) + + +describe('Test FieldButton', () => { + + it('Should render correctly', () => { + const wrapper = mount(FieldButton, { props }) + expect(wrapper.find('button').exists()).toBe(true) + expect(wrapper.find('button').element.innerHTML).toContain(props.field.buttonText) + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldButton } + + const formWrapper = mountFormGenerator(form.schema, form.model) + const buttonField = formWrapper.findComponent(FieldButton) + expect(buttonField.exists()).toBeTruthy() + expect(buttonField.find('button').element.innerHTML).toContain(props.field.buttonText) + }) + + it('Should update model values', async () => { + config.global.components = { FieldPassword, FieldButton, FieldCheckbox } + + const formWrapper = mountFormGenerator(form.schema, form.model) + expect(formWrapper.find('input[type=password]').exists()).toBe(true) + expect(formWrapper.find('input[type=checkbox]').exists()).toBe(true) + expect(formWrapper.find('button').exists()).toBe(true) + + await formWrapper.find('input[type=password]').setValue('password') + expect(formWrapper.vm.model.password).toBe('password') + + const buttonField = formWrapper.findComponent(FieldButton) + expect(buttonField.exists()).toBe(true) + + await buttonField.find('button').trigger('click.prevent') + expect(formWrapper.vm.model.password).toBe('') + }) + +}) \ No newline at end of file diff --git a/__tests__/components/fields/FieldCheckbox.spec.js b/__tests__/components/fields/FieldCheckbox.spec.js new file mode 100644 index 0000000..496180a --- /dev/null +++ b/__tests__/components/fields/FieldCheckbox.spec.js @@ -0,0 +1,107 @@ +import { generatePropsSingleField, generateSchemaSingleField, mountFormGenerator } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldCheckbox from '@/fields/input/FieldCheckbox.vue' + +const form = generateSchemaSingleField( + 'checkboxTestName', + 'checkboxTestModel', + 'input', + 'checkbox', + 'Checkbox Test', + false +) + +const props = generatePropsSingleField(form) + +describe('Test FieldCheckbox', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldCheckbox, { props }) + // Checkbox should be rendered. + expect(wrapper.find('input[type=checkbox]').exists()).toBe(true) + // Label should be rendered and have the correct text. + expect(wrapper.find('label').text()).toContain(props.field.label) + await wrapper.vm.$nextTick() + // Checked attribute should be false, since the default value is set to false inside the model. + expect(wrapper.find('input[type=checkbox]').element.checked).toBe(false) + }) + + it('Should render correctly inside form generator', async() => { + config.global.components = { FieldCheckbox } + const formWrapper = mountFormGenerator(form.schema, form.model) + + expect(formWrapper.findComponent(FieldCheckbox).exists()).toBe(true) + expect(formWrapper.find('input[type=checkbox]').exists()).toBe(true) + }) + + it('Should be disabled, when specified with a conditional function', async () => { + const model = { ...form.model, otherTestProperty: false } + const disabled = (model, _field) => model.otherTestProperty === false + const field = { ...form.schema.fields[0], disabled } + + expect(disabled(model)).toBe(true) + + const wrapper = mount(FieldCheckbox, { props: { ...props, model, field } }) + expect(wrapper.find('input[type=checkbox]').exists()).toBe(true) + await wrapper.vm.$nextTick() + + expect(wrapper.vm.isDisabled).toBe(true) + expect(wrapper.find('input[type=checkbox]').element.disabled).toBe(true) + }) + + it('Should be disabled, when specified with boolean', async () => { + const field = { ...props.field, disabled: true } + const wrapper = mount(FieldCheckbox, { props: { ...props, field } }) + + const checkbox = wrapper.find('input[type=checkbox]') + + expect(checkbox.exists()).toBe(true) + expect(wrapper.vm.isDisabled).toBe(true) + + await wrapper.vm.$nextTick() + + expect(wrapper.find('input[type=checkbox]').element.disabled).toBe(true) + }) + + it('Checked state should be the same as default value', async () => { + const model = { checkboxTestModel: true } + const wrapper = mount(FieldCheckbox, { props: { ...props, model } }) + + await wrapper.vm.$nextTick() + + expect(wrapper.find('input[type=checkbox]').element.checked).toBe(model.checkboxTestModel) + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldCheckbox, { props }) + await wrapper.find('input[type=checkbox]').trigger('change' ) + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldCheckbox } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + const wrapper = formWrapper.findComponent(FieldCheckbox) + expect(wrapper.exists()).toBe(true) + + await wrapper.vm.$nextTick() + expect(wrapper.find('input').element.checked).toBe(false) + + await wrapper.find('input').trigger('click') + expect(wrapper.find('input').element.checked).toBe(true) + + await wrapper.find('input').trigger('change') + expect(wrapper.emitted()).toHaveProperty('onInput', [ [ true ] ]) + await wrapper.vm.$nextTick() + + expect(formWrapper.vm.model.checkboxTestModel).toBe(true) + // Model value of wrapper should be updated as well, since this gets passed down from the Form Generator. + expect(wrapper.vm.model.checkboxTestModel).toBe(true) + }) + +}) diff --git a/__tests__/components/fields/FieldColor.spec.js b/__tests__/components/fields/FieldColor.spec.js new file mode 100644 index 0000000..fc78b84 --- /dev/null +++ b/__tests__/components/fields/FieldColor.spec.js @@ -0,0 +1,50 @@ +import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField } from '@test/_resources/utils.js' +import { mount, config } from '@vue/test-utils' +import { describe, it, expect } from 'vitest' + +import FieldColor from '@/fields/input/FieldColor.vue' + +const form = generateSchemaSingleField( + 'testColor', + 'colorModel', + 'input', + 'color', + 'Pick a color', + '' +) + +const props = generatePropsSingleField(form) + +describe('Test FieldColor', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldColor, { props }) + expect(wrapper.find('input[type=color]').exists()).toBe(true) + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldColor } + + const formWrapper = mountFormGenerator(form.schema, form.model) + expect(formWrapper.findComponent(FieldColor).exists()).toBe(true) + expect(formWrapper.find('input[type=color]').exists()).toBe(true) + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldColor, { props }) + await wrapper.find('input[type=color]').trigger('change') + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldColor } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + const wrapper = formWrapper.findComponent(FieldColor) + await wrapper.find('input[type=color]').setValue('#efefef') + expect(wrapper.emitted()).toHaveProperty('onInput', [ [ '#efefef' ] ]) + expect(formWrapper.vm.model.colorModel).toBe('#efefef') + }) + +}) diff --git a/__tests__/components/fields/FieldNumber.spec.js b/__tests__/components/fields/FieldNumber.spec.js new file mode 100644 index 0000000..efecb0d --- /dev/null +++ b/__tests__/components/fields/FieldNumber.spec.js @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest' +import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField } from '@test/_resources/utils.js' +import { mount, config } from '@vue/test-utils' + +import FieldNumber from '@/fields/input/FieldNumber.vue' + +const form = generateSchemaSingleField( + 'testNumber', + 'numberModel', + 'input', + 'number', + 'Number input', + 0, + { + max: 5, + min: 2 + } +) + +const props = generatePropsSingleField(form) + +describe('Test FieldNumber', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldNumber, { props }) + expect(wrapper.find('input[type=number]').exists()).toBeTruthy() + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldNumber } + const formWrapper = mountFormGenerator(form.schema, form.model) + expect(formWrapper.find('input[type=number]').exists()).toBeTruthy() + + const wrapper = formWrapper.findComponent(FieldNumber) + expect(wrapper.exists()).toBeTruthy() + expect(wrapper.attributes().min).toBe('2') + expect(wrapper.attributes().max).toBe('5') + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldNumber, { props }) + await wrapper.find('input').trigger('input') + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldNumber } + + const formWrapper = mountFormGenerator(form.schema, form.model) + const numberField = formWrapper.findComponent(FieldNumber) + expect(numberField.exists()).toBeTruthy() + + await numberField.find('input').setValue(4) + expect(formWrapper.vm.model.numberModel).toBe(4) + }) +}) diff --git a/__tests__/components/fields/FieldPassword.spec.js b/__tests__/components/fields/FieldPassword.spec.js new file mode 100644 index 0000000..0b3773d --- /dev/null +++ b/__tests__/components/fields/FieldPassword.spec.js @@ -0,0 +1,113 @@ +import { mountFormGenerator, generateSchemaSingleField, generatePropsSingleField } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldPassword from '@/fields/input/FieldPassword.vue' + +const form = generateSchemaSingleField( + 'passwordTest', + 'password', + 'input', + 'password', + 'Password', + '' +) + +const props = generatePropsSingleField(form) + +const propsWithIndicator = { + ...props, + field: { ...props.field, indicator: true } +} + +describe('Test FieldPassword', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldPassword, { props }) + expect(wrapper.find('input[type=password]').exists()).toBe(true) + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldPassword } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + expect(formWrapper.find('input[type=password]').exists()).toBe(true) + expect(formWrapper.findComponent(FieldPassword).exists()).toBe(true) + }) + + it('Shouldn\'t display password strength meter, if not specified', async () => { + const wrapper = mount(FieldPassword, { props }) + expect(wrapper.find('input[type=password]').exists()).toBe(true) + expect(wrapper.find('div[class=password-strength-indicator]').exists()).toBe(false) + }) + + it('Should display password strength meter, if specified', async () => { + const wrapper = mount(FieldPassword, { props: propsWithIndicator }) + expect(wrapper.find('div[class=password-strength-indicator]').exists()).toBe(true) + }) + + it('Should display no password strength', async() => { + const wrapper = mount(FieldPassword, { props: propsWithIndicator }) + expect(wrapper.vm.passwordStrength).toBe(0) + }) + + it('Should display weak password strength', async() => { + const weakPasswordModel = { password: 'test' } + const wrapper = mount(FieldPassword, { props: { ...propsWithIndicator, model: weakPasswordModel } }) + expect(wrapper.vm.passwordStrength).toBe(1) + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('15%') + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('red') + }) + + it('Should display medium password strength', async() => { + const mediumPasswordModel = { password: 'shouldbemedium12' } + const wrapper = mount(FieldPassword, { props: { ...propsWithIndicator, model: mediumPasswordModel } }) + expect(wrapper.vm.passwordStrength).toBe(2) + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('50%') + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('orange') + }) + + it('Should display good password strength', async() => { + const goodPasswordModel = { password: 'This-shOuld-be-a-good-password12!' } + const wrapper = mount(FieldPassword, { props: { ...propsWithIndicator, model: goodPasswordModel } }) + expect(wrapper.vm.passwordStrength).toBe(3) + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('100%') + expect(wrapper.find('div[class=password-strength-indicator]').attributes().style).toContain('green') + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldPassword, { props }) + await wrapper.find('input[type=password]').setValue('password') + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldPassword } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + const wrapper = formWrapper.findComponent(FieldPassword) + expect(wrapper.exists()).toBe(true) + await wrapper.find('input').setValue('testpassword') + expect(wrapper.emitted()).toHaveProperty('onInput', [ [ 'testpassword' ] ]) + await wrapper.vm.$nextTick() + + expect(formWrapper.vm.model.password).toBe('testpassword') + expect(wrapper.vm.model.password).toBe('testpassword') + }) + + it('Should give an error when required and empty', async () => { + const fieldProps = { ...props, field: { ...props.field, required: true } } + const wrapper = mount(FieldPassword, { props: fieldProps }) + + const input = wrapper.find('input[type=password]') + await input.trigger('change') + await wrapper.vm.$nextTick() + await input.trigger('blur') + expect(wrapper.vm.errors.length).toBe(1) + }) + +}) diff --git a/__tests__/components/fields/FieldRadio.spec.js b/__tests__/components/fields/FieldRadio.spec.js new file mode 100644 index 0000000..439d7c1 --- /dev/null +++ b/__tests__/components/fields/FieldRadio.spec.js @@ -0,0 +1,86 @@ +import { mountFormGenerator, generatePropsSingleField, generateSchemaSingleField } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldRadio from '@/fields/input/FieldRadio.vue' + +const form = generateSchemaSingleField( + 'radioTest', + 'radioModel', + 'input', + 'radio', + 'Which one?', + '', + { + options: [ + { name: 'Test 1', value: 'value_1' }, + { name: 'Test 2', value: 'value_2' }, + { name: 'Test 3', value: 'value_3' } + ] + } +) + +const props = generatePropsSingleField(form) + +const getFieldId = (fieldName, optionName) => { + return `${fieldName}_${optionName}` +} + +const checkRadioInputs = (radioInputs, wrapper) => { + // Check if all radio inputs are correct. + for (const [ idx, input ] of radioInputs.entries()) { + expect(input.attributes().value).toBe(form.schema.fields[0].options[idx].value) + expect(input.attributes().name).toBe('radioTest') + const _id = getFieldId(input.element.name, form.schema.fields[0].options[idx].name) + expect(wrapper.find(`label[for='${_id}']`).exists()).toBeTruthy() + } +} + +describe('Test FieldRadio', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldRadio, { props }) + expect(wrapper.find('input[type=radio]').exists()).toBe(true) + + const radioInputs = wrapper.findAll('input[type=radio]') + expect(radioInputs).toHaveLength(3) + + // Check if all radio inputs are correct. + checkRadioInputs(radioInputs, wrapper) + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldRadio } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + expect(formWrapper.findComponent(FieldRadio).exists()).toBeTruthy() + const radioInputs = formWrapper.findAll('input[type=radio]') + expect(radioInputs).toHaveLength(3) + + // Check if all radio inputs are correct. + checkRadioInputs(radioInputs, formWrapper) + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldRadio, { props }) + await wrapper.find('input[type=radio]').setChecked() + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldRadio } + + const formWrapper = mountFormGenerator(form.schema, form.model) + expect(formWrapper.findComponent(FieldRadio)).toBeTruthy() + expect(formWrapper.vm.model.radioModel).toBe('') + + await formWrapper.find('input[type=radio]').setChecked() + + expect(formWrapper.vm.model.radioModel).toBe('value_1') + + await formWrapper.findAll('input[type=radio]')[1].setChecked() + expect(formWrapper.vm.model.radioModel).toBe('value_2') + }) + +}) \ No newline at end of file diff --git a/__tests__/components/fields/FieldReset.spec.js b/__tests__/components/fields/FieldReset.spec.js new file mode 100644 index 0000000..30748a2 --- /dev/null +++ b/__tests__/components/fields/FieldReset.spec.js @@ -0,0 +1,92 @@ +import { mountFormGenerator } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldReset from '@/fields/buttons/FieldReset.vue' +import FieldCheckbox from '@/fields/input/FieldCheckbox.vue' +import FieldPassword from '@/fields/input/FieldPassword.vue' + +const form = { + model: { + password: '', + checkboxTestModel: false + }, + schema: { + fields: [ + { + name: 'checkboxTestName', + model: 'checkboxTestModel', + label: 'Checkbox Test', + type: 'checkbox' + }, + { + name: 'passwordTest', + model: 'password', + label: 'Password', + type: 'input', + inputType: 'password' + }, + { + type: 'reset', + buttonText: 'Reset all' + } + ] + } +} + +const props = { + id: 'reset_test', + formGenerator: {}, + field: { ...form.schema.fields[2] }, + model: { ...form.model } +} + +describe('Test FieldReset', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldReset, { props }) + const resetButton = wrapper.find('input[type=reset]') + expect(resetButton.exists()).toBeTruthy() + expect(resetButton.attributes().value).toBe('Reset all') + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldPassword, FieldCheckbox, FieldReset } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + expect(formWrapper.findComponent(FieldReset).exists()).toBeTruthy() + expect(formWrapper.find('input[type=reset]').exists()).toBeTruthy() + }) + + it('Should reset all values in a form\'s model', async () => { + config.global.components = { FieldPassword, FieldCheckbox, FieldReset } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + // Check that the initial values have been passed down correctly + expect(formWrapper.vm.model.password).toBeFalsy() + expect(formWrapper.vm.model.checkboxTestModel).toBeFalsy() + + expect(formWrapper.findAllComponents([ FieldPassword, FieldCheckbox, FieldReset ])).toBeTruthy() + + const passwordField = formWrapper.findComponent(FieldPassword) + const checkboxField = formWrapper.findComponent(FieldCheckbox) + + await passwordField.find('input').setValue('password') + expect(formWrapper.vm.model.password).toBe('password') + + await checkboxField.find('input').setChecked(true) + expect(formWrapper.vm.model.checkboxTestModel).toBe(true) + + // Reset will be triggered when clicking the reset button, however + // this isn't the case when using vue test utils. + await formWrapper.find('form').trigger('reset') + expect(formWrapper.emitted()).toHaveProperty('reset') + + + expect(formWrapper.vm.model.password).toBe('') + }) + +}) \ No newline at end of file diff --git a/__tests__/components/fields/FieldSelect.spec.js b/__tests__/components/fields/FieldSelect.spec.js new file mode 100644 index 0000000..7e28792 --- /dev/null +++ b/__tests__/components/fields/FieldSelect.spec.js @@ -0,0 +1,66 @@ +import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator } from '@test/_resources/utils.js' +import { mount, config } from '@vue/test-utils' +import { describe, it, expect } from 'vitest' + +import FieldSelect from '@/fields/input/FieldSelect.vue' + +const form = generateSchemaSingleField( + 'testSelect', + 'selectModel', + 'select', + null, + 'What is this?', + '', + { + placeholder: 'Select a test value', + options: [ + { value: 'test_1', name: 'Test 1' }, + { value: 'test_2', name: 'Test 2' }, + { value: 'test_3', name: 'Test 3' } + ] + } +) + +const props = generatePropsSingleField(form) + +describe('Test FieldSelect', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldSelect, { props }) + expect(wrapper.find('select').exists()).toBeTruthy() + expect(wrapper.findAll('option').length).toBe(4) + // First option should be filled with placeholder and value should be empty + expect(wrapper.find('option').element.innerHTML).toContain(props.field.placeholder) + expect(wrapper.find('option').attributes().value).toBe('') + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldSelect } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + const selectField = formWrapper.findComponent(FieldSelect) + expect(selectField.exists()).toBeTruthy() + expect(selectField.findAll('option').length).toBe(4) + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldSelect, { props }) + await wrapper.find('select').trigger('change') + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldSelect } + + const formWrapper = mountFormGenerator(form.schema, form.model) + const selectField = formWrapper.findComponent(FieldSelect) + expect(selectField.exists()).toBeTruthy() + + await selectField.find('select').setValue('test_2') + expect(formWrapper.vm.model.selectModel).toBe('test_2') + await selectField.find('select').setValue('test_3') + expect(formWrapper.vm.model.selectModel).toBe('test_3') + }) + +}) diff --git a/__tests__/components/fields/FieldSubmit.spec.js b/__tests__/components/fields/FieldSubmit.spec.js new file mode 100644 index 0000000..cc8b9fe --- /dev/null +++ b/__tests__/components/fields/FieldSubmit.spec.js @@ -0,0 +1,90 @@ +import { mountFormGenerator } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldCheckbox from '@/fields/input/FieldCheckbox.vue' +import FieldPassword from '@/fields/input/FieldPassword.vue' +import FieldSubmit from '@/fields/buttons/FieldSubmit.vue' + +const form = { + model: { + password: '', + checkboxTestModel: false + }, + schema: { + fields: [ + { + name: 'checkboxTestName', + model: 'checkboxTestModel', + label: 'Checkbox Test', + type: 'checkbox' + }, + { + name: 'passwordTest', + model: 'password', + label: 'Password', + type: 'input', + inputType: 'password' + }, + { + type: 'submit', + buttonText: 'Submit all' + } + ] + } +} + +const props = { + id: 'submit_test', + formGenerator: {}, + field: { ...form.schema.fields[2] }, + model: { ...form.model } +} + +describe('Test FieldSubmit', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldSubmit, { props }) + const submitButton = wrapper.find('input[type=submit]') + expect(submitButton.exists()).toBeTruthy() + expect(submitButton.attributes().value).toBe('Submit all') + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldPassword, FieldCheckbox, FieldSubmit } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + expect(formWrapper.findComponent(FieldSubmit).exists()).toBeTruthy() + expect(formWrapper.find('input[type=submit]').exists()).toBeTruthy() + }) + + it('Should reset all values in a form\'s model', async () => { + config.global.components = { FieldPassword, FieldCheckbox, FieldSubmit } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + // Check that the initial values have been passed down correctly + expect(formWrapper.vm.model.password).toBeFalsy() + expect(formWrapper.vm.model.checkboxTestModel).toBeFalsy() + + expect(formWrapper.findAllComponents([ FieldPassword, FieldCheckbox, FieldSubmit ])).toBeTruthy() + + const passwordField = formWrapper.findComponent(FieldPassword) + const checkboxField = formWrapper.findComponent(FieldCheckbox) + + await passwordField.find('input').setValue('password') + expect(formWrapper.vm.model.password).toBe('password') + + await checkboxField.find('input').setChecked(true) + expect(formWrapper.vm.model.checkboxTestModel).toBe(true) + + // Submit will be triggered when clicking the submit button, however + // this isn't the case when using vue test utils. + await formWrapper.find('form').trigger('submit') + expect(formWrapper.emitted()).toHaveProperty('submit') + + }) + +}) \ No newline at end of file diff --git a/__tests__/components/fields/FieldSwitch.spec.js b/__tests__/components/fields/FieldSwitch.spec.js new file mode 100644 index 0000000..e549fca --- /dev/null +++ b/__tests__/components/fields/FieldSwitch.spec.js @@ -0,0 +1,57 @@ +import { generatePropsSingleField, generateSchemaSingleField, mountFormGenerator } from '@test/_resources/utils.js' +import { describe, it, expect } from 'vitest' +import { mount, config } from '@vue/test-utils' + +import FieldSwitch from '@/fields/input/FieldSwitch.vue' + +const form = generateSchemaSingleField( + 'switchTest', + 'testToggle', + 'switch', + null, + 'Testing', + false +) + +const props = generatePropsSingleField(form) + +describe('Test FieldSwitch', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldSwitch, { props }) + await wrapper.vm.$nextTick() + expect(wrapper.find('input[type=checkbox]').exists()).toBe(true) + expect(wrapper.find('span[class=slider]').exists()).toBe(true) + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldSwitch } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + expect(formWrapper.findComponent(FieldSwitch).exists()).toBe(true) + expect(formWrapper.find('input[type=checkbox]').exists()).toBe(true) + }) + + it('Should be checked when toggling switch', async () => { + const wrapper = mount(FieldSwitch, { props }) + await wrapper.vm.$nextTick() + await wrapper.find('label[class=field-switch]').trigger('click') + expect(wrapper.find('input[type=checkbox]').element.checked).toBe(true) + }) + + it('Should update model value', async () => { + config.global.components = { FieldSwitch } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + const wrapper = formWrapper.findComponent(FieldSwitch) + expect(wrapper.exists()).toBe(true) + await wrapper.find('input[type=checkbox]').setChecked(true) + expect(wrapper.emitted()).toHaveProperty('onInput', [ [ true ] ]) + expect(formWrapper.vm.model.testToggle).toBe(true) + }) + +}) + diff --git a/__tests__/components/fields/FieldText.spec.js b/__tests__/components/fields/FieldText.spec.js new file mode 100644 index 0000000..2647093 --- /dev/null +++ b/__tests__/components/fields/FieldText.spec.js @@ -0,0 +1,63 @@ +import { generateSchemaSingleField, generatePropsSingleField, mountFormGenerator } from '@test/_resources/utils.js' +import { mount, config } from '@vue/test-utils' +import { describe, it, expect } from 'vitest' + +import FieldText from '@/fields/input/FieldText.vue' + +const form = generateSchemaSingleField( + 'textTest', + 'modelText', + 'input', + 'text', + 'A test for text', + '', + { + placeholder: 'test placeholder' + } +) + +const props = generatePropsSingleField(form) + +describe('Test FieldText', () => { + + it('Should render correctly', async () => { + const wrapper = mount(FieldText, { props }) + + expect(wrapper.findComponent(FieldText).exists()).toBe(true) + const textInput = wrapper.find('input[type=text]') + + expect(textInput.exists()).toBe(true) + expect(textInput.attributes().placeholder).toBe('test placeholder') + expect(textInput.attributes().disabled).toBeFalsy() + }) + + it('Should render correctly inside form generator', async () => { + config.global.components = { FieldText } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + expect(formWrapper.findComponent(FieldText).exists()).toBeTruthy() + expect(formWrapper.find('input[type=text]').exists()).toBeTruthy() + expect(formWrapper.find('input[type=text]').attributes().placeholder).toBe('test placeholder') + }) + + it('Should emit onInput event', async () => { + const wrapper = mount(FieldText, { props }) + await wrapper.find('input').trigger('input') + expect(wrapper.emitted()).toHaveProperty('onInput') + }) + + it('Should update model value', async () => { + config.global.components = { FieldText } + + const formWrapper = mountFormGenerator(form.schema, form.model) + + + const textField = formWrapper.findComponent(FieldText) + expect(textField.exists()).toBeTruthy() + await textField.find('input').setValue('I have the high ground') + expect(formWrapper.vm.model.modelText).toBe('I have the high ground') + }) + +}) diff --git a/package.json b/package.json index 2443d90..467151e 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "module": "./dist/vue3-form-generator.es.js", "scripts": { "dev": "vite", + "test": "vitest", "build": "vite build", - "build:publish": "vite build && npm publish --access public", + "build:publish": "vitest && vite build && npm publish --access public", "preview": "vite preview" }, "exports": { @@ -36,13 +37,16 @@ "devDependencies": { "@stylistic/eslint-plugin": "^2.8.0", "@vitejs/plugin-vue": "^5.1.3", + "@vue/test-utils": "^2.4.6", "clipboard": "^2.0.11", "eslint": "^9.10.0", "eslint-plugin-import-alias": "^1.2.0", "eslint-plugin-vue": "9.27.0", + "jsdom": "^25.0.1", "terser": "^5.33.0", "typescript": "^5.4.5", "vite": "^5.2.14", + "vitest": "^2.1.1", "vue": "^3.5.6", "vue3-json-viewer": "^2.2.2" } diff --git a/src/FormGenerator.vue b/src/FormGenerator.vue index 9a78bad..83624dc 100644 --- a/src/FormGenerator.vue +++ b/src/FormGenerator.vue @@ -1,5 +1,6 @@