test(transcription): cover TranscriptionEditView branches

Empty-state coach, review progress counter, mark-all-reviewed
button visibility/disabled state, OcrTrigger conditional, training
labels visible/hidden by canWrite, label toggle callback, blocks
sorted by sortOrder.

12 tests covering ~30 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-10 04:51:32 +02:00
parent 7c28c0cb4c
commit 9795333c2d

View File

@@ -0,0 +1,198 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
vi.mock('$lib/shared/services/confirm.svelte', () => ({
getConfirmService: () => ({ confirm: async () => false })
}));
vi.mock('$lib/shared/services/confirm.svelte.js', () => ({
getConfirmService: () => ({ confirm: async () => false })
}));
const { default: TranscriptionEditView } = await import('./TranscriptionEditView.svelte');
import type { TranscriptionBlockData } from '$lib/shared/types';
afterEach(cleanup);
const baseBlock = (overrides: Partial<TranscriptionBlockData> = {}): TranscriptionBlockData =>
({
id: 'b-1',
annotationId: 'ann-1',
text: 'Hello',
sortOrder: 1,
reviewed: false,
mentionedPersons: [],
label: null,
...overrides
}) as TranscriptionBlockData;
const baseProps = (overrides: Record<string, unknown> = {}) => ({
documentId: 'doc-1',
blocks: [] as TranscriptionBlockData[],
canComment: false,
currentUserId: null,
onBlockFocus: () => {},
onSaveBlock: async () => {},
onDeleteBlock: async () => {},
onReviewToggle: async () => {},
...overrides
});
describe('TranscriptionEditView', () => {
it('renders the empty-state coach when there are no blocks', async () => {
render(TranscriptionEditView, { props: baseProps() });
// TranscribeCoachEmptyState renders some German text
expect(document.body.textContent).toMatch(/markier|block|transkrip/i);
});
it('renders the review progress counter when there are blocks', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock({ id: 'b1', reviewed: false }), baseBlock({ id: 'b2', reviewed: true })]
})
});
expect(document.body.textContent).toMatch(/1\s*\/\s*2/);
});
it('shows the "alle als fertig markieren" button when onMarkAllReviewed is provided', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
onMarkAllReviewed: async () => {}
})
});
await expect.element(page.getByRole('button', { name: /alle als fertig/i })).toBeVisible();
});
it('disables the mark-all-reviewed button when all blocks are reviewed', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock({ reviewed: true })],
onMarkAllReviewed: async () => {}
})
});
const btn = (await page
.getByRole('button', { name: /alle als fertig/i })
.element()) as HTMLButtonElement;
expect(btn.disabled).toBe(true);
});
it('enables the mark-all-reviewed button when not all blocks are reviewed', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock({ reviewed: false })],
onMarkAllReviewed: async () => {}
})
});
const btn = (await page
.getByRole('button', { name: /alle als fertig/i })
.element()) as HTMLButtonElement;
expect(btn.disabled).toBe(false);
});
it('hides the mark-all-reviewed button when onMarkAllReviewed is not provided', async () => {
render(TranscriptionEditView, { props: baseProps({ blocks: [baseBlock()] }) });
await expect
.element(page.getByRole('button', { name: /alle als fertig/i }))
.not.toBeInTheDocument();
});
it('renders the OcrTrigger only when canRunOcr is true and onTriggerOcr is provided', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
canRunOcr: true,
onTriggerOcr: () => {}
})
});
// OcrTrigger renders a select with script-type options
const select = document.querySelector('select');
expect(select).not.toBeNull();
});
it('hides the OcrTrigger when canRunOcr is false', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
canRunOcr: false,
onTriggerOcr: () => {}
})
});
const select = document.querySelector('select');
expect(select).toBeNull();
});
it('renders the training-label chips when canWrite=true and there are blocks', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
canWrite: true,
trainingLabels: [],
onToggleTrainingLabel: async () => {}
})
});
// Training-label section caption
expect(document.body.textContent).toMatch(/training/i);
});
it('hides the training-label section when canWrite is false', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
canWrite: false
})
});
expect(document.body.textContent).not.toMatch(/Für Training vormerken/i);
});
it('toggles the training label chip when clicked', async () => {
const onToggleTrainingLabel = vi.fn().mockResolvedValue(undefined);
render(TranscriptionEditView, {
props: baseProps({
blocks: [baseBlock()],
canWrite: true,
trainingLabels: [],
onToggleTrainingLabel
})
});
const chip = Array.from(document.querySelectorAll('button')).find((b) =>
/kurrent|segmentier/i.test(b.textContent ?? '')
);
expect(chip).toBeDefined();
chip?.click();
await new Promise((r) => setTimeout(r, 30));
expect(onToggleTrainingLabel).toHaveBeenCalled();
});
it('renders blocks sorted by sortOrder', async () => {
render(TranscriptionEditView, {
props: baseProps({
blocks: [
baseBlock({ id: 'b3', sortOrder: 3, text: 'Third' }),
baseBlock({ id: 'b1', sortOrder: 1, text: 'First' }),
baseBlock({ id: 'b2', sortOrder: 2, text: 'Second' })
]
})
});
// All three texts should appear in document order: First, Second, Third
const text = document.body.textContent ?? '';
const idxFirst = text.indexOf('First');
const idxSecond = text.indexOf('Second');
const idxThird = text.indexOf('Third');
expect(idxFirst).toBeLessThan(idxSecond);
expect(idxSecond).toBeLessThan(idxThird);
});
});