180 lines
6.9 KiB
JavaScript
180 lines
6.9 KiB
JavaScript
/**
|
|
* 回归用例:页面 keep-alive 缓存(曾因包装组件读「当前路由」导致缓存全灭)。
|
|
* 用轻量桩组件替换三个页面(统计挂载/激活/卸载、持有自身状态),其余
|
|
* (布局、页签、store、路由)全部真实,覆盖:
|
|
* 1. 切走再切回:不重新挂载、状态保留(keep-alive 生效)
|
|
* 2. 缓存页的内部状态在其它路由切换后仍保留(包装组件不得依赖当前路由)
|
|
* 3. 双击页签刷新:页面组件重新挂载、状态归零(缓存槽位不变)
|
|
*/
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
import { onActivated, onDeactivated, onUnmounted, ref } from 'vue'
|
|
|
|
// —— 桩组件共享的计数器(vi.hoisted 保证 mock 工厂可用)——
|
|
const counters = vi.hoisted(() => ({
|
|
ModuleManagement: { mounts: 0, activated: 0, deactivated: 0, unmounted: 0 },
|
|
MenuManagement: { mounts: 0, activated: 0, deactivated: 0, unmounted: 0 },
|
|
Dashboard: { mounts: 0, activated: 0, deactivated: 0, unmounted: 0 },
|
|
}))
|
|
|
|
function makeStub(label) {
|
|
return {
|
|
name: label,
|
|
template: `<div data-page="${label}">{{ label }}#{{ clicks }}<button data-inc @click="clicks++">+</button></div>`,
|
|
setup() {
|
|
const c = counters[label]
|
|
// 页面自身状态:模拟树选中、面板展开等 —— 切走再切回应保留
|
|
const clicks = ref(0)
|
|
c.mounts++
|
|
onActivated(() => c.activated++)
|
|
onDeactivated(() => c.deactivated++)
|
|
onUnmounted(() => c.unmounted++)
|
|
return { clicks }
|
|
},
|
|
}
|
|
}
|
|
|
|
vi.mock('@/views/dashboard/index.vue', () => ({ default: makeStub('Dashboard') }))
|
|
vi.mock('@/views/module/module-management/index.vue', () => ({
|
|
default: makeStub('ModuleManagement'),
|
|
}))
|
|
vi.mock('@/views/module/menu-management/index.vue', () => ({
|
|
default: makeStub('MenuManagement'),
|
|
}))
|
|
vi.mock('@/views/detail/DetailPage.vue', () => ({
|
|
// 详情页桩:显示自身 id prop,验证包装组件从自身 fullPath(而非当前路由)取参
|
|
default: {
|
|
name: 'DetailPageStub',
|
|
props: ['id'],
|
|
template: '<div data-page="Detail">{{ id }}</div>',
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/services/api', () => ({
|
|
loadDataApi: vi.fn(() => Promise.resolve({ data: [], code: 0, success: true })),
|
|
describeApi: vi.fn(() => Promise.resolve({ data: [], code: 0, success: true })),
|
|
saveObjectApi: vi.fn(() => Promise.resolve({ code: 0, success: true })),
|
|
nextIdApi: vi.fn(() => Promise.resolve({ data: 1, code: 0, success: true })),
|
|
}))
|
|
|
|
import App from '@/App.vue'
|
|
import router from '@/router'
|
|
import { createTestPinia } from '../helpers/app.js'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useAppStore } from '@/stores/app'
|
|
|
|
beforeEach(async () => {
|
|
localStorage.clear()
|
|
sessionStorage.clear()
|
|
Object.values(counters).forEach((c) => {
|
|
c.mounts = 0
|
|
c.activated = 0
|
|
c.deactivated = 0
|
|
c.unmounted = 0
|
|
})
|
|
createTestPinia()
|
|
// 模块中心 /module/* 在路由层仅 g3soft 放行,缓存测试用管理员账号避免被守卫拦截
|
|
useAuthStore().setSession({ token: 't', user: { id: 1, account: 'g3soft', name: 'U' } }, 'ORG')
|
|
await router.push('/dashboard')
|
|
await router.isReady()
|
|
})
|
|
|
|
async function mountApp() {
|
|
const wrapper = mount(App, { global: { plugins: [router] }, attachTo: document.body })
|
|
await flushPromises()
|
|
return wrapper
|
|
}
|
|
|
|
describe('页面 keep-alive 缓存', () => {
|
|
it('模块管理 ↔ 菜单管理 ↔ 工作台 来回切换:不重新挂载、状态保留', async () => {
|
|
const wrapper = await mountApp()
|
|
expect(counters.Dashboard.mounts).toBe(1)
|
|
|
|
// 进入模块管理,点两下按钮制造页面状态
|
|
await router.push('/module/module-management')
|
|
await flushPromises()
|
|
expect(counters.ModuleManagement.mounts).toBe(1)
|
|
const inc = () => wrapper.find('[data-page="ModuleManagement"] [data-inc]').trigger('click')
|
|
await inc()
|
|
await inc()
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#2')
|
|
|
|
// 切到菜单管理再切回:不重新挂载,clicks 仍为 2
|
|
await router.push('/module/menu-management')
|
|
await flushPromises()
|
|
expect(counters.ModuleManagement.unmounted).toBe(0)
|
|
expect(counters.ModuleManagement.deactivated).toBe(1)
|
|
|
|
await router.push('/module/module-management')
|
|
await flushPromises()
|
|
expect(counters.ModuleManagement.mounts).toBe(1)
|
|
expect(counters.ModuleManagement.unmounted).toBe(0)
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#2')
|
|
|
|
// 再绕经工作台切回:缓存依旧(包装组件不得依赖当前路由)
|
|
await router.push('/dashboard')
|
|
await flushPromises()
|
|
await router.push('/module/menu-management')
|
|
await flushPromises()
|
|
await router.push('/module/module-management')
|
|
await flushPromises()
|
|
expect(counters.ModuleManagement.mounts).toBe(1)
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#2')
|
|
|
|
// 关闭页签后缓存销毁:重新进入应重新挂载
|
|
const appStore = useAppStore()
|
|
appStore.removeVisitedTab('/module/module-management')
|
|
await router.push('/dashboard')
|
|
await flushPromises()
|
|
await router.push('/module/module-management')
|
|
await flushPromises()
|
|
expect(counters.ModuleManagement.mounts).toBe(2)
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#0')
|
|
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('详情页:按参数独立缓存,切走再切回 id 不串、互不影响', async () => {
|
|
const wrapper = await mountApp()
|
|
|
|
await router.push('/detail/1')
|
|
await flushPromises()
|
|
expect(wrapper.find('[data-page="Detail"]').text()).toBe('1')
|
|
|
|
// /detail/2 与 /detail/1 是两个独立缓存实例
|
|
await router.push('/detail/2')
|
|
await flushPromises()
|
|
expect(wrapper.find('[data-page="Detail"]').text()).toBe('2')
|
|
|
|
await router.push('/detail/1')
|
|
await flushPromises()
|
|
expect(wrapper.find('[data-page="Detail"]').text()).toBe('1')
|
|
|
|
// 携带 query 时 id 仍正确(query 变化会产生独立缓存槽,但 id 不能被 query 污染)
|
|
await router.push('/detail/1?tab=2')
|
|
await flushPromises()
|
|
expect(wrapper.find('[data-page="Detail"]').text()).toBe('1')
|
|
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('双击页签刷新:页面组件重新挂载、状态归零(缓存槽位不变)', async () => {
|
|
const wrapper = await mountApp()
|
|
|
|
await router.push('/module/module-management')
|
|
await flushPromises()
|
|
await wrapper.find('[data-page="ModuleManagement"] [data-inc]').trigger('click')
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#1')
|
|
|
|
const appStore = useAppStore()
|
|
appStore.refreshRoute('/module/module-management')
|
|
await flushPromises()
|
|
|
|
expect(counters.ModuleManagement.mounts).toBe(2)
|
|
expect(counters.ModuleManagement.unmounted).toBe(1)
|
|
expect(wrapper.find('[data-page="ModuleManagement"]').text()).toContain('#0')
|
|
|
|
wrapper.unmount()
|
|
})
|
|
})
|