20260904232445
This commit is contained in:
1 parent
4e167df0f0
commit
1b01a31252
19 files changed
+1099
-1013
No files matched your search
@@ -230,7 +230,6 @@ describe('Input 组件', () => {
|
||||
- [ ] `dashboard`:渲染、菜单/权限入口
|
||||
- [ ] `detail`:id 渲染、计数交互
|
||||
- [ ] `demo`:各组件演示区基本渲染
|
||||
- [ ] `test/TestPage`:基本渲染
|
||||
|
||||
### 4.6 集成
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useMenuStore } from '@/stores/menus'
|
||||
|
||||
// mock 后端数据接口,避免真实网络请求
|
||||
vi.mock('@/services/api', () => ({
|
||||
loadDataApi: vi.fn(),
|
||||
}))
|
||||
|
||||
import { loadDataApi } from '@/services/api'
|
||||
|
||||
const mockMenus = [
|
||||
{
|
||||
b_id: '1',
|
||||
b_code: 'sys',
|
||||
b_name: '系统',
|
||||
b_menu_type: 'directory',
|
||||
b_parent_id: null,
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: '2',
|
||||
b_code: 'module',
|
||||
b_name: '模块管理',
|
||||
b_menu_type: 'page',
|
||||
b_parent_id: '1',
|
||||
b_route: '/module/module-management',
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: '3',
|
||||
b_code: 'menu',
|
||||
b_name: '菜单管理',
|
||||
b_menu_type: 'page',
|
||||
b_parent_id: '1',
|
||||
b_route: '/module/menu-management',
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: '4',
|
||||
b_code: 'i18n',
|
||||
b_name: '多语言类型',
|
||||
b_menu_type: 'page',
|
||||
b_parent_id: '1',
|
||||
b_route: '/module/i18n-type',
|
||||
b_canuse: 1,
|
||||
},
|
||||
]
|
||||
|
||||
function mockSModuleData(menus) {
|
||||
loadDataApi.mockImplementation((view) =>
|
||||
Promise.resolve({
|
||||
data: view === 's_menu' ? menus : [],
|
||||
code: 0,
|
||||
success: true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function setUser(account, id) {
|
||||
const auth = useAuthStore()
|
||||
auth.setSession({ token: 't', user: { id, account } }, 'ORG')
|
||||
}
|
||||
|
||||
/** 冲刷微任务:让 setSession 触发的 watch(clear) 先执行 */
|
||||
const flush = () => Promise.resolve()
|
||||
|
||||
function collectCodes(nodes) {
|
||||
const codes = []
|
||||
for (const node of nodes) {
|
||||
codes.push(String(node.b_code))
|
||||
if (node.children) codes.push(...collectCodes(node.children))
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
loadDataApi.mockReset()
|
||||
})
|
||||
|
||||
describe('menus store(s_menu 动态菜单)', () => {
|
||||
it('普通用户:隐藏编码 module 的菜单(含空目录剪枝)', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
const codes = collectCodes(menus.visibleMenus)
|
||||
expect(codes).toContain('sys')
|
||||
expect(codes).toContain('menu')
|
||||
expect(codes).toContain('i18n')
|
||||
expect(codes).not.toContain('module')
|
||||
})
|
||||
|
||||
it('普通用户:目录下仅剩 module 子项时,目录整体不渲染', async () => {
|
||||
// 系统目录下只有「模块管理」这一个可用子菜单
|
||||
mockSModuleData(mockMenus.filter((m) => m.b_code !== 'menu' && m.b_code !== 'i18n'))
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
expect(menus.visibleMenus).toEqual([])
|
||||
})
|
||||
|
||||
it('g3soft:module 菜单可见', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
setUser('g3soft', 1)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
expect(menus.isG3soft).toBe(true)
|
||||
expect(collectCodes(menus.visibleMenus)).toContain('module')
|
||||
})
|
||||
|
||||
it('同一主体重复 load 不重复发请求', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
await menus.load()
|
||||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('切换账号:缓存清空并重新加载', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
expect(menus.rawMenus.length).toBe(mockMenus.length)
|
||||
|
||||
// 切换到另一个主体:watch(clear) 清空已加载数据
|
||||
setUser('u2', 999)
|
||||
await flush()
|
||||
await flush()
|
||||
expect(menus.rawMenus.length).toBe(0)
|
||||
expect(menus.visibleMenus).toEqual([])
|
||||
|
||||
// 新主体 load 重新拉取
|
||||
loadDataApi.mockClear()
|
||||
await menus.load()
|
||||
expect(menus.rawMenus.length).toBe(mockMenus.length)
|
||||
})
|
||||
})
|
||||
@@ -57,7 +57,7 @@ describe('路由守卫 权限加载(三场景)', () => {
|
||||
expect(usePermissionStore().modules.length).toBe(1)
|
||||
// 再次进入另一受保护页(同主体)不应重复发请求
|
||||
loadDataApi.mockClear()
|
||||
await router.push('/test/list')
|
||||
await router.push('/demo')
|
||||
expect(loadDataApi).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('路由守卫 权限加载(三场景)', () => {
|
||||
// 切换到另一账号
|
||||
auth.setSession({ token: 't2', user: { id: 999, account: 'u2', name: 'U2' } }, 'ORG2')
|
||||
loadDataApi.mockClear()
|
||||
await router.push('/test/list')
|
||||
await router.push('/demo')
|
||||
expect(loadDataApi).toHaveBeenCalled() // 新主体重新加载
|
||||
expect(usePermissionStore().modules.length).toBe(1)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
|
||||
// mock 后端数据接口(权限/菜单/多语言都走 loadDataApi)
|
||||
vi.mock('@/services/api', () => ({
|
||||
loadDataApi: vi.fn(() => Promise.resolve({ data: [], code: 0, success: true })),
|
||||
}))
|
||||
|
||||
beforeEach(async () => {
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
setActivePinia(createPinia())
|
||||
// 复位到登录页,避免上一用例的受保护路由状态泄漏
|
||||
await router.push('/login').catch(() => {})
|
||||
})
|
||||
|
||||
function login(account, id) {
|
||||
useAuthStore().setSession({ token: 't', user: { id, account, name: account } }, 'ORG')
|
||||
}
|
||||
|
||||
describe('路由守卫:模块中心 /module/* 仅 g3soft 可访问', () => {
|
||||
it('普通用户:访问 /module/* 一律拦截回工作台', async () => {
|
||||
login('001', 123)
|
||||
await router.push('/module/i18n-type')
|
||||
expect(router.currentRoute.value.path).toBe('/dashboard')
|
||||
|
||||
await router.push('/module/module-management')
|
||||
expect(router.currentRoute.value.path).toBe('/dashboard')
|
||||
})
|
||||
|
||||
it('普通用户:其它受保护页面不受影响', async () => {
|
||||
login('001', 123)
|
||||
await router.push('/demo')
|
||||
expect(router.currentRoute.value.path).toBe('/demo')
|
||||
})
|
||||
|
||||
it('g3soft:模块中心页面可正常访问', async () => {
|
||||
login('g3soft', 1)
|
||||
await router.push('/module/i18n-type')
|
||||
expect(router.currentRoute.value.path).toBe('/module/i18n-type')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
||||
import { menuRouteOptions, routes } from '@/router/routes'
|
||||
|
||||
/**
|
||||
* 路由分组(/module)的契约测试。
|
||||
* 分组节点必须保持「无 component」:一旦给它配容器组件(内部再放 router-view),
|
||||
* 页面会下沉一层、脱离 DefaultLayout 的 keep-alive,缓存静默失效。
|
||||
*/
|
||||
function createTestRouter() {
|
||||
return createRouter({ history: createMemoryHistory(), routes })
|
||||
}
|
||||
|
||||
describe('路由分组(嵌套结构)', () => {
|
||||
it('分组内子路由解析出完整路径,分组节点不带可渲染组件', async () => {
|
||||
const router = createTestRouter()
|
||||
await router.push('/module/i18n-type')
|
||||
const matched = router.currentRoute.value.matched
|
||||
|
||||
expect(matched.map((record) => record.path)).toEqual(['/', '/module', '/module/i18n-type'])
|
||||
// 分组节点无 components:RouterView 会跳过它,子页仍渲染在 DefaultLayout 的 router-view 上
|
||||
expect(matched[1].components).toBeFalsy()
|
||||
// meta 由父到子合并,子记录的 componentName 决定 keep-alive 的 include 名
|
||||
expect(router.currentRoute.value.meta.componentName).toBe('I18nTypeManagement')
|
||||
})
|
||||
|
||||
it('访问分组路径本身会重定向到首个子页(不会白屏)', async () => {
|
||||
const router = createTestRouter()
|
||||
await router.push('/module')
|
||||
expect(router.currentRoute.value.path).toBe('/module/module-management')
|
||||
})
|
||||
|
||||
it('菜单路由选项:含分组内子路由的完整路径,排除分组节点与动态/通配路由', () => {
|
||||
const values = menuRouteOptions.map((option) => option.value)
|
||||
|
||||
expect(values).toContain('/module/module-management')
|
||||
expect(values).toContain('/module/menu-management')
|
||||
expect(values).toContain('/module/i18n-type')
|
||||
expect(values).toContain('/dashboard')
|
||||
// 分组节点本身、详情页(:id)、404 通配、登录页都不是菜单入口
|
||||
expect(values).not.toContain('/module')
|
||||
expect(values).not.toContain('/detail/:id')
|
||||
expect(values).not.toContain('/login')
|
||||
})
|
||||
})
|
||||
@@ -73,7 +73,8 @@ beforeEach(async () => {
|
||||
c.unmounted = 0
|
||||
})
|
||||
createTestPinia()
|
||||
useAuthStore().setSession({ token: 't', user: { id: 1, account: 'u', name: 'U' } }, 'ORG')
|
||||
// 模块中心 /module/* 在路由层仅 g3soft 放行,缓存测试用管理员账号避免被守卫拦截
|
||||
useAuthStore().setSession({ token: 't', user: { id: 1, account: 'g3soft', name: 'U' } }, 'ORG')
|
||||
await router.push('/dashboard')
|
||||
await router.isReady()
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user