import { describe, test, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BulkRetryModal } from '../BulkRetryModal'; // Mock the API const mockBulkRetryOcr = vi.fn(); vi.mock('../../services/api', () => ({ documentService: { bulkRetryOcr: mockBulkRetryOcr, }, })); describe('BulkRetryModal', () => { const mockProps = { open: true, onClose: vi.fn(), onSuccess: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); mockBulkRetryOcr.mockResolvedValue({ data: { success: true, queued_count: 5, matched_count: 5, documents: [], estimated_total_time_minutes: 2.5, message: 'Operation completed successfully', }, }); }); test('renders modal with title and form elements', () => { render(); expect(screen.getByText('Bulk OCR Retry')).toBeInTheDocument(); expect(screen.getByText('Retry Mode')).toBeInTheDocument(); expect(screen.getByText('Retry all failed OCR documents')).toBeInTheDocument(); expect(screen.getByText('Retry documents matching criteria')).toBeInTheDocument(); }); test('closes modal when close button is clicked', async () => { const user = userEvent.setup(); render(); const closeButton = screen.getByText('Cancel'); await user.click(closeButton); expect(mockProps.onClose).toHaveBeenCalled(); }); test('shows preview by default', () => { render(); const previewButton = screen.getByText('Preview'); expect(previewButton).toBeInTheDocument(); }); test('allows switching to filter mode', async () => { const user = userEvent.setup(); render(); const filterRadio = screen.getByLabelText('Retry documents matching criteria'); await user.click(filterRadio); // Should show the accordion with filter criteria expect(screen.getByText('Filter Criteria')).toBeInTheDocument(); // Expand the accordion to see filter options const filterAccordion = screen.getByText('Filter Criteria'); await user.click(filterAccordion); expect(screen.getByText('File Types')).toBeInTheDocument(); expect(screen.getByText('Failure Reasons')).toBeInTheDocument(); expect(screen.getByText('Maximum File Size')).toBeInTheDocument(); }); test('can select MIME types in filter mode', async () => { const user = userEvent.setup(); render(); // Switch to filter mode const filterRadio = screen.getByLabelText('Retry documents matching criteria'); await user.click(filterRadio); // Expand the accordion to see filter options const filterAccordion = screen.getByText('Filter Criteria'); await user.click(filterAccordion); // Should show MIME type chips const pdfChip = screen.getByText('PDF'); expect(pdfChip).toBeInTheDocument(); // Click on the PDF chip to select it await user.click(pdfChip); // The chip should now be selected (filled variant) expect(pdfChip.closest('[data-testid], .MuiChip-root')).toBeInTheDocument(); }); test('can set priority override', async () => { const user = userEvent.setup(); render(); // Expand the Advanced Options accordion const advancedAccordion = screen.getByText('Advanced Options'); await user.click(advancedAccordion); // Enable priority override const priorityCheckbox = screen.getByLabelText('Override processing priority'); await user.click(priorityCheckbox); // Now the slider should be visible const prioritySlider = screen.getByRole('slider'); fireEvent.change(prioritySlider, { target: { value: 15 } }); expect(prioritySlider).toHaveValue('15'); }); test('executes preview request successfully', async () => { const user = userEvent.setup(); mockBulkRetryOcr.mockResolvedValue({ data: { success: true, queued_count: 0, matched_count: 3, documents: [ { id: '1', filename: 'doc1.pdf', file_size: 1024, mime_type: 'application/pdf' }, { id: '2', filename: 'doc2.pdf', file_size: 2048, mime_type: 'application/pdf' }, ], estimated_total_time_minutes: 1.5, }, }); render(); const previewButton = screen.getByText('Preview'); await user.click(previewButton); await waitFor(() => { expect(screen.getByText('Preview Results')).toBeInTheDocument(); }); expect(screen.getByText('Documents matched:')).toBeInTheDocument(); expect(screen.getByText('Estimated processing time:')).toBeInTheDocument(); }); test('executes actual retry request successfully', async () => { const user = userEvent.setup(); // Set up different responses for preview and execute mockBulkRetryOcr .mockResolvedValueOnce({ data: { success: true, queued_count: 0, matched_count: 5, documents: [], estimated_total_time_minutes: 2.5, message: 'Preview completed', }, }) .mockResolvedValueOnce({ data: { success: true, queued_count: 5, matched_count: 5, documents: [], estimated_total_time_minutes: 2.5, message: 'Operation completed successfully', }, }); render(); // First do a preview const previewButton = screen.getByText('Preview'); await user.click(previewButton); await waitFor(() => { expect(screen.getByText('Preview Results')).toBeInTheDocument(); }); // Now execute the retry const executeButton = screen.getByText('Retry 5 Documents'); await user.click(executeButton); await waitFor(() => { expect(mockBulkRetryOcr).toHaveBeenCalledTimes(2); }); expect(mockBulkRetryOcr).toHaveBeenNthCalledWith(1, expect.objectContaining({ mode: 'all', preview_only: true, }) ); expect(mockBulkRetryOcr).toHaveBeenNthCalledWith(2, expect.objectContaining({ mode: 'all', preview_only: false, }) ); expect(mockProps.onSuccess).toHaveBeenCalled(); expect(mockProps.onClose).toHaveBeenCalled(); }); test('handles API errors gracefully', async () => { const user = userEvent.setup(); mockBulkRetryOcr.mockRejectedValue(new Error('API Error')); render(); const previewButton = screen.getByText('Preview'); await user.click(previewButton); await waitFor(() => { expect(screen.getByText('Failed to preview retry operation')).toBeInTheDocument(); }); }); test('can set document limit in filter mode', async () => { const user = userEvent.setup(); render(); // Switch to filter mode const filterRadio = screen.getByLabelText('Retry documents matching criteria'); await user.click(filterRadio); // Expand the accordion to see filter options const filterAccordion = screen.getByText('Filter Criteria'); await user.click(filterAccordion); // Find and set the document limit const limitInput = screen.getByLabelText('Maximum Documents to Retry'); await user.clear(limitInput); await user.type(limitInput, '100'); expect(limitInput).toHaveValue(100); }); test('shows loading state during API calls', async () => { const user = userEvent.setup(); // Make the API call never resolve mockBulkRetryOcr.mockImplementation(() => new Promise(() => {})); render(); const previewButton = screen.getByText('Preview'); await user.click(previewButton); // Should show loading state await waitFor(() => { expect(screen.getByRole('progressbar')).toBeInTheDocument(); }); }); test('resets form when modal is closed and reopened', () => { const { rerender } = render(); // Reopen the modal rerender(); // Should be back to default state expect(screen.getByLabelText('Retry all failed OCR documents')).toBeChecked(); // Note: slider is not visible by default as it's in an accordion }); test('does not render when modal is closed', () => { render(); expect(screen.queryByText('Bulk OCR Retry')).not.toBeInTheDocument(); }); });