fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
390
admin-v2/components/ocr/__tests__/BlockReviewPanel.test.tsx
Normal file
390
admin-v2/components/ocr/__tests__/BlockReviewPanel.test.tsx
Normal file
@@ -0,0 +1,390 @@
|
||||
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(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Block 5')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display progress percentage', () => {
|
||||
const reviewData: Record<number, BlockReviewData> = {
|
||||
5: {
|
||||
blockNumber: 5,
|
||||
cell: createMockGrid().cells[1][1],
|
||||
methodResults: [],
|
||||
status: 'approved',
|
||||
correctedText: 'approved text',
|
||||
},
|
||||
}
|
||||
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={reviewData}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('25%')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show cell position information', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Position:')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display method results', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Erkannte Texte:')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onSkip when skip button is clicked', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Überspringen'))
|
||||
expect(mockOnSkip).toHaveBeenCalledWith(5)
|
||||
})
|
||||
|
||||
it('should show manual correction button', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('+ Manuell korrigieren')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show correction input when correction button is clicked', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('+ Manuell korrigieren'))
|
||||
expect(screen.getByPlaceholderText('Korrekten Text eingeben...')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onCorrect when correction is submitted', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
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<number, BlockReviewData> = {
|
||||
5: {
|
||||
blockNumber: 5,
|
||||
cell: createMockGrid().cells[1][1],
|
||||
methodResults: [],
|
||||
status: 'approved',
|
||||
correctedText: 'approved text',
|
||||
},
|
||||
}
|
||||
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={reviewData}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/Freigegeben:/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should disable previous button on first block', () => {
|
||||
render(
|
||||
<BlockReviewPanel
|
||||
grid={createMockGrid()}
|
||||
methodResults={createMockMethodResults()}
|
||||
currentBlockNumber={5}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
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(
|
||||
<BlockReviewPanel
|
||||
grid={emptyGrid}
|
||||
methodResults={{}}
|
||||
currentBlockNumber={1}
|
||||
onBlockChange={mockOnBlockChange}
|
||||
onApprove={mockOnApprove}
|
||||
onCorrect={mockOnCorrect}
|
||||
onSkip={mockOnSkip}
|
||||
reviewData={{}}
|
||||
/>
|
||||
)
|
||||
|
||||
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<number, BlockReviewData> = {
|
||||
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(
|
||||
<BlockReviewSummary
|
||||
reviewData={reviewData}
|
||||
totalBlocks={5}
|
||||
onBlockClick={mockOnBlockClick}
|
||||
/>
|
||||
)
|
||||
|
||||
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<number, BlockReviewData> = {
|
||||
5: { blockNumber: 5, cell: {} as GridCell, methodResults: [], status: 'approved', correctedText: 'text' },
|
||||
}
|
||||
|
||||
render(
|
||||
<BlockReviewSummary
|
||||
reviewData={reviewData}
|
||||
totalBlocks={10}
|
||||
onBlockClick={mockOnBlockClick}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Block 5'))
|
||||
expect(mockOnBlockClick).toHaveBeenCalledWith(5)
|
||||
})
|
||||
|
||||
it('should show correct counts for each status', () => {
|
||||
const reviewData: Record<number, BlockReviewData> = {
|
||||
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(
|
||||
<BlockReviewSummary
|
||||
reviewData={reviewData}
|
||||
totalBlocks={5}
|
||||
onBlockClick={mockOnBlockClick}
|
||||
/>
|
||||
)
|
||||
|
||||
// 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<number, BlockReviewData> = {
|
||||
1: {
|
||||
blockNumber: 1,
|
||||
cell: {} as GridCell,
|
||||
methodResults: [],
|
||||
status: 'approved',
|
||||
correctedText: 'This is a very long text that should be truncated'
|
||||
},
|
||||
}
|
||||
|
||||
render(
|
||||
<BlockReviewSummary
|
||||
reviewData={reviewData}
|
||||
totalBlocks={1}
|
||||
onBlockClick={mockOnBlockClick}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/This is a very long t\.\.\./)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
267
admin-v2/components/ocr/__tests__/GridOverlay.test.tsx
Normal file
267
admin-v2/components/ocr/__tests__/GridOverlay.test.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
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(<GridOverlay grid={createMockGrid()} />)
|
||||
expect(document.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render image when imageUrl is provided', () => {
|
||||
render(<GridOverlay grid={createMockGrid()} imageUrl="https://example.com/image.jpg" />)
|
||||
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(<GridOverlay grid={grid} onCellClick={mockOnClick} />)
|
||||
|
||||
// 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(<GridOverlay grid={emptyGrid} onCellClick={mockOnClick} showEmpty />)
|
||||
|
||||
const cell = document.querySelector('rect')
|
||||
if (cell) {
|
||||
fireEvent.click(cell)
|
||||
}
|
||||
|
||||
expect(mockOnClick).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should show column labels when showLabels is true', () => {
|
||||
render(<GridOverlay grid={createMockGrid()} showLabels />)
|
||||
|
||||
// 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(<GridOverlay grid={grid} showEmpty={false} />)
|
||||
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(<GridOverlay grid={createMockGrid()} showNumbers />)
|
||||
|
||||
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(<GridOverlay grid={grid} showNumbers highlightedBlockNumber={5} />)
|
||||
|
||||
// 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(<GridOverlay grid={createMockGrid()} className="custom-class" />)
|
||||
|
||||
expect(container.firstChild).toHaveClass('custom-class')
|
||||
})
|
||||
|
||||
it('should render row and column boundary lines', () => {
|
||||
render(<GridOverlay grid={createMockGrid()} />)
|
||||
|
||||
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(<GridStats stats={mockStats} />)
|
||||
expect(screen.getByText('Erkannt: 10')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display problematic count when greater than 0', () => {
|
||||
render(<GridStats stats={mockStats} />)
|
||||
expect(screen.getByText('Problematisch: 3')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not display problematic when count is 0', () => {
|
||||
render(<GridStats stats={{ ...mockStats, problematic: 0 }} />)
|
||||
expect(screen.queryByText(/Problematisch:/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display manual count when greater than 0', () => {
|
||||
render(<GridStats stats={mockStats} />)
|
||||
expect(screen.getByText('Manuell: 2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not display manual when count is 0', () => {
|
||||
render(<GridStats stats={{ ...mockStats, manual: 0 }} />)
|
||||
expect(screen.queryByText(/Manuell:/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display empty count', () => {
|
||||
render(<GridStats stats={mockStats} />)
|
||||
expect(screen.getByText('Leer: 5')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display coverage percentage', () => {
|
||||
render(<GridStats stats={mockStats} />)
|
||||
expect(screen.getByText('Abdeckung: 75%')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display deskew angle when provided and non-zero', () => {
|
||||
render(<GridStats stats={mockStats} deskewAngle={2.5} />)
|
||||
expect(screen.getByText('Begradigt: 2.5')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not display deskew angle when 0', () => {
|
||||
render(<GridStats stats={mockStats} deskewAngle={0} />)
|
||||
expect(screen.queryByText(/Begradigt:/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply custom className', () => {
|
||||
const { container } = render(<GridStats stats={mockStats} className="custom-stats" />)
|
||||
expect(container.firstChild).toHaveClass('custom-stats')
|
||||
})
|
||||
})
|
||||
|
||||
describe('GridLegend', () => {
|
||||
it('should display all status labels', () => {
|
||||
render(<GridLegend />)
|
||||
|
||||
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(<GridLegend />)
|
||||
|
||||
const colorIndicators = container.querySelectorAll('.w-4.h-4.rounded')
|
||||
expect(colorIndicators.length).toBe(4)
|
||||
})
|
||||
|
||||
it('should apply custom className', () => {
|
||||
const { container } = render(<GridLegend className="custom-legend" />)
|
||||
expect(container.firstChild).toHaveClass('custom-legend')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user