0ec8502fd4
- Created frontend/src/api/reports.ts: React Query hooks for templates, presets, generate - Created frontend/src/pages/Reports.tsx: 3-column layout (template list, editor, generate) - Preset quick-action buttons with format selection (PDF/Print/CSV/Excel) - Template editor with Jinja2 code textarea, name, output format selector - JSON data input for report parameters - Download history tracking - Route /reports registered in index.tsx (lazy-loaded) - i18n keys added to de.json and en.json (reports section + nav.reports) - 5 frontend tests: page render, template list, new template, select template, download history - TSC: 0 new errors (2 pre-existing Dms.tsx errors only)
139 lines
4.8 KiB
TypeScript
139 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
import { ReportsPage } from '../Reports';
|
|
|
|
// Mock the API hooks
|
|
vi.mock('@/api/reports', () => ({
|
|
useReportTemplates: vi.fn(() => ({
|
|
data: [
|
|
{ id: 'tpl-1', name: 'My Report', description: 'Test', template_type: 'jinja2', content: '<html></html>', output_format: 'pdf', created_by: 'user-1' },
|
|
{ id: 'tpl-2', name: 'CSV Report', description: 'Test CSV', template_type: 'jinja2', content: 'name,email', output_format: 'csv', created_by: 'user-1' },
|
|
],
|
|
isLoading: false,
|
|
})),
|
|
useReportPresets: vi.fn(() => ({
|
|
data: [
|
|
{ key: 'contact_list', name: 'Kontaktliste', description: 'Liste aller Kontakte', icon: 'Users', output_formats: ['pdf', 'print', 'csv', 'excel'] },
|
|
{ key: 'company_list', name: 'Firmenliste', description: 'Liste aller Firmen', icon: 'Building2', output_formats: ['pdf', 'print', 'csv', 'excel'] },
|
|
],
|
|
})),
|
|
useCreateReportTemplate: vi.fn(() => ({
|
|
mutateAsync: vi.fn().mockResolvedValue({}),
|
|
isPending: false,
|
|
})),
|
|
useUpdateReportTemplate: vi.fn(() => ({
|
|
mutateAsync: vi.fn().mockResolvedValue({}),
|
|
isPending: false,
|
|
})),
|
|
useDeleteReportTemplate: vi.fn(() => ({
|
|
mutateAsync: vi.fn().mockResolvedValue({}),
|
|
isPending: false,
|
|
})),
|
|
useGenerateReport: vi.fn(() => ({
|
|
mutateAsync: vi.fn().mockResolvedValue({ data: new Blob(['test'], { type: 'application/pdf' }) }),
|
|
isPending: false,
|
|
})),
|
|
useGeneratePresetReport: vi.fn(() => ({
|
|
mutateAsync: vi.fn().mockResolvedValue({ data: new Blob(['test'], { type: 'application/pdf' }) }),
|
|
isPending: false,
|
|
})),
|
|
downloadBlob: vi.fn(),
|
|
}));
|
|
|
|
// Mock i18next
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string, fallback?: string) => fallback || key,
|
|
i18n: { language: 'de' },
|
|
}),
|
|
}));
|
|
|
|
// Mock toast
|
|
vi.mock('@/components/ui/Toast', () => ({
|
|
useToast: () => ({
|
|
toast: { success: vi.fn(), error: vi.fn() },
|
|
}),
|
|
}));
|
|
|
|
// Mock lucide-react icons
|
|
vi.mock('lucide-react', () => {
|
|
const Icon = ({ className }: { className?: string }) => <div className={className} data-testid="icon" />;
|
|
return {
|
|
BarChart3: Icon,
|
|
Building2: Icon,
|
|
Calendar: Icon,
|
|
CalendarDays: Icon,
|
|
Download: Icon,
|
|
FileText: Icon,
|
|
Loader2: Icon,
|
|
Plus: Icon,
|
|
Printer: Icon,
|
|
Save: Icon,
|
|
ShieldCheck: Icon,
|
|
Trash2: Icon,
|
|
Users: Icon,
|
|
};
|
|
});
|
|
|
|
// Helper to render with providers
|
|
function renderWithProviders(ui: React.ReactElement) {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
|
});
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<BrowserRouter>
|
|
{ui}
|
|
</BrowserRouter>
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
describe('ReportsPage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders the reports page with title and preset quick actions', () => {
|
|
renderWithProviders(<ReportsPage />);
|
|
expect(screen.getByTestId('reports-page')).toBeInTheDocument();
|
|
expect(screen.getByTestId('preset-quick-actions')).toBeInTheDocument();
|
|
// Should have preset buttons for contact_list and company_list
|
|
expect(screen.getByTestId('preset-contact_list-pdf')).toBeInTheDocument();
|
|
expect(screen.getByTestId('preset-company_list-pdf')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays templates in the template list', () => {
|
|
renderWithProviders(<ReportsPage />);
|
|
expect(screen.getByTestId('template-list-panel')).toBeInTheDocument();
|
|
expect(screen.getByText('My Report')).toBeInTheDocument();
|
|
expect(screen.getByText('CSV Report')).toBeInTheDocument();
|
|
});
|
|
|
|
it('clicking new template button shows editor with default content', () => {
|
|
renderWithProviders(<ReportsPage />);
|
|
const newBtn = screen.getByTestId('btn-new-template');
|
|
fireEvent.click(newBtn);
|
|
// Editor should be visible with template name input
|
|
expect(screen.getByTestId('input-template-name')).toBeInTheDocument();
|
|
expect(screen.getByTestId('textarea-template-content')).toBeInTheDocument();
|
|
expect(screen.getByTestId('btn-save-template')).toBeInTheDocument();
|
|
});
|
|
|
|
it('selecting a template loads it into the editor', () => {
|
|
renderWithProviders(<ReportsPage />);
|
|
// Click on the first template
|
|
fireEvent.click(screen.getByText('My Report'));
|
|
// Editor should show the template name
|
|
const nameInput = screen.getByTestId('input-template-name') as HTMLInputElement;
|
|
expect(nameInput.value).toBe('My Report');
|
|
});
|
|
|
|
it('shows download history section', () => {
|
|
renderWithProviders(<ReportsPage />);
|
|
expect(screen.getByTestId('download-history')).toBeInTheDocument();
|
|
});
|
|
});
|