Skip to content

Commit 47062d0

Browse files
committed
feat(FieldObject): add new field
1 parent f1bfd0d commit 47062d0

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

src/fields/core/FieldObject.vue

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<div class="field-object">
3+
<FormGenerator
4+
ref="formGenerator"
5+
:schema="field.schema"
6+
:model="currentModelValue"
7+
:options="props.formOptions"
8+
@field-validated="onFieldValidated"
9+
/>
10+
</div>
11+
</template>
12+
13+
<script setup>
14+
import FormGenerator from '@/FormGenerator.vue'
15+
import { useFieldProps, useFieldEmits, useFormModel } from '@/composables'
16+
import { toRefs, useTemplateRef, computed } from 'vue'
17+
18+
const emits = defineEmits(useFieldEmits())
19+
const props = defineProps(useFieldProps())
20+
21+
const formGenerator = useTemplateRef('formGenerator')
22+
const hasErrors = computed(() => formGenerator.value?.hasErrors ?? false)
23+
24+
const { field, model } = toRefs(props)
25+
26+
const { currentModelValue } = useFormModel(model.value, field.value)
27+
28+
/**
29+
* Emits the validated event
30+
* @param validation
31+
*/
32+
const onFieldValidated = (validation) => {
33+
const key = `${field.value.model}.${validation.field.model}`
34+
emits(
35+
'validated',
36+
validation.fieldErrors.length === 0,
37+
validation.fieldErrors,
38+
{ ...field.value, model: key }
39+
)
40+
}
41+
42+
defineExpose({ hasErrors })
43+
</script>

src/fields/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import FieldTextarea from '@/fields/core/FieldTextarea.vue'
1313
import FieldMask from '@/fields/core/FieldMask.vue'
1414
import FieldChecklist from '@/fields/core/FieldChecklist.vue'
1515
import FieldCheckbox from '@/fields/core/FieldCheckbox.vue'
16+
import FieldObject from '@/fields/core/FieldObject.vue'
1617

1718
import FieldSubmit from '@/fields/core/FieldSubmit.vue'
1819
import FieldReset from '@/fields/core/FieldReset.vue'
@@ -22,7 +23,7 @@ import FieldButton from '@/fields/core/FieldButton.vue'
2223
const fieldComponents = {
2324
FieldColor, FieldText, FieldCheckBox, FieldPassword, FieldSelect, FieldSelectNative, FieldRadio,
2425
FieldNumber, FieldSubmit, FieldReset, FieldButton, FieldSwitch, FieldTextarea, FieldMask, FieldChecklist,
25-
FieldCheckbox
26+
FieldCheckbox, FieldObject
2627
} as const
2728

2829
type FieldComponentNames = keyof typeof fieldComponents
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { generatePropsSingleField, mountFormGenerator } from '@test/_resources/utils.js'
2+
import { expect, it, describe, beforeAll } from 'vitest'
3+
import { config, mount } from '@vue/test-utils'
4+
5+
import FieldObject from '@/fields/core/FieldObject.vue'
6+
import FieldNumber from '@/fields/core/FieldNumber.vue'
7+
import FieldText from '@/fields/core/FieldText.vue'
8+
import FormGenerator from '@/FormGenerator.vue'
9+
10+
beforeAll(() => {
11+
config.global.components = { FieldObject, FieldNumber, FieldText }
12+
})
13+
14+
const shouldBeOverEighteen = (value) => value && value >= 18
15+
16+
const form = {
17+
model: {
18+
person: {
19+
name: '',
20+
age: null
21+
}
22+
},
23+
schema: {
24+
fields: [
25+
{
26+
type: 'object',
27+
model: 'person',
28+
schema: {
29+
fields: [
30+
{
31+
type: 'text',
32+
name: 'name',
33+
model: 'name',
34+
label: 'Full name'
35+
},
36+
{
37+
type: 'number',
38+
name: 'age',
39+
model: 'age',
40+
label: 'Age',
41+
validator: [ shouldBeOverEighteen ]
42+
}
43+
]
44+
}
45+
}
46+
]
47+
}
48+
}
49+
50+
const props = generatePropsSingleField(form)
51+
52+
describe('FieldObject', () => {
53+
54+
// For rendering, we'll only need to test if the components are actually present within the Field or FormGenerator.
55+
// This is because each Field will have their own individual tests for checking if it renders correctly.
56+
it('Should render properly', async () => {
57+
const wrapper = mount(FieldObject, { props })
58+
// Since the FieldObject basically renders a form inside the form, this component should be there
59+
expect(wrapper.findComponent(FormGenerator)).toBeTruthy()
60+
expect(wrapper.findComponent(FieldNumber)).toBeTruthy()
61+
expect(wrapper.findComponent(FieldText)).toBeTruthy()
62+
})
63+
64+
it('Should render properly inside form generator', async () => {
65+
const wrapper = mountFormGenerator(form.schema, form.model)
66+
// Since the FieldObject basically renders a form inside the form, this component should be there
67+
expect(wrapper.findComponent(FormGenerator)).toBeTruthy()
68+
expect(wrapper.findComponent(FieldNumber)).toBeTruthy()
69+
expect(wrapper.findComponent(FieldText)).toBeTruthy()
70+
})
71+
72+
it('Should properly update model value', async () => {
73+
const wrapper = mountFormGenerator(form.schema, form.model)
74+
expect(typeof wrapper.vm.model.person).toBe('object')
75+
76+
const fieldWrapper = wrapper.findComponent(FieldObject)
77+
fieldWrapper.find('input[type=number]').setValue(21)
78+
// Both values should match.
79+
expect(fieldWrapper.vm.model.person.age).toBe(21)
80+
expect(wrapper.vm.model.person.age).toBe(21)
81+
82+
fieldWrapper.find('input[type=text]').setValue('Test subject')
83+
// Both values should match.
84+
expect(fieldWrapper.vm.model.person.name).toBe('Test subject')
85+
expect(wrapper.vm.model.person.name).toBe('Test subject')
86+
})
87+
88+
it('Should properly pass errors to parent form', async () => {
89+
const wrapper = mountFormGenerator(form.schema, form.model)
90+
const fieldWrapper = wrapper.findComponent(FieldObject)
91+
92+
const ageInput = fieldWrapper.find('input[type=number]')
93+
ageInput.setValue(15)
94+
await ageInput.trigger('blur')
95+
expect(fieldWrapper.emitted()).toHaveProperty('validated')
96+
// Default models shouldn't show up in the main form, as the model is an object.
97+
expect(wrapper.vm.formErrors).not.toHaveProperty('age')
98+
expect(wrapper.vm.formErrors).not.toHaveProperty('person')
99+
// The only correct way
100+
expect(wrapper.vm.formErrors).toHaveProperty('person.age')
101+
expect(wrapper.vm.formErrors['person.age']).toHaveLength(1)
102+
expect(wrapper.vm.formErrors['person.age'][0]).toBe('Field is invalid')
103+
})
104+
105+
it('Should remove old errors', async () => {
106+
const wrapper = mountFormGenerator(form.schema, form.model)
107+
const fieldWrapper = wrapper.findComponent(FieldObject)
108+
109+
const ageInput = fieldWrapper.find('input[type=number]')
110+
await ageInput.setValue(15)
111+
await ageInput.trigger('blur')
112+
expect(wrapper.vm.formErrors).toHaveProperty('person.age')
113+
114+
await ageInput.setValue(18)
115+
await ageInput.trigger('blur')
116+
expect(wrapper.vm.formErrors).not.toHaveProperty('person.age')
117+
})
118+
119+
})

0 commit comments

Comments
 (0)