- 41 unit and component suites under admin/tests/{app,state,shell,list,form,relation,views,ui}
covering states and a11y roles; every src module is imported by a test
- typed fixture helper assigns each JSON fixture to its generated OpenAPI type
- fix: iconFor ignores inherited object members such as "constructor"
- fix: field controls import ./control instead of the registry (import cycle
left a renderer unregistered depending on module load order)
- fix: dropdown shows the placeholder for an unknown stored value next to an emptyOption
- fix: list announces a failed schema load even when the rows arrive after it
- tailwind no longer scans admin/tests; boardwalk/dist rebuilt
93 lines
4.4 KiB
TypeScript
93 lines
4.4 KiB
TypeScript
import { beforeEach, describe, expect, it } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import FieldRenderer from '../../src/components/form/FieldRenderer.vue'
|
|
import FormErrorBanner from '../../src/components/form/FormErrorBanner.vue'
|
|
import FormField from '../../src/components/form/FormField.vue'
|
|
import { formField } from '../fixtures/typed'
|
|
import { resetState } from '../helpers'
|
|
|
|
beforeEach(() => {
|
|
resetState()
|
|
})
|
|
|
|
describe('form field', () => {
|
|
it('links the label to the control and hides the required asterisk from assistive tech', () => {
|
|
const wrapper = mount(FormField, { props: { field: formField('name'), modelValue: 'Blue', idPrefix: 'f' } })
|
|
const label = wrapper.find('label')
|
|
expect(label.attributes('for')).toBe('f-name')
|
|
expect(label.text()).toBe('Name *')
|
|
expect(label.find('[aria-hidden="true"]').text()).toBe('*')
|
|
const input = wrapper.find('#f-name')
|
|
expect(input.attributes('aria-required')).toBe('true')
|
|
expect(input.attributes('aria-invalid')).toBeUndefined()
|
|
expect(input.attributes('aria-describedby')).toBeUndefined()
|
|
})
|
|
|
|
it('describes the control by its comment and, when invalid, by its error line', () => {
|
|
const wrapper = mount(FormField, {
|
|
props: { field: formField('code'), modelValue: '', errors: ['Taken', 'Too short'], idPrefix: 'f' },
|
|
})
|
|
const input = wrapper.find('#f-code')
|
|
expect(input.attributes('aria-invalid')).toBe('true')
|
|
expect(input.attributes('aria-describedby')).toBe('f-code-comment f-code-error')
|
|
expect(wrapper.find('#f-code-comment').text()).toBe('Unique widget code')
|
|
expect(wrapper.findAll('#f-code-error p').map((line) => line.text())).toEqual(['Taken', 'Too short'])
|
|
})
|
|
|
|
it('uses the field name without a label and the default id prefix', () => {
|
|
const field = { ...formField('code'), label: '', comment: undefined }
|
|
const wrapper = mount(FormField, { props: { field, modelValue: '' } })
|
|
expect(wrapper.find('label').text()).toBe('code')
|
|
expect(wrapper.find('label').attributes('for')).toBe('field-code')
|
|
})
|
|
|
|
it('lets a toggle card render its own label and still points at its comment', () => {
|
|
const wrapper = mount(FormField, { props: { field: formField('active'), modelValue: false, errors: ['Bad'], idPrefix: 'f' } })
|
|
expect(wrapper.findAll('label')).toHaveLength(1)
|
|
expect(wrapper.find('label').attributes('for')).toBe('f-active')
|
|
expect(wrapper.find('[role="switch"]').attributes('aria-describedby')).toBe('f-active-comment f-active-error')
|
|
})
|
|
|
|
it('forwards value updates from the control', async () => {
|
|
const wrapper = mount(FormField, { props: { field: formField('name'), modelValue: '', idPrefix: 'f' } })
|
|
await wrapper.find('input').setValue('New')
|
|
expect(wrapper.emitted('update:modelValue')).toEqual([['New']])
|
|
})
|
|
})
|
|
|
|
describe('field renderer', () => {
|
|
it('picks the control by type and forwards the shared props and updates', async () => {
|
|
const wrapper = mount(FieldRenderer, {
|
|
props: { field: formField('weight'), modelValue: 3, controlId: 'x', invalid: true, describedBy: 'x-error' },
|
|
})
|
|
const input = wrapper.find('input[type="number"]')
|
|
expect(input.attributes('id')).toBe('x')
|
|
expect(input.attributes('aria-invalid')).toBe('true')
|
|
expect(input.attributes('aria-describedby')).toBe('x-error')
|
|
await input.setValue('7')
|
|
expect(wrapper.emitted('update:modelValue')).toEqual([[7]])
|
|
})
|
|
|
|
it('renders the unsupported box for an unknown type', () => {
|
|
const wrapper = mount(FieldRenderer, { props: { field: formField('color'), modelValue: '#fff', controlId: 'c' } })
|
|
expect(wrapper.find('[data-unsupported-field]').exists()).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('form error banner (D-09)', () => {
|
|
it('is an alert with the plural field count that lists messages of non-field keys', () => {
|
|
const wrapper = mount(FormErrorBanner, {
|
|
props: { errors: { name: ['Required'], code: ['Taken'], _form: ['Record is locked'] }, fieldNames: ['name', 'code'] },
|
|
})
|
|
const banner = wrapper.find('[role="alert"]')
|
|
expect(banner.text()).toContain('Nie udało się zapisać.')
|
|
expect(banner.text()).toContain('Popraw 3 pola oznaczone poniżej.')
|
|
expect(banner.findAll('p').at(-1)!.text()).toBe('Record is locked')
|
|
})
|
|
|
|
it('renders nothing without errors', () => {
|
|
const wrapper = mount(FormErrorBanner, { props: { errors: {}, fieldNames: ['name'] } })
|
|
expect(wrapper.find('[role="alert"]').exists()).toBe(false)
|
|
})
|
|
})
|