import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import OcrTrainingCard from './OcrTrainingCard.svelte'; afterEach(cleanup); afterEach(() => vi.restoreAllMocks()); const baseInfo = { availableBlocks: 10, totalOcrBlocks: 20, availableDocuments: 3, ocrServiceAvailable: true, lastRun: null, runs: [] }; describe('OcrTrainingCard — disabled states', () => { it('disables button and shows hint when availableBlocks is 0', async () => { render(OcrTrainingCard, { trainingInfo: { ...baseInfo, availableBlocks: 0 } }); const btn = page.getByRole('button', { name: /Training starten/i }); await expect.element(btn).toBeDisabled(); await expect .element(page.getByText(/Mindestens 5 geprüfte Blöcke erforderlich/i)) .toBeInTheDocument(); }); it('disables button and shows hint when availableBlocks is less than 5', async () => { render(OcrTrainingCard, { trainingInfo: { ...baseInfo, availableBlocks: 3 } }); const btn = page.getByRole('button', { name: /Training starten/i }); await expect.element(btn).toBeDisabled(); await expect.element(page.getByText(/Mindestens 5/i)).toBeInTheDocument(); }); it('disables button and shows service-down warning when ocrServiceAvailable is false', async () => { render(OcrTrainingCard, { trainingInfo: { ...baseInfo, ocrServiceAvailable: false } }); const btn = page.getByRole('button', { name: /Training starten/i }); await expect.element(btn).toBeDisabled(); await expect.element(page.getByText(/OCR-Dienst ist nicht erreichbar/i)).toBeInTheDocument(); }); it('does not show service-down warning when blocks are insufficient', async () => { // tooFewBlocks hint takes priority over serviceDown hint render(OcrTrainingCard, { trainingInfo: { ...baseInfo, availableBlocks: 2, ocrServiceAvailable: false } }); await expect.element(page.getByText(/Mindestens 5/i)).toBeInTheDocument(); // serviceDown text should NOT appear because tooFewBlocks branch hides it const serviceMsg = document.querySelector('.text-orange-600'); expect(serviceMsg).toBeNull(); }); }); describe('OcrTrainingCard — enabled state', () => { it('enables button when availableBlocks >= 5 and service is up', async () => { render(OcrTrainingCard, { trainingInfo: baseInfo }); const btn = page.getByRole('button', { name: /Training starten/i }); await expect.element(btn).not.toBeDisabled(); }); it('shows block count info text', async () => { render(OcrTrainingCard, { trainingInfo: { ...baseInfo, availableBlocks: 7, totalOcrBlocks: 15 } }); await expect.element(page.getByText(/7/)).toBeInTheDocument(); await expect.element(page.getByText(/von 15 OCR-Blöcken/i)).toBeInTheDocument(); }); }); describe('OcrTrainingCard — success dismiss button', () => { it('dismiss button has 44×44px touch target (h-11 w-11)', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true })); render(OcrTrainingCard, { trainingInfo: baseInfo }); const btn = page.getByRole('button', { name: /Training starten/i }); await btn.click(); const dismissBtn = page.getByRole('button', { name: /Schließen/i }); await expect.element(dismissBtn).toBeInTheDocument(); const el = await dismissBtn.element(); expect(el.classList.contains('h-11')).toBe(true); expect(el.classList.contains('w-11')).toBe(true); }); }); describe('OcrTrainingCard — in-flight state', () => { it('shows "…" while POST is in-flight', async () => { let resolveFetch!: (v: unknown) => void; const pendingFetch = new Promise((resolve) => { resolveFetch = resolve; }); vi.stubGlobal('fetch', vi.fn().mockReturnValue(pendingFetch)); render(OcrTrainingCard, { trainingInfo: baseInfo }); const btn = page.getByRole('button', { name: /Training starten/i }); await btn.click(); // While fetch is still pending the button label becomes "…" await expect.element(page.getByRole('button', { name: '…' })).toBeInTheDocument(); // Cleanup: resolve the pending promise resolveFetch({ ok: false }); }); });