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
This commit is contained in:
Jakub Zych
2026-09-27 17:53:57 +02:00
parent 8259a456bb
commit 1c2a66df45
61 changed files with 4299 additions and 91 deletions

View File

@@ -0,0 +1,84 @@
import { beforeEach, describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import Pagination from '../../src/components/list/Pagination.vue'
import type { ListMeta } from '../../src/api/types'
import { resetState } from '../helpers'
function meta(page: number, lastPage: number, total = lastPage * 20, perPage = 20): ListMeta {
return { page, last_page: lastPage, total, per_page: perPage }
}
function pager(page: number, lastPage: number) {
const wrapper = mount(Pagination, { props: { meta: meta(page, lastPage), loading: false, perPageOptions: [20], perPage: 20 } })
return wrapper
.findAll('nav > *')
.filter((element) => !['prev', 'next'].includes(element.attributes('data-page') ?? ''))
.map((element) => element.text())
.join(' ')
}
beforeEach(() => {
resetState()
})
describe('pagination', () => {
it('shows the range of the page and a labelled pager', () => {
const wrapper = mount(Pagination, { props: { meta: meta(2, 3, 45), loading: false, perPageOptions: [20], perPage: 20 } })
expect(wrapper.find('[data-range]').text()).toBe('21–40 z 45')
expect(wrapper.find('nav').attributes('aria-label')).toBe('Stronicowanie')
expect(wrapper.find('[data-page="2"]').attributes('aria-current')).toBe('page')
expect(wrapper.find('[data-page="1"]').attributes('aria-current')).toBeUndefined()
})
it.each([
[1, 1, '1'],
[1, 5, '1 2 3 … 5'],
[3, 5, '1 2 3 4 5'],
[5, 10, '1 … 4 5 6 … 10'],
[10, 10, '1 … 8 9 10'],
[2, 3, '1 2 3'],
])('page %i of %i shows %s', (page, last, expected) => {
expect(pager(page, last)).toBe(expected)
})
it('disables prev on the first and next on the last page and emits pages', async () => {
const wrapper = mount(Pagination, { props: { meta: meta(1, 3), loading: false, perPageOptions: [20], perPage: 20 } })
expect(wrapper.find('[data-page="prev"]').attributes('disabled')).toBeDefined()
expect(wrapper.find('[data-page="next"]').attributes('disabled')).toBeUndefined()
await wrapper.find('[data-page="next"]').trigger('click')
await wrapper.find('[data-page="3"]').trigger('click')
await wrapper.find('[data-page="1"]').trigger('click')
expect(wrapper.emitted('page')).toEqual([[2], [3]])
await wrapper.setProps({ meta: meta(3, 3) })
expect(wrapper.find('[data-page="next"]').attributes('disabled')).toBeDefined()
await wrapper.find('[data-page="prev"]').trigger('click')
expect(wrapper.emitted('page')!.at(-1)).toEqual([2])
})
it('shows the plural zero-results message and no pager without rows', () => {
const wrapper = mount(Pagination, { props: { meta: meta(1, 0, 0), loading: false, perPageOptions: [20], perPage: 20 } })
expect(wrapper.find('[data-range]').text()).toBe('0 wyników')
expect(wrapper.find('nav').exists()).toBe(false)
const none = mount(Pagination, { props: { meta: null, loading: false, perPageOptions: [20], perPage: 20 } })
expect(none.find('[data-range]').text()).toBe('0 wyników')
})
it('shows the loading text and hides the pager while loading', () => {
const wrapper = mount(Pagination, { props: { meta: meta(1, 2), loading: true, perPageOptions: [20], perPage: 20 } })
expect(wrapper.find('[data-range]').text()).toBe('Wczytywanie…')
expect(wrapper.find('nav').exists()).toBe(false)
})
it('offers the per-page select only with more than one option and emits a valid choice', async () => {
const one = mount(Pagination, { props: { meta: meta(1, 2), loading: false, perPageOptions: [20], perPage: 20 } })
expect(one.find('[data-per-page]').exists()).toBe(false)
const wrapper = mount(Pagination, { props: { meta: meta(1, 2), loading: false, perPageOptions: [10, 20, 50], perPage: 20 } })
const select = wrapper.find('[data-per-page]')
expect(select.findAll('option').map((option) => option.text())).toEqual(['10', '20', '50'])
expect((select.element as HTMLSelectElement).value).toBe('20')
await select.setValue('50')
expect(wrapper.emitted('perPage')).toEqual([[50]])
})
})