import React from 'react'; import '@testing-library/jest-dom'; import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { ToastProvider } from './toast'; import { I18nProvider } from './i18n/I18nProvider'; import GmailReviewPage from './pages/GmailReviewPage'; import { api } from './api'; jest.mock('./api', () => ({ api: { get: jest.fn(), post: jest.fn(), put: jest.fn(), patch: jest.fn(), delete: jest.fn(), interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } }, }, getApiErrorMessage: (error: any, fallback?: string) => fallback || 'Request failed.', })); const mockedApi = api as jest.Mocked; function renderPage() { return render( , ); } describe('GmailReviewPage', () => { beforeEach(() => { mockedApi.get.mockImplementation((url: string) => { if (url === '/gmail/review-candidates') { return Promise.resolve({ data: { queries: ['"Acme" "Backend Developer" newer_than:365d'], candidateThreadCount: 2, autoLinkThreadCount: 1, reviewThreadCount: 1, unmatchedThreadCount: 0, threads: [ { threadId: 'thread-1', subject: 'Backend Developer interview', latestDate: new Date().toISOString(), messageCount: 2, routing: 'review', hasImportedMessages: false, matchedQueries: ['"Acme" "Backend Developer" newer_than:365d'], jobCandidates: [ { jobApplicationId: 42, jobTitle: 'Backend Developer', companyName: 'Acme', score: 24, confidence: 'medium', reasons: [{ label: 'company', value: 'Acme', points: 18 }] }, ], messages: [], }, ], }, } as any); } if (url === '/gmail/suggested-jobs') { return Promise.resolve({ data: { count: 0, items: [] } } as any); } return Promise.resolve({ data: {} } as any); }); }); afterEach(() => { jest.clearAllMocks(); }); test('renders Gmail review queue summary and candidate threads', async () => { renderPage(); expect(await screen.findByText(/gmail review queue/i)).toBeInTheDocument(); expect(await screen.findByText(/2 candidate threads/i)).toBeInTheDocument(); expect(screen.getByText(/backend developer interview/i)).toBeInTheDocument(); expect(screen.getByText(/acme • backend developer \(24\)/i)).toBeInTheDocument(); await waitFor(() => { expect(mockedApi.get).toHaveBeenCalledWith('/gmail/review-candidates'); }); }); test('persists a review decision for the top job', async () => { renderPage(); fireEvent.click(await screen.findByRole('button', { name: /link top job/i })); await waitFor(() => { expect(mockedApi.post).toHaveBeenCalledWith('/gmail/review-decision', { threadId: 'thread-1', decision: 'linked', jobApplicationId: 42, note: null, }); }); }); });