test(admin): list state round trip and storage coverage
- Form back, cancel, save-and-close and delete land on the list with the stored query; every list request after the return carries it - listState helpers: key prefix, malformed stored values removed, empty query stores nothing, throwing storage - ListView entry: URL wins, cleared controls forget the query, same-list navigation, 422 forgets, page clamp - Logout removes every list key and keeps the sidebar preference
This commit is contained in:
@@ -515,3 +515,126 @@ describe('list state in the URL and browser storage (D-01)', () => {
|
||||
expect(readListState('acme.demo.widgets')).toEqual({ search: 'blue' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('list state entry logic (D-01)', () => {
|
||||
const ID = 'acme.demo.widgets'
|
||||
const KEY = `summer-admin.list:${ID}`
|
||||
|
||||
function listQueries(calls: Request[]): URLSearchParams[] {
|
||||
return requestsTo(calls, 'GET', BASE).map(queryOf)
|
||||
}
|
||||
|
||||
it('makes no list request without the stored params when the URL has none', async () => {
|
||||
writeListState(ID, { search: 'blue', sort: 'code', dir: 'desc', per_page: 50, filter: { active: 'true' } })
|
||||
const { calls, router } = await mountApp('/acme/demo/widgets', routes())
|
||||
await flushPromises()
|
||||
const sent = listQueries(calls)
|
||||
expect(sent.length).toBeGreaterThanOrEqual(1)
|
||||
for (const query of sent) {
|
||||
expect(query.get('search')).toBe('blue')
|
||||
expect(query.get('sort')).toBe('code')
|
||||
expect(query.get('dir')).toBe('desc')
|
||||
expect(query.get('per_page')).toBe('50')
|
||||
expect(query.get('filter[active]')).toBe('true')
|
||||
}
|
||||
expect(router.currentRoute.value.query).toEqual({
|
||||
search: 'blue',
|
||||
sort: 'code',
|
||||
dir: 'desc',
|
||||
per_page: '50',
|
||||
'filter[active]': 'true',
|
||||
})
|
||||
})
|
||||
|
||||
it('lets every list param of the URL win and overwrites the whole stored query', async () => {
|
||||
writeListState(ID, { search: 'old', sort: 'code', dir: 'desc', filter: { active: 'true' } })
|
||||
const { calls } = await mountApp('/acme/demo/widgets?sort=name&dir=asc&filter[active]=false', routes())
|
||||
await flushPromises()
|
||||
const sent = listQueries(calls)
|
||||
expect(sent).toHaveLength(1)
|
||||
expect(sent[0]!.get('sort')).toBe('name')
|
||||
expect(sent[0]!.get('search')).toBeNull()
|
||||
expect(sent[0]!.get('filter[active]')).toBe('false')
|
||||
expect(readListState(ID)).toEqual({ sort: 'name', dir: 'asc', filter: { active: 'false' } })
|
||||
})
|
||||
|
||||
it('forgets the stored query when every control is cleared, so the next entry loads unfiltered', async () => {
|
||||
const first = await mountApp('/acme/demo/widgets?search=blue&sort=code&dir=desc&filter[active]=true', routes())
|
||||
await flushPromises()
|
||||
expect(window.localStorage.getItem(KEY)).not.toBeNull()
|
||||
|
||||
await first.wrapper.find('[data-list-search]').setValue('')
|
||||
await wait(350)
|
||||
await flushPromises()
|
||||
expect(first.router.currentRoute.value.query.search).toBeUndefined()
|
||||
|
||||
await first.wrapper.find('[data-sort="code"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(first.router.currentRoute.value.query.sort).toBeUndefined()
|
||||
expect(window.localStorage.getItem(KEY)).not.toBeNull()
|
||||
|
||||
await first.wrapper.find('select[data-filter="active"]').setValue('')
|
||||
await flushPromises()
|
||||
expect(first.router.currentRoute.value.query).toEqual({})
|
||||
expect(window.localStorage.getItem(KEY)).toBeNull()
|
||||
first.wrapper.unmount()
|
||||
|
||||
const second = await mountApp('/acme/demo/widgets', routes())
|
||||
await flushPromises()
|
||||
const sent = listQueries(second.calls)
|
||||
expect(sent).toHaveLength(1)
|
||||
expect([...sent[0]!.keys()]).toEqual([])
|
||||
expect(second.router.currentRoute.value.fullPath).toBe('/acme/demo/widgets')
|
||||
})
|
||||
|
||||
it('keeps the filters when the same list is opened again from the navigation', async () => {
|
||||
const { calls, router } = await mountApp('/acme/demo/widgets?sort=code&dir=desc&filter[active]=true', routes())
|
||||
await flushPromises()
|
||||
const before = requestsTo(calls, 'GET', BASE).length
|
||||
await router.push('/acme/demo/widgets')
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.query).toEqual({ sort: 'code', dir: 'desc', 'filter[active]': 'true' })
|
||||
for (const query of listQueries(calls).slice(before)) {
|
||||
expect(query.get('sort')).toBe('code')
|
||||
expect(query.get('filter[active]')).toBe('true')
|
||||
}
|
||||
expect(listQueries(calls).every((query) => query.get('filter[active]') === 'true')).toBe(true)
|
||||
expect(readListState(ID)).toEqual({ sort: 'code', dir: 'desc', filter: { active: 'true' } })
|
||||
})
|
||||
|
||||
it('drops the stored query when the list answers 422', async () => {
|
||||
const invalid = {
|
||||
status: 422,
|
||||
body: { error: { code: 'validation_failed', message: 'Invalid', details: { sort: ['unknown column'] } } },
|
||||
}
|
||||
writeListState(ID, { sort: 'gone', dir: 'asc' })
|
||||
const { calls } = await mountApp('/acme/demo/widgets', routes({ [`GET ${BASE}`]: invalid }))
|
||||
await flushPromises()
|
||||
expect(queryOf(requestsTo(calls, 'GET', BASE)[0]!).get('sort')).toBe('gone')
|
||||
expect(window.localStorage.getItem(KEY)).toBeNull()
|
||||
expect(readListState(ID)).toEqual({})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['from the URL', '/acme/demo/widgets?page=5', false],
|
||||
['restored from storage', '/acme/demo/widgets', true],
|
||||
])('moves a page beyond last_page %s to last_page and requests that page', async (_label, url, stored) => {
|
||||
if (stored) {
|
||||
writeListState(ID, { page: 5 })
|
||||
}
|
||||
const pageOf = (request: Request) => Number(queryOf(request).get('page') ?? '1')
|
||||
const { calls, router } = await mountApp(
|
||||
url,
|
||||
routes({
|
||||
[`GET ${BASE}`]: (request: Request): Reply => {
|
||||
const page = pageOf(request)
|
||||
return { body: { data: page <= 2 ? listFixture.data : [], meta: { page, per_page: 20, total: 25, last_page: 2 } } }
|
||||
},
|
||||
}),
|
||||
)
|
||||
await flushPromises()
|
||||
expect(requestsTo(calls, 'GET', BASE).map(pageOf)).toEqual([5, 2])
|
||||
expect(router.currentRoute.value.query).toEqual({ page: '2' })
|
||||
expect(readListState(ID)).toEqual({ page: 2 })
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user