20260920220520
This commit is contained in:
1 parent
6f03d92425
commit
b3364fe6dc
44 files changed
+2313
-2737
No files matched your search
@@ -1,7 +1,12 @@
|
||||
/**
|
||||
* menus store(s_menu 动态菜单):按 menu.* 授权裁剪可见菜单 +
|
||||
* 路由地址 → 菜单反查(路由守卫用它兜住手输地址)。
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useMenuStore } from '@/stores/menus'
|
||||
import { usePermissionStore } from '@/stores/permissions'
|
||||
import { createTestPinia } from './helpers/app.js'
|
||||
|
||||
// mock 后端数据接口,避免真实网络请求
|
||||
vi.mock('@/services/api', () => ({
|
||||
@@ -14,26 +19,38 @@ import { loadDataApi } from '@/services/api'
|
||||
// 因此这里用业务菜单构造数据,验证菜单 store 本身无任何编码/账号特判。
|
||||
const mockMenus = [
|
||||
{
|
||||
b_id: '1',
|
||||
b_code: 'base',
|
||||
b_id: 'base',
|
||||
b_name: '基础数据',
|
||||
b_menu_type: 'directory',
|
||||
b_parent_id: null,
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: '2',
|
||||
b_code: 'contact',
|
||||
b_id: 'contact',
|
||||
b_name: '往来单位',
|
||||
b_menu_type: 'page',
|
||||
b_parent_id: '1',
|
||||
b_parent_id: 'base',
|
||||
b_route: '/base/contact',
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: 'system',
|
||||
b_name: '系统管理',
|
||||
b_menu_type: 'directory',
|
||||
b_parent_id: null,
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
b_id: 'user',
|
||||
b_name: '用户管理',
|
||||
b_menu_type: 'page',
|
||||
b_parent_id: 'system',
|
||||
b_route: '/system/user',
|
||||
b_canuse: 1,
|
||||
},
|
||||
{
|
||||
// 没有任何子项的目录:可见性裁剪后不应渲染
|
||||
b_id: '3',
|
||||
b_code: 'empty-dir',
|
||||
b_id: 'empty-dir',
|
||||
b_name: '空目录',
|
||||
b_menu_type: 'directory',
|
||||
b_parent_id: null,
|
||||
@@ -41,14 +58,18 @@ const mockMenus = [
|
||||
},
|
||||
]
|
||||
|
||||
function mockSModuleData(menus) {
|
||||
loadDataApi.mockImplementation((view) =>
|
||||
Promise.resolve({
|
||||
data: view === 's_menu' ? menus : [],
|
||||
code: 0,
|
||||
success: true,
|
||||
}),
|
||||
)
|
||||
function mockData({ menus = mockMenus, powers = [], userPowers = [] } = {}) {
|
||||
loadDataApi.mockImplementation((table) => {
|
||||
const data =
|
||||
table === 's_menu'
|
||||
? menus
|
||||
: table === 's_power'
|
||||
? powers
|
||||
: table === 's_user_power'
|
||||
? userPowers
|
||||
: []
|
||||
return Promise.resolve({ data, code: 0, success: true })
|
||||
})
|
||||
}
|
||||
|
||||
function setUser(account, id) {
|
||||
@@ -59,61 +80,64 @@ function setUser(account, id) {
|
||||
/** 冲刷微任务:让 setSession 触发的 watch(clear) 先执行 */
|
||||
const flush = () => Promise.resolve()
|
||||
|
||||
function collectCodes(nodes) {
|
||||
const codes = []
|
||||
function collectIds(nodes) {
|
||||
const ids = []
|
||||
for (const node of nodes) {
|
||||
codes.push(String(node.b_code))
|
||||
if (node.children) codes.push(...collectCodes(node.children))
|
||||
ids.push(String(node.b_id))
|
||||
if (node.children) ids.push(...collectIds(node.children))
|
||||
}
|
||||
return codes
|
||||
return ids
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
createTestPinia()
|
||||
loadDataApi.mockReset()
|
||||
})
|
||||
|
||||
describe('menus store(s_menu 动态菜单)', () => {
|
||||
it('普通用户:全部启用菜单按树渲染,空目录剪枝(无编码特判)', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
it('普通用户:只渲染有 menu.* 授权的页面菜单,空目录剪枝', async () => {
|
||||
mockData({
|
||||
powers: [{ b_id: 'menu.contact' }, { b_id: 'menu.user' }],
|
||||
// 只授权联系人:用户管理虽在 s_power 里,但用户没有该授权 → 系统管理目录随之消失
|
||||
userPowers: [{ b_user_id: '123', b_power_id: 'menu.contact', b_canuse: 1 }],
|
||||
})
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
const codes = collectCodes(menus.visibleMenus)
|
||||
expect(codes).toContain('base')
|
||||
expect(codes).toContain('contact')
|
||||
// 目录没有可见子菜单时不渲染
|
||||
expect(codes).not.toContain('empty-dir')
|
||||
const perm = usePermissionStore()
|
||||
const menus = useMenuStore()
|
||||
await Promise.all([perm.load(), menus.load()])
|
||||
|
||||
expect(collectIds(menus.visibleMenus)).toEqual(['base', 'contact'])
|
||||
})
|
||||
|
||||
it('管理员特判不在菜单 store:g3soft 与普通用户渲染一致', async () => {
|
||||
it('g3soft 提权:不查授权也全部可见(管理员不裁剪)', async () => {
|
||||
// 模块中心的管控在路由守卫(meta.adminOnly)与侧栏渲染(仅 g3soft 追加固定入口),
|
||||
// 菜单 store 对所有登录主体一视同仁
|
||||
mockSModuleData(mockMenus)
|
||||
// 菜单 store 对管理员只跳过授权裁剪,不做编码特判
|
||||
mockData()
|
||||
setUser('g3soft', 1)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
const codes = collectCodes(menus.visibleMenus)
|
||||
expect(codes).toHaveLength(2)
|
||||
expect(codes).toEqual(expect.arrayContaining(['base', 'contact']))
|
||||
const perm = usePermissionStore()
|
||||
const menus = useMenuStore()
|
||||
await Promise.all([perm.load(), menus.load()])
|
||||
|
||||
expect(collectIds(menus.visibleMenus)).toEqual(['base', 'contact', 'system', 'user'])
|
||||
})
|
||||
|
||||
it('同一主体重复 load 不重复发请求', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
mockData()
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
await menus.load()
|
||||
// 只请求 s_menu(权限 store 未参与加载)
|
||||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('切换账号:缓存清空并重新加载', async () => {
|
||||
mockSModuleData(mockMenus)
|
||||
mockData()
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
@@ -133,3 +157,24 @@ describe('menus store(s_menu 动态菜单)', () => {
|
||||
expect(menus.rawMenus.length).toBe(mockMenus.length)
|
||||
})
|
||||
})
|
||||
|
||||
describe('menus store:路由反查(findMenuByRoute)', () => {
|
||||
it('命中菜单路由与其子路径,非菜单路由返回 null', async () => {
|
||||
mockData()
|
||||
setUser('u1', 123)
|
||||
await flush()
|
||||
const menus = useMenuStore()
|
||||
await menus.load()
|
||||
|
||||
expect(menus.findMenuByRoute('/base/contact')?.b_id).toBe('contact')
|
||||
// 详情页等子路径同样算作该菜单的入口
|
||||
expect(menus.findMenuByRoute('/base/contact/123')?.b_id).toBe('contact')
|
||||
expect(menus.findMenuByRoute('/base/contact/?x=1')?.b_id).toBe('contact')
|
||||
// 工作台、演示页、模块中心都不在 s_menu
|
||||
expect(menus.findMenuByRoute('/dashboard')).toBeNull()
|
||||
expect(menus.findMenuByRoute('/demo')).toBeNull()
|
||||
expect(menus.findMenuByRoute('/module/module-management')).toBeNull()
|
||||
// 目录没有路由,不会被匹配到
|
||||
expect(menus.findMenuByRoute('/base')).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user