diff --git a/novalon-manage-web/src/__tests__/stores/permission.test.ts b/novalon-manage-web/src/__tests__/stores/permission.test.ts new file mode 100644 index 0000000..17a0f3d --- /dev/null +++ b/novalon-manage-web/src/__tests__/stores/permission.test.ts @@ -0,0 +1,167 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { setActivePinia, createPinia } from 'pinia' +import { usePermissionStore } from '@/stores/permission' + +describe('Permission Store', () => { + beforeEach(() => { + setActivePinia(createPinia()) + localStorage.clear() + }) + + describe('基础功能', () => { + it('应该正确初始化状态', () => { + const store = usePermissionStore() + + expect(store.roles).toEqual([]) + expect(store.permissions).toEqual([]) + expect(store.menus).toEqual([]) + expect(store.loaded).toBe(false) + }) + + it('应该正确设置权限数据', () => { + const store = usePermissionStore() + + store.setPermissionData({ + roles: ['admin'], + permissions: ['user:read', 'user:delete'], + menus: [ + { + id: 1, + name: '仪表盘', + path: '/dashboard', + icon: 'Odometer', + sort: 1 + } + ] + }) + + expect(store.roles).toEqual(['admin']) + expect(store.permissions).toEqual(['user:read', 'user:delete']) + expect(store.menus).toHaveLength(1) + expect(store.loaded).toBe(true) + }) + + it('应该正确清除权限数据', () => { + const store = usePermissionStore() + + store.setPermissionData({ + roles: ['admin'], + permissions: ['user:read'], + menus: [] + }) + + store.clearPermissionData() + + expect(store.roles).toEqual([]) + expect(store.permissions).toEqual([]) + expect(store.menus).toEqual([]) + expect(store.loaded).toBe(false) + }) + }) + + describe('权限检查方法', () => { + it('应该正确检查单个角色', () => { + const store = usePermissionStore() + store.setPermissionData({ + roles: ['admin', 'user'], + permissions: [], + menus: [] + }) + + expect(store.hasRole('admin')).toBe(true) + expect(store.hasRole('manager')).toBe(false) + }) + + it('应该正确检查多个角色(满足任一即可)', () => { + const store = usePermissionStore() + store.setPermissionData({ + roles: ['user'], + permissions: [], + menus: [] + }) + + expect(store.hasRole(['admin', 'user'])).toBe(true) + expect(store.hasRole(['admin', 'manager'])).toBe(false) + }) + + it('应该正确检查单个权限', () => { + const store = usePermissionStore() + store.setPermissionData({ + roles: [], + permissions: ['user:read', 'user:delete'], + menus: [] + }) + + expect(store.hasPermission('user:read')).toBe(true) + expect(store.hasPermission('user:create')).toBe(false) + }) + + it('应该正确检查多个权限(满足任一即可)', () => { + const store = usePermissionStore() + store.setPermissionData({ + roles: [], + permissions: ['user:read'], + menus: [] + }) + + expect(store.hasPermission(['user:read', 'user:create'])).toBe(true) + expect(store.hasPermission(['user:create', 'user:update'])).toBe(false) + }) + }) + + describe('localStorage 持久化', () => { + it('应该正确保存到 localStorage', () => { + const store = usePermissionStore() + + store.setPermissionData({ + roles: ['admin'], + permissions: ['user:read'], + menus: [ + { + id: 1, + name: '仪表盘', + path: '/dashboard', + sort: 1 + } + ] + }) + + const stored = localStorage.getItem('permission') + expect(stored).toBeTruthy() + + const data = JSON.parse(stored!) + expect(data.roles).toEqual(['admin']) + expect(data.permissions).toEqual(['user:read']) + expect(data.menus).toHaveLength(1) + }) + + it('应该正确从 localStorage 恢复', () => { + localStorage.setItem('permission', JSON.stringify({ + roles: ['user'], + permissions: ['user:read:self'], + menus: [] + })) + + const store = usePermissionStore() + store.initFromStorage() + + expect(store.roles).toEqual(['user']) + expect(store.permissions).toEqual(['user:read:self']) + expect(store.loaded).toBe(true) + }) + + it('清除数据时应该同时清除 localStorage', () => { + const store = usePermissionStore() + + store.setPermissionData({ + roles: ['admin'], + permissions: [], + menus: [] + }) + + store.clearPermissionData() + + expect(localStorage.getItem('permission')).toBeNull() + }) + }) +}) diff --git a/novalon-manage-web/src/stores/permission.ts b/novalon-manage-web/src/stores/permission.ts new file mode 100644 index 0000000..169a116 --- /dev/null +++ b/novalon-manage-web/src/stores/permission.ts @@ -0,0 +1,91 @@ +import { defineStore } from 'pinia' + +export interface MenuItem { + id: number + name: string + path: string + icon?: string + parentId?: number + sort: number + children?: MenuItem[] +} + +interface PermissionState { + roles: string[] + permissions: string[] + menus: MenuItem[] + loaded: boolean +} + +export const usePermissionStore = defineStore('permission', { + state: (): PermissionState => ({ + roles: [], + permissions: [], + menus: [], + loaded: false + }), + + getters: { + hasRole: (state) => (role: string | string[]) => { + if (Array.isArray(role)) { + return role.some(r => state.roles.includes(r)) + } + return state.roles.includes(role) + }, + + hasPermission: (state) => (permission: string | string[]) => { + if (Array.isArray(permission)) { + return permission.some(p => state.permissions.includes(p)) + } + return state.permissions.includes(permission) + } + }, + + actions: { + setPermissionData(data: { + roles: string[] + permissions: string[] + menus: MenuItem[] + }) { + this.roles = data.roles + this.permissions = data.permissions + this.menus = data.menus + this.loaded = true + + this.saveToStorage() + }, + + clearPermissionData() { + this.roles = [] + this.permissions = [] + this.menus = [] + this.loaded = false + + localStorage.removeItem('permission') + }, + + saveToStorage() { + const data = { + roles: this.roles, + permissions: this.permissions, + menus: this.menus + } + localStorage.setItem('permission', JSON.stringify(data)) + }, + + initFromStorage() { + const stored = localStorage.getItem('permission') + if (stored) { + try { + const data = JSON.parse(stored) + this.roles = data.roles || [] + this.permissions = data.permissions || [] + this.menus = data.menus || [] + this.loaded = true + } catch (error) { + console.error('从 localStorage 恢复权限数据失败:', error) + } + } + } + } +}) diff --git a/novalon-manage-web/src/test/setup.ts b/novalon-manage-web/src/test/setup.ts index 8beb362..acb4577 100644 --- a/novalon-manage-web/src/test/setup.ts +++ b/novalon-manage-web/src/test/setup.ts @@ -20,20 +20,42 @@ Object.defineProperty(window, 'matchMedia', { })), }) +const localStorageMock = (() => { + let store: Record = {} + return { + getItem: vi.fn((key: string) => store[key] || null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value + }), + removeItem: vi.fn((key: string) => { + delete store[key] + }), + clear: vi.fn(() => { + store = {} + }), + } +})() + Object.defineProperty(window, 'localStorage', { - value: { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), - clear: vi.fn(), - }, + value: localStorageMock, }) +const sessionStorageMock = (() => { + let store: Record = {} + return { + getItem: vi.fn((key: string) => store[key] || null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value + }), + removeItem: vi.fn((key: string) => { + delete store[key] + }), + clear: vi.fn(() => { + store = {} + }), + } +})() + Object.defineProperty(window, 'sessionStorage', { - value: { - getItem: vi.fn(), - setItem: vi.fn(), - removeItem: vi.fn(), - clear: vi.fn(), - }, + value: sessionStorageMock, })