import { flushPromises, mount } from '@vue/test-utils' import { h, nextTick } from 'vue' import { createMemoryHistory, createRouter } from 'vue-router' import AppMain from '@/layout/components/AppMain.vue' function createStatefulPage(name, mounted) { return { name, data() { return { value: '' } }, mounted, render() { return h('input', { 'data-test': name, value: this.value, onInput: event => { this.value = event.target.value } }) } } } async function mountAppMain(routes, cachedViews, initialPath) { const router = createRouter({ history: createMemoryHistory(), routes }) await router.push(initialPath) await router.isReady() const wrapper = mount(AppMain, { global: { plugins: [router], mocks: { $store: { state: { tagsView: { cachedViews }, settings: { fixedHeader: false, tagsView: true } } } } } }) await flushPromises() await nextTick() return { router, wrapper } } async function navigate(router, path) { await router.push(path) await flushPromises() await nextTick() } describe('AppMain route cache', () => { it('restores a named page instance after switching tabs', async() => { const mounted = { pageA: 0, pageB: 0 } const PageA = createStatefulPage('StatefulPageA', () => mounted.pageA++) const PageB = createStatefulPage('StatefulPageB', () => mounted.pageB++) const { router, wrapper } = await mountAppMain([ { path: '/page-a', name: 'StatefulPageA', component: PageA }, { path: '/page-b', name: 'StatefulPageB', component: PageB } ], ['StatefulPageA', 'StatefulPageB'], '/page-a') await wrapper.get('[data-test="StatefulPageA"]').setValue('retained state') await navigate(router, '/page-b') await navigate(router, '/page-a') expect(wrapper.get('[data-test="StatefulPageA"]').element.value).toBe('retained state') expect(mounted).toEqual({ pageA: 1, pageB: 1 }) wrapper.unmount() }) it('keeps separate cached instances for dynamic routes with the same name', async() => { let mountCount = 0 const DictDataPage = createStatefulPage('DictDataPage', () => mountCount++) const { router, wrapper } = await mountAppMain([ { path: '/dict/data/:dictId', name: 'DictDataPage', component: DictDataPage } ], ['DictDataPage'], '/dict/data/1') await wrapper.get('[data-test="DictDataPage"]').setValue('dict 1') await navigate(router, '/dict/data/2') await wrapper.get('[data-test="DictDataPage"]').setValue('dict 2') await navigate(router, '/dict/data/3') await wrapper.get('[data-test="DictDataPage"]').setValue('dict 3') await navigate(router, '/dict/data/1') expect(wrapper.get('[data-test="DictDataPage"]').element.value).toBe('dict 1') await navigate(router, '/dict/data/2') expect(wrapper.get('[data-test="DictDataPage"]').element.value).toBe('dict 2') await navigate(router, '/dict/data/3') expect(wrapper.get('[data-test="DictDataPage"]').element.value).toBe('dict 3') expect(mountCount).toBe(3) wrapper.unmount() }) it('does not use RouterView directly inside KeepAlive or Transition', async() => { const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}) const Page = createStatefulPage('WarningCheckPage', () => {}) const { wrapper } = await mountAppMain([ { path: '/warning-check', name: 'WarningCheckPage', component: Page } ], ['WarningCheckPage'], '/warning-check') const routerViewWarnings = warn.mock.calls.filter(args => args.some(arg => typeof arg === 'string' && arg.includes('RouterView can no longer be used directly inside') )) expect(routerViewWarnings).toHaveLength(0) wrapper.unmount() warn.mockRestore() }) })