Improve caching
This commit is contained in:
@@ -21,6 +21,8 @@ async function harness(blockB: boolean | 'redirect' = false) {
|
||||
{ path: '/left', component: Page, meta: { native: { siblingOrder: 0, siblingHistory: 'replace' } } },
|
||||
{ path: '/middle', component: Page, meta: { native: { siblingOrder: 1, siblingHistory: 'replace' } } },
|
||||
{ path: '/right', component: Page, meta: { native: { siblingOrder: 2, siblingHistory: 'replace' } } },
|
||||
{ path: '/no-cache', component: Page, meta: { native: { cache: false, parent: '/a' } } },
|
||||
{ path: '/pinned', component: Page, meta: { native: { cache: 'pin', parent: '/a' } } },
|
||||
],
|
||||
})
|
||||
if (blockB) router.beforeEach((to) => to.path === '/b' ? (blockB === 'redirect' ? '/modal' : false) : undefined)
|
||||
@@ -145,6 +147,97 @@ describe('native router transactions', () => {
|
||||
await native.cancelInteractive()
|
||||
})
|
||||
|
||||
it('creates sibling views lazily and retains visited replace-style siblings', async () => {
|
||||
const { native } = await harness()
|
||||
expect(native.entries.value.map((entry) => entry.route.path)).toEqual(['/a'])
|
||||
|
||||
await native.replace('/left', { presentation: 'none' })
|
||||
expect(native.entries.value.some((entry) => entry.route.path === '/middle')).toBe(false)
|
||||
await native.sibling('/middle', { replace: true })
|
||||
|
||||
expect(native.entries.value.find((entry) => entry.route.path === '/left')).toMatchObject({ mounted: true, status: 'inactive' })
|
||||
expect(native.entries.value.find((entry) => entry.route.path === '/middle')).toMatchObject({ mounted: true, status: 'active' })
|
||||
expect(native.entries.value.some((entry) => entry.route.path === '/right')).toBe(false)
|
||||
})
|
||||
|
||||
it('evicts the least-recently-used inactive view when the cache limit is exceeded', async () => {
|
||||
let clock = 0
|
||||
vi.spyOn(performance, 'now').mockImplementation(() => ++clock)
|
||||
const { native } = await harness()
|
||||
await native.replace('/left', { presentation: 'none' })
|
||||
await native.sibling('/middle', { replace: true })
|
||||
await native.sibling('/right', { replace: true })
|
||||
await native.sibling('/a', { replace: true })
|
||||
|
||||
expect(native.entries.value.find((entry) => entry.route.path === '/left')).toMatchObject({
|
||||
mounted: false,
|
||||
evictionReason: 'cache-limit',
|
||||
})
|
||||
expect(native.entries.value.find((entry) => entry.route.path === '/middle')?.mounted).toBe(true)
|
||||
expect(native.entries.value.find((entry) => entry.route.path === '/right')?.mounted).toBe(true)
|
||||
expect(native.cacheStats.value.inactive).toBe(2)
|
||||
})
|
||||
|
||||
it('evicts a pushed route after it is popped out of history', async () => {
|
||||
const { native } = await harness()
|
||||
await native.push('/b')
|
||||
const pushedKey = native.activeKey.value
|
||||
await native.pop()
|
||||
|
||||
expect(native.entries.value.find((entry) => entry.key === pushedKey)).toMatchObject({
|
||||
mounted: false,
|
||||
status: 'evicted',
|
||||
evictionReason: 'popped',
|
||||
})
|
||||
})
|
||||
|
||||
it('honors cache opt-out even for a route that remains in back history', async () => {
|
||||
const { native } = await harness()
|
||||
await native.push('/no-cache')
|
||||
const noCacheKey = native.activeKey.value
|
||||
await native.push('/c')
|
||||
|
||||
expect(native.entries.value.find((entry) => entry.key === noCacheKey)).toMatchObject({
|
||||
mounted: false,
|
||||
evictionReason: 'cache-disabled',
|
||||
})
|
||||
expect(await native.beginInteractive('pop')).not.toBeNull()
|
||||
expect(native.entries.value.find((entry) => entry.key === noCacheKey)?.mounted).toBe(true)
|
||||
await native.cancelInteractive()
|
||||
})
|
||||
|
||||
it('keeps pinned views during normal trims and releases them when requested', async () => {
|
||||
const { native } = await harness()
|
||||
await native.replace('/pinned', { presentation: 'none' })
|
||||
const pinnedKey = native.activeKey.value
|
||||
await native.push('/c')
|
||||
|
||||
native.trimCache()
|
||||
expect(native.entries.value.find((entry) => entry.key === pinnedKey)?.mounted).toBe(true)
|
||||
native.trimCache({ includePinned: true })
|
||||
expect(native.entries.value.find((entry) => entry.key === pinnedKey)).toMatchObject({
|
||||
mounted: false,
|
||||
evictionReason: 'trimmed',
|
||||
})
|
||||
})
|
||||
|
||||
it('evicts a previously cached target when its guard rejects re-entry', async () => {
|
||||
const { router, native } = await harness()
|
||||
await native.replace('/left', { presentation: 'none' })
|
||||
await native.sibling('/middle', { replace: true })
|
||||
const cachedLeft = native.entries.value.find((entry) => entry.route.path === '/left')
|
||||
expect(cachedLeft?.mounted).toBe(true)
|
||||
const removeGuard = router.beforeEach((to) => to.path === '/left' ? false : undefined)
|
||||
|
||||
expect(await native.sibling('/left', { replace: true })).toBe(false)
|
||||
expect(native.entries.value.find((entry) => entry.key === cachedLeft?.key)).toMatchObject({
|
||||
mounted: false,
|
||||
evictionReason: 'navigation-rejected',
|
||||
})
|
||||
expect(native.cacheStats.value.totalEvictions).toBeGreaterThan(0)
|
||||
removeGuard()
|
||||
})
|
||||
|
||||
it('does not preview a stale forward entry after pop then push', async () => {
|
||||
const { native } = await harness()
|
||||
await native.push('/b')
|
||||
|
||||
Reference in New Issue
Block a user