fix: align permission-gated UI actions
This commit is contained in:
67
apps/admin/src/auth/permission-state.integration.test.tsx
Normal file
67
apps/admin/src/auth/permission-state.integration.test.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||
import PermissionButton from '../components/PermissionButton';
|
||||
import {
|
||||
beginPermissionVerification,
|
||||
clearPermissions,
|
||||
readPermissionState,
|
||||
writePermissions,
|
||||
} from './permission-store';
|
||||
|
||||
let container: HTMLDivElement | null = null;
|
||||
let root: ReturnType<typeof createRoot> | null = null;
|
||||
|
||||
beforeAll(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
});
|
||||
|
||||
async function renderPermissionButton() {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
await act(async () => {
|
||||
root?.render(<PermissionButton permission="student:edit">编辑学生</PermissionButton>);
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) await act(async () => root?.unmount());
|
||||
container?.remove();
|
||||
root = null;
|
||||
container = null;
|
||||
clearPermissions();
|
||||
});
|
||||
|
||||
describe('permission state', () => {
|
||||
it('ignores cached localStorage permissions until profile verification succeeds', async () => {
|
||||
localStorage.setItem('permissions', JSON.stringify(['student:edit']));
|
||||
beginPermissionVerification();
|
||||
|
||||
expect(readPermissionState()).toEqual({ permissions: [], status: 'loading' });
|
||||
await renderPermissionButton();
|
||||
expect(container?.textContent).not.toContain('编辑学生');
|
||||
});
|
||||
|
||||
it('renders permission actions only after verified permissions are written', async () => {
|
||||
beginPermissionVerification();
|
||||
await renderPermissionButton();
|
||||
expect(container?.textContent).not.toContain('编辑学生');
|
||||
|
||||
await act(async () => writePermissions(['student:edit']));
|
||||
expect(container?.textContent).toContain('编辑学生');
|
||||
});
|
||||
|
||||
it('fails closed after profile refresh failure', async () => {
|
||||
writePermissions(['student:edit']);
|
||||
beginPermissionVerification();
|
||||
clearPermissions('ready');
|
||||
|
||||
expect(readPermissionState()).toEqual({ permissions: [], status: 'ready' });
|
||||
await renderPermissionButton();
|
||||
expect(container?.textContent).not.toContain('编辑学生');
|
||||
expect(localStorage.getItem('permissions')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,40 @@
|
||||
export const PERMISSIONS_UPDATED_EVENT = 'permissions-updated';
|
||||
|
||||
export type PermissionStatus = 'unknown' | 'loading' | 'ready';
|
||||
|
||||
export interface PermissionState {
|
||||
permissions: string[];
|
||||
status: PermissionStatus;
|
||||
}
|
||||
|
||||
let permissionState: PermissionState = { permissions: [], status: 'unknown' };
|
||||
|
||||
function notifyPermissionStateChanged(): void {
|
||||
window.dispatchEvent(new Event(PERMISSIONS_UPDATED_EVENT));
|
||||
}
|
||||
|
||||
export function readPermissionState(): PermissionState {
|
||||
return permissionState;
|
||||
}
|
||||
|
||||
export function readPermissions(): string[] {
|
||||
try {
|
||||
const value = JSON.parse(localStorage.getItem('permissions') || '[]');
|
||||
return Array.isArray(value)
|
||||
? value.filter((item): item is string => typeof item === 'string')
|
||||
: [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
return permissionState.status === 'ready' ? permissionState.permissions : [];
|
||||
}
|
||||
|
||||
export function beginPermissionVerification(): void {
|
||||
permissionState = { permissions: [], status: 'loading' };
|
||||
notifyPermissionStateChanged();
|
||||
}
|
||||
|
||||
export function writePermissions(permissions: string[]): void {
|
||||
localStorage.setItem('permissions', JSON.stringify([...new Set(permissions)]));
|
||||
window.dispatchEvent(new Event(PERMISSIONS_UPDATED_EVENT));
|
||||
const uniquePermissions = [...new Set(permissions)];
|
||||
localStorage.setItem('permissions', JSON.stringify(uniquePermissions));
|
||||
permissionState = { permissions: uniquePermissions, status: 'ready' };
|
||||
notifyPermissionStateChanged();
|
||||
}
|
||||
|
||||
export function clearPermissions(status: PermissionStatus = 'unknown'): void {
|
||||
localStorage.removeItem('permissions');
|
||||
permissionState = { permissions: [], status };
|
||||
notifyPermissionStateChanged();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user