+
{{ fieldComponent.hint }}
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/src/directives/onClickOutside.ts b/src/directives/onClickOutside.ts
index eb4f072..4adefd0 100644
--- a/src/directives/onClickOutside.ts
+++ b/src/directives/onClickOutside.ts
@@ -1,6 +1,6 @@
import { Directive, DirectiveBinding } from 'vue'
-const onClickOutside: Directive
= {
+const onClickOutside: Directive void> = {
beforeMount(el: HTMLElement, binding: DirectiveBinding): void {
el.clickOutsideEvent = (event: Event) => {
if (!(el === event.target || el.contains(event.target))) {
diff --git a/src/fields/core/FieldButton.vue b/src/fields/core/FieldButton.vue
index c181003..3ec00ef 100644
--- a/src/fields/core/FieldButton.vue
+++ b/src/fields/core/FieldButton.vue
@@ -1,20 +1,27 @@
-
\ No newline at end of file
diff --git a/src/fields/core/FieldCheckbox.vue b/src/fields/core/FieldCheckbox.vue
index e603ea7..1de35a3 100644
--- a/src/fields/core/FieldCheckbox.vue
+++ b/src/fields/core/FieldCheckbox.vue
@@ -28,7 +28,7 @@ const props = defineProps>()
const { field, model }: FieldPropRefs = toRefs(props)
const { currentModelValue } = useFormModel(model.value, field.value)
-const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { errors, validate } = useFieldValidate(
model.value,
field.value,
@@ -50,5 +50,5 @@ const onFieldValueChanged = (event: Event) => {
emits('onInput', target.checked)
}
-defineExpose({ hint, noLabel: true, errors })
-
\ No newline at end of file
+defineExpose({ hint, noLabel: true, errors, isVisible })
+
diff --git a/src/fields/core/FieldChecklist.vue b/src/fields/core/FieldChecklist.vue
index 03377f7..e6df313 100644
--- a/src/fields/core/FieldChecklist.vue
+++ b/src/fields/core/FieldChecklist.vue
@@ -34,7 +34,7 @@ const emits = defineEmits(useFieldEmits())
const props = defineProps>()
const { field, model }: FieldPropRefs = toRefs(props)
-const { hint } = useFieldAttributes(model.value, field.value)
+const { hint, isVisible } = useFieldAttributes(model.value, field.value)
const { currentModelValue }: { currentModelValue: Ref } = useFormModel(model.value, field.value)
const { validate, errors } = useFieldValidate(model.value, field.value)
@@ -63,5 +63,5 @@ const onFieldValueChanged = (event: Event) => {
})
}
-defineExpose({ hint, errors })
-
\ No newline at end of file
+defineExpose({ hint, errors, isVisible })
+
diff --git a/src/fields/core/FieldMask.vue b/src/fields/core/FieldMask.vue
index b1861a1..c645bbb 100644
--- a/src/fields/core/FieldMask.vue
+++ b/src/fields/core/FieldMask.vue
@@ -38,7 +38,7 @@ const maskOptions: ComputedRef = computed(() => {
})
const { currentModelValue } = useFormModel(model.value, field.value)
-const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { errors, validate } = useFieldValidate(
model.value,
field.value,
@@ -76,5 +76,5 @@ onBeforeMount(() => {
}
})
-defineExpose({ unmaskedValue, hint, errors })
-
\ No newline at end of file
+defineExpose({ unmaskedValue, hint, errors, isVisible })
+
diff --git a/src/fields/core/FieldNumber.vue b/src/fields/core/FieldNumber.vue
index 2ae7a3b..7e65857 100644
--- a/src/fields/core/FieldNumber.vue
+++ b/src/fields/core/FieldNumber.vue
@@ -31,7 +31,7 @@ const emits = defineEmits(useFieldEmits())
const { field, model }: FieldPropRefs = toRefs(props)
-const { isDisabled, isRequired, hint } = useFieldAttributes(model.value, field.value)
+const { isDisabled, isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
const { errors, validate } = useFieldValidate(
model.value,
@@ -60,5 +60,5 @@ const onFieldValueChanged = (event: Event) => {
emits('onInput', parseFloat(target.value))
}
-defineExpose({ hint, errors })
-
\ No newline at end of file
+defineExpose({ hint, errors, isVisible })
+
diff --git a/src/fields/core/FieldObject.vue b/src/fields/core/FieldObject.vue
index d69e773..9c1116d 100644
--- a/src/fields/core/FieldObject.vue
+++ b/src/fields/core/FieldObject.vue
@@ -13,7 +13,7 @@
\ No newline at end of file
+defineExpose({ hasErrors, isVisible })
+
diff --git a/src/fields/core/FieldPassword.vue b/src/fields/core/FieldPassword.vue
index c9bc1c3..0c3a98a 100644
--- a/src/fields/core/FieldPassword.vue
+++ b/src/fields/core/FieldPassword.vue
@@ -32,7 +32,7 @@ const props = defineProps>()
const emits = defineEmits(useFieldEmits())
const { model, field }: FieldPropRefs = toRefs(props)
-const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
const { errors, validate } = useFieldValidate(
@@ -79,5 +79,5 @@ const onBlur = () => {
})
}
-defineExpose({ hint, errors })
-
\ No newline at end of file
+defineExpose({ hint, errors, isVisible })
+
diff --git a/src/fields/core/FieldRadio.vue b/src/fields/core/FieldRadio.vue
index d5937a0..cdbb1ae 100644
--- a/src/fields/core/FieldRadio.vue
+++ b/src/fields/core/FieldRadio.vue
@@ -28,7 +28,7 @@ const props = defineProps>()
const emits = defineEmits(useFieldEmits())
const { field, model }: FieldPropRefs = toRefs(props)
-const { isRequired, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
const getFieldId = (optionName: string) => `${field.value.name}_${optionName}`
@@ -37,5 +37,5 @@ const onFieldValueChanged = (event: Event) => {
emits('onInput', (event.target as HTMLInputElement).value)
}
-defineExpose({ hint })
-
\ No newline at end of file
+defineExpose({ hint, isVisible })
+
diff --git a/src/fields/core/FieldReset.vue b/src/fields/core/FieldReset.vue
index 3df89a1..93ed816 100644
--- a/src/fields/core/FieldReset.vue
+++ b/src/fields/core/FieldReset.vue
@@ -1,14 +1,22 @@
-
+
\ No newline at end of file
+defineExpose({ noLabel: true, isVisible })
+
diff --git a/src/fields/core/FieldSelect.vue b/src/fields/core/FieldSelect.vue
index 87a095f..967f837 100644
--- a/src/fields/core/FieldSelect.vue
+++ b/src/fields/core/FieldSelect.vue
@@ -72,6 +72,7 @@ import type { FieldProps, FieldPropRefs, SelectField } from '@/resources/types/f
import {
useFieldAttributes,
useFieldEmits,
+ useFieldValidate,
useFormModel
} from '@/composables'
import { FieldOption } from '@/resources/types/fieldAttributes'
@@ -82,7 +83,8 @@ const { controlLeft, metaLeft } = useMagicKeys()
const isOpened: Ref = ref(false)
const { field, model }: FieldPropRefs = toRefs(props)
-const { hint } = useFieldAttributes(model.value, field.value)
+const { hint, isVisible } = useFieldAttributes(model.value, field.value)
+const { errors, validate } = useFieldValidate(model.value, field.value)
/** Names of the selected values */
const selectedNames: ComputedRef = computed(() => {
@@ -126,6 +128,7 @@ function handleClickOutside (event: Event) {
}
function selectOption (option: FieldOption) {
+ errors.value = []
const optionSelected = isSelected(option)
if (!field.value.multiple) {
@@ -140,10 +143,19 @@ function selectOption (option: FieldOption) {
}
emits('onInput', selectedValues)
- if (metaLeft.value || controlLeft.value) return
}
- isOpened.value = false
+ if (!(metaLeft.value || controlLeft.value)) {
+ isOpened.value = false
+ }
+ validate(currentModelValue.value).then(validationErrors => {
+ emits(
+ 'validated',
+ validationErrors.length === 0,
+ validationErrors,
+ field.value
+ )
+ })
}
-defineExpose({ hint })
-
\ No newline at end of file
+defineExpose({ hint, isVisible, errors })
+
diff --git a/src/fields/core/FieldSelectNative.vue b/src/fields/core/FieldSelectNative.vue
index 954d3f7..f43ce68 100644
--- a/src/fields/core/FieldSelectNative.vue
+++ b/src/fields/core/FieldSelectNative.vue
@@ -32,9 +32,9 @@ const emits = defineEmits(useFieldEmits())
const { field, model }: FieldPropRefs = toRefs(props)
-const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
-const { validate } = useFieldValidate(model.value, field.value)
+const { validate, errors } = useFieldValidate(model.value, field.value)
const onBlur = () => {
validate(currentModelValue.value).then((validationErrors) => {
@@ -47,8 +47,9 @@ const onBlur = () => {
}
const onFieldValueChanged = (event: Event) => {
+ errors.value = []
emits('onInput', (event.target as HTMLSelectElement).value)
}
-defineExpose({ hint })
+defineExpose({ hint, isVisible, errors })
\ No newline at end of file
diff --git a/src/fields/core/FieldSubmit.vue b/src/fields/core/FieldSubmit.vue
index be487d1..303a49d 100644
--- a/src/fields/core/FieldSubmit.vue
+++ b/src/fields/core/FieldSubmit.vue
@@ -17,7 +17,7 @@ const props = defineProps>()
const { model, field }: FieldPropRefs = toRefs(props)
-const { isDisabled } = useFieldAttributes(model.value, field.value)
+const { isDisabled, isVisible } = useFieldAttributes(model.value, field.value)
-defineExpose({ noLabel: true })
+defineExpose({ noLabel: true, isVisible })
diff --git a/src/fields/core/FieldSwitch.vue b/src/fields/core/FieldSwitch.vue
index 7fcbc01..ef01813 100644
--- a/src/fields/core/FieldSwitch.vue
+++ b/src/fields/core/FieldSwitch.vue
@@ -17,6 +17,7 @@ import type { SwitchField, FieldPropRefs, FieldProps } from '@/resources/types/f
import {
useFieldEmits,
useFieldAttributes,
+ useFieldValidate,
useFormModel
} from '@/composables'
@@ -26,10 +27,21 @@ const emits = defineEmits(useFieldEmits())
const { field, model }: FieldPropRefs = toRefs(props)
-const { isDisabled } = useFieldAttributes(model.value, field.value)
+const { isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
+const { errors, validate } = useFieldValidate(model.value, field.value)
const onFieldValueChanged = (event: Event) => {
- emits('onInput', (event.target as HTMLInputElement).checked)
+ const target = event.target as HTMLInputElement
+ emits('onInput', target.checked)
+ validate(target.checked).then(validationErrors => {
+ emits('validated',
+ validationErrors.length === 0,
+ validationErrors,
+ field.value
+ )
+ })
}
-
\ No newline at end of file
+
+defineExpose({ isVisible, hint, errors })
+
diff --git a/src/fields/core/FieldText.vue b/src/fields/core/FieldText.vue
index 714fe04..9f16bc8 100644
--- a/src/fields/core/FieldText.vue
+++ b/src/fields/core/FieldText.vue
@@ -6,6 +6,7 @@
:name="field.name"
:required="isRequired"
:disabled="isDisabled"
+ :readonly="isReadonly"
:placeholder="field.placeholder"
:autocomplete="autoCompleteState"
:value="currentModelValue"
@@ -27,7 +28,7 @@ const { field, model }: FieldPropRefs = toRefs(props)
const autoCompleteState: ComputedRef = computed(() => field.value.autocomplete ? 'on' : 'off')
const { currentModelValue } = useFormModel(model.value, field.value)
-const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isReadonly, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { errors, validate } = useFieldValidate(
model.value,
field.value,
@@ -51,5 +52,5 @@ const onFieldValueChanged = (event: Event) => {
emits('onInput', (event.target as HTMLInputElement).value)
}
-defineExpose({ errors, hint })
+defineExpose({ errors, hint, isVisible })
\ No newline at end of file
diff --git a/src/fields/core/FieldTextarea.vue b/src/fields/core/FieldTextarea.vue
index ff0f92b..bd306c9 100644
--- a/src/fields/core/FieldTextarea.vue
+++ b/src/fields/core/FieldTextarea.vue
@@ -31,7 +31,7 @@ const emits = defineEmits(useFieldEmits())
const { field, model }: FieldPropRefs = toRefs(props)
-const { isRequired, isDisabled, isReadonly, hint } = useFieldAttributes(model.value, field.value)
+const { isRequired, isDisabled, isReadonly, isVisible, hint } = useFieldAttributes(model.value, field.value)
const { currentModelValue } = useFormModel(model.value, field.value)
const { validate, errors } = useFieldValidate(model.value, field.value)
@@ -50,5 +50,5 @@ const onFieldValueChanged = (event: Event) => {
emits('onInput', (event.target as HTMLTextAreaElement).value)
}
-defineExpose({ hint, errors })
-
\ No newline at end of file
+defineExpose({ hint, errors, isVisible })
+
diff --git a/src/resources/types/field/base.ts b/src/resources/types/field/base.ts
index 16f6968..33ed78b 100644
--- a/src/resources/types/field/base.ts
+++ b/src/resources/types/field/base.ts
@@ -22,6 +22,7 @@ export type FieldBase = {
hint?: string | TDynamicAttributeStringFunction;
validator?: TValidatorFunction | TValidatorFunction[],
onValidated?: TOnValidatedFunction
+ noLabel?: boolean;
}
/**
diff --git a/src/resources/types/field/fields.ts b/src/resources/types/field/fields.ts
index 8464057..6eb651b 100644
--- a/src/resources/types/field/fields.ts
+++ b/src/resources/types/field/fields.ts
@@ -109,8 +109,8 @@ export interface FieldPropRefs {
export interface FieldProps {
id: string;
- formGenerator: object;
- formOptions: FormOptions;
+ formGenerator?: object;
+ formOptions?: FormOptions;
field: T;
model: Record;
}
diff --git a/src/resources/types/generic.ts b/src/resources/types/generic.ts
index 969b21f..f882c24 100644
--- a/src/resources/types/generic.ts
+++ b/src/resources/types/generic.ts
@@ -1,6 +1,6 @@
import { TValidatorFunction } from '@/resources/types/functions'
-import { Component } from 'vue'
-import type { Field } from '@/resources/types/field/fields'
+import type { ComponentPublicInstance, Component, Ref } from 'vue'
+import type { Field, FieldProps } from '@/resources/types/field/fields'
import { FormModel } from '@/resources/types/fieldAttributes'
export type ValidatorMap = Record
@@ -33,6 +33,13 @@ export type FormGeneratorSchema = {
},
}
+export type FormGroupProps = {
+ formOptions?: FormOptions;
+ model: FormModel;
+ field: Field;
+ errors?: string[];
+}
+
export type FormGeneratorProps = {
id?: string;
idPrefix?: string;
@@ -42,6 +49,19 @@ export type FormGeneratorProps = {
enctype?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'
}
+export type FieldExposedValues = {
+ // Whether the field is visible.
+ isVisible: Ref;
+ // Errors occurred during validation of this field.
+ errors: Ref;
+ // Hint displayed underneath the field.
+ hint?: Ref;
+ // If true, the field component manages the label or doesn't have a label at all.
+ noLabel?: boolean;
+}
+
+export type FieldComponent = ComponentPublicInstance
+
export type FormOptions = {
idPrefix?: string;
}
diff --git a/tests/_resources/utils.js b/tests/_resources/utils.js
index d20fdcc..4c0cd0e 100644
--- a/tests/_resources/utils.js
+++ b/tests/_resources/utils.js
@@ -12,6 +12,17 @@ export function mountFormGenerator (schema, model) {
return mount(FormGenerator, { props: { schema, model } })
}
+/**
+ * Clear all emitted events from a component, used for testing different emit phases in the same
+ * test case.
+ * @param {VueWrapper} wrapper - wrapper to clear emits for.
+ */
+export function clearEmittedEvents (wrapper) {
+ Object.keys(wrapper.emitted()).forEach(key => {
+ wrapper.emitted()[key].length = 0
+ })
+}
+
/**
* Generate a form schema for a single field component
* @param {String} name - name of the field
diff --git a/tests/components/FormGenerator.spec.js b/tests/components/FormGenerator.spec.js
index ff378d2..a9cac4e 100644
--- a/tests/components/FormGenerator.spec.js
+++ b/tests/components/FormGenerator.spec.js
@@ -1,14 +1,16 @@
import { expect, it, describe, beforeAll } from 'vitest'
import { config, mount } from '@vue/test-utils'
+import { generateSchemaSingleField, clearEmittedEvents } from '@test/_resources/utils.js'
+import { mountFormGenerator } from '@test/_resources/utils.js'
import FormGenerator from '@/FormGenerator.vue'
import FieldText from '@/fields/core/FieldText.vue'
import FieldTextarea from '@/fields/core/FieldTextarea.vue'
import FieldSubmit from '@/fields/core/FieldSubmit.vue'
-import { generateSchemaSingleField } from '@test/_resources/utils.js'
+import FieldReset from '@/fields/core/FieldReset.vue'
beforeAll(() => {
- config.global.components = { FieldText, FieldTextarea, FieldSubmit }
+ config.global.components = { FieldText, FieldTextarea, FieldSubmit, FieldReset }
})
const textSchema = generateSchemaSingleField(
@@ -33,8 +35,20 @@ const textAreaSchema = generateSchemaSingleField(
{}
)
+const resetSchema = {
+ name: 'reset',
+ type: 'reset',
+ buttonText: 'Reset'
+}
+
+const submitSchema = {
+ name: 'submit',
+ type: 'submit',
+ buttonText: 'Submit'
+}
+
const schema = {
- schema: { fields: [ ...textSchema.schema.fields, ...textAreaSchema.schema.fields ] },
+ schema: { fields: [ ...textSchema.schema.fields, ...textAreaSchema.schema.fields, resetSchema, submitSchema ] },
model: { ...textSchema.model, ...textAreaSchema.model }
}
@@ -46,10 +60,66 @@ describe('FormGenerator', () => {
})
it('Should render with a schema', async () => {
- const wrapper = mount(FormGenerator, { props: { model: schema.model, schema: schema.schema } })
+ const wrapper = mountFormGenerator(schema.schema, schema.model)
expect(wrapper.find('form').exists()).toBeTruthy()
expect(wrapper.findComponent(FieldText).exists()).toBeTruthy()
expect(wrapper.findComponent(FieldTextarea).exists()).toBeTruthy()
})
+ it('Should have correct internals', async () => {
+ const wrapper = mountFormGenerator(schema.schema, schema.model)
+
+ /** FieldElements, unexposed, contains the refs of the field components. */
+ expect(wrapper.vm.fieldElements).toHaveLength(schema.schema.fields.length)
+ // All field elements must be of type FormGroup
+ wrapper.vm.fieldElements.forEach(field => {
+ expect(field.$.type.__name).toBe('FormGroup')
+ })
+
+ const isObject = (o) => !Array.isArray(o) && typeof o === 'object' && o !== null
+ expect(isObject(wrapper.vm.formOptions)).toBeTruthy()
+ })
+
+ it('Should properly update and reset model', async () => {
+ const wrapper = mountFormGenerator(schema.schema, schema.model)
+ await wrapper.vm.$nextTick()
+
+ const textField = wrapper.findComponent(FieldText)
+ expect(wrapper.vm.model.textModel).toBe('')
+ await textField.find('input').setValue('Test update')
+ expect(wrapper.vm.model.textModel).toBe('Test update')
+
+ const resetField = wrapper.findComponent(FieldReset)
+ await resetField.find('input').trigger('reset')
+ expect(wrapper.vm.model.textModel).toBe('')
+ })
+
+ it('Should properly pass emits', async () => {
+ const wrapper = mountFormGenerator(schema.schema, schema.model)
+ await wrapper.vm.$nextTick()
+
+ // Field-validated
+ const textField = wrapper.findComponent(FieldText)
+ await textField.find('input').setValue('Test emit')
+ await textField.find('input').trigger('blur')
+ expect(textField.emitted()).toHaveProperty('validated')
+ expect(wrapper.emitted()).toHaveProperty('field-validated')
+
+ // Submit
+ const submitField = wrapper.findComponent(FieldSubmit)
+ await submitField.find('input').trigger('submit')
+ expect(submitField.emitted().submit).toHaveLength(1)
+ expect(wrapper.emitted().submit).toHaveLength(1)
+
+ clearEmittedEvents(submitField)
+ clearEmittedEvents(wrapper)
+
+ // No submit when form has errors
+ wrapper.vm.model.textModel = ''
+ wrapper.vm.formErrors = { textModel: 'Field is required' }
+ await submitField.find('input').trigger('submit')
+ expect(submitField.emitted().submit).toHaveLength(1)
+ expect(wrapper.emitted().submit).toHaveLength(0)
+ })
+
})
\ No newline at end of file
diff --git a/tests/components/FormGroup.spec.js b/tests/components/FormGroup.spec.js
new file mode 100644
index 0000000..18d8ffb
--- /dev/null
+++ b/tests/components/FormGroup.spec.js
@@ -0,0 +1,73 @@
+import { expect, it, describe, beforeAll } from 'vitest'
+import { mount, config } from '@vue/test-utils'
+import FieldText from '@/fields/core/FieldText.vue'
+import FormGroup from '@/FormGroup.vue'
+import { generateSchemaSingleField } from '@test/_resources/utils.js'
+
+const mountFormGroup = (props) => mount(FormGroup, { props })
+
+beforeAll(() => {
+ config.global.components = { FieldText }
+})
+
+const textFieldSchema = generateSchemaSingleField(
+ 'testField',
+ 'testFieldModel',
+ 'input',
+ 'text',
+ 'Test label',
+ ''
+)
+const field = textFieldSchema.schema.fields[0]
+const model = textFieldSchema.model
+
+describe('FormGroup', () => {
+
+ it('Should render properly', async () => {
+ const wrapper = mountFormGroup({ field, model })
+ const textField = wrapper.findComponent(FieldText)
+ expect(textField.exists()).toBeTruthy()
+ expect(wrapper.find('.field-wrap').exists()).toBeTruthy()
+ expect(wrapper.find('.form-group').exists()).toBeTruthy()
+ expect(wrapper.find('label').exists()).toBeTruthy()
+ })
+
+ it('Should hide field if not visible', async () => {
+ const localField = { ...field, visible: false }
+ const wrapper = mountFormGroup({ field: localField, model })
+ const textField = wrapper.findComponent(FieldText)
+ expect(textField.exists()).toBeTruthy()
+ expect(wrapper.vm.fieldStyle).toHaveProperty('display', 'none')
+ })
+
+ it('Should not render label when field schema or component tells it not to render', async () => {
+ const localField = { ...field, noLabel: true } // noLabel: true here is the same as in the component
+ const wrapper = mountFormGroup({ field: localField, model })
+ const textField = wrapper.findComponent(FieldText)
+ expect(textField.exists()).toBeTruthy()
+ expect(wrapper.find('label').exists()).toBeFalsy()
+ })
+
+ it('Should display errors', async () => {
+ const localField = { ...field, validator: () => false }
+ const wrapper = mountFormGroup({ field: localField, model })
+ const textField = wrapper.findComponent(FieldText)
+ expect(textField.exists()).toBeTruthy()
+ await textField.find('input').trigger('blur')
+ expect(wrapper.vm.fieldHasErrors).toBeTruthy()
+ await wrapper.vm.$nextTick()
+ expect(wrapper.find('.errors').exists()).toBeTruthy()
+ expect(wrapper.find('.error').text()).toBe('Field is invalid')
+ })
+
+ it('Should display hints', async () => {
+ const localField = { ...field, hint: 'This is a hint' }
+ const wrapper = mountFormGroup({ field: localField, model })
+ const textField = wrapper.findComponent(FieldText)
+ expect(textField.exists()).toBeTruthy()
+ await wrapper.vm.$nextTick()
+ expect(wrapper.find('.hint').exists()).toBeTruthy()
+ expect(wrapper.find('.hint').text()).toBe('This is a hint')
+ })
+
+})
\ No newline at end of file