test: cover PersonEditForm and SegmentationTrainingCard branches
PersonEditForm: PERSON vs INSTITUTION/GROUP visibility matrix (firstName, title, alias, birth/deathYear toggle), lastName label switch, prop hydration of all populated fields, fallback to PERSON for unknown type, empty-string handling for null fields. 10 tests, ~30 branches. SegmentationTrainingCard: trainingInfo null vs populated, block count display, button disabled-state matrix (training × tooFewBlocks × serviceDown), too-few-blocks and service-down hints, success message after a mocked fetch, training history heading. 10 tests, ~25 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
110
frontend/src/lib/ocr/SegmentationTrainingCard.svelte.test.ts
Normal file
110
frontend/src/lib/ocr/SegmentationTrainingCard.svelte.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect, afterEach, vi } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import SegmentationTrainingCard from './SegmentationTrainingCard.svelte';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const baseInfo = (overrides: Record<string, unknown> = {}) => ({
|
||||
availableSegBlocks: 10,
|
||||
ocrServiceAvailable: true,
|
||||
runs: [],
|
||||
...overrides
|
||||
});
|
||||
|
||||
describe('SegmentationTrainingCard', () => {
|
||||
it('renders the heading and description', async () => {
|
||||
render(SegmentationTrainingCard, { props: { trainingInfo: baseInfo() } });
|
||||
|
||||
await expect
|
||||
.element(page.getByRole('heading', { name: /segmentierung trainieren/i }))
|
||||
.toBeVisible();
|
||||
await expect.element(page.getByText(/Starte ein neues Training/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows the count of available segmentation blocks', async () => {
|
||||
render(SegmentationTrainingCard, {
|
||||
props: { trainingInfo: baseInfo({ availableSegBlocks: 42 }) }
|
||||
});
|
||||
|
||||
await expect.element(page.getByText('42 Segmentierungsblöcke bereit')).toBeVisible();
|
||||
});
|
||||
|
||||
it('shows zero blocks when trainingInfo is null', async () => {
|
||||
render(SegmentationTrainingCard, { props: { trainingInfo: null } });
|
||||
|
||||
await expect.element(page.getByText('0 Segmentierungsblöcke bereit')).toBeVisible();
|
||||
});
|
||||
|
||||
it('disables the start button when fewer than 5 blocks are available', async () => {
|
||||
render(SegmentationTrainingCard, {
|
||||
props: { trainingInfo: baseInfo({ availableSegBlocks: 3 }) }
|
||||
});
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /training starten/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
expect(btn.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('shows the too-few-blocks hint when fewer than 5 blocks are available', async () => {
|
||||
render(SegmentationTrainingCard, {
|
||||
props: { trainingInfo: baseInfo({ availableSegBlocks: 3 }) }
|
||||
});
|
||||
|
||||
await expect
|
||||
.element(page.getByText(/Mindestens 5 Segmentierungsblöcke erforderlich/i))
|
||||
.toBeVisible();
|
||||
});
|
||||
|
||||
it('disables the start button when the OCR service is reported down', async () => {
|
||||
render(SegmentationTrainingCard, {
|
||||
props: { trainingInfo: baseInfo({ ocrServiceAvailable: false }) }
|
||||
});
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /training starten/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
expect(btn.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('shows the service-down hint when ocrServiceAvailable is false', async () => {
|
||||
render(SegmentationTrainingCard, {
|
||||
props: { trainingInfo: baseInfo({ ocrServiceAvailable: false }) }
|
||||
});
|
||||
|
||||
await expect.element(page.getByText('OCR-Dienst ist nicht erreichbar.')).toBeVisible();
|
||||
});
|
||||
|
||||
it('enables the start button when blocks are sufficient and the service is up', async () => {
|
||||
render(SegmentationTrainingCard, { props: { trainingInfo: baseInfo() } });
|
||||
|
||||
const btn = (await page
|
||||
.getByRole('button', { name: /training starten/i })
|
||||
.element()) as HTMLButtonElement;
|
||||
expect(btn.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('shows the success message after a successful training POST', async () => {
|
||||
const fetchSpy = vi
|
||||
.spyOn(globalThis, 'fetch')
|
||||
.mockResolvedValue(new Response('{}', { status: 200 }));
|
||||
try {
|
||||
render(SegmentationTrainingCard, { props: { trainingInfo: baseInfo() } });
|
||||
|
||||
await page.getByRole('button', { name: /training starten/i }).click();
|
||||
|
||||
await expect
|
||||
.element(page.getByText('Training wurde gestartet und abgeschlossen.'))
|
||||
.toBeVisible();
|
||||
} finally {
|
||||
fetchSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('renders the training history heading', async () => {
|
||||
render(SegmentationTrainingCard, { props: { trainingInfo: baseInfo() } });
|
||||
|
||||
await expect.element(page.getByRole('heading', { name: /verlauf/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import PersonEditForm from './PersonEditForm.svelte';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const personPersonal = {
|
||||
id: 'p1',
|
||||
personType: 'PERSON',
|
||||
title: 'Frau Dr.',
|
||||
firstName: 'Anna',
|
||||
lastName: 'Schmidt',
|
||||
alias: 'Anni',
|
||||
birthYear: 1899 as number | null,
|
||||
deathYear: 1972 as number | null,
|
||||
notes: 'Wohnte in Berlin.'
|
||||
};
|
||||
|
||||
const personInstitution = {
|
||||
id: 'p2',
|
||||
personType: 'INSTITUTION',
|
||||
title: null,
|
||||
firstName: null,
|
||||
lastName: 'Acme GmbH',
|
||||
alias: null,
|
||||
birthYear: null,
|
||||
deathYear: null,
|
||||
notes: null
|
||||
};
|
||||
|
||||
describe('PersonEditForm', () => {
|
||||
it('renders the firstName input for the PERSON personType', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
await expect.element(page.getByLabelText(/vorname/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders the title input only for the PERSON personType', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
await expect.element(page.getByLabelText(/titel/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('hides the firstName / title / alias / year fields for INSTITUTION', async () => {
|
||||
render(PersonEditForm, { props: { person: personInstitution } });
|
||||
|
||||
await expect.element(page.getByLabelText(/vorname/i)).not.toBeInTheDocument();
|
||||
await expect.element(page.getByLabelText(/^titel$/i)).not.toBeInTheDocument();
|
||||
await expect.element(page.getByLabelText(/rufname/i)).not.toBeInTheDocument();
|
||||
await expect.element(page.getByLabelText(/geburtsjahr/i)).not.toBeInTheDocument();
|
||||
await expect.element(page.getByLabelText(/todesjahr/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('uses the "Nachname" label for PERSON', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
await expect.element(page.getByLabelText(/nachname \*/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('uses the "Name" label for INSTITUTION', async () => {
|
||||
render(PersonEditForm, { props: { person: personInstitution } });
|
||||
|
||||
await expect.element(page.getByLabelText(/^name \*$/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('hydrates inputs from the person prop', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
const firstName = (await page.getByLabelText(/vorname/i).element()) as HTMLInputElement;
|
||||
const lastName = (await page.getByLabelText(/nachname/i).element()) as HTMLInputElement;
|
||||
const alias = (await page.getByLabelText(/rufname/i).element()) as HTMLInputElement;
|
||||
const title = (await page.getByLabelText(/^titel/i).element()) as HTMLInputElement;
|
||||
expect(firstName.value).toBe('Anna');
|
||||
expect(lastName.value).toBe('Schmidt');
|
||||
expect(alias.value).toBe('Anni');
|
||||
expect(title.value).toBe('Frau Dr.');
|
||||
});
|
||||
|
||||
it('renders birthYear and deathYear inputs with prior values', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
const birthYear = (await page.getByLabelText(/geburtsjahr/i).element()) as HTMLInputElement;
|
||||
const deathYear = (await page.getByLabelText(/todesjahr/i).element()) as HTMLInputElement;
|
||||
expect(birthYear.value).toBe('1899');
|
||||
expect(deathYear.value).toBe('1972');
|
||||
});
|
||||
|
||||
it('renders the notes textarea pre-filled with prior content', async () => {
|
||||
render(PersonEditForm, { props: { person: personPersonal } });
|
||||
|
||||
const notes = (await page.getByLabelText(/notizen/i).element()) as HTMLTextAreaElement;
|
||||
expect(notes.value).toBe('Wohnte in Berlin.');
|
||||
});
|
||||
|
||||
it('falls back to PERSON when an unknown personType is supplied', async () => {
|
||||
render(PersonEditForm, {
|
||||
props: { person: { ...personPersonal, personType: 'NOT_A_TYPE' } }
|
||||
});
|
||||
|
||||
await expect.element(page.getByLabelText(/vorname/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('renders empty inputs when nullable fields are null', async () => {
|
||||
render(PersonEditForm, {
|
||||
props: { person: { ...personPersonal, title: null, alias: null, birthYear: null } }
|
||||
});
|
||||
|
||||
const title = (await page.getByLabelText(/^titel/i).element()) as HTMLInputElement;
|
||||
const alias = (await page.getByLabelText(/rufname/i).element()) as HTMLInputElement;
|
||||
const birthYear = (await page.getByLabelText(/geburtsjahr/i).element()) as HTMLInputElement;
|
||||
expect(title.value).toBe('');
|
||||
expect(alias.value).toBe('');
|
||||
expect(birthYear.value).toBe('');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user