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:
74
admin/tests/app/listQuery.test.ts
Normal file
74
admin/tests/app/listQuery.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { listQueryKey, parseListQuery, toListQuery } from '../../src/app/listQuery'
|
||||
|
||||
describe('parseListQuery', () => {
|
||||
it('reads search, sort, dir, page, per_page and filter[<name>]', () => {
|
||||
expect(
|
||||
parseListQuery({
|
||||
search: ' blue ',
|
||||
sort: 'name',
|
||||
dir: 'desc',
|
||||
page: '3',
|
||||
per_page: '50',
|
||||
'filter[active]': 'true',
|
||||
'filter[created]': '2026-01-01..2026-02-01',
|
||||
}),
|
||||
).toEqual({
|
||||
search: 'blue',
|
||||
sort: 'name',
|
||||
dir: 'desc',
|
||||
page: 3,
|
||||
per_page: 50,
|
||||
filter: { active: 'true', created: '2026-01-01..2026-02-01' },
|
||||
})
|
||||
})
|
||||
|
||||
it.each(['0', '-1', '1.5', 'abc', '', '01', ' 2'])('drops the invalid page and per_page %j', (value) => {
|
||||
const out = parseListQuery({ page: value, per_page: value })
|
||||
expect(out.page).toBeUndefined()
|
||||
expect(out.per_page).toBeUndefined()
|
||||
})
|
||||
|
||||
it('defaults the direction to asc with a sort and ignores a direction without one', () => {
|
||||
expect(parseListQuery({ sort: 'name', dir: 'sideways' })).toEqual({ sort: 'name', dir: 'asc' })
|
||||
expect(parseListQuery({ dir: 'desc' })).toEqual({})
|
||||
expect(parseListQuery({ sort: ' ' })).toEqual({})
|
||||
})
|
||||
|
||||
it('takes the last value of a repeated key and drops empty filters and non-filter keys', () => {
|
||||
expect(parseListQuery({ search: ['a', 'b'], 'filter[x]': '', 'filter[]': 'y', filters: 'z', other: '1' })).toEqual({
|
||||
search: 'b',
|
||||
})
|
||||
expect(parseListQuery({ page: ['2', null] })).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('toListQuery', () => {
|
||||
it('writes only meaningful values and omits page 1', () => {
|
||||
expect(toListQuery({})).toEqual({})
|
||||
expect(toListQuery({ page: 1, search: '' })).toEqual({})
|
||||
expect(toListQuery({ page: 2, per_page: 10 })).toEqual({ page: '2', per_page: '10' })
|
||||
expect(toListQuery({ sort: 'code' })).toEqual({ sort: 'code', dir: 'asc' })
|
||||
expect(toListQuery({ filter: { b: '2', a: '1', c: '' } })).toEqual({ 'filter[a]': '1', 'filter[b]': '2' })
|
||||
})
|
||||
|
||||
it('round-trips through parseListQuery', () => {
|
||||
const input = {
|
||||
search: 'blue',
|
||||
sort: 'name',
|
||||
dir: 'desc' as const,
|
||||
page: 4,
|
||||
per_page: 25,
|
||||
filter: { active: 'false', maker: '2' },
|
||||
}
|
||||
const raw = toListQuery(input)
|
||||
const back = parseListQuery(raw as Record<string, string>)
|
||||
expect(back).toEqual(input)
|
||||
})
|
||||
|
||||
it('keys equal queries alike regardless of filter order', () => {
|
||||
expect(listQueryKey({ filter: { a: '1', b: '2' } })).toBe(listQueryKey({ filter: { b: '2', a: '1' } }))
|
||||
expect(listQueryKey({ page: 1 })).toBe(listQueryKey({}))
|
||||
expect(listQueryKey({ page: 2 })).not.toBe(listQueryKey({}))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user