fix(admin): keep list state in the URL and browser storage

- listState.ts stores each list's query (search, sort, dir, page, per_page,
  filter[name]) under summer-admin.list:<controller>; an empty query is
  never stored, malformed stored values are dropped like a hand-edited URL
- ListView restores the stored query into the URL before its first list
  request when entered without list params; a URL with list params wins
- a 422 forgets the stored query; an out-of-range page clamps to last_page
- logout clears every stored list query
- check-phase10.sh allows browser storage in useSidebar.ts and listState.ts
- list smoke tests start from a clean list state
- rebuilt modules/boardwalk/dist
This commit is contained in:
2026-10-09 21:04:00 +02:00
parent bd8626e5fc
commit 9cac45c389
10 changed files with 186 additions and 18 deletions

View File

@@ -3,6 +3,7 @@ import { enableAutoUnmount, flushPromises } from '@vue/test-utils'
import { setBundle } from '../../src/app/i18n'
import ListView from '../../src/views/ListView.vue'
import { toasts } from '../../src/state/useToasts'
import { readListState, writeListState } from '../../src/state/listState'
import { OWNER_ATTRIBUTE } from '../../src/app/pluginAssets'
import {
clone,
@@ -492,3 +493,25 @@ describe('bulk actions on the list (UI-SPEC S1, D-09)', () => {
expect(wrapper.find('[data-selected-pill]').exists()).toBe(false)
})
})
describe('list state in the URL and browser storage (D-01)', () => {
it('restores the stored query before the first list request when the URL has no list params', async () => {
writeListState('acme.demo.widgets', { sort: 'code', dir: 'desc', filter: { active: 'true' } })
const { calls, router } = await mountApp('/acme/demo/widgets', routes())
await flushPromises()
const [first] = requestsTo(calls, 'GET', BASE)
const query = queryOf(first!)
expect(query.get('sort')).toBe('code')
expect(query.get('dir')).toBe('desc')
expect(query.get('filter[active]')).toBe('true')
expect(router.currentRoute.value.query).toMatchObject({ sort: 'code', dir: 'desc', 'filter[active]': 'true' })
})
it('lets a URL with list params win and stores its query', async () => {
writeListState('acme.demo.widgets', { sort: 'code', dir: 'desc' })
const { calls } = await mountApp('/acme/demo/widgets?search=blue', routes())
await flushPromises()
expect(queryOf(requestsTo(calls, 'GET', BASE)[0]!).get('sort')).toBeNull()
expect(readListState('acme.demo.widgets')).toEqual({ search: 'blue' })
})
})

View File

@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { flushPromises } from '@vue/test-utils'
import { setBundle } from '../../src/app/i18n'
import { parseListQuery, toListQuery } from '../../src/app/listQuery'
import { clearListStates } from '../../src/state/listState'
import { clearUser } from '../../src/state/useAuth'
import { setNavigation } from '../../src/state/useNavigation'
import { clearToasts } from '../../src/state/useToasts'
@@ -39,6 +40,8 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
beforeEach(() => {
clearUser()
// Lists remember their query (D-01); every test starts from a clean list.
clearListStates()
setNavigation([])
clearToasts()
setBundle(lang.data, 'pl')