133 lines
4.8 KiB
JavaScript
133 lines
4.8 KiB
JavaScript
/**
|
|
* 诊断:侧栏菜单与页签的选中态(is-active)翻转序列。
|
|
*
|
|
* 背景:在"其他数据 / 联系人"之间来回切换时,旧项的选中背景会「再亮一次」再灭。
|
|
* 本用例用 MutationObserver 记录完整的类变化序列,用于二分定位:
|
|
* - 状态层问题(类被加回):序列出现多余的翻转 → 需要修导航/渲染时序
|
|
* - 序列干净(每项只按导航翻转):闪烁来自 CSS 过渡的视觉 → 需要调过渡
|
|
*/
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
|
|
// 顶级菜单(不经目录):目录的展开状态只在 NavMenuItem 初始化时计算,
|
|
// 初始路由 /dashboard 不在目录下 → 目录收起 → 子项不渲染,这里用顶级项保证可见
|
|
const MENU_ROWS = [
|
|
{
|
|
b_id: 'm1',
|
|
b_name: '其他数据',
|
|
b_menu_type: 'page',
|
|
b_canuse: 1,
|
|
b_xh: 1,
|
|
b_route: '/base/otherdata',
|
|
b_parent_id: '',
|
|
},
|
|
{
|
|
b_id: 'm2',
|
|
b_name: '联系人',
|
|
b_menu_type: 'page',
|
|
b_canuse: 1,
|
|
b_xh: 2,
|
|
b_route: '/base/contact',
|
|
b_parent_id: '',
|
|
},
|
|
]
|
|
|
|
vi.mock('@/views/dashboard/index.vue', () => ({
|
|
default: { name: 'Dashboard', template: '<div data-page="Dashboard" />' },
|
|
}))
|
|
vi.mock('@/views/base/otherdata/index.vue', () => ({
|
|
default: { name: 'BaseOtherdata', template: '<div data-page="Otherdata" />' },
|
|
}))
|
|
vi.mock('@/views/base/contact/index.vue', () => ({
|
|
default: { name: 'BaseContact', template: '<div data-page="Contact" />' },
|
|
}))
|
|
vi.mock('@/views/module/module-management/index.vue', () => ({
|
|
default: { name: 'ModuleManagement', template: '<div data-page="MM" />' },
|
|
}))
|
|
vi.mock('@/views/module/menu-management/index.vue', () => ({
|
|
default: { name: 'MenuManagement', template: '<div data-page="MenuM" />' },
|
|
}))
|
|
vi.mock('@/views/detail/DetailPage.vue', () => ({
|
|
default: { name: 'DetailPageStub', props: ['id'], template: '<div />' },
|
|
}))
|
|
vi.mock('@/services/api', () => ({
|
|
loadDataApi: vi.fn((table) =>
|
|
Promise.resolve({ data: table === 's_menu' ? MENU_ROWS : [], 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'
|
|
|
|
beforeEach(async () => {
|
|
localStorage.clear()
|
|
sessionStorage.clear()
|
|
createTestPinia()
|
|
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('选中态翻转序列(闪烁诊断)', () => {
|
|
it('其他数据/联系人来回切换:is-active 序列无多余翻转', async () => {
|
|
const wrapper = await mountApp()
|
|
await router.push('/base/otherdata')
|
|
await flushPromises()
|
|
|
|
const navItem = (name) =>
|
|
Array.from(document.querySelectorAll('.fms-nav-link')).find((el) => el.textContent.includes(name))
|
|
expect(navItem('其他数据')).toBeTruthy()
|
|
|
|
const read = () => ({
|
|
other: navItem('其他数据')?.classList.contains('is-active') ? 1 : 0,
|
|
contact: navItem('联系人')?.classList.contains('is-active') ? 1 : 0,
|
|
})
|
|
const flips = []
|
|
let last = JSON.stringify(read())
|
|
const mo = new MutationObserver(() => {
|
|
const now = JSON.stringify(read())
|
|
if (now !== last) {
|
|
flips.push(now)
|
|
last = now
|
|
}
|
|
})
|
|
mo.observe(document.body, { subtree: true, attributes: true, attributeFilter: ['class'] })
|
|
|
|
// 场景 1:慢速来回(每次等导航完成)—— 期望恰好 2 次翻转
|
|
await router.push('/base/contact')
|
|
await flushPromises()
|
|
await router.push('/base/otherdata')
|
|
await flushPromises()
|
|
|
|
// 场景 2:快速来回(不等前一个导航完成)—— 模拟用户快速连点
|
|
router.push('/base/contact')
|
|
router.push('/base/otherdata')
|
|
router.push('/base/contact')
|
|
await flushPromises()
|
|
|
|
mo.disconnect()
|
|
console.log('[诊断] is-active 翻转序列:', flips.join(' -> '))
|
|
|
|
// 最终态:最后 push 的是联系人
|
|
const final = read()
|
|
expect(final.contact).toBe(1)
|
|
expect(final.other).toBe(0)
|
|
// 场景 1 恰好 2 次翻转;场景 2 的快速连点可能合并为至多 3 次 → 上限 5。
|
|
// 若状态层存在「旧项再亮一次」(类被加回),翻转次数会超过此上限并在这里变红
|
|
expect(flips.length).toBeLessThanOrEqual(5)
|
|
|
|
wrapper.unmount()
|
|
})
|
|
})
|