From c51a99051f5fa88c482a34a71b1af6b01379bbde Mon Sep 17 00:00:00 2001 From: oabrivard Date: Fri, 27 Mar 2026 14:32:39 +0100 Subject: [PATCH] fix: update failing frontend tests for multi-theme migration Settings tests now reference fields that exist in the current UI (max articles per source instead of removed theme field). Generate tests now mock the themes API and use current pipeline step names. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/__tests__/pages/generate.test.tsx | 40 ++++++++++++++----- .../src/__tests__/pages/settings.test.tsx | 29 +++++--------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/frontend/src/__tests__/pages/generate.test.tsx b/frontend/src/__tests__/pages/generate.test.tsx index 4da4885..3fe8962 100644 --- a/frontend/src/__tests__/pages/generate.test.tsx +++ b/frontend/src/__tests__/pages/generate.test.tsx @@ -28,6 +28,15 @@ vi.mock('~/api/config', () => ({ }, })); +vi.mock('~/api/themes', () => ({ + themesApi: { + list: vi.fn(), + create: vi.fn(), + update: vi.fn(), + remove: vi.fn(), + }, +})); + // Mock the SSE connection utility vi.mock('~/utils/sse', () => ({ createSSEConnection: vi.fn(), @@ -36,6 +45,7 @@ vi.mock('~/utils/sse', () => ({ import { synthesesApi } from '~/api/syntheses'; import { settingsApi } from '~/api/settings'; import { configApi } from '~/api/config'; +import { themesApi } from '~/api/themes'; import { createSSEConnection } from '~/utils/sse'; import GenerateSynthesis from '~/pages/GenerateSynthesis'; @@ -44,6 +54,11 @@ const mockedListProviders = vi.mocked(configApi.listProviders); const mockedGenerate = vi.mocked(synthesesApi.generate); const mockedProgressUrl = vi.mocked(synthesesApi.progressUrl); const mockedCreateSSE = vi.mocked(createSSEConnection); +const mockedListThemes = vi.mocked(themesApi.list); + +const MOCK_THEMES = [ + { id: 'theme-1', name: 'IA', theme: 'Intelligence Artificielle', categories: ['Cat1'], max_items_per_category: 5, max_age_days: 7, summary_length: 2, created_at: '2026-03-01T00:00:00Z', updated_at: '2026-03-01T00:00:00Z' }, +]; afterEach(() => { vi.clearAllMocks(); @@ -53,6 +68,7 @@ describe('GenerateSynthesis Page', () => { it('should render launch button with settings info', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); renderWithProviders(() => ); @@ -69,6 +85,7 @@ describe('GenerateSynthesis Page', () => { it('should call POST /syntheses/generate on launch', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); @@ -101,15 +118,16 @@ describe('GenerateSynthesis Page', () => { it('should show progress bar from SSE events', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); const mockSSEConnection = { events: vi.fn(() => [ - { type: 'progress' as const, data: { step: 'search', message: 'Recherche...', percent: 25 } }, + { type: 'progress' as const, data: { step: 'sources', message: 'Analyse des sources...', percent: 25 } }, ]), status: vi.fn(() => 'connected' as const), - latestProgress: vi.fn(() => ({ step: 'search', message: 'Recherche...', percent: 25 })), + latestProgress: vi.fn(() => ({ step: 'sources', message: 'Analyse des sources...', percent: 25 })), completedSynthesisId: vi.fn(() => null), errorMessage: vi.fn(() => null), close: vi.fn(), @@ -128,22 +146,23 @@ describe('GenerateSynthesis Page', () => { await waitFor(() => { expect(screen.getByText('25%')).toBeInTheDocument(); }); - expect(screen.getByText('Recherche...')).toBeInTheDocument(); + expect(screen.getByText('Analyse des sources...')).toBeInTheDocument(); }); it('should render step checklist with correct states', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); const mockSSEConnection = { events: vi.fn(() => [ - { type: 'progress' as const, data: { step: 'search', message: 'Recherche...', percent: 25 } }, - { type: 'progress' as const, data: { step: 'scraping', message: 'Verification...', percent: 50 } }, + { type: 'progress' as const, data: { step: 'sources', message: 'Analyse...', percent: 25 } }, + { type: 'progress' as const, data: { step: 'websearch', message: 'Recherche web...', percent: 50 } }, ]), status: vi.fn(() => 'connected' as const), - latestProgress: vi.fn(() => ({ step: 'scraping', message: 'Verification...', percent: 50 })), + latestProgress: vi.fn(() => ({ step: 'websearch', message: 'Recherche web...', percent: 50 })), completedSynthesisId: vi.fn(() => null), errorMessage: vi.fn(() => null), close: vi.fn(), @@ -161,14 +180,11 @@ describe('GenerateSynthesis Page', () => { await waitFor(() => { expect( - screen.getByText("Recherche d'actualites"), + screen.getByText('Sources personnalisees'), ).toBeInTheDocument(); }); expect( - screen.getByText('Verification des sources'), - ).toBeInTheDocument(); - expect( - screen.getByText('Redaction des resumes'), + screen.getByText('Recherche web'), ).toBeInTheDocument(); expect(screen.getByText('Sauvegarde')).toBeInTheDocument(); }); @@ -176,6 +192,7 @@ describe('GenerateSynthesis Page', () => { it('should show retry button on error', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); @@ -209,6 +226,7 @@ describe('GenerateSynthesis Page', () => { it('should show completion message on success', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(MOCK_PROVIDER_CONFIGS); + mockedListThemes.mockResolvedValue(MOCK_THEMES); mockedGenerate.mockResolvedValue({ job_id: 'job-123' }); mockedProgressUrl.mockReturnValue('/api/v1/syntheses/generate/job-123/progress'); diff --git a/frontend/src/__tests__/pages/settings.test.tsx b/frontend/src/__tests__/pages/settings.test.tsx index d8fee71..cebaccc 100644 --- a/frontend/src/__tests__/pages/settings.test.tsx +++ b/frontend/src/__tests__/pages/settings.test.tsx @@ -69,10 +69,10 @@ describe('Settings Page', () => { renderWithProviders(() => ); await waitFor(() => { - const themeInput = screen.getByLabelText( - 'Theme de la recherche', + const maxArticlesInput = screen.getByLabelText( + 'Articles max par source', ) as HTMLInputElement; - expect(themeInput.value).toBe('Intelligence Artificielle'); + expect(maxArticlesInput.value).toBe('3'); }); }); @@ -86,7 +86,7 @@ describe('Settings Page', () => { await waitFor(() => { expect( - screen.getByLabelText('Theme de la recherche'), + screen.getByLabelText('Articles max par source'), ).toBeInTheDocument(); }); @@ -113,7 +113,8 @@ describe('Settings Page', () => { expect(screen.getAllByText('OpenAI').length).toBeGreaterThanOrEqual(1); // Verify the provider select has the options - const providerSelect = screen.getByLabelText("Fournisseur d'IA") as HTMLSelectElement; + const providerSelect = document.getElementById('aiProvider') as HTMLSelectElement; + expect(providerSelect).toBeTruthy(); const options = providerSelect.querySelectorAll('option'); const optionTexts = Array.from(options).map((o) => o.textContent); expect(optionTexts).toContain('Google Gemini'); @@ -138,9 +139,7 @@ describe('Settings Page', () => { expect(geminiTexts).toContain('Gemini 2.5 Pro'); // Change provider to OpenAI - const providerSelect = screen.getByLabelText( - "Fournisseur d'IA", - ) as HTMLSelectElement; + const providerSelect = document.getElementById('aiProvider') as HTMLSelectElement; fireEvent.change(providerSelect, { target: { value: 'openai' } }); await waitFor(() => { @@ -291,36 +290,30 @@ describe('Settings Page', () => { }); }); - it('should show validation error for empty theme', async () => { + it('should show validation error from backend on save', async () => { mockedGetSettings.mockResolvedValue(MOCK_SETTINGS); mockedListProviders.mockResolvedValue(mockProvidersWithOpenAI); mockedListKeys.mockResolvedValue([]); // Simulate backend returning a validation error mockedUpdateSettings.mockRejectedValue({ status: 422, - message: 'Theme is required', + message: 'Validation error', }); renderWithProviders(() => ); await waitFor(() => { expect( - screen.getByLabelText('Theme de la recherche'), + screen.getByLabelText('Articles max par source'), ).toBeInTheDocument(); }); - // Clear the theme field - const themeInput = screen.getByLabelText( - 'Theme de la recherche', - ) as HTMLInputElement; - fireEvent.input(themeInput, { target: { value: '' } }); - // Click save const saveButton = screen.getByText('Enregistrer les parametres'); fireEvent.click(saveButton); await waitFor(() => { - expect(screen.getByText('Theme is required')).toBeInTheDocument(); + expect(screen.getByText('Validation error')).toBeInTheDocument(); }); }); });