feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user