import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { BlockReviewPanel, BlockReviewSummary, type BlockReviewData } from '../BlockReviewPanel' import type { GridData, GridCell } from '../GridOverlay' // 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: r === 0 || c === 0 ? '' : `cell-${r}-${c}`, confidence: 0.85, status: r === 0 || c === 0 ? 'empty' : '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: 0, empty: 5, total: 9, coverage: 0.44, }, } } const createMockMethodResults = () => ({ vision_llm: { vocabulary: [ { english: 'word1', german: 'Wort1' }, { english: 'word2', german: 'Wort2' }, ], }, tesseract: { vocabulary: [ { english: 'word1', german: 'Wort1' }, { english: 'word2', german: 'Wort2' }, ], }, }) describe('BlockReviewPanel', () => { const mockOnBlockChange = vi.fn() const mockOnApprove = vi.fn() const mockOnCorrect = vi.fn() const mockOnSkip = vi.fn() beforeEach(() => { vi.clearAllMocks() }) it('should render the current block number', () => { render( ) expect(screen.getByText('Block 5')).toBeInTheDocument() }) it('should display progress percentage', () => { const reviewData: Record = { 5: { blockNumber: 5, cell: createMockGrid().cells[1][1], methodResults: [], status: 'approved', correctedText: 'approved text', }, } render( ) expect(screen.getByText('25%')).toBeInTheDocument() }) it('should show cell position information', () => { render( ) expect(screen.getByText('Position:')).toBeInTheDocument() }) it('should display method results', () => { render( ) expect(screen.getByText('Erkannte Texte:')).toBeInTheDocument() }) it('should call onSkip when skip button is clicked', () => { render( ) fireEvent.click(screen.getByText('Überspringen')) expect(mockOnSkip).toHaveBeenCalledWith(5) }) it('should show manual correction button', () => { render( ) expect(screen.getByText('+ Manuell korrigieren')).toBeInTheDocument() }) it('should show correction input when correction button is clicked', () => { render( ) fireEvent.click(screen.getByText('+ Manuell korrigieren')) expect(screen.getByPlaceholderText('Korrekten Text eingeben...')).toBeInTheDocument() }) it('should call onCorrect when correction is submitted', () => { render( ) fireEvent.click(screen.getByText('+ Manuell korrigieren')) const input = screen.getByPlaceholderText('Korrekten Text eingeben...') fireEvent.change(input, { target: { value: 'corrected text' } }) fireEvent.click(screen.getByText('Übernehmen')) expect(mockOnCorrect).toHaveBeenCalledWith(5, 'corrected text') }) it('should show approved status when block is approved', () => { const reviewData: Record = { 5: { blockNumber: 5, cell: createMockGrid().cells[1][1], methodResults: [], status: 'approved', correctedText: 'approved text', }, } render( ) expect(screen.getByText(/Freigegeben:/)).toBeInTheDocument() }) it('should disable previous button on first block', () => { render( ) const prevButton = screen.getByText('Zurück').closest('button') expect(prevButton).toBeDisabled() }) it('should show empty message when no blocks available', () => { const emptyGrid: GridData = { ...createMockGrid(), cells: [[{ row: 0, col: 0, x: 0, y: 0, width: 100, height: 100, text: '', confidence: 0, status: 'empty' }]], } render( ) expect(screen.getByText('Keine Blöcke zum Überprüfen')).toBeInTheDocument() }) }) describe('BlockReviewSummary', () => { const mockOnBlockClick = vi.fn() beforeEach(() => { vi.clearAllMocks() }) it('should display summary statistics', () => { const reviewData: Record = { 1: { blockNumber: 1, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 'text1' }, 2: { blockNumber: 2, cell: {} as GridCell, methodResults: [], status: 'corrected', correctedText: 'text2' }, 3: { blockNumber: 3, cell: {} as GridCell, methodResults: [], status: 'skipped' }, } render( ) expect(screen.getByText('Überprüfungsübersicht')).toBeInTheDocument() expect(screen.getByText('1')).toBeInTheDocument() // approved count expect(screen.getByText('Freigegeben')).toBeInTheDocument() expect(screen.getByText('Korrigiert')).toBeInTheDocument() expect(screen.getByText('Übersprungen')).toBeInTheDocument() expect(screen.getByText('Ausstehend')).toBeInTheDocument() }) it('should call onBlockClick when a block is clicked', () => { const reviewData: Record = { 5: { blockNumber: 5, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 'text' }, } render( ) fireEvent.click(screen.getByText('Block 5')) expect(mockOnBlockClick).toHaveBeenCalledWith(5) }) it('should show correct counts for each status', () => { const reviewData: Record = { 1: { blockNumber: 1, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 't1' }, 2: { blockNumber: 2, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 't2' }, 3: { blockNumber: 3, cell: {} as GridCell, methodResults: [], status: 'corrected', correctedText: 't3' }, } render( ) // 2 approved, 1 corrected, 0 skipped, 2 pending const approvedCount = screen.getAllByText('2')[0] expect(approvedCount).toBeInTheDocument() }) it('should truncate long corrected text', () => { const reviewData: Record = { 1: { blockNumber: 1, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 'This is a very long text that should be truncated' }, } render( ) expect(screen.getByText(/This is a very long t\.\.\./)).toBeInTheDocument() }) })