250 lines
7.0 KiB
TypeScript
250 lines
7.0 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { mount, VueWrapper } from '@vue/test-utils';
|
|
import { createPinia, setActivePinia } from 'pinia';
|
|
import { createRouter, createMemoryHistory } from 'vue-router';
|
|
import { useAppStore } from '@/stores/app.store';
|
|
import { useAuthStore } from '@/stores/auth.store';
|
|
|
|
const mockRoutes = [
|
|
{
|
|
path: '/',
|
|
name: 'Layout',
|
|
component: { template: '<div></div>' },
|
|
children: [
|
|
{
|
|
path: '/dashboard',
|
|
name: 'Dashboard',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '仪表盘', icon: 'DashboardOutlined' }
|
|
},
|
|
{
|
|
path: '/users',
|
|
name: 'Users',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '用户管理', icon: 'UserOutlined' }
|
|
},
|
|
{
|
|
path: '/system',
|
|
name: 'System',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '系统管理', icon: 'SettingOutlined' },
|
|
children: [
|
|
{
|
|
path: '/system/roles',
|
|
name: 'Roles',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '角色管理', icon: 'LockOutlined' }
|
|
},
|
|
{
|
|
path: '/system/menus',
|
|
name: 'Menus',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '菜单管理', icon: 'MenuOutlined' }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
path: '/hidden',
|
|
name: 'Hidden',
|
|
component: { template: '<div></div>' },
|
|
meta: { title: '隐藏菜单', icon: 'SettingOutlined', hidden: true }
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
const router = createRouter({
|
|
history: createMemoryHistory(),
|
|
routes: mockRoutes
|
|
});
|
|
|
|
vi.mock('@/router', () => ({
|
|
resetRouter: vi.fn(),
|
|
default: router
|
|
}));
|
|
|
|
vi.mock('vue-router', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('vue-router')>();
|
|
return {
|
|
...actual,
|
|
useRouter: () => router,
|
|
useRoute: () => router.currentRoute.value
|
|
};
|
|
});
|
|
|
|
import Sidebar from '@/components/Sidebar.vue';
|
|
|
|
describe('Sidebar 组件', () => {
|
|
let wrapper: VueWrapper;
|
|
let appStore: ReturnType<typeof useAppStore>;
|
|
let authStore: ReturnType<typeof useAuthStore>;
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia());
|
|
appStore = useAppStore();
|
|
authStore = useAuthStore();
|
|
|
|
authStore.user = {
|
|
id: 1,
|
|
username: 'admin',
|
|
email: 'admin@example.com',
|
|
phone: '13800138000',
|
|
status: 'ACTIVE',
|
|
permissions: ['dashboard:view', 'user:view', 'role:view', 'menu:view']
|
|
};
|
|
|
|
wrapper = mount(Sidebar, {
|
|
global: {
|
|
plugins: [router],
|
|
stubs: {
|
|
'MenuOutlined': true,
|
|
'DashboardOutlined': true,
|
|
'UserOutlined': true,
|
|
'LockOutlined': true,
|
|
'SettingOutlined': true
|
|
}
|
|
}
|
|
});
|
|
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('初始状态', () => {
|
|
it('应该正确渲染组件', () => {
|
|
expect(wrapper.exists()).toBe(true);
|
|
});
|
|
|
|
it('应该显示侧边栏', () => {
|
|
expect(wrapper.find('.sidebar').exists()).toBe(true);
|
|
});
|
|
|
|
it('应该显示菜单', () => {
|
|
expect(wrapper.find('.sidebar-menu').exists()).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('侧边栏折叠', () => {
|
|
it('应该响应侧边栏折叠状态', () => {
|
|
appStore.toggleSidebar();
|
|
expect(wrapper.vm.collapsed).toBe(true);
|
|
});
|
|
|
|
it('折叠时应该显示图标而不是文字', async () => {
|
|
appStore.setSidebarCollapsed(true);
|
|
await wrapper.vm.$nextTick();
|
|
expect(wrapper.vm.collapsed).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('菜单项生成', () => {
|
|
it('应该根据路由生成菜单项', () => {
|
|
const menuItems = wrapper.vm.menuItems;
|
|
expect(menuItems).toBeDefined();
|
|
expect(Array.isArray(menuItems)).toBe(true);
|
|
});
|
|
|
|
it('应该隐藏标记为hidden的菜单项', () => {
|
|
const menuItems = wrapper.vm.menuItems;
|
|
const hiddenItem = menuItems.find((item: any) => item.key === 'Hidden');
|
|
expect(hiddenItem).toBeUndefined();
|
|
});
|
|
|
|
it('应该包含仪表盘菜单项', () => {
|
|
const menuItems = wrapper.vm.menuItems;
|
|
const dashboardItem = menuItems.find((item: any) => item.key === 'Dashboard');
|
|
expect(dashboardItem).toBeDefined();
|
|
expect(dashboardItem.label).toBe('仪表盘');
|
|
});
|
|
});
|
|
|
|
describe('菜单选择', () => {
|
|
it('应该能够选择菜单项', async () => {
|
|
await wrapper.vm.handleMenuSelect({ key: 'Dashboard' });
|
|
expect(wrapper.emitted('menuSelect')).toBeTruthy();
|
|
expect(wrapper.emitted('menuSelect')?.[0]).toEqual(['Dashboard']);
|
|
});
|
|
|
|
it('选择菜单项应该导航到对应路由', async () => {
|
|
const pushSpy = vi.spyOn(router, 'push');
|
|
await wrapper.vm.handleMenuSelect({ key: 'Dashboard' });
|
|
expect(pushSpy).toHaveBeenCalledWith('/dashboard');
|
|
pushSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('权限控制', () => {
|
|
it('应该检查用户权限', () => {
|
|
const hasPermission = wrapper.vm.hasPermission('user:view');
|
|
expect(hasPermission).toBe(true);
|
|
});
|
|
|
|
it('没有权限的菜单项应该被隐藏', () => {
|
|
authStore.user = {
|
|
id: 1,
|
|
username: 'admin',
|
|
email: 'admin@example.com',
|
|
phone: '13800138000',
|
|
status: 'ACTIVE',
|
|
permissions: []
|
|
};
|
|
|
|
const hasPermission = wrapper.vm.hasPermission('user:view');
|
|
expect(hasPermission).toBe(false);
|
|
});
|
|
|
|
it('未定义权限的菜单项应该显示', () => {
|
|
const hasPermission = wrapper.vm.hasPermission(undefined);
|
|
expect(hasPermission).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('路由监听', () => {
|
|
it('应该更新选中的菜单项', () => {
|
|
expect(wrapper.vm.selectedKeys).toContain('Dashboard');
|
|
});
|
|
|
|
it('应该展开包含当前路由的父菜单', () => {
|
|
const openKeys = wrapper.vm.openKeys;
|
|
expect(Array.isArray(openKeys)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('主题和模式', () => {
|
|
it('应该接受主题属性', () => {
|
|
const wrapperWithLightTheme = mount(Sidebar, {
|
|
props: { theme: 'light' },
|
|
global: {
|
|
plugins: [router],
|
|
stubs: {
|
|
'a-layout-sider': {
|
|
template: '<div class="sidebar-stub"><slot /></div>'
|
|
},
|
|
'a-menu': {
|
|
template: '<div class="menu-stub"><slot /></div>'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
expect(wrapperWithLightTheme.exists()).toBe(true);
|
|
});
|
|
|
|
it('应该接受模式属性', () => {
|
|
const wrapperWithVerticalMode = mount(Sidebar, {
|
|
props: { mode: 'vertical' },
|
|
global: {
|
|
plugins: [router],
|
|
stubs: {
|
|
'a-layout-sider': {
|
|
template: '<div class="sidebar-stub"><slot /></div>'
|
|
},
|
|
'a-menu': {
|
|
template: '<div class="menu-stub"><slot /></div>'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
expect(wrapperWithVerticalMode.exists()).toBe(true);
|
|
});
|
|
});
|
|
});
|