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) <noreply@anthropic.com>
master
oabrivard 3 months ago
parent b30d04c6c5
commit c51a99051f

@ -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(() => <GenerateSynthesis />);
@ -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');

@ -69,10 +69,10 @@ describe('Settings Page', () => {
renderWithProviders(() => <Settings />);
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(() => <Settings />);
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();
});
});
});

Loading…
Cancel
Save