Files
summercms/admin/tests/form/FormField.test.ts
Jakub Zych 1c2a66df45 test(10-05): bring every SPA module, composable and component under Vitest
- 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
2026-09-27 17:53:57 +02:00

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)
})
})