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:
@@ -5,11 +5,14 @@ import FormView from '../../src/views/FormView.vue'
|
||||
import { recordCrumb } from '../../src/state/useBreadcrumbs'
|
||||
import { toasts } from '../../src/state/useToasts'
|
||||
import { WIDGET_EVENT } from '../../src/components/form/formContext'
|
||||
import { readListState, writeListState } from '../../src/state/listState'
|
||||
import {
|
||||
clone,
|
||||
extensionFormSchemaFixture,
|
||||
formSchemaFixture,
|
||||
langFixture,
|
||||
listFixture,
|
||||
listSchemaFixture,
|
||||
recordFixture,
|
||||
rosterFormSchemaFixture,
|
||||
rosterRecordFixture,
|
||||
@@ -662,3 +665,83 @@ describe('password and preset on the form (UI-SPEC S7, D-19, D-27 G7)', () => {
|
||||
expect(body.name).toEqual({ en: 'Ada\nLovelace', pl: 'Ada po polsku' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('returning to the list keeps its query (D-01)', () => {
|
||||
const LIST_ID = 'acme.demo.widgets'
|
||||
const STORED = { sort: 'code', dir: 'desc' as const, filter: { active: 'true' } }
|
||||
|
||||
/** The form routes plus the list schema and rows, so the form can return to the list. */
|
||||
function roundTrip(overrides: Record<string, Route> = {}): Record<string, Route> {
|
||||
return routes({
|
||||
[`GET ${BASE}/schema/list`]: { body: listSchemaFixture },
|
||||
[`GET ${BASE}`]: { body: listFixture },
|
||||
[`PUT ${RECORD}`]: { body: recordFixture },
|
||||
[`DELETE ${RECORD}`]: { body: { data: { deleted: true }, meta: {} } },
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
|
||||
/** The list lands on its path with the stored query, and every list request carries it. */
|
||||
function expectRestored(router: { currentRoute: { value: { path: string; query: Record<string, unknown> } } }, calls: Request[]) {
|
||||
expect(router.currentRoute.value.path).toBe('/acme/demo/widgets')
|
||||
expect(router.currentRoute.value.query).toEqual({ sort: 'code', dir: 'desc', 'filter[active]': 'true' })
|
||||
const lists = requestsTo(calls, 'GET', BASE)
|
||||
expect(lists.length).toBeGreaterThanOrEqual(1)
|
||||
for (const request of lists) {
|
||||
const query = queryOf(request)
|
||||
expect(query.get('sort')).toBe('code')
|
||||
expect(query.get('dir')).toBe('desc')
|
||||
expect(query.get('filter[active]')).toBe('true')
|
||||
}
|
||||
expect(readListState(LIST_ID)).toEqual(STORED)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
writeListState(LIST_ID, STORED)
|
||||
})
|
||||
|
||||
it('tracer: the back action lands on the list with the stored query before the first list request', async () => {
|
||||
const { wrapper, calls, router } = await mountApp('/acme/demo/widgets/1', roundTrip())
|
||||
expect(requestsTo(calls, 'GET', BASE)).toHaveLength(0)
|
||||
await wrapper.find('[data-action="back"]').trigger('click')
|
||||
await flushPromises()
|
||||
expectRestored(router, calls)
|
||||
})
|
||||
|
||||
it('cancel lands on the list with the stored query', async () => {
|
||||
const { wrapper, calls, router } = await mountApp('/acme/demo/widgets/1', roundTrip())
|
||||
await wrapper.find('[data-action="cancel"]').trigger('click')
|
||||
await flushPromises()
|
||||
expectRestored(router, calls)
|
||||
})
|
||||
|
||||
it('save and close follows a redirectClose to the list and lands with the stored query', async () => {
|
||||
expect(formSchemaFixture.data.redirects.update.redirectClose).toBe('acme/demo/widgets')
|
||||
const { wrapper, calls, router } = await mountApp('/acme/demo/widgets/1', roundTrip())
|
||||
await wrapper.find('[data-action="save-close"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(requestsTo(calls, 'PUT', RECORD)).toHaveLength(1)
|
||||
expectRestored(router, calls)
|
||||
})
|
||||
|
||||
it('delete, once confirmed, lands on the list with the stored query', async () => {
|
||||
const { wrapper, calls, router } = await mountApp('/acme/demo/widgets/1', roundTrip(), { attach: true })
|
||||
await wrapper.find('[data-action="delete"]').trigger('click')
|
||||
await flushPromises()
|
||||
;(document.body.querySelector('[data-confirm-dialog] [data-action="confirm"]') as HTMLElement).click()
|
||||
await flushPromises()
|
||||
expect(requestsTo(calls, 'DELETE', RECORD)).toHaveLength(1)
|
||||
expectRestored(router, calls)
|
||||
})
|
||||
|
||||
it('lands on the unfiltered list when nothing is stored', async () => {
|
||||
writeListState(LIST_ID, {})
|
||||
const { wrapper, calls, router } = await mountApp('/acme/demo/widgets/1', roundTrip())
|
||||
await wrapper.find('[data-action="back"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.fullPath).toBe('/acme/demo/widgets')
|
||||
const lists = requestsTo(calls, 'GET', BASE)
|
||||
expect(lists).toHaveLength(1)
|
||||
expect([...queryOf(lists[0]!).keys()]).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user