import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { GridOverlay, GridStats, GridLegend, getCellBlockNumber } from '../GridOverlay' import type { GridData, GridCell } from '../GridOverlay' // Helper to create mock grid data const createMockGrid = (rows: number = 3, cols: number = 3): GridData => { const cells: GridCell[][] = [] for (let r = 0; r < rows; r++) { const row: GridCell[] = [] for (let c = 0; c < cols; c++) { row.push({ row: r, col: c, x: (c / cols) * 100, y: (r / rows) * 100, width: 100 / cols, height: 100 / rows, text: `cell-${r}-${c}`, confidence: 0.85, status: r === 0 ? 'empty' : c === 0 ? 'problematic' : 'recognized', column_type: c === 1 ? 'english' : c === 2 ? 'german' : 'unknown', }) } cells.push(row) } return { rows, columns: cols, cells, column_types: ['unknown', 'english', 'german'], column_boundaries: [0, 33.33, 66.66, 100], row_boundaries: [0, 33.33, 66.66, 100], deskew_angle: 0, stats: { recognized: 4, problematic: 2, empty: 3, manual: 0, total: 9, coverage: 0.67, }, } } describe('getCellBlockNumber', () => { it('should return correct block number for first cell', () => { const grid = createMockGrid(3, 4) const cell: GridCell = { row: 0, col: 0, x: 0, y: 0, width: 25, height: 33, text: '', confidence: 1, status: 'empty' } expect(getCellBlockNumber(cell, grid)).toBe(1) }) it('should return correct block number for cell in second row', () => { const grid = createMockGrid(3, 4) const cell: GridCell = { row: 1, col: 0, x: 0, y: 33, width: 25, height: 33, text: '', confidence: 1, status: 'empty' } expect(getCellBlockNumber(cell, grid)).toBe(5) }) it('should return correct block number for last cell', () => { const grid = createMockGrid(3, 4) const cell: GridCell = { row: 2, col: 3, x: 75, y: 66, width: 25, height: 33, text: '', confidence: 1, status: 'empty' } expect(getCellBlockNumber(cell, grid)).toBe(12) }) it('should calculate correctly for different grid sizes', () => { const grid = createMockGrid(5, 5) const cell: GridCell = { row: 2, col: 3, x: 60, y: 40, width: 20, height: 20, text: '', confidence: 1, status: 'empty' } expect(getCellBlockNumber(cell, grid)).toBe(14) // row 2 * 5 cols + col 3 + 1 = 14 }) }) describe('GridOverlay', () => { it('should render without crashing', () => { render() expect(document.querySelector('svg')).toBeInTheDocument() }) it('should render image when imageUrl is provided', () => { render() const img = screen.getByAltText('Document') expect(img).toBeInTheDocument() expect(img).toHaveAttribute('src', 'https://example.com/image.jpg') }) it('should call onCellClick when a non-empty cell is clicked', () => { const mockOnClick = vi.fn() const grid = createMockGrid() render() // Find a recognized cell (non-empty) and click it const recognizedCells = document.querySelectorAll('rect') // Click on a cell that should be clickable (recognized status) recognizedCells.forEach(rect => { if (rect.getAttribute('fill')?.includes('34, 197, 94')) { fireEvent.click(rect) } }) // onCellClick should have been called for recognized cells expect(mockOnClick).toHaveBeenCalled() }) it('should not call onCellClick for empty cells', () => { const mockOnClick = vi.fn() const emptyGrid: GridData = { ...createMockGrid(), cells: [[{ row: 0, col: 0, x: 0, y: 0, width: 100, height: 100, text: '', confidence: 0, status: 'empty' }]], } render() const cell = document.querySelector('rect') if (cell) { fireEvent.click(cell) } expect(mockOnClick).not.toHaveBeenCalled() }) it('should show column labels when showLabels is true', () => { render() // Check for column type labels const svgTexts = document.querySelectorAll('text') const labels = Array.from(svgTexts).map(t => t.textContent) expect(labels).toContain('EN') expect(labels).toContain('DE') }) it('should hide empty cells when showEmpty is false', () => { const grid = createMockGrid() const emptyCellCount = grid.cells.flat().filter(c => c.status === 'empty').length const { container } = render() const allRects = container.querySelectorAll('g > rect') // Should have fewer rects when empty cells are hidden const totalCells = grid.rows * grid.columns expect(allRects.length).toBeLessThan(totalCells * 2) // each cell can have multiple rects }) it('should show block numbers when showNumbers is true', () => { render() const svgTexts = document.querySelectorAll('text') const numbers = Array.from(svgTexts).map(t => t.textContent).filter(t => /^\d+$/.test(t || '')) expect(numbers.length).toBeGreaterThan(0) }) it('should highlight a specific block when highlightedBlockNumber is set', () => { const grid = createMockGrid() render() // Check that there's a highlighted element (with indigo color) const rects = document.querySelectorAll('rect') const highlightedRect = Array.from(rects).find( rect => rect.getAttribute('stroke') === '#4f46e5' ) expect(highlightedRect).toBeTruthy() }) it('should apply custom className', () => { const { container } = render() expect(container.firstChild).toHaveClass('custom-class') }) it('should render row and column boundary lines', () => { render() const lines = document.querySelectorAll('line') expect(lines.length).toBeGreaterThan(0) }) }) describe('GridStats', () => { const mockStats = { recognized: 10, problematic: 3, empty: 5, manual: 2, total: 20, coverage: 0.75, } it('should display recognized count', () => { render() expect(screen.getByText('Erkannt: 10')).toBeInTheDocument() }) it('should display problematic count when greater than 0', () => { render() expect(screen.getByText('Problematisch: 3')).toBeInTheDocument() }) it('should not display problematic when count is 0', () => { render() expect(screen.queryByText(/Problematisch:/)).not.toBeInTheDocument() }) it('should display manual count when greater than 0', () => { render() expect(screen.getByText('Manuell: 2')).toBeInTheDocument() }) it('should not display manual when count is 0', () => { render() expect(screen.queryByText(/Manuell:/)).not.toBeInTheDocument() }) it('should display empty count', () => { render() expect(screen.getByText('Leer: 5')).toBeInTheDocument() }) it('should display coverage percentage', () => { render() expect(screen.getByText('Abdeckung: 75%')).toBeInTheDocument() }) it('should display deskew angle when provided and non-zero', () => { render() expect(screen.getByText('Begradigt: 2.5')).toBeInTheDocument() }) it('should not display deskew angle when 0', () => { render() expect(screen.queryByText(/Begradigt:/)).not.toBeInTheDocument() }) it('should apply custom className', () => { const { container } = render() expect(container.firstChild).toHaveClass('custom-stats') }) }) describe('GridLegend', () => { it('should display all status labels', () => { render() expect(screen.getByText('Erkannt')).toBeInTheDocument() expect(screen.getByText('Problematisch')).toBeInTheDocument() expect(screen.getByText('Manuell korrigiert')).toBeInTheDocument() expect(screen.getByText('Leer')).toBeInTheDocument() }) it('should render color indicators for each status', () => { const { container } = render() const colorIndicators = container.querySelectorAll('.w-4.h-4.rounded') expect(colorIndicators.length).toBe(4) }) it('should apply custom className', () => { const { container } = render() expect(container.firstChild).toHaveClass('custom-legend') }) })