Items 1-5 of the agreed frontend upgrades. The one-time Prettier reformatting of files not touched here is in the FOLLOW-UP commit so this one stays reviewable. Testing (vitest + @testing-library) - vitest 3 + @testing-library/react + jsdom. test / test:watch / test:ui scripts; setup.ts stubs matchMedia + ResizeObserver for AntD under jsdom. - 15 tests, 4 files: RequireRole, LoginPage, measurements API client, extractApiError. Route-level code splitting - Every page converted to React.lazy() behind a Suspense boundary in App.tsx. Main bundle 1490 KB -> 714 KB; ECharts isolated in the DashboardPage chunk and only fetched when a dashboard is opened. Global 401 handling - api/client.ts response interceptor redirects any 401 from a non-/auth path to /login?returnTo=<current>; LoginPage honours the param. - Shared extractApiError replaces four near-identical per-page copies (UsersPage, AdminCustomersPage, MyProfilePage, TariffDrawer). Query devtools - @tanstack/react-query-devtools, lazy + import.meta.env.DEV-gated so a production build dead-code-eliminates it. Verified: no devtools code in any shipped .js chunk. ESLint + Prettier - ESLint 9 flat config (js + typescript-eslint + react-hooks + react-refresh, eslint-config-prettier last). Prettier config tuned to the existing style. lint / lint:fix / format / format:check scripts + lint-staged config. - Fixed one real react-hooks/exhaustive-deps warning (useMemo on DashboardsPage's `dashboards`). - End state: eslint 0 problems, tsc clean, 15/15 tests pass. Pre-commit hook (lint-staged) is installed locally in .git/hooks/ -- not version-controlled, see follow-up note. Husky was deliberately not used: it needs a core.hooksPath change and the monorepo layout makes it awkward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.4 KiB
TypeScript
48 lines
1.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { RequireRole } from './RequireRole';
|
|
import * as useAuthModule from '../hooks/useAuth';
|
|
|
|
function mockAuth(user: { roles: string[] } | null) {
|
|
vi.spyOn(useAuthModule, 'useAuth').mockReturnValue({
|
|
user: user as never,
|
|
loading: false,
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
setUser: vi.fn(),
|
|
});
|
|
}
|
|
|
|
describe('<RequireRole>', () => {
|
|
it('renders children when the user has the required role', () => {
|
|
mockAuth({ roles: ['Admin'] });
|
|
render(
|
|
<RequireRole role="Admin">
|
|
<div>Admin-only content</div>
|
|
</RequireRole>,
|
|
);
|
|
expect(screen.getByText('Admin-only content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders a 403 when the user is missing the required role', () => {
|
|
mockAuth({ roles: ['User'] });
|
|
render(
|
|
<RequireRole role="Admin">
|
|
<div>Admin-only content</div>
|
|
</RequireRole>,
|
|
);
|
|
expect(screen.queryByText('Admin-only content')).not.toBeInTheDocument();
|
|
expect(screen.getByText('403')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders a 403 when the user is null (unauthenticated edge)', () => {
|
|
mockAuth(null);
|
|
render(
|
|
<RequireRole role="Admin">
|
|
<div>Admin-only content</div>
|
|
</RequireRole>,
|
|
);
|
|
expect(screen.getByText('403')).toBeInTheDocument();
|
|
});
|
|
});
|