import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { render, screen, fireEvent, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import Scanner from '@/components/Scanner' // Helper to reduce render() duplication const renderScanner = (props = {}) => { const defaultProps = { onScanSuccess: vi.fn(), onOCRMatch: vi.fn(), paused: false, ...props, } return render() } describe('Scanner Component', () => { afterEach(() => { vi.clearAllMocks() }) // ============================================================================ // UNIT TESTS: Rendering (Core Structure) // ============================================================================ describe('Rendering', () => { it('should render scanner with viewport and controls', () => { const { container } = renderScanner() const viewport = container.querySelector('.aspect-square') const controls = container.querySelector('.bg-surface\\/50') expect(viewport).toBeInTheDocument() expect(controls).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Callback Props // ============================================================================ describe('Callback Props', () => { it('should accept onScanSuccess and onOCRMatch callbacks', () => { const mockSuccess = vi.fn() const mockOCR = vi.fn() renderScanner({ onScanSuccess: mockSuccess, onOCRMatch: mockOCR }) expect(mockSuccess).toBeDefined() expect(mockOCR).toBeDefined() }) it('should render with minimal props (onScanSuccess only)', () => { const mockSuccess = vi.fn() const { container } = render( ) expect(container).toBeInTheDocument() }) it('should accept paused prop and pass through', () => { const { container } = renderScanner({ paused: true }) expect(container).toBeInTheDocument() }) }) // ============================================================================ // UNIT TESTS: Zoom Controls & Accessibility // ============================================================================ describe('Zoom Control & Buttons', () => { it('should render buttons with accessibility attributes', () => { const { container } = renderScanner() const buttons = container.querySelectorAll('button') expect(buttons.length).toBeGreaterThanOrEqual(0) buttons.forEach(btn => { // Button should have proper styling expect(btn.className).toBeTruthy() }) }) it('should have focus-ring styling on interactive elements', () => { const { container } = renderScanner() const buttons = container.querySelectorAll('button') if (buttons.length > 0) { const firstButton = buttons[0] expect(firstButton.className).toMatch(/focus.*ring|focus-visible/) } }) }) // ============================================================================ // UNIT TESTS: Error Handling // ============================================================================ describe('Error Handling', () => { it('should render without throwing on mount with valid props', () => { expect(() => { renderScanner() }).not.toThrow() }) it('should handle unmount without errors', () => { const { unmount } = renderScanner() expect(() => unmount()).not.toThrow() }) }) // ============================================================================ // INTEGRATION TESTS: Scan Workflow // ============================================================================ describe('Scan Workflow (Integration)', () => { it('should render scanner with callbacks available', () => { const mockSuccess = vi.fn() const mockOCR = vi.fn() const { container } = renderScanner({ onScanSuccess: mockSuccess, onOCRMatch: mockOCR }) expect(mockSuccess).toBeDefined() expect(mockOCR).toBeDefined() expect(container.querySelector('.aspect-square')).toBeInTheDocument() }) it('should clean up on unmount', () => { const { unmount, container } = renderScanner() expect(container).toBeInTheDocument() expect(() => unmount()).not.toThrow() }) }) // ============================================================================ // UNIT TESTS: Pause State // ============================================================================ describe('Pause State', () => { it('should handle paused prop changes without errors', () => { const mockSuccess = vi.fn() const mockOCR = vi.fn() const { rerender, container } = render( ) expect(container).toBeInTheDocument() rerender( ) expect(container).toBeInTheDocument() }) }) // ============================================================================ // LAYOUT & ACCESSIBILITY TESTS // ============================================================================ describe('Layout & Accessibility', () => { it('should have responsive Tailwind layout structure', () => { const { container } = renderScanner() const mainDiv = container.querySelector('.w-full.max-w-md') expect(mainDiv).toBeInTheDocument() expect(mainDiv?.className).toMatch(/flex/) }) it('should contain text elements for readability', () => { const { container } = renderScanner() const textElements = container.querySelectorAll('p, span, button') expect(textElements.length).toBeGreaterThan(0) }) it('should use Lucide icons (SVG elements)', () => { const { container } = renderScanner() const svgs = container.querySelectorAll('svg') expect(svgs.length).toBeGreaterThanOrEqual(0) }) }) // ============================================================================ // COMPONENT PROP VARIATIONS & STABILITY // ============================================================================ describe('Component Prop Variations & Stability', () => { it('should render with all props (onScanSuccess, onOCRMatch, paused)', () => { const { container } = renderScanner({ paused: false }) expect(container).toBeInTheDocument() }) it('should render with minimal props (onScanSuccess only)', () => { const { container } = render( ) expect(container).toBeInTheDocument() }) it('should handle multiple rapid re-renders without errors', () => { const mockScan = vi.fn() const mockOCR = vi.fn() const { rerender } = renderScanner({ onScanSuccess: mockScan, onOCRMatch: mockOCR }) for (let i = 0; i < 3; i++) { expect(() => { rerender( ) }).not.toThrow() } }) it('should handle callback function changes between renders', () => { const mockScan1 = vi.fn() const mockScan2 = vi.fn() const { rerender } = render( ) rerender( ) expect(mockScan1).toBeDefined() expect(mockScan2).toBeDefined() }) }) // ============================================================================ // INTEGRATION: UI Interaction States // ============================================================================ describe('UI Interaction States', () => { it('should render viewport and controls in proper visual hierarchy', () => { const { container } = renderScanner() const viewport = container.querySelector('.aspect-square') const controls = container.querySelector('.bg-surface\\/50') expect(viewport).toBeInTheDocument() expect(controls).toBeInTheDocument() }) it('should have consistent spacing with gap utilities', () => { const { container } = renderScanner() const mainContainer = container.querySelector('.flex.flex-col') expect(mainContainer?.className).toMatch(/gap-/) }) }) // ============================================================================ // COMPONENT STABILITY TESTS // ============================================================================ describe('Component Stability', () => { it('should mount and unmount without errors', () => { expect(() => renderScanner()).not.toThrow() const { unmount } = renderScanner() expect(() => unmount()).not.toThrow() }) it('should handle rapid paused prop changes', () => { const { rerender } = renderScanner({ paused: false }) for (let i = 0; i < 5; i++) { expect(() => { rerender( ) }).not.toThrow() } }) it('should render with only required prop (onScanSuccess)', () => { expect(() => { render() }).not.toThrow() }) }) // ============================================================================ // COMPONENT STRUCTURE TESTS // ============================================================================ describe('Component Structure', () => { it('should maintain DOM structure on initial render and after prop updates', () => { const { rerender, container } = renderScanner({ paused: false }) // Verify initial structure expect(container.querySelector('.w-full.max-w-md')).toBeInTheDocument() expect(container.querySelector('.aspect-square')).toBeInTheDocument() expect(container.querySelector('.bg-surface\\/50')).toBeInTheDocument() // Update props rerender( ) // Structure preserved expect(container.querySelector('.aspect-square')).toBeInTheDocument() }) }) })