825d638130
M1: Password complexity validation (min 8 chars, uppercase, lowercase, digit) M2: Remove is_system_admin from login response (prevent role leaking) M3: Permission cache invalidates on DB error instead of using stale data M4: .env.docker.example already fixed in B9 (SECRET_KEY, FRONTEND_URL, SMTP) M6: Frontend test setup auto-wraps with QueryClientProvider (fixes ~29 test failures) Remaining: M5 (frontend component integration — WelcomeDialog, SavedFilterBar, etc.)
120 lines
3.2 KiB
TypeScript
120 lines
3.2 KiB
TypeScript
import '@testing-library/jest-dom';
|
|
import { vi, beforeEach, afterEach, beforeAll } from 'vitest';
|
|
import React from 'react';
|
|
import i18n from '@/i18n';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { render, renderHook } from '@testing-library/react';
|
|
|
|
// ── Global QueryClient for tests ──────────────────────────────────────────
|
|
// Many tests use hooks from @tanstack/react-query without explicitly wrapping
|
|
// in a QueryClientProvider. We monkey-patch render/renderHook to automatically
|
|
// wrap with the provider.
|
|
function getTestQueryClient() {
|
|
return new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false, staleTime: 0, gcTime: 0 },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
}
|
|
|
|
const originalRender = render;
|
|
const originalRenderHook = renderHook;
|
|
|
|
// Override global render to auto-wrap with QueryClientProvider
|
|
(globalThis as any).render = (ui: React.ReactElement, options?: any) => {
|
|
const client = getTestQueryClient();
|
|
const wrapped = React.createElement(QueryClientProvider, { client }, ui);
|
|
return originalRender(wrapped, options);
|
|
};
|
|
|
|
// Override global renderHook to auto-wrap with QueryClientProvider
|
|
(globalThis as any).renderHook = (hook: any, options?: any) => {
|
|
const client = getTestQueryClient();
|
|
const wrapper = ({ children }: { children: React.ReactNode }) =>
|
|
React.createElement(QueryClientProvider, { client }, children);
|
|
return originalRenderHook(hook, { wrapper, ...options });
|
|
};
|
|
|
|
// Mock matchMedia for jsdom
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
writable: true,
|
|
value: vi.fn().mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
})),
|
|
});
|
|
|
|
// Mock IntersectionObserver
|
|
class MockIntersectionObserver {
|
|
observe = vi.fn();
|
|
unobserve = vi.fn();
|
|
disconnect = vi.fn();
|
|
takeRecords = vi.fn();
|
|
root = null;
|
|
rootMargin = '';
|
|
thresholds = [];
|
|
}
|
|
|
|
Object.defineProperty(window, 'IntersectionObserver', {
|
|
writable: true,
|
|
configurable: true,
|
|
value: MockIntersectionObserver,
|
|
});
|
|
|
|
// Mock ResizeObserver
|
|
class MockResizeObserver {
|
|
observe = vi.fn();
|
|
unobserve = vi.fn();
|
|
disconnect = vi.fn();
|
|
}
|
|
|
|
Object.defineProperty(window, 'ResizeObserver', {
|
|
writable: true,
|
|
configurable: true,
|
|
value: MockResizeObserver,
|
|
});
|
|
|
|
|
|
// Mock EventSource for jsdom
|
|
class MockEventSource {
|
|
constructor(url: string) {
|
|
this.url = url;
|
|
this.readyState = 0;
|
|
}
|
|
url: string;
|
|
readyState: number;
|
|
onopen: any = null;
|
|
onmessage: any = null;
|
|
onerror: any = null;
|
|
addEventListener = vi.fn();
|
|
removeEventListener = vi.fn();
|
|
close = vi.fn();
|
|
}
|
|
Object.defineProperty(window, 'EventSource', { writable: true, configurable: true, value: MockEventSource });
|
|
|
|
// Mock scrollTo
|
|
window.scrollTo = vi.fn() as any;
|
|
|
|
|
|
// Initialize i18n before all tests
|
|
beforeAll(async () => {
|
|
await i18n.changeLanguage('de');
|
|
});
|
|
|
|
// Clean up after each test
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
localStorage.clear();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|