/** * @ai-summary Unit tests for DocumentPreview component * @ai-context Tests image/PDF preview with mocked API calls */ import { render, screen, waitFor } from '@testing-library/react'; import { DocumentPreview } from './DocumentPreview'; import { documentsApi } from '../api/documents.api'; import type { DocumentRecord } from '../types/documents.types'; // Mock the documents API jest.mock('../api/documents.api'); const mockDocumentsApi = jest.mocked(documentsApi); // Mock URL.createObjectURL and revokeObjectURL const mockCreateObjectURL = jest.fn(); const mockRevokeObjectURL = jest.fn(); Object.defineProperty(global.URL, 'createObjectURL', { value: mockCreateObjectURL, }); Object.defineProperty(global.URL, 'revokeObjectURL', { value: mockRevokeObjectURL, }); describe('DocumentPreview', () => { const mockPdfDocument: DocumentRecord = { id: 'doc-1', user_id: 'user-1', vehicle_id: 'vehicle-1', document_type: 'insurance', title: 'Insurance Document', content_type: 'application/pdf', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }; const mockImageDocument: DocumentRecord = { id: 'doc-2', user_id: 'user-1', vehicle_id: 'vehicle-1', document_type: 'registration', title: 'Registration Photo', content_type: 'image/jpeg', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }; const mockNonPreviewableDocument: DocumentRecord = { id: 'doc-3', user_id: 'user-1', vehicle_id: 'vehicle-1', document_type: 'insurance', title: 'Text Document', content_type: 'text/plain', created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z', }; beforeEach(() => { jest.clearAllMocks(); mockCreateObjectURL.mockReturnValue('blob:http://localhost/test-blob'); }); afterEach(() => { jest.restoreAllMocks(); }); describe('PDF Preview', () => { it('should render PDF preview for PDF documents', async () => { const mockBlob = new Blob(['fake pdf content'], { type: 'application/pdf' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); render(); // Should show loading initially expect(screen.getByText('Loading preview...')).toBeInTheDocument(); // Wait for the PDF object to appear await waitFor(() => { const pdfObject = screen.getByRole('application', { name: 'PDF Preview' }); expect(pdfObject).toBeInTheDocument(); expect(pdfObject).toHaveAttribute('data', 'blob:http://localhost/test-blob'); expect(pdfObject).toHaveAttribute('type', 'application/pdf'); }); expect(mockDocumentsApi.download).toHaveBeenCalledWith('doc-1'); expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob); }); it('should provide fallback link for PDF when object fails', async () => { const mockBlob = new Blob(['fake pdf content'], { type: 'application/pdf' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); render(); await waitFor(() => { // Check that fallback link exists within the object element const fallbackLink = screen.getByRole('link', { name: 'Open PDF' }); expect(fallbackLink).toBeInTheDocument(); expect(fallbackLink).toHaveAttribute('href', 'blob:http://localhost/test-blob'); expect(fallbackLink).toHaveAttribute('target', '_blank'); }); }); }); describe('Image Preview', () => { it('should render image preview for image documents', async () => { const mockBlob = new Blob(['fake image content'], { type: 'image/jpeg' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); render(); // Should show loading initially expect(screen.getByText('Loading preview...')).toBeInTheDocument(); // Wait for the image to appear await waitFor(() => { const image = screen.getByRole('img', { name: 'Registration Photo' }); expect(image).toBeInTheDocument(); expect(image).toHaveAttribute('src', 'blob:http://localhost/test-blob'); }); expect(mockDocumentsApi.download).toHaveBeenCalledWith('doc-2'); expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob); }); it('should have proper image styling', async () => { const mockBlob = new Blob(['fake image content'], { type: 'image/jpeg' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); render(); await waitFor(() => { const image = screen.getByRole('img'); expect(image).toHaveClass('max-w-full', 'h-auto', 'rounded-lg', 'border'); }); }); }); describe('Non-previewable Documents', () => { it('should show no preview message for non-previewable documents', () => { render(); expect(screen.getByText('No preview available.')).toBeInTheDocument(); expect(mockDocumentsApi.download).not.toHaveBeenCalled(); }); it('should not create blob URL for non-previewable documents', () => { render(); expect(mockCreateObjectURL).not.toHaveBeenCalled(); }); }); describe('Error Handling', () => { it('should show error message when download fails', async () => { mockDocumentsApi.download.mockRejectedValue(new Error('Download failed')); render(); await waitFor(() => { expect(screen.getByText('Failed to load preview')).toBeInTheDocument(); }); expect(mockDocumentsApi.download).toHaveBeenCalledWith('doc-1'); expect(mockCreateObjectURL).not.toHaveBeenCalled(); }); it('should handle network errors gracefully', async () => { mockDocumentsApi.download.mockRejectedValue(new Error('Network error')); render(); await waitFor(() => { expect(screen.getByText('Failed to load preview')).toBeInTheDocument(); }); }); }); describe('Content Type Detection', () => { it('should detect PDF from content type', () => { render(); // PDF should be considered previewable expect(screen.queryByText('No preview available.')).not.toBeInTheDocument(); expect(screen.getByText('Loading preview...')).toBeInTheDocument(); }); it('should detect images from content type', () => { render(); // Image should be considered previewable expect(screen.queryByText('No preview available.')).not.toBeInTheDocument(); expect(screen.getByText('Loading preview...')).toBeInTheDocument(); }); it('should handle PNG images', async () => { const pngDocument = { ...mockImageDocument, content_type: 'image/png' }; const mockBlob = new Blob(['fake png content'], { type: 'image/png' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); render(); await waitFor(() => { const image = screen.getByRole('img'); expect(image).toBeInTheDocument(); }); }); it('should handle documents with undefined content type', () => { const undefinedTypeDoc = { ...mockPdfDocument, content_type: undefined }; render(); expect(screen.getByText('No preview available.')).toBeInTheDocument(); }); }); describe('Memory Management', () => { it('should clean up blob URL on unmount', async () => { const mockBlob = new Blob(['fake content'], { type: 'application/pdf' }); mockDocumentsApi.download.mockResolvedValue(mockBlob); const { unmount } = render(); await waitFor(() => { expect(mockCreateObjectURL).toHaveBeenCalled(); }); unmount(); expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:http://localhost/test-blob'); }); it('should clean up blob URL when document changes', async () => { const mockBlob1 = new Blob(['fake content 1'], { type: 'application/pdf' }); const mockBlob2 = new Blob(['fake content 2'], { type: 'image/jpeg' }); mockDocumentsApi.download .mockResolvedValueOnce(mockBlob1) .mockResolvedValueOnce(mockBlob2); const { rerender } = render(); await waitFor(() => { expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob1); }); // Change to different document rerender(); await waitFor(() => { expect(mockRevokeObjectURL).toHaveBeenCalledWith('blob:http://localhost/test-blob'); expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob2); }); }); }); });