2026-06-30 14:14:12 +08:00
|
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
|
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
|
|
|
|
|
|
|
|
|
import { buildBreadcrumbs, buildNavigation } from '@/layouts/main-layout/navigation'
|
|
|
|
|
import type { MessageKey } from '@/i18n'
|
|
|
|
|
|
|
|
|
|
const translate = (key: MessageKey) => `tx:${key}`
|
|
|
|
|
|
|
|
|
|
function route(path: string, name?: string, meta: Record<string, unknown> = {}): RouteLocationNormalizedLoaded {
|
|
|
|
|
return {
|
|
|
|
|
path,
|
|
|
|
|
fullPath: path,
|
|
|
|
|
query: {},
|
|
|
|
|
hash: '',
|
|
|
|
|
name,
|
|
|
|
|
params: {},
|
|
|
|
|
matched: [],
|
|
|
|
|
meta,
|
|
|
|
|
redirectedFrom: undefined,
|
|
|
|
|
} as RouteLocationNormalizedLoaded
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('main layout navigation builder', () => {
|
|
|
|
|
it('builds user navigation from translation keys and active modules', () => {
|
|
|
|
|
const navigation = buildNavigation({
|
|
|
|
|
canAccessAdmin: false,
|
|
|
|
|
modules: {},
|
|
|
|
|
isModuleActive: (name) => name === 'referral',
|
|
|
|
|
t: translate,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(navigation.map(group => group.title)).toEqual([
|
|
|
|
|
'tx:nav.group.overview',
|
|
|
|
|
'tx:nav.group.resources',
|
|
|
|
|
'tx:nav.group.account',
|
|
|
|
|
])
|
|
|
|
|
expect(navigation.flatMap(group => group.items.map(item => item.name))).toContain('tx:nav.myReferral')
|
|
|
|
|
})
|
|
|
|
|
|
2026-09-06 00:13:57 +08:00
|
|
|
it('exposes remote control to users and active administrators', () => {
|
|
|
|
|
const userNavigation = buildNavigation({
|
|
|
|
|
canAccessAdmin: false,
|
2026-09-01 20:08:36 +08:00
|
|
|
modules: {},
|
|
|
|
|
isModuleActive: () => false,
|
|
|
|
|
t: translate,
|
|
|
|
|
})
|
|
|
|
|
const adminNavigation = buildNavigation({
|
|
|
|
|
canAccessAdmin: true,
|
2026-09-06 00:13:57 +08:00
|
|
|
modules: {
|
|
|
|
|
vscodex: {
|
|
|
|
|
active: true,
|
2026-09-07 21:14:27 +08:00
|
|
|
name: 'test-module',
|
|
|
|
|
available: true,
|
|
|
|
|
enabled: true,
|
|
|
|
|
config_validated: true,
|
|
|
|
|
config_error: null,
|
|
|
|
|
description: '',
|
|
|
|
|
category: 'integration',
|
|
|
|
|
health: 'healthy',
|
2026-09-06 00:13:57 +08:00
|
|
|
admin_route: '/dashboard/vscodex',
|
|
|
|
|
admin_menu_group: 'overview',
|
|
|
|
|
admin_menu_order: 80,
|
|
|
|
|
admin_menu_icon: 'SquareTerminal',
|
|
|
|
|
display_name: '远程控制',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
isModuleActive: () => false,
|
|
|
|
|
t: translate,
|
2026-09-01 20:08:36 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const findVscodeControl = (navigation: ReturnType<typeof buildNavigation>) => (
|
|
|
|
|
navigation
|
|
|
|
|
.flatMap(group => group.items)
|
|
|
|
|
.find(item => item.href === '/dashboard/vscodex')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
expect(findVscodeControl(userNavigation)).toMatchObject({
|
|
|
|
|
name: 'tx:nav.vscodex',
|
|
|
|
|
href: '/dashboard/vscodex',
|
|
|
|
|
})
|
|
|
|
|
expect(findVscodeControl(adminNavigation)).toMatchObject({
|
2026-09-06 00:13:57 +08:00
|
|
|
name: '远程控制',
|
2026-09-01 20:08:36 +08:00
|
|
|
href: '/dashboard/vscodex',
|
|
|
|
|
})
|
2026-09-06 00:13:57 +08:00
|
|
|
|
|
|
|
|
const overviewItems = adminNavigation.find(group => group.title === 'tx:nav.group.overview')?.items ?? []
|
|
|
|
|
expect(overviewItems.findIndex(item => item.name === '远程控制')).toBe(
|
2026-10-01 11:48:17 +08:00
|
|
|
overviewItems.findIndex(item => item.name === 'tx:nav.healthMonitor') + 1,
|
2026-09-06 00:13:57 +08:00
|
|
|
)
|
2026-09-01 20:08:36 +08:00
|
|
|
})
|
|
|
|
|
|
2026-10-01 11:48:17 +08:00
|
|
|
it('keeps the five fixed overview destinations in product order', () => {
|
|
|
|
|
const navigation = buildNavigation({ canAccessAdmin: true, modules: {}, isModuleActive: () => false })
|
|
|
|
|
expect(navigation[0]?.items.map(item => item.href)).toEqual([
|
|
|
|
|
'/admin/dashboard', '/admin/operations', '/admin/user-stats', '/admin/cost-analysis', '/admin/health-monitor',
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
2026-06-30 14:14:12 +08:00
|
|
|
it('builds admin navigation with dynamic module menu items sorted by menu order', () => {
|
|
|
|
|
const navigation = buildNavigation({
|
|
|
|
|
canAccessAdmin: true,
|
|
|
|
|
modules: {
|
|
|
|
|
first: {
|
|
|
|
|
active: true,
|
2026-09-07 21:14:27 +08:00
|
|
|
name: 'test-module',
|
|
|
|
|
available: true,
|
|
|
|
|
enabled: true,
|
|
|
|
|
config_validated: true,
|
|
|
|
|
config_error: null,
|
|
|
|
|
description: '',
|
|
|
|
|
category: 'integration',
|
|
|
|
|
health: 'healthy',
|
2026-06-30 14:14:12 +08:00
|
|
|
admin_route: '/admin/first',
|
|
|
|
|
admin_menu_group: 'management',
|
|
|
|
|
admin_menu_order: 2,
|
|
|
|
|
admin_menu_icon: 'Gift',
|
|
|
|
|
display_name: 'First module',
|
|
|
|
|
},
|
|
|
|
|
second: {
|
|
|
|
|
active: true,
|
2026-09-07 21:14:27 +08:00
|
|
|
name: 'test-module',
|
|
|
|
|
available: true,
|
|
|
|
|
enabled: true,
|
|
|
|
|
config_validated: true,
|
|
|
|
|
config_error: null,
|
|
|
|
|
description: '',
|
|
|
|
|
category: 'integration',
|
|
|
|
|
health: 'healthy',
|
2026-06-30 14:14:12 +08:00
|
|
|
admin_route: '/admin/second',
|
|
|
|
|
admin_menu_group: 'management',
|
|
|
|
|
admin_menu_order: 1,
|
|
|
|
|
admin_menu_icon: 'Key',
|
|
|
|
|
display_name: 'Second module',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
isModuleActive: () => false,
|
|
|
|
|
t: translate,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const managementItems = navigation.find(group => group.title === 'tx:nav.group.management')?.items ?? []
|
|
|
|
|
expect(managementItems.map(item => item.name)).toEqual(expect.arrayContaining(['Second module', 'First module']))
|
|
|
|
|
expect(managementItems.findIndex(item => item.name === 'Second module')).toBeLessThan(
|
|
|
|
|
managementItems.findIndex(item => item.name === 'First module')
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('builds translated breadcrumbs for settings and routing detail pages', () => {
|
|
|
|
|
const navigation = buildNavigation({
|
|
|
|
|
canAccessAdmin: true,
|
|
|
|
|
modules: {},
|
|
|
|
|
isModuleActive: () => false,
|
|
|
|
|
t: translate,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(buildBreadcrumbs({
|
|
|
|
|
route: route('/dashboard/settings'),
|
|
|
|
|
navigation,
|
|
|
|
|
modules: {},
|
|
|
|
|
isNavActive: () => false,
|
|
|
|
|
t: translate,
|
|
|
|
|
})).toEqual([
|
|
|
|
|
{ label: 'tx:nav.group.account' },
|
|
|
|
|
{ label: 'tx:breadcrumb.personalSettings' },
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
expect(buildBreadcrumbs({
|
|
|
|
|
route: route('/admin/routing/new', 'RoutingProfileCreate'),
|
|
|
|
|
navigation,
|
|
|
|
|
modules: {},
|
|
|
|
|
isNavActive: href => href === '/admin/routing',
|
|
|
|
|
t: translate,
|
|
|
|
|
})).toEqual([
|
|
|
|
|
{ label: 'tx:nav.group.management' },
|
|
|
|
|
{ label: 'tx:nav.routing', href: '/admin/routing' },
|
|
|
|
|
{ label: 'tx:breadcrumb.routingCreate' },
|
|
|
|
|
])
|
2026-09-01 20:08:36 +08:00
|
|
|
|
|
|
|
|
expect(buildBreadcrumbs({
|
|
|
|
|
route: route('/dashboard/vscodex'),
|
2026-09-06 00:13:57 +08:00
|
|
|
navigation: buildNavigation({
|
|
|
|
|
canAccessAdmin: true,
|
|
|
|
|
modules: {
|
|
|
|
|
vscodex: {
|
|
|
|
|
active: true,
|
2026-09-07 21:14:27 +08:00
|
|
|
name: 'test-module',
|
|
|
|
|
available: true,
|
|
|
|
|
enabled: true,
|
|
|
|
|
config_validated: true,
|
|
|
|
|
config_error: null,
|
|
|
|
|
description: '',
|
|
|
|
|
category: 'integration',
|
|
|
|
|
health: 'healthy',
|
2026-09-06 00:13:57 +08:00
|
|
|
admin_route: '/dashboard/vscodex',
|
|
|
|
|
admin_menu_group: 'overview',
|
|
|
|
|
admin_menu_order: 80,
|
|
|
|
|
admin_menu_icon: 'SquareTerminal',
|
|
|
|
|
display_name: '远程控制',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
isModuleActive: () => false,
|
|
|
|
|
t: translate,
|
|
|
|
|
}),
|
|
|
|
|
modules: {
|
|
|
|
|
vscodex: {
|
|
|
|
|
active: true,
|
2026-09-07 21:14:27 +08:00
|
|
|
name: 'test-module',
|
|
|
|
|
available: true,
|
|
|
|
|
enabled: true,
|
|
|
|
|
config_validated: true,
|
|
|
|
|
config_error: null,
|
|
|
|
|
description: '',
|
|
|
|
|
category: 'integration',
|
|
|
|
|
health: 'healthy',
|
2026-09-06 00:13:57 +08:00
|
|
|
admin_route: '/dashboard/vscodex',
|
|
|
|
|
admin_menu_group: 'overview',
|
|
|
|
|
admin_menu_order: 80,
|
|
|
|
|
admin_menu_icon: 'SquareTerminal',
|
|
|
|
|
display_name: '远程控制',
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-09-01 20:08:36 +08:00
|
|
|
isNavActive: href => href === '/dashboard/vscodex',
|
|
|
|
|
t: translate,
|
|
|
|
|
})).toEqual([
|
|
|
|
|
expect.objectContaining({ label: expect.any(String) }),
|
2026-09-06 00:13:57 +08:00
|
|
|
{ label: '远程控制' },
|
2026-09-01 20:08:36 +08:00
|
|
|
])
|
2026-06-30 14:14:12 +08:00
|
|
|
})
|
|
|
|
|
})
|