feat(tests): create unit tests for retry
This commit is contained in:
parent
4e8c96a518
commit
d6c0f412b5
|
|
@ -365,13 +365,13 @@ export const BulkRetryModal: React.FC<BulkRetryModalProps> = ({
|
|||
{formatDuration(previewResult.estimated_total_time_minutes)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{previewResult.documents.length > 0 && (
|
||||
{previewResult.documents && previewResult.documents.length > 0 && (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Sample Documents:
|
||||
</Typography>
|
||||
<Box maxHeight={200} overflow="auto">
|
||||
{previewResult.documents.slice(0, 10).map((doc) => (
|
||||
{(previewResult.documents || []).slice(0, 10).map((doc) => (
|
||||
<Box key={doc.id} py={0.5}>
|
||||
<Typography variant="body2">
|
||||
{doc.filename} ({formatFileSize(doc.file_size)})
|
||||
|
|
@ -385,7 +385,7 @@ export const BulkRetryModal: React.FC<BulkRetryModalProps> = ({
|
|||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
{previewResult.documents.length > 10 && (
|
||||
{previewResult.documents && previewResult.documents.length > 10 && (
|
||||
<Typography variant="body2" color="text.secondary" mt={1}>
|
||||
... and {previewResult.documents.length - 10} more documents
|
||||
</Typography>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,254 @@
|
|||
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();
|
||||
const mockDocumentService = {
|
||||
bulkRetryOcr: mockBulkRetryOcr,
|
||||
};
|
||||
const mockApi = {
|
||||
bulkRetryOcr: mockBulkRetryOcr,
|
||||
};
|
||||
|
||||
vi.mock('../../services/api', () => ({
|
||||
default: mockApi,
|
||||
documentService: mockDocumentService,
|
||||
}));
|
||||
|
||||
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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
const closeButton = screen.getByText('Cancel');
|
||||
await user.click(closeButton);
|
||||
|
||||
expect(mockProps.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('shows preview by default', () => {
|
||||
render(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
const previewButton = screen.getByText('Preview');
|
||||
expect(previewButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('allows switching to filter mode', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
// 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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
// 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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
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();
|
||||
render(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
// First do a preview
|
||||
const previewButton = screen.getByText('Preview');
|
||||
await user.click(previewButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Retry \d+ Documents/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Now execute the retry
|
||||
const executeButton = screen.getByText(/Retry \d+ Documents/);
|
||||
await user.click(executeButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalledWith({
|
||||
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(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
const previewButton = screen.getByText('Preview');
|
||||
await user.click(previewButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Failed to preview retry/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('can set document limit in filter mode', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
// 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 take time
|
||||
mockBulkRetryOcr.mockImplementation(() => new Promise(resolve =>
|
||||
setTimeout(() => resolve({
|
||||
data: { success: true, queued_count: 0, matched_count: 0, documents: [] }
|
||||
}), 100)
|
||||
));
|
||||
|
||||
render(<BulkRetryModal {...mockProps} />);
|
||||
|
||||
const previewButton = screen.getByText('Preview');
|
||||
await user.click(previewButton);
|
||||
|
||||
// Should show loading state
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
// The button should remain as "Preview" during loading, not change text
|
||||
expect(screen.getByText('Preview')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('resets form when modal is closed and reopened', () => {
|
||||
const { rerender } = render(<BulkRetryModal {...mockProps} open={false} />);
|
||||
|
||||
// Reopen the modal
|
||||
rerender(<BulkRetryModal {...mockProps} open={true} />);
|
||||
|
||||
// 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(<BulkRetryModal {...mockProps} open={false} />);
|
||||
|
||||
expect(screen.queryByText('Bulk OCR Retry')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { RetryHistoryModal } from '../RetryHistoryModal';
|
||||
|
||||
// Mock the API
|
||||
const mockGetDocumentRetryHistory = vi.fn();
|
||||
|
||||
const mockDocumentService = {
|
||||
getDocumentRetryHistory: mockGetDocumentRetryHistory,
|
||||
};
|
||||
|
||||
vi.mock('../../services/api', () => ({
|
||||
documentService: mockDocumentService,
|
||||
}));
|
||||
|
||||
describe('RetryHistoryModal', () => {
|
||||
const mockProps = {
|
||||
open: true,
|
||||
onClose: vi.fn(),
|
||||
documentId: 'test-doc-123',
|
||||
documentName: 'test-document.pdf',
|
||||
};
|
||||
|
||||
const sampleRetryHistory = [
|
||||
{
|
||||
id: 'retry-1',
|
||||
retry_reason: 'bulk_retry_all',
|
||||
previous_status: 'failed',
|
||||
previous_failure_reason: 'low_confidence',
|
||||
previous_error: 'OCR confidence too low: 45%',
|
||||
priority: 15,
|
||||
queue_id: 'queue-1',
|
||||
created_at: '2024-01-15T10:30:00Z',
|
||||
},
|
||||
{
|
||||
id: 'retry-2',
|
||||
retry_reason: 'manual_retry',
|
||||
previous_status: 'failed',
|
||||
previous_failure_reason: 'image_quality',
|
||||
previous_error: 'Image resolution too low',
|
||||
priority: 12,
|
||||
queue_id: 'queue-2',
|
||||
created_at: '2024-01-14T14:20:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: sampleRetryHistory,
|
||||
total_retries: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('renders modal with title and document name', () => {
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
expect(screen.getByText('OCR Retry History')).toBeInTheDocument();
|
||||
expect(screen.getByText('test-document.pdf')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('does not render when modal is closed', () => {
|
||||
render(<RetryHistoryModal {...mockProps} open={false} />);
|
||||
|
||||
expect(screen.queryByText('OCR Retry History')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('loads and displays retry history on mount', async () => {
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Bulk Retry (All Documents)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('Manual Retry')).toBeInTheDocument();
|
||||
expect(screen.getByText('Low Confidence')).toBeInTheDocument();
|
||||
expect(screen.getByText('Image Quality')).toBeInTheDocument();
|
||||
expect(screen.getByText('High')).toBeInTheDocument(); // Priority 15
|
||||
expect(screen.getByText('Medium')).toBeInTheDocument(); // Priority 12
|
||||
});
|
||||
|
||||
test('shows loading state initially', () => {
|
||||
mockGetDocumentRetryHistory.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.getByText('Loading retry history...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('handles API errors gracefully', async () => {
|
||||
mockGetDocumentRetryHistory.mockRejectedValue(new Error('API Error'));
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Failed to load retry history/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('shows empty state when no retry history exists', async () => {
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: [],
|
||||
total_retries: 0,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No retry history found for this document.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('closes modal when close button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
const closeButton = screen.getByText('Close');
|
||||
await user.click(closeButton);
|
||||
|
||||
expect(mockProps.onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('formats retry reasons correctly', async () => {
|
||||
const customHistory = [
|
||||
{ ...sampleRetryHistory[0], retry_reason: 'bulk_retry_all' },
|
||||
{ ...sampleRetryHistory[0], retry_reason: 'bulk_retry_specific' },
|
||||
{ ...sampleRetryHistory[0], retry_reason: 'bulk_retry_filtered' },
|
||||
{ ...sampleRetryHistory[0], retry_reason: 'manual_retry' },
|
||||
{ ...sampleRetryHistory[0], retry_reason: 'unknown_reason' },
|
||||
];
|
||||
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: customHistory,
|
||||
total_retries: customHistory.length,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Bulk Retry (All Documents)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bulk Retry (Specific Documents)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Bulk Retry (Filtered)')).toBeInTheDocument();
|
||||
expect(screen.getByText('Manual Retry')).toBeInTheDocument();
|
||||
expect(screen.getByText('unknown_reason')).toBeInTheDocument(); // Unknown reasons show as-is
|
||||
});
|
||||
});
|
||||
|
||||
test('formats priority levels correctly', async () => {
|
||||
const customHistory = [
|
||||
{ ...sampleRetryHistory[0], priority: 20 },
|
||||
{ ...sampleRetryHistory[0], priority: 15 },
|
||||
{ ...sampleRetryHistory[0], priority: 10 },
|
||||
{ ...sampleRetryHistory[0], priority: 5 },
|
||||
{ ...sampleRetryHistory[0], priority: 1 },
|
||||
];
|
||||
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: customHistory,
|
||||
total_retries: customHistory.length,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const highPriorities = screen.getAllByText('High');
|
||||
const mediumPriorities = screen.getAllByText('Medium');
|
||||
const lowPriorities = screen.getAllByText('Low');
|
||||
|
||||
expect(highPriorities).toHaveLength(2); // Priority 20 and 15
|
||||
expect(mediumPriorities).toHaveLength(1); // Priority 10
|
||||
expect(lowPriorities).toHaveLength(2); // Priority 5 and 1
|
||||
});
|
||||
});
|
||||
|
||||
test('formats failure reasons correctly', async () => {
|
||||
const customHistory = [
|
||||
{ ...sampleRetryHistory[0], previous_failure_reason: 'low_confidence' },
|
||||
{ ...sampleRetryHistory[0], previous_failure_reason: 'image_quality' },
|
||||
{ ...sampleRetryHistory[0], previous_failure_reason: 'processing_timeout' },
|
||||
{ ...sampleRetryHistory[0], previous_failure_reason: 'unknown_error' },
|
||||
{ ...sampleRetryHistory[0], previous_failure_reason: null },
|
||||
];
|
||||
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: customHistory,
|
||||
total_retries: customHistory.length,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence')).toBeInTheDocument();
|
||||
expect(screen.getByText('Image Quality')).toBeInTheDocument();
|
||||
expect(screen.getByText('Processing Timeout')).toBeInTheDocument();
|
||||
expect(screen.getByText('Unknown Error')).toBeInTheDocument();
|
||||
expect(screen.getByText('N/A')).toBeInTheDocument(); // null reason
|
||||
});
|
||||
});
|
||||
|
||||
test('displays previous error messages', async () => {
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('OCR confidence too low: 45%')).toBeInTheDocument();
|
||||
expect(screen.getByText('Image resolution too low')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('formats dates correctly', async () => {
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Check that dates are formatted (exact format may vary by locale)
|
||||
expect(screen.getByText(/Jan/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/2024/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('shows total retry count', async () => {
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Total retries: 2')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('handles missing documentName gracefully', async () => {
|
||||
render(<RetryHistoryModal {...mockProps} documentName={undefined} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('test-doc-123')).toBeInTheDocument(); // Falls back to documentId
|
||||
});
|
||||
});
|
||||
|
||||
test('handles history entries with missing fields', async () => {
|
||||
const incompleteHistory = [
|
||||
{
|
||||
id: 'retry-1',
|
||||
retry_reason: null,
|
||||
previous_status: null,
|
||||
previous_failure_reason: null,
|
||||
previous_error: null,
|
||||
priority: null,
|
||||
queue_id: null,
|
||||
created_at: '2024-01-15T10:30:00Z',
|
||||
},
|
||||
];
|
||||
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-123',
|
||||
retry_history: incompleteHistory,
|
||||
total_retries: 1,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should not crash and should show N/A for missing fields
|
||||
expect(screen.getAllByText('N/A')).toHaveLength(4); // reason, failure reason, previous error, priority
|
||||
});
|
||||
});
|
||||
|
||||
test('loads fresh data when documentId changes', async () => {
|
||||
const { rerender } = render(<RetryHistoryModal {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetDocumentRetryHistory).toHaveBeenCalledWith('test-doc-123');
|
||||
});
|
||||
|
||||
// Change document ID
|
||||
rerender(<RetryHistoryModal {...mockProps} documentId="different-doc-456" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockGetDocumentRetryHistory).toHaveBeenCalledWith('different-doc-456');
|
||||
});
|
||||
|
||||
expect(mockGetDocumentRetryHistory).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { RetryRecommendations } from '../RetryRecommendations';
|
||||
|
||||
// Mock the API
|
||||
const mockGetRetryRecommendations = vi.fn();
|
||||
const mockBulkRetryOcr = vi.fn();
|
||||
|
||||
const mockDocumentService = {
|
||||
getRetryRecommendations: mockGetRetryRecommendations,
|
||||
};
|
||||
|
||||
const mockApi = {
|
||||
bulkRetryOcr: mockBulkRetryOcr,
|
||||
};
|
||||
|
||||
vi.mock('../../services/api', () => ({
|
||||
documentService: mockDocumentService,
|
||||
default: mockApi,
|
||||
}));
|
||||
|
||||
describe('RetryRecommendations', () => {
|
||||
const mockProps = {
|
||||
onRetrySuccess: vi.fn(),
|
||||
onRetryClick: vi.fn(),
|
||||
};
|
||||
|
||||
const sampleRecommendations = [
|
||||
{
|
||||
reason: 'low_confidence',
|
||||
title: 'Low Confidence Results',
|
||||
description: 'Documents with OCR confidence below 70%',
|
||||
estimated_success_rate: 0.8,
|
||||
document_count: 15,
|
||||
filter: {
|
||||
failure_reasons: ['low_confidence'],
|
||||
min_confidence: 0,
|
||||
max_confidence: 70,
|
||||
},
|
||||
},
|
||||
{
|
||||
reason: 'image_quality',
|
||||
title: 'Image Quality Issues',
|
||||
description: 'Documents that failed due to poor image quality',
|
||||
estimated_success_rate: 0.6,
|
||||
document_count: 8,
|
||||
filter: {
|
||||
failure_reasons: ['image_quality', 'resolution_too_low'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: sampleRecommendations,
|
||||
total_recommendations: 2,
|
||||
},
|
||||
});
|
||||
mockBulkRetryOcr.mockResolvedValue({
|
||||
data: {
|
||||
success: true,
|
||||
queued_count: 10,
|
||||
matched_count: 15,
|
||||
documents: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('renders loading state initially', () => {
|
||||
mockGetRetryRecommendations.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(screen.getByText('Loading retry recommendations...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('loads and displays recommendations on mount', async () => {
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('OCR Retry Recommendations')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
expect(screen.getByText('Image Quality Issues')).toBeInTheDocument();
|
||||
expect(screen.getByText('15 documents')).toBeInTheDocument();
|
||||
expect(screen.getByText('8 documents')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('displays success rate badges with correct colors', async () => {
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('80% (High)')).toBeInTheDocument();
|
||||
expect(screen.getByText('60% (Medium)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check that the badges have the correct colors
|
||||
const highBadge = screen.getByText('80% (High)').closest('.MuiChip-root');
|
||||
const mediumBadge = screen.getByText('60% (Medium)').closest('.MuiChip-root');
|
||||
|
||||
expect(highBadge).toHaveClass('MuiChip-colorSuccess');
|
||||
expect(mediumBadge).toHaveClass('MuiChip-colorWarning');
|
||||
});
|
||||
|
||||
test('handles retry click with onRetryClick callback', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getAllByText('Retry Now')[0];
|
||||
await user.click(retryButton);
|
||||
|
||||
expect(mockProps.onRetryClick).toHaveBeenCalledWith(sampleRecommendations[0]);
|
||||
});
|
||||
|
||||
test('executes retry directly when onRetryClick is not provided', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RetryRecommendations onRetrySuccess={mockProps.onRetrySuccess} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getAllByText('Retry Now')[0];
|
||||
await user.click(retryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalledWith({
|
||||
mode: 'filter',
|
||||
filter: sampleRecommendations[0].filter,
|
||||
priority_override: 12,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockProps.onRetrySuccess).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('shows loading state during retry execution', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockBulkRetryOcr.mockImplementation(() => new Promise(resolve =>
|
||||
setTimeout(() => resolve({
|
||||
data: { success: true, queued_count: 10, matched_count: 10, documents: [] }
|
||||
}), 100)
|
||||
));
|
||||
|
||||
render(<RetryRecommendations onRetrySuccess={mockProps.onRetrySuccess} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getAllByText('Retry Now')[0];
|
||||
await user.click(retryButton);
|
||||
|
||||
// Should show loading state
|
||||
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
||||
expect(retryButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test('handles API errors gracefully', async () => {
|
||||
mockGetRetryRecommendations.mockRejectedValue(new Error('API Error'));
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Failed to load retry recommendations/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('handles retry API errors gracefully', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockBulkRetryOcr.mockRejectedValue({
|
||||
response: { data: { message: 'Retry failed' } }
|
||||
});
|
||||
|
||||
render(<RetryRecommendations onRetrySuccess={mockProps.onRetrySuccess} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getAllByText('Retry Now')[0];
|
||||
await user.click(retryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Retry failed')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('shows empty state when no recommendations are available', async () => {
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: [],
|
||||
total_recommendations: 0,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No retry recommendations available')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('All documents have been processed successfully')).toBeInTheDocument();
|
||||
expect(screen.getByText('No failed documents found')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows correct success rate labels', () => {
|
||||
const { rerender } = render(<div />);
|
||||
|
||||
// Test high success rate (>= 70%)
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: [{
|
||||
...sampleRecommendations[0],
|
||||
estimated_success_rate: 0.85,
|
||||
}],
|
||||
total_recommendations: 1,
|
||||
},
|
||||
});
|
||||
|
||||
rerender(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText('85% (High)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Test medium success rate (40-69%)
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: [{
|
||||
...sampleRecommendations[0],
|
||||
estimated_success_rate: 0.55,
|
||||
}],
|
||||
total_recommendations: 1,
|
||||
},
|
||||
});
|
||||
|
||||
rerender(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText('55% (Medium)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Test low success rate (< 40%)
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: [{
|
||||
...sampleRecommendations[0],
|
||||
estimated_success_rate: 0.25,
|
||||
}],
|
||||
total_recommendations: 1,
|
||||
},
|
||||
});
|
||||
|
||||
rerender(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
waitFor(() => {
|
||||
expect(screen.getByText('25% (Low)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('refreshes recommendations after successful retry', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<RetryRecommendations onRetrySuccess={mockProps.onRetrySuccess} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Low Confidence Results')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockGetRetryRecommendations).toHaveBeenCalledTimes(1);
|
||||
|
||||
const retryButton = screen.getAllByText('Retry Now')[0];
|
||||
await user.click(retryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should reload recommendations after successful retry
|
||||
expect(mockGetRetryRecommendations).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('handles null/undefined recommendations safely', async () => {
|
||||
mockGetRetryRecommendations.mockResolvedValue({
|
||||
data: {
|
||||
recommendations: null,
|
||||
total_recommendations: 0,
|
||||
},
|
||||
});
|
||||
|
||||
render(<RetryRecommendations {...mockProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No retry recommendations available')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Should not crash
|
||||
expect(screen.getByText('OCR Retry Recommendations')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
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 { MemoryRouter, Routes, Route } from 'react-router-dom';
|
||||
import DocumentDetailsPage from '../DocumentDetailsPage';
|
||||
|
||||
// Mock the entire API module
|
||||
const mockBulkRetryOcr = vi.fn();
|
||||
const mockGetById = vi.fn();
|
||||
const mockGetOcrText = vi.fn();
|
||||
const mockGetThumbnail = vi.fn();
|
||||
const mockGetDocumentRetryHistory = vi.fn();
|
||||
|
||||
const mockDocumentService = {
|
||||
getById: mockGetById,
|
||||
getOcrText: mockGetOcrText,
|
||||
getThumbnail: mockGetThumbnail,
|
||||
bulkRetryOcr: mockBulkRetryOcr,
|
||||
getDocumentRetryHistory: mockGetDocumentRetryHistory,
|
||||
download: vi.fn(),
|
||||
getProcessedImage: vi.fn(),
|
||||
};
|
||||
|
||||
const mockApi = {
|
||||
get: vi.fn(),
|
||||
post: vi.fn(),
|
||||
put: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock('../../services/api', () => ({
|
||||
documentService: mockDocumentService,
|
||||
default: mockApi,
|
||||
}));
|
||||
|
||||
// Mock the RetryHistoryModal component
|
||||
vi.mock('../../components/RetryHistoryModal', () => ({
|
||||
RetryHistoryModal: ({ open, onClose, documentId, documentName }: any) => (
|
||||
open ? (
|
||||
<div data-testid="retry-history-modal">
|
||||
<div>Retry History for {documentName}</div>
|
||||
<div>Document ID: {documentId}</div>
|
||||
<button onClick={onClose}>Close</button>
|
||||
</div>
|
||||
) : null
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock other components
|
||||
vi.mock('../../components/DocumentViewer', () => ({
|
||||
default: ({ documentId, filename }: any) => (
|
||||
<div data-testid="document-viewer">
|
||||
Viewing {filename} (ID: {documentId})
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../../components/Labels/LabelSelector', () => ({
|
||||
default: ({ selectedLabels, onLabelsChange }: any) => (
|
||||
<div data-testid="label-selector">
|
||||
<div>Selected: {selectedLabels.length} labels</div>
|
||||
<button onClick={() => onLabelsChange([])}>Clear Labels</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('../../components/MetadataDisplay', () => ({
|
||||
default: ({ metadata, title }: any) => (
|
||||
<div data-testid="metadata-display">
|
||||
<h3>{title}</h3>
|
||||
<pre>{JSON.stringify(metadata, null, 2)}</pre>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('DocumentDetailsPage - Retry Functionality', () => {
|
||||
const mockDocument = {
|
||||
id: 'test-doc-1',
|
||||
original_filename: 'test-document.pdf',
|
||||
filename: 'test-document.pdf',
|
||||
file_size: 1024000,
|
||||
mime_type: 'application/pdf',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
has_ocr_text: true,
|
||||
tags: ['important'],
|
||||
};
|
||||
|
||||
const mockOcrData = {
|
||||
document_id: 'test-doc-1',
|
||||
filename: 'test-document.pdf',
|
||||
has_ocr_text: true,
|
||||
ocr_text: 'Sample OCR text content',
|
||||
ocr_confidence: 95,
|
||||
ocr_word_count: 100,
|
||||
ocr_processing_time_ms: 5000,
|
||||
ocr_status: 'completed',
|
||||
ocr_completed_at: '2023-01-01T00:05:00Z',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockGetById.mockResolvedValue({
|
||||
data: mockDocument,
|
||||
});
|
||||
|
||||
mockGetOcrText.mockResolvedValue({
|
||||
data: mockOcrData,
|
||||
});
|
||||
|
||||
mockGetThumbnail.mockRejectedValue(new Error('Thumbnail not available'));
|
||||
|
||||
mockBulkRetryOcr.mockResolvedValue({
|
||||
data: {
|
||||
success: true,
|
||||
queued_count: 1,
|
||||
matched_count: 1,
|
||||
documents: [mockDocument],
|
||||
estimated_total_time_minutes: 2.0,
|
||||
message: 'OCR retry queued successfully',
|
||||
},
|
||||
});
|
||||
|
||||
mockGetDocumentRetryHistory.mockResolvedValue({
|
||||
data: {
|
||||
document_id: 'test-doc-1',
|
||||
retry_history: [],
|
||||
total_retries: 0,
|
||||
},
|
||||
});
|
||||
|
||||
mockApi.get.mockResolvedValue({ data: [] });
|
||||
});
|
||||
|
||||
const renderDocumentDetailsPage = () => {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/documents/test-doc-1']}>
|
||||
<Routes>
|
||||
<Route path="/documents/:id" element={<DocumentDetailsPage />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
test('renders retry OCR button', async () => {
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('Retry OCR')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can retry OCR for document', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getByText('Retry OCR');
|
||||
expect(retryButton).toBeInTheDocument();
|
||||
|
||||
// Clear previous calls to track only the retry call
|
||||
mockBulkRetryOcr.mockClear();
|
||||
|
||||
await user.click(retryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalledWith({
|
||||
mode: 'specific',
|
||||
document_ids: ['test-doc-1'],
|
||||
priority_override: 15,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('shows loading state during retry', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
// Make the retry take some time
|
||||
mockBulkRetryOcr.mockImplementation(() =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(() => resolve({
|
||||
data: {
|
||||
success: true,
|
||||
queued_count: 1,
|
||||
matched_count: 1,
|
||||
documents: [mockDocument],
|
||||
estimated_total_time_minutes: 2.0,
|
||||
message: 'OCR retry queued successfully',
|
||||
},
|
||||
}), 100)
|
||||
)
|
||||
);
|
||||
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getByText('Retry OCR');
|
||||
await user.click(retryButton);
|
||||
|
||||
// Should show loading state
|
||||
expect(screen.getByText('Retrying...')).toBeInTheDocument();
|
||||
|
||||
// Wait for retry to complete
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Retry OCR')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('handles retry OCR error gracefully', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
// Mock retry to fail
|
||||
mockBulkRetryOcr.mockRejectedValue(new Error('Retry failed'));
|
||||
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const retryButton = screen.getByText('Retry OCR');
|
||||
await user.click(retryButton);
|
||||
|
||||
// Should still show the retry button (not stuck in loading state)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Retry OCR')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('renders retry history button', async () => {
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('Retry History')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can open retry history modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const historyButton = screen.getByText('Retry History');
|
||||
await user.click(historyButton);
|
||||
|
||||
// Should open the retry history modal
|
||||
expect(screen.getByTestId('retry-history-modal')).toBeInTheDocument();
|
||||
expect(screen.getByText('Retry History for test-document.pdf')).toBeInTheDocument();
|
||||
expect(screen.getByText('Document ID: test-doc-1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('can close retry history modal', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Open modal
|
||||
const historyButton = screen.getByText('Retry History');
|
||||
await user.click(historyButton);
|
||||
|
||||
expect(screen.getByTestId('retry-history-modal')).toBeInTheDocument();
|
||||
|
||||
// Close modal
|
||||
const closeButton = screen.getByText('Close');
|
||||
await user.click(closeButton);
|
||||
|
||||
expect(screen.queryByTestId('retry-history-modal')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('refreshes document details after successful retry', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
// Mock successful retry
|
||||
mockBulkRetryOcr.mockResolvedValue({
|
||||
data: {
|
||||
success: true,
|
||||
queued_count: 1,
|
||||
matched_count: 1,
|
||||
documents: [mockDocument],
|
||||
estimated_total_time_minutes: 2.0,
|
||||
message: 'OCR retry queued successfully',
|
||||
},
|
||||
});
|
||||
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Clear previous calls
|
||||
mockGetById.mockClear();
|
||||
|
||||
const retryButton = screen.getByText('Retry OCR');
|
||||
await user.click(retryButton);
|
||||
|
||||
// Should call getById again to refresh document details after delay
|
||||
await waitFor(() => {
|
||||
expect(mockGetById).toHaveBeenCalledWith('test-doc-1');
|
||||
}, { timeout: 2000 });
|
||||
});
|
||||
|
||||
test('retry functionality works with documents without OCR text', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
// Mock document without OCR text
|
||||
mockGetById.mockResolvedValue({
|
||||
data: {
|
||||
...mockDocument,
|
||||
has_ocr_text: false,
|
||||
},
|
||||
});
|
||||
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Retry button should still be available
|
||||
const retryButton = screen.getByText('Retry OCR');
|
||||
expect(retryButton).toBeInTheDocument();
|
||||
|
||||
await user.click(retryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockBulkRetryOcr).toHaveBeenCalledWith({
|
||||
mode: 'specific',
|
||||
document_ids: ['test-doc-1'],
|
||||
priority_override: 15,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('retry history modal receives correct props', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderDocumentDetailsPage();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Details')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const historyButton = screen.getByText('Retry History');
|
||||
await user.click(historyButton);
|
||||
|
||||
// Verify modal props are passed correctly
|
||||
expect(screen.getByText('Document ID: test-doc-1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Retry History for test-document.pdf')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue