2 changed files with 257 additions and 2 deletions
@ -0,0 +1,240 @@ |
|||
import type { RouteRecordRaw } from '@vben/types'; |
|||
|
|||
import { describe, expect, it } from 'vitest'; |
|||
|
|||
import { generateAccessible } from '../accessible'; |
|||
|
|||
// generateAccessible 会操作传入的 router 实例。这里用最小 stub 覆盖它实际调用的方法:
|
|||
// - getRoutes(): 返回 [] -> 不存在根路由 '/', 走 router.addRoute 分支
|
|||
// - addRoute/removeRoute: 空实现
|
|||
// 我们只断言返回的 accessibleRoutes 上自动生成的 redirect。
|
|||
function createRouterStub() { |
|||
return { |
|||
addRoute: () => {}, |
|||
getRoutes: () => [], |
|||
removeRoute: () => {}, |
|||
} as any; |
|||
} |
|||
|
|||
async function generate(routes: RouteRecordRaw[]) { |
|||
const { accessibleRoutes } = await generateAccessible('frontend', { |
|||
router: createRouterStub(), |
|||
routes, |
|||
}); |
|||
return accessibleRoutes; |
|||
} |
|||
|
|||
function findByName( |
|||
routes: RouteRecordRaw[], |
|||
name: string, |
|||
): RouteRecordRaw | undefined { |
|||
for (const route of routes) { |
|||
if (route.name === name) { |
|||
return route; |
|||
} |
|||
if (route.children) { |
|||
const found = findByName(route.children as RouteRecordRaw[], name); |
|||
if (found) { |
|||
return found; |
|||
} |
|||
} |
|||
} |
|||
return undefined; |
|||
} |
|||
|
|||
describe('generateAccessible - redirect normalization', () => { |
|||
it('不为动态参数(:id)首子路由的父级生成 redirect', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'DyeSets', |
|||
path: 'dye-sets', |
|||
children: [ |
|||
{ |
|||
name: 'DyeSetDetail', |
|||
path: ':id', |
|||
meta: { hideInMenu: true, title: 'detail' }, |
|||
}, |
|||
], |
|||
meta: { title: 'dye-sets' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
expect(findByName(result, 'DyeSets')?.redirect).toBeUndefined(); |
|||
}); |
|||
|
|||
it('父级为对象 redirect({name}) 且含 :id 子路由时不抛异常且不生成 redirect', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'Production', |
|||
path: '/production', |
|||
redirect: { name: 'ProductionTasks' }, |
|||
children: [ |
|||
{ |
|||
name: 'ProductionTasks', |
|||
path: 'production-tasks', |
|||
children: [ |
|||
{ |
|||
name: 'ProductionTaskDetail', |
|||
path: ':id', |
|||
meta: { hideInMenu: true, title: 'detail' }, |
|||
}, |
|||
{ |
|||
name: 'ProductionTaskMatch', |
|||
path: ':id/match', |
|||
meta: { hideInMenu: true, title: 'match' }, |
|||
}, |
|||
], |
|||
meta: { title: 'tasks' }, |
|||
}, |
|||
], |
|||
meta: { title: 'production' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
// 顶级对象 redirect 保持不变
|
|||
expect(findByName(result, 'Production')?.redirect).toEqual({ |
|||
name: 'ProductionTasks', |
|||
}); |
|||
// :id 首子路由的父级不生成 redirect
|
|||
expect(findByName(result, 'ProductionTasks')?.redirect).toBeUndefined(); |
|||
}); |
|||
|
|||
it('父级为对象 redirect 时,普通相对首子路由回退用 parent.path 拼接', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'Setting', |
|||
path: '/setting', |
|||
redirect: { name: 'SettingService' }, |
|||
children: [ |
|||
{ |
|||
name: 'SettingGroup', |
|||
path: 'group', |
|||
children: [ |
|||
{ |
|||
name: 'SettingService', |
|||
path: 'service', |
|||
meta: { title: 'service' }, |
|||
}, |
|||
], |
|||
meta: { title: 'group' }, |
|||
}, |
|||
], |
|||
meta: { title: 'setting' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
expect(findByName(result, 'SettingGroup')?.redirect).toBe( |
|||
'/setting/group/service', |
|||
); |
|||
}); |
|||
|
|||
it('深层嵌套(上游风格)相对路径逐级生成正确的累计绝对 redirect', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'Demos', |
|||
path: '/demos', |
|||
children: [ |
|||
{ |
|||
name: 'NestedDemos', |
|||
path: 'nested', |
|||
children: [ |
|||
{ |
|||
name: 'Menu1Demo', |
|||
path: 'menu1', |
|||
meta: { title: 'menu1' }, |
|||
}, |
|||
{ |
|||
name: 'Menu2Demo', |
|||
path: 'menu2', |
|||
children: [ |
|||
{ |
|||
name: 'Menu21Demo', |
|||
path: 'menu2-1', |
|||
meta: { title: 'menu2-1' }, |
|||
}, |
|||
], |
|||
meta: { title: 'menu2' }, |
|||
}, |
|||
], |
|||
meta: { title: 'nested' }, |
|||
}, |
|||
], |
|||
meta: { title: 'demos' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
// Demos 重定向到第一级子路由,子路由继续级联到叶子
|
|||
expect(findByName(result, 'Demos')?.redirect).toBe('/demos/nested'); |
|||
expect(findByName(result, 'NestedDemos')?.redirect).toBe( |
|||
'/demos/nested/menu1', |
|||
); |
|||
expect(findByName(result, 'Menu2Demo')?.redirect).toBe( |
|||
'/demos/nested/menu2/menu2-1', |
|||
); |
|||
}); |
|||
|
|||
it('首子路由为绝对路径(/foo)时不生成 redirect', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'Dashboard', |
|||
path: '/dashboard', |
|||
children: [ |
|||
{ |
|||
name: 'Analytics', |
|||
path: '/analytics', |
|||
meta: { title: 'analytics' }, |
|||
}, |
|||
], |
|||
meta: { title: 'dashboard' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
expect(findByName(result, 'Dashboard')?.redirect).toBeUndefined(); |
|||
}); |
|||
|
|||
it('首子路由为空 path 时不生成 redirect', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'HideChildrenParent', |
|||
path: 'hide-menu-children', |
|||
children: [ |
|||
{ |
|||
name: 'HideChildren', |
|||
path: '', |
|||
meta: { title: 'hide' }, |
|||
}, |
|||
], |
|||
meta: { title: 'parent' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
expect(findByName(result, 'HideChildrenParent')?.redirect).toBeUndefined(); |
|||
}); |
|||
|
|||
it('已存在的 redirect 保持不变', async () => { |
|||
const routes = [ |
|||
{ |
|||
name: 'Custom', |
|||
path: '/custom', |
|||
redirect: '/custom/keep', |
|||
children: [ |
|||
{ |
|||
name: 'CustomChild', |
|||
path: 'child', |
|||
meta: { title: 'child' }, |
|||
}, |
|||
], |
|||
meta: { title: 'custom' }, |
|||
}, |
|||
] as unknown as RouteRecordRaw[]; |
|||
|
|||
const result = await generate(routes); |
|||
expect(findByName(result, 'Custom')?.redirect).toBe('/custom/keep'); |
|||
}); |
|||
}); |
|||
Loading…
Reference in new issue