From 38cdaa538a7b2421259c09d77a9a2bd08508a938 Mon Sep 17 00:00:00 2001 From: mina Date: Fri, 5 Sep 2025 17:30:17 +0200 Subject: [PATCH 01/12] implement test verschiedene Buttons beim click von editButton --- src/lib/components/NameItemEditor.svelte | 28 ++++++-- tests/ComponentNameItemEditor.view.test.ts | 84 ++++++++++++++++++++++ tests/TatortList.view.test.ts | 2 + 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 tests/ComponentNameItemEditor.view.test.ts diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index 4a1bddf..8a1d299 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -1,6 +1,8 @@
{ + isEditing = true; + }} onblur={commitIfValid} onkeydown={handleKeydown} /> - - + {#if isEditing} + + + {:else} + + + {/if} {#if error} -

{error}

+

{error}

{/if}
diff --git a/tests/ComponentNameItemEditor.view.test.ts b/tests/ComponentNameItemEditor.view.test.ts new file mode 100644 index 0000000..873cd6e --- /dev/null +++ b/tests/ComponentNameItemEditor.view.test.ts @@ -0,0 +1,84 @@ +import { fireEvent, render } from '@testing-library/svelte'; +import { describe, expect, it, vi } from "vitest"; +import NameItemEditor from '$lib/components/NameItemEditor.svelte'; + +const testUser = { + admin: true, +exp: 1757067123, +iat: 1757063523, +id: "admin", +} +const testCrimesList = [ + { + name: 'model-A', + lastModified: '2025-08-28T09:44:12.453Z', + etag: '558f35716f6af953f9bb5d75f6d77e6a', + size: 8947140, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + }, + { + name: 'model-z', + lastModified: '2025-08-28T10:37:20.142Z', + etag: '43e3989c32c4682bee407baaf83b6fa0', + size: 35788560, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + } + ]; + +const testVorgangsList = [ + { +vorgangName: "vorgang-1", +vorgangPIN: "pin-123", +vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" + }, + { +vorgangName: "vorgang-2", +vorgangPIN: "pin-2", +vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" + } + +] + +const baseData = { + user: testUser, + vorgang: testVorgangsList[0], + vorgangList: testVorgangsList, + crimesList: testCrimesList, + url: URL, + crimeNames: [ "modell-A" ], +} + +const testCrimesListIndex = 0; +const testItem = baseData.crimesList[testCrimesListIndex]; + const editedName = baseData.crimeNames[testCrimesListIndex]; + const currentName = testItem.name; + const onSave = vi.fn(); + const onDelete = vi.fn(); + +describe('Button-Anzeige mit Icons', () => { + const baseProps = {list: baseData.crimesList, editedName, currentName, onDelete, onSave} + it('zeigt Edit/Delete Buttons initial', () => { + const { getByTestId, queryByTestId } = render(NameItemEditor, { + props: baseProps + + }); + + expect(getByTestId('edit-button')).toBeInTheDocument(); + expect(getByTestId('delete-button')).toBeInTheDocument(); + expect(queryByTestId('commit-button')).toBeNull(); + expect(queryByTestId('cancel-button')).toBeNull(); + }); + + it('zeigt Commit/Cancel Buttons nach Klick auf Edit', async () => { + const { getByTestId } = render(NameItemEditor, { + props: baseProps + }); + + await fireEvent.click(getByTestId('edit-button')); + + expect(getByTestId('commit-button')).toBeInTheDocument(); + expect(getByTestId('cancel-button')).toBeInTheDocument(); + }); +}); diff --git a/tests/TatortList.view.test.ts b/tests/TatortList.view.test.ts index 61dd68c..c8816c9 100644 --- a/tests/TatortList.view.test.ts +++ b/tests/TatortList.view.test.ts @@ -69,3 +69,5 @@ const items = getAllByTestId('test-list-item'); expect(items).toHaveLength(2); }); }) + + From 650cfd0061aff29fb1b7b67da3ed756a3282ef61 Mon Sep 17 00:00:00 2001 From: mina Date: Mon, 8 Sep 2025 17:46:33 +0200 Subject: [PATCH 02/12] implement tests, Tatort List , ComponentEmptyList refactoring --- .../(token-based)/list/[vorgang]/+page.svelte | 3 +- tests/ComponentEmptyList.view.test.ts | 9 ++ tests/ComponentNameItemEditor.view.test.ts | 6 +- tests/TatortList.view.test.ts | 84 +++++++++++++++---- tests/VorgangList.view.test.ts | 4 +- 5 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 tests/ComponentEmptyList.view.test.ts diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index 2893b11..549f032 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -30,7 +30,7 @@ let crimesList: ListItem[] = $state(data.crimesList); const vorgangPIN: string = data.vorgang.vorgangPIN; let vorgangToken: string = data.vorgang.vorgangToken; - let isEmptyList = $derived(crimesList.length === 0); + let isEmptyList = $derived(crimesList && crimesList.length === 0); //Variablen für Modal let open = $state(false); @@ -162,6 +162,7 @@ Mit freundlichen Grüßen,
  • { + it('zeigt Hinweistext "Keine Einträge"', () => { + render(EmptyList); + expect(screen.getByText(/keine Einträge/i)).toBeInTheDocument(); }); +}); diff --git a/tests/ComponentNameItemEditor.view.test.ts b/tests/ComponentNameItemEditor.view.test.ts index 873cd6e..d5bc1f5 100644 --- a/tests/ComponentNameItemEditor.view.test.ts +++ b/tests/ComponentNameItemEditor.view.test.ts @@ -72,7 +72,7 @@ describe('Button-Anzeige mit Icons', () => { }); it('zeigt Commit/Cancel Buttons nach Klick auf Edit', async () => { - const { getByTestId } = render(NameItemEditor, { + const { getByTestId, queryByTestId } = render(NameItemEditor, { props: baseProps }); @@ -80,5 +80,9 @@ describe('Button-Anzeige mit Icons', () => { expect(getByTestId('commit-button')).toBeInTheDocument(); expect(getByTestId('cancel-button')).toBeInTheDocument(); + expect(queryByTestId('edit-button')).toBeNull(); + expect(queryByTestId('delete-button')).toBeNull(); }); + + }); diff --git a/tests/TatortList.view.test.ts b/tests/TatortList.view.test.ts index c8816c9..f47f86e 100644 --- a/tests/TatortList.view.test.ts +++ b/tests/TatortList.view.test.ts @@ -1,5 +1,5 @@ -import { render } from '@testing-library/svelte'; -import { describe, expect, it } from "vitest"; +import { render, screen, within } from '@testing-library/svelte'; +import { describe, expect, it, test } from "vitest"; import TatortListPage from "../src/routes/(token-based)/list/[vorgang]/+page.svelte"; const testUser = { @@ -47,27 +47,75 @@ const baseData = { vorgangList: testVorgangsList, crimesList: testCrimesList, url: URL, - crimeNames: [ "modell-A" ], - + crimeNames: [ "modell-A" ] } -describe('Tatort Liste Page EmptyList-Komponente View', ()=>{ - it('zeigt EmptyList-Komponente an, wenn Liste leer ist', () => { - const testData = { ...baseData, crimesList: [] }; - const { getByTestId } = render(TatortListPage, {props:{data: testData}}); +describe('Seite: Vorgangsansicht', () => { + test.todo('zeigt PIN und Share-Link, wenn Admin'); + + describe('Szenario: Liste leer (unabhängig von Rolle)', () => { + it('zeigt Hinweistext bei leerer Liste', () => { + const testData = { ...baseData, crimesList: [] }; + const { getByTestId } = render(TatortListPage, {props:{data: testData}}); + + expect(getByTestId('empty-list')).toBeInTheDocument(); + }); + it('zeigt keinen Listeneintrag', () => { + const items = screen.queryAllByTestId('test-list-item'); + + expect(items).toHaveLength(0); + }); + }); + describe('Szenario: Liste gefüllt (unabhängig von Rolle)', () => { + it('rendert mindestens ein Listenelement bei vorhandenen crimesList-Daten und prüft ob Link vorhanden', () => { + const testData = { ...baseData }; + const { queryAllByTestId } = render(TatortListPage, {props:{data: testData}}); + const items = queryAllByTestId('test-list-item'); + + expect(items.length).toBeGreaterThan(0); + }); + + it('zeigt für jeden Eintrag einen Link', () => { + const testData = { ...baseData }; + render(TatortListPage, { props: { data: testData } }); + const links = screen.queryAllByTestId('crime-link'); + + expect(links).toHaveLength(testData.crimesList.length); + }); + + it('prüft href und title jedes Links', () => { + const testData = { ...baseData }; + const { queryAllByTestId } = render(TatortListPage, { props: { data: testData } }); + const items = queryAllByTestId('test-list-item'); + + items.forEach((item, i) => { + const link = within(item).getByRole('link'); + const expectedHref = `/view/${testData.vorgang.vorgangToken}/${testData.crimesList[i].name}?pin=${testData.vorgang.vorgangPIN}`; + + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', expectedHref); + expect(link).toHaveAttribute('title', testData.crimesList[i].name); + }); + }); - expect(getByTestId('empty-list')).toBeInTheDocument(); - }); + test.todo('testet zuletzt angezeigt, wenn item.lastModified'); + test.todo('zeigt Dateigröße, wenn item.size vorhanden ist'); + }); - it('zeigt Liste(min. 1 li-Element) an, wenn Liste vorhanden ist', () => { - const testData = { ...baseData }; - const { getAllByTestId } = render(TatortListPage, {props:{data: testData}}); -const items = getAllByTestId('test-list-item'); - - expect(items).toHaveLength(2); - }); -}) + describe('Szenario: Admin + Liste gefüllt', () => { + it('zeigt PIN und Share-Link disabeld, wenn Liste leer', () => { }); + it('zeigt PIN und Share-Link disabeld=false', () => { }); + it('zeigt Listeneinträge mit Edit/Delete', () => { }); + it('gibt Edit/Delete-Events korrekt weiter', () => { }); + }); + describe('Szenario: Viewer + Liste gefüllt', () => { + it('zeigt Listeneinträge ohne Edit/Delete', () => { }); + it('zeigt Link und Änderungsdatum', () => { }); + it('zeigt keinen Share-Link oder PIN', () => { }); + }); + test.todo('Modal testen, wenn open') +}); diff --git a/tests/VorgangList.view.test.ts b/tests/VorgangList.view.test.ts index 13f6480..ae467e4 100644 --- a/tests/VorgangList.view.test.ts +++ b/tests/VorgangList.view.test.ts @@ -58,11 +58,11 @@ describe('Vorgänge Liste Page EmptyList-Komponente View', ()=>{ expect(getByTestId('empty-list')).toBeInTheDocument(); }); - it('zeigt Liste(min. 1 li-Element) an, wenn Liste vorhanden ist', () => { + it('zeigt Liste(mockData 2 Elemente) an, wenn Liste vorhanden ist', () => { const testData = { ...baseData }; const { getAllByTestId } = render(VorgangListPage, {props:{data: testData}}); const items = getAllByTestId('test-list-item'); - expect(items).toHaveLength(2); + expect(items.length).toBeGreaterThan(0); }); }) From 8803187ce1326799ca61571293ac5a1cb392f560 Mon Sep 17 00:00:00 2001 From: mina Date: Tue, 9 Sep 2025 18:25:04 +0200 Subject: [PATCH 03/12] implement tests TatortList.view, check delete/edit Item --- src/lib/components/NameItemEditor.svelte | 3 +- src/lib/config.ts | 2 +- .../(token-based)/list/[vorgang]/+page.svelte | 38 ++++----- tests/ComponentNameItemEditor.view.test.ts | 47 +---------- tests/TatortList.test.ts | 77 +++++++++++++++++ tests/TatortList.view.test.ts | 84 ++++++------------- tests/VorgangList.view.test.ts | 49 +---------- tests/fixtures.ts | 47 +++++++++++ 8 files changed, 167 insertions(+), 180 deletions(-) create mode 100644 tests/TatortList.test.ts create mode 100644 tests/fixtures.ts diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index 8a1d299..a1207ea 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -81,8 +81,9 @@ } -
    +
    { diff --git a/src/lib/config.ts b/src/lib/config.ts index 0775514..2e2bf34 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,3 +1,3 @@ import { readFileSync } from 'fs'; -export default JSON.parse(readFileSync('./config.json').toString()); +export default JSON.parse(readFileSync('./config_prod.json').toString()); diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index 549f032..1d65d2a 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -25,6 +25,7 @@ prefix?: string; // add other properties as needed } + console.log(data.url); let vorgangName: string = data.vorgang.vorgangName; let crimesList: ListItem[] = $state(data.crimesList); @@ -53,25 +54,18 @@ }) .then(() => { inProgress = false; + invalidateAll(); + crimesList = data.crimesList; + open = false; }) .catch((err) => { inProgress = false; isError = true; console.log('ERROR', err); }); - - if (!res.ok) { - const msg = await res.text(); - console.error('❌ Fehler beim Umbenennen:', msg); - isError = true; - inProgress = false; - } else { - await invalidateAll(); - crimesList = data.crimesList; - open = false; - inProgress = false; - } } catch (err) { + isError = true; + inProgress = false; console.error('⚠️ Netzwerkfehler:', err); inProgress = false; } @@ -82,7 +76,6 @@ inProgress = true; let url = new URL(data.url); url.pathname += `/${tatort}`; - console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname); try { const res = await fetch(`/api${url.pathname}`, { @@ -94,21 +87,15 @@ }) .then(() => { inProgress = false; + console.log('🗑️ Erfolgreich gelöscht:', url.pathname); + invalidateAll(); + crimesList = data.crimesList; }) .catch((err) => { isError = true; inProgress = false; console.log('ERROR', err); }); - if (!res.ok) { - const msg = await res.text(); - console.error('❌ Fehler beim Löschen:', msg); - } else { - console.log('🗑️ Erfolgreich gelöscht:', url.pathname); - await invalidateAll(); - - crimesList = data.crimesList; - } } catch (err) { isError = true; inProgress = false; @@ -180,9 +167,12 @@ Mit freundlichen Grüßen, onDelete={handleDelete} > {:else} - {item.name} + {item.name} +

    {/if} {#if item.size}

    diff --git a/tests/ComponentNameItemEditor.view.test.ts b/tests/ComponentNameItemEditor.view.test.ts index d5bc1f5..d25b7a9 100644 --- a/tests/ComponentNameItemEditor.view.test.ts +++ b/tests/ComponentNameItemEditor.view.test.ts @@ -1,54 +1,9 @@ import { fireEvent, render } from '@testing-library/svelte'; import { describe, expect, it, vi } from "vitest"; import NameItemEditor from '$lib/components/NameItemEditor.svelte'; +import { baseData } from './fixtures'; -const testUser = { - admin: true, -exp: 1757067123, -iat: 1757063523, -id: "admin", -} -const testCrimesList = [ - { - name: 'model-A', - lastModified: '2025-08-28T09:44:12.453Z', - etag: '558f35716f6af953f9bb5d75f6d77e6a', - size: 8947140, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - }, - { - name: 'model-z', - lastModified: '2025-08-28T10:37:20.142Z', - etag: '43e3989c32c4682bee407baaf83b6fa0', - size: 35788560, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - } - ]; -const testVorgangsList = [ - { -vorgangName: "vorgang-1", -vorgangPIN: "pin-123", -vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" - }, - { -vorgangName: "vorgang-2", -vorgangPIN: "pin-2", -vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" - } - -] - -const baseData = { - user: testUser, - vorgang: testVorgangsList[0], - vorgangList: testVorgangsList, - crimesList: testCrimesList, - url: URL, - crimeNames: [ "modell-A" ], -} const testCrimesListIndex = 0; const testItem = baseData.crimesList[testCrimesListIndex]; diff --git a/tests/TatortList.test.ts b/tests/TatortList.test.ts new file mode 100644 index 0000000..beeb8a3 --- /dev/null +++ b/tests/TatortList.test.ts @@ -0,0 +1,77 @@ +import { fireEvent, getAllByTestId, queryAllByTestId, render, screen, within } from '@testing-library/svelte'; +import { describe, expect, it, vi } from "vitest"; +import TatortListPage from "../src/routes/(token-based)/list/[vorgang]/+page.svelte"; +import { baseData } from './fixtures'; + + +// Mock für invalidateAll +vi.mock('$app/navigation', () => ({ + invalidateAll: vi.fn() +})); + +// Minimaler fetch-Mock +global.fetch = vi.fn().mockResolvedValue({ ok: true }); + +describe('Seite: Vorgangsansicht', () => { + describe('Szenario: Admin + Liste gefüllt', () => { +it('ändert den Namen nach Speichern', async () => { + const testData = structuredClone(baseData); + const oldName = testData.crimesList[0].name; + + render(TatortListPage, { props: { data: testData } }); + + const firstItem = screen.getAllByTestId('test-list-item')[0]; + await fireEvent.click(within(firstItem).getByTestId('edit-button')); + + const input = within(firstItem).getByRole('textbox'); + await fireEvent.input(input, { target: { value: 'Fall-B' } }); + +await fireEvent.click(within(firstItem).getByTestId('commit-button')); + +// Erwartung: fetch wurde aufgerufen +expect(global.fetch).toHaveBeenCalledWith( + expect.stringContaining(`/api/list/${testData.vorgang.vorgangToken}/${oldName}`), + expect.any(Object) +); + +// Erwartung: neuer Name ist sofort im DOM sichtbar +expect(within(firstItem).getByRole('textbox')).toHaveValue('Fall-B'); + +}); + +it('entfernt das Listenelement nach Löschen', async () => { + const testData = structuredClone(baseData); + testData.url = new URL('https://example.com/vorgang-1'); // Fix für Invalid URL + const toDelete = testData.crimesList[0]; + + global.fetch = vi.fn().mockResolvedValue({ ok: true }); + +render(TatortListPage, { props: { data: testData } }); + const deletedFirstItem = screen.getAllByTestId('test-list-item')[0]; + + + const deletedLink = within(deletedFirstItem).getByRole('link'); + + const deletedExpectedHref = `/view/${testData.vorgang.vorgangToken}/${toDelete.name}?pin=${testData.vorgang.vorgangPIN}`; + + expect(deletedLink).toBeInTheDocument(); + + expect(deletedLink).toHaveAttribute('href', deletedExpectedHref); + expect(deletedLink).toHaveAttribute('title', toDelete.name); + + +await fireEvent.click(within(deletedFirstItem).getByTestId('delete-button')); + +// Erwartung: fetch wurde aufgerufen +expect(global.fetch).toHaveBeenCalledWith( + expect.stringContaining(`/api/vorgang-1/${toDelete.name}`), + expect.any(Object) +); + +// Erwartung: Element ist nicht mehr im DOM +expect(within(deletedFirstItem).getByRole('textbox')).toHaveValue(toDelete.name); + }); + }); +}); + + diff --git a/tests/TatortList.view.test.ts b/tests/TatortList.view.test.ts index f47f86e..878da66 100644 --- a/tests/TatortList.view.test.ts +++ b/tests/TatortList.view.test.ts @@ -1,58 +1,11 @@ import { render, screen, within } from '@testing-library/svelte'; import { describe, expect, it, test } from "vitest"; import TatortListPage from "../src/routes/(token-based)/list/[vorgang]/+page.svelte"; - -const testUser = { - admin: true, -exp: 1757067123, -iat: 1757063523, -id: "admin", -} -const testCrimesList = [ - { - name: 'model-A', - lastModified: '2025-08-28T09:44:12.453Z', - etag: '558f35716f6af953f9bb5d75f6d77e6a', - size: 8947140, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - }, - { - name: 'model-z', - lastModified: '2025-08-28T10:37:20.142Z', - etag: '43e3989c32c4682bee407baaf83b6fa0', - size: 35788560, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - } - ]; - -const testVorgangsList = [ - { -vorgangName: "vorgang-1", -vorgangPIN: "pin-123", -vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" - }, - { -vorgangName: "vorgang-2", -vorgangPIN: "pin-2", -vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" - } - -] - -const baseData = { - user: testUser, - vorgang: testVorgangsList[0], - vorgangList: testVorgangsList, - crimesList: testCrimesList, - url: URL, - crimeNames: [ "modell-A" ] -} - +import { baseData } from './fixtures'; describe('Seite: Vorgangsansicht', () => { test.todo('zeigt PIN und Share-Link, wenn Admin'); + test.todo('zeigt PIN und Share-Link disabeld, wenn Liste leer') describe('Szenario: Liste leer (unabhängig von Rolle)', () => { it('zeigt Hinweistext bei leerer Liste', () => { @@ -105,17 +58,28 @@ describe('Seite: Vorgangsansicht', () => { }); describe('Szenario: Admin + Liste gefüllt', () => { - it('zeigt PIN und Share-Link disabeld, wenn Liste leer', () => { }); - it('zeigt PIN und Share-Link disabeld=false', () => { }); - it('zeigt Listeneinträge mit Edit/Delete', () => { }); - it('gibt Edit/Delete-Events korrekt weiter', () => { }); - }); + const testData = { ...baseData, user: { ...baseData.user, admin: true }}; + it('zeigt Listeneinträge mit Komponente NameItemEditor', () => { + const { getAllByTestId } = render(TatortListPage, {props:{data: testData}}); + const items = getAllByTestId('test-nameItemEditor'); + + expect(items.length).toBeGreaterThan(0); + }); + }); + describe('Szenario: Viewer + Liste gefüllt', () => { - it('zeigt Listeneinträge ohne Edit/Delete', () => { }); - it('zeigt Link und Änderungsdatum', () => { }); - it('zeigt keinen Share-Link oder PIN', () => { }); - }); + const testData = { ...baseData, user: { ...baseData.user, admin: false }}; + it('zeigt Listeneinträge mit p', () => { + render(TatortListPage, { props: { data: testData } }); + const paragraphs = screen.queryAllByTestId('test-nameItem-p'); - test.todo('Modal testen, wenn open') + expect(paragraphs).toHaveLength(testData.crimesList.length); + paragraphs.forEach((p, i) => { + expect(p).toHaveTextContent(testData.crimesList[i].name); + }); + }); + + test.todo('zeigt keinen Share-Link oder PIN') + test.todo('Modal testen, wenn open') + }); }); - diff --git a/tests/VorgangList.view.test.ts b/tests/VorgangList.view.test.ts index ae467e4..36334e2 100644 --- a/tests/VorgangList.view.test.ts +++ b/tests/VorgangList.view.test.ts @@ -1,54 +1,7 @@ import { render } from '@testing-library/svelte'; import { describe, expect, it } from "vitest"; import VorgangListPage from '../src/routes/(angemeldet)/list/+page.svelte'; -const testUser = { - admin: true, -exp: 1757067123, -iat: 1757063523, -id: "admin", -} -const testCrimesList = [ - { - name: 'model-A', - lastModified: '2025-08-28T09:44:12.453Z', - etag: '558f35716f6af953f9bb5d75f6d77e6a', - size: 8947140, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - }, - { - name: 'model-z', - lastModified: '2025-08-28T10:37:20.142Z', - etag: '43e3989c32c4682bee407baaf83b6fa0', - size: 35788560, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - } - ]; - -const testVorgangsList = [ - { -vorgangName: "vorgang-1", -vorgangPIN: "pin-123", -vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" - }, - { -vorgangName: "vorgang-2", -vorgangPIN: "pin-2", -vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" - } - -] - -const baseData = { - user: testUser, - vorgang: testVorgangsList[0], - vorgangList: testVorgangsList, - crimesList: testCrimesList, - url: URL, - crimeNames: [ "modell-A" ], - -} +import { baseData } from './fixtures'; describe('Vorgänge Liste Page EmptyList-Komponente View', ()=>{ it('zeigt EmptyList-Komponente an, wenn Liste leer ist', () => { diff --git a/tests/fixtures.ts b/tests/fixtures.ts new file mode 100644 index 0000000..cb40c9f --- /dev/null +++ b/tests/fixtures.ts @@ -0,0 +1,47 @@ +const testUser = { + admin: true, +exp: 1757067123, +iat: 1757063523, +id: "admin", +} +const testCrimesList = [ + { + name: 'modell-A', + lastModified: '2025-08-28T09:44:12.453Z', + etag: '558f35716f6af953f9bb5d75f6d77e6a', + size: 8947140, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + }, + { + name: 'Fall-A', + lastModified: '2025-08-28T10:37:20.142Z', + etag: '43e3989c32c4682bee407baaf83b6fa0', + size: 35788560, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + } + ]; + +const testVorgangsList = [ + { +vorgangName: "vorgang-1", +vorgangPIN: "pin-123", +vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" + }, + { +vorgangName: "vorgang-2", +vorgangPIN: "pin-2", +vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" + } + +] + +export const baseData = { + user: testUser, + vorgang: testVorgangsList[0], + vorgangList: testVorgangsList, + crimesList: testCrimesList, + url: new URL(`https://example.com/${testVorgangsList[0].vorgangToken}`), + crimeNames: [ "modell-A", "Fall-A" ], +} From 47ca05f2d43501b770b150b1d19dc9de5cc493a7 Mon Sep 17 00:00:00 2001 From: mina Date: Wed, 10 Sep 2025 17:50:10 +0200 Subject: [PATCH 04/12] save changes, bin aber noch nicht fertig --- src/lib/components/NameItemEditor.svelte | 92 +++++--------- .../(token-based)/list/[vorgang]/+page.svelte | 32 ++--- tests/TatortList.test.ts | 113 ++++++++++-------- tests/TatortList.view.test.ts | 22 ++-- 4 files changed, 125 insertions(+), 134 deletions(-) diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index a1207ea..ddfab02 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -10,27 +10,20 @@ token?: string; // add other properties as needed } - let { - list, - editedName = $bindable(), - currentName, - onSave = () => {}, - onDelete = () => {} - } = $props(); + let { list, currentName, onSave = () => {}, onDelete = () => {} } = $props(); let localName = $state(currentName); - let wasCancelled = $state(false); - - let error: string = $derived(validateName(localName)); let isEditing = $state(false); - let inputRef: HTMLInputElement; - function validateName(name: string) { + let error = $derived(() => validateName(localName)); + + let inputRef = $state(null); + + function validateName(name: string | undefined | null) { + if (!name) return 'Name darf nicht leer sein.'; const trimmed = name.trim(); - if (!trimmed) { - return 'Name darf nicht leer sein.'; - } + if (!trimmed) return 'Name darf nicht leer sein.'; const duplicate = list.some( (item: ListItem) => item.name === trimmed && item.name !== currentName @@ -41,67 +34,44 @@ return ''; } - function commitIfValid() { - if (!error && !wasCancelled && localName != currentName) { - const trimmedName: string = localName.trim(); - inputRef?.blur(); - isEditing = false; - onSave(trimmedName, currentName); - } else { - cancelEdit(); - } + function startEdit() { + isEditing = true; + tick().then(() => inputRef?.focus()); } - function resetEdit() { - inputRef?.blur(); + function cancelEdit() { + localName = currentName; + isEditing = false; + } + + function commitEdit() { + if (!error() && localName != currentName) onSave(localName, currentName); + isEditing = false; } function handleKeydown(event: KeyboardEvent) { - if (event.key === 'Enter') { - event.preventDefault(); - commitIfValid(); - } else if (event.key === 'Escape') { - event.preventDefault(); - localName = currentName; - resetEdit(); - } - } - - async function startEdit() { - isEditing = true; - await tick(); - inputRef?.focus(); - } - - function cancelEdit() { - resetEdit(); - wasCancelled = true; - localName = currentName; + if (event.key === 'Enter') commitEdit(); + if (event.key === 'Escape') cancelEdit(); }

    - { - isEditing = true; - }} - onblur={commitIfValid} - onkeydown={handleKeydown} - /> {#if isEditing} - + + {:else} + {localName} {/if} - {#if error} -

    {error}

    + {#if error()} +

    {error()}

    {/if}
    diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index 1d65d2a..c84fefb 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -25,7 +25,6 @@ prefix?: string; // add other properties as needed } - console.log(data.url); let vorgangName: string = data.vorgang.vorgangName; let crimesList: ListItem[] = $state(data.crimesList); @@ -44,6 +43,8 @@ async function handleSave(newName: string, oldName: string) { open = true; inProgress = true; + console.log('debug handleSave', newName, oldName); + try { const res = await fetch(`/api/list/${vorgangToken}/${oldName}`, { method: 'PUT', @@ -51,18 +52,18 @@ 'Content-Type': 'application/json' }, body: JSON.stringify({ vorgangToken, oldName, newName }) - }) - .then(() => { - inProgress = false; - invalidateAll(); - crimesList = data.crimesList; - open = false; - }) - .catch((err) => { - inProgress = false; - isError = true; - console.log('ERROR', err); - }); + }); + + if (res.ok) { + inProgress = false; + invalidateAll(); + data.crimesList = newName; + open = false; + } else { + inProgress = false; + isError = true; + throw new Error('Fehler beim Speichern'); + } } catch (err) { isError = true; inProgress = false; @@ -94,7 +95,7 @@ .catch((err) => { isError = true; inProgress = false; - console.log('ERROR', err); + console.error('ERROR', err); }); } catch (err) { isError = true; @@ -145,7 +146,7 @@ Mit freundlichen Grüßen, {#if isEmptyList} {:else} - {#each data.crimesList as item, crimeListItemIndex} + {#each data.crimesList as item}
  • ({ invalidateAll: vi.fn() })); -// Minimaler fetch-Mock -global.fetch = vi.fn().mockResolvedValue({ ok: true }); describe('Seite: Vorgangsansicht', () => { - describe('Szenario: Admin + Liste gefüllt', () => { -it('ändert den Namen nach Speichern', async () => { - const testData = structuredClone(baseData); - const oldName = testData.crimesList[0].name; + test.todo('Share Link disabled wenn Liste leer'); + describe('Szenario: Admin + Liste gefüllt', () => { + test.todo('Share Link Link generierung richtig'); - render(TatortListPage, { props: { data: testData } }); + it('ändert den Namen nach Speichern', async () => { + const testData = structuredClone(baseData); + const oldName = testData.crimesList[0].name; + const newName = 'Fall-B'; + const list = testData.crimesList + const vorgangToken = testData.vorgang.vorgangToken + // Minimaler fetch-Mock +global.fetch = vi.fn().mockResolvedValue({ ok: true }) as typeof fetch; - const firstItem = screen.getAllByTestId('test-list-item')[0]; - await fireEvent.click(within(firstItem).getByTestId('edit-button')); + const { getAllByTestId } = render(TatortListPage, { props: { data: testData } }); - const input = within(firstItem).getByRole('textbox'); - await fireEvent.input(input, { target: { value: 'Fall-B' } }); + const firstItem = getAllByTestId('test-list-item')[0]; + const editButton = within(firstItem).getByTestId('edit-button'); + await fireEvent.click(editButton); -await fireEvent.click(within(firstItem).getByTestId('commit-button')); + const input = within(firstItem).getByTestId('test-input'); + expect(input).toHaveValue(oldName) + await fireEvent.input(input, { target: { value: newName } }); -// Erwartung: fetch wurde aufgerufen -expect(global.fetch).toHaveBeenCalledWith( - expect.stringContaining(`/api/list/${testData.vorgang.vorgangToken}/${oldName}`), - expect.any(Object) -); + const commitButton = within(firstItem).getByTestId('commit-button'); + await fireEvent.click(commitButton); -// Erwartung: neuer Name ist sofort im DOM sichtbar -expect(within(firstItem).getByRole('textbox')).toHaveValue('Fall-B'); + // const fetchMock = global.fetch as ReturnType; + // console.log('Fetch calls:', fetchMock.mock.calls); -}); + // // Erwartung: fetch wurde aufgerufen + // expect(global.fetch).toHaveBeenCalledWith( + // expect.stringContaining(`/api/list/${vorgangToken}/${oldName}`), + // expect.objectContaining({ + // method: 'PUT', + // headers: { 'Content-Type': 'application/json' }, + // body: JSON.stringify({vorgangToken, oldName, newName}) + // }) + // ); -it('entfernt das Listenelement nach Löschen', async () => { - const testData = structuredClone(baseData); - testData.url = new URL('https://example.com/vorgang-1'); // Fix für Invalid URL - const toDelete = testData.crimesList[0]; + // expect(invalidateAll).toHaveBeenCalled(); + // Erwartung: neuer Name ist sofort im DOM sichtbar + // expect(within(firstItem).getByRole('textbox')).toHaveValue(newName); + // const editedLink = within(firstItem).getByRole('link'); + // const editedExpectedHref = `/view/${vorgangToken}/${newName}?pin=${testData.vorgang.vorgangPIN}`; - global.fetch = vi.fn().mockResolvedValue({ ok: true }); + // expect(editedLink).toBeInTheDocument(); + // expect(editedLink).toHaveAttribute('href', editedExpectedHref); + // expect(editedLink).toHaveAttribute('title', newName); + }); -render(TatortListPage, { props: { data: testData } }); - const deletedFirstItem = screen.getAllByTestId('test-list-item')[0]; + // it('entfernt das Listenelement nach Löschen', async () => { + // const testData = structuredClone(baseData); + // testData.url = new URL('https://example.com/vorgang-1'); // Fix für Invalid URL + // const toDelete = testData.crimesList[0]; + // global.fetch = vi.fn().mockResolvedValue({ ok: true }); - const deletedLink = within(deletedFirstItem).getByRole('link'); + // render(TatortListPage, { props: { data: testData } }); + // const deletedFirstItem = screen.getAllByTestId('test-list-item')[0]; + // const deletedLink = within(deletedFirstItem).getByRole('link'); + // const deletedExpectedHref = `/view/${testData.vorgang.vorgangToken}/${toDelete.name}?pin=${testData.vorgang.vorgangPIN}`; - const deletedExpectedHref = `/view/${testData.vorgang.vorgangToken}/${toDelete.name}?pin=${testData.vorgang.vorgangPIN}`; + // expect(deletedLink).toBeInTheDocument(); + // expect(deletedLink).toHaveAttribute('href', deletedExpectedHref); + // expect(deletedLink).toHaveAttribute('title', toDelete.name); + // await fireEvent.click(within(deletedFirstItem).getByTestId('delete-button')); - expect(deletedLink).toBeInTheDocument(); + // // Erwartung: fetch wurde aufgerufen + // expect(global.fetch).toHaveBeenCalledWith( + // expect.stringContaining(`/api/vorgang-1/${toDelete.name}`), + // expect.any(Object) + // ); - expect(deletedLink).toHaveAttribute('href', deletedExpectedHref); - expect(deletedLink).toHaveAttribute('title', toDelete.name); - - -await fireEvent.click(within(deletedFirstItem).getByTestId('delete-button')); - -// Erwartung: fetch wurde aufgerufen -expect(global.fetch).toHaveBeenCalledWith( - expect.stringContaining(`/api/vorgang-1/${toDelete.name}`), - expect.any(Object) -); - -// Erwartung: Element ist nicht mehr im DOM -expect(within(deletedFirstItem).getByRole('textbox')).toHaveValue(toDelete.name); - }); - }); + // // Erwartung: Element ist nicht mehr im DOM + // expect(within(deletedFirstItem).getByRole('textbox')).toHaveValue(toDelete.name); + // }); + }); }); diff --git a/tests/TatortList.view.test.ts b/tests/TatortList.view.test.ts index 878da66..929649b 100644 --- a/tests/TatortList.view.test.ts +++ b/tests/TatortList.view.test.ts @@ -9,19 +9,21 @@ describe('Seite: Vorgangsansicht', () => { describe('Szenario: Liste leer (unabhängig von Rolle)', () => { it('zeigt Hinweistext bei leerer Liste', () => { - const testData = { ...baseData, crimesList: [] }; - const { getByTestId } = render(TatortListPage, {props:{data: testData}}); + const testData = { ...baseData, crimesList: [] }; + const { getByTestId } = render(TatortListPage, {props:{data: testData}}); - expect(getByTestId('empty-list')).toBeInTheDocument(); + expect(getByTestId('empty-list')).toBeInTheDocument(); }); - it('zeigt keinen Listeneintrag', () => { - const items = screen.queryAllByTestId('test-list-item'); - expect(items).toHaveLength(0); + it('zeigt keinen Listeneintrag', () => { + const items = screen.queryAllByTestId('test-list-item'); + + expect(items).toHaveLength(0); }); }); + describe('Szenario: Liste gefüllt (unabhängig von Rolle)', () => { - it('rendert mindestens ein Listenelement bei vorhandenen crimesList-Daten und prüft ob Link vorhanden', () => { + it('rendert mindestens ein Listenelement bei vorhandenen crimesList-Daten', () => { const testData = { ...baseData }; const { queryAllByTestId } = render(TatortListPage, {props:{data: testData}}); const items = queryAllByTestId('test-list-item'); @@ -65,7 +67,9 @@ describe('Seite: Vorgangsansicht', () => { expect(items.length).toBeGreaterThan(0); }); - }); + + test.todo('Modal testen, wenn open') + }); describe('Szenario: Viewer + Liste gefüllt', () => { const testData = { ...baseData, user: { ...baseData.user, admin: false }}; @@ -80,6 +84,6 @@ describe('Seite: Vorgangsansicht', () => { }); test.todo('zeigt keinen Share-Link oder PIN') - test.todo('Modal testen, wenn open') + }); }); From bcf24122bca50145b9a7fa6204b5ca4c95cc6f14 Mon Sep 17 00:00:00 2001 From: mina Date: Thu, 11 Sep 2025 16:52:54 +0200 Subject: [PATCH 05/12] fixed tests and Code edit and delete Name in TatortList --- src/lib/components/NameItemEditor.svelte | 54 ++++--- .../(token-based)/list/[vorgang]/+page.svelte | 60 ++++--- tests/TatortList.test.ts | 147 ++++++++++-------- tests/fixtures.ts | 6 +- 4 files changed, 139 insertions(+), 128 deletions(-) diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index ddfab02..58c4297 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -8,35 +8,33 @@ interface ListItem { name: string; token?: string; - // add other properties as needed } - let { list, currentName, onSave = () => {}, onDelete = () => {} } = $props(); - let localName = $state(currentName); - let isEditing = $state(false); + export let list: ListItem[] = []; + export let currentName: string = ''; + export let onSave: (n: string, o: string) => unknown = () => {}; + export let onDelete: (n: string) => unknown = () => {}; - let error = $derived(() => validateName(localName)); + // lokaler State + let localName = currentName; + let isEditing = false; + let inputRef: HTMLInputElement | null = null; - let inputRef = $state(null); - - function validateName(name: string | undefined | null) { - if (!name) return 'Name darf nicht leer sein.'; - const trimmed = name.trim(); + $: error = validateName(localName); + function validateName(name: string): string { + const trimmed = name?.trim() ?? ''; if (!trimmed) return 'Name darf nicht leer sein.'; - - const duplicate = list.some( - (item: ListItem) => item.name === trimmed && item.name !== currentName - ); - - if (duplicate) return 'Name existiert bereits.'; - + if (list.some((item) => item.name === trimmed && item.name !== currentName)) { + return 'Name existiert bereits.'; + } return ''; } - function startEdit() { + async function startEdit() { isEditing = true; - tick().then(() => inputRef?.focus()); + await tick(); + inputRef?.focus(); } function cancelEdit() { @@ -45,7 +43,7 @@ } function commitEdit() { - if (!error() && localName != currentName) onSave(localName, currentName); + if (!error && localName != currentName) onSave(localName, currentName); isEditing = false; } @@ -54,6 +52,10 @@ if (event.key === 'Enter') commitEdit(); if (event.key === 'Escape') cancelEdit(); } + + function handleDeleteClick() { + onDelete(currentName); + }
    @@ -64,14 +66,18 @@ bind:value={localName} onkeydown={handleKeydown} /> - + {:else} {localName} - + {/if} - {#if error()} -

    {error()}

    + {#if error} +

    {error}

    {/if}
    diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index c84fefb..e22bc2c 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -26,11 +26,12 @@ // add other properties as needed } + // 2) Lokaler, reaktiver State mit $state + let crimesList = $state(data.crimesList); let vorgangName: string = data.vorgang.vorgangName; - let crimesList: ListItem[] = $state(data.crimesList); const vorgangPIN: string = data.vorgang.vorgangPIN; let vorgangToken: string = data.vorgang.vorgangToken; - let isEmptyList = $derived(crimesList && crimesList.length === 0); + let isEmptyList = $derived(crimesList.length === 0); //Variablen für Modal let open = $state(false); @@ -38,11 +39,12 @@ let isError = $state(false); //Variable um nur admin UI anzuzeigen - let admin = data?.user?.admin; + let admin = $state(data?.user?.admin); async function handleSave(newName: string, oldName: string) { open = true; inProgress = true; + isError = false; console.log('debug handleSave', newName, oldName); try { @@ -54,20 +56,15 @@ body: JSON.stringify({ vorgangToken, oldName, newName }) }); - if (res.ok) { - inProgress = false; - invalidateAll(); - data.crimesList = newName; - open = false; - } else { - inProgress = false; - isError = true; + if (!res.ok) { throw new Error('Fehler beim Speichern'); } + await invalidateAll(); + open = false; } catch (err) { + console.error('⚠️ Netzwerkfehler beim Speichern', err); isError = true; - inProgress = false; - console.error('⚠️ Netzwerkfehler:', err); + } finally { inProgress = false; } } @@ -75,32 +72,31 @@ async function handleDelete(tatort: string) { open = true; inProgress = true; - let url = new URL(data.url); - url.pathname += `/${tatort}`; + isError = false; + let path = new URL(data.url).pathname; + path += `/${tatort}`; try { - const res = await fetch(`/api${url.pathname}`, { + const res = await fetch(`/api${path}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ vorgangToken, tatort }) - }) - .then(() => { - inProgress = false; - console.log('🗑️ Erfolgreich gelöscht:', url.pathname); - invalidateAll(); - crimesList = data.crimesList; - }) - .catch((err) => { - isError = true; - inProgress = false; - console.error('ERROR', err); - }); + }); + + if (!res.ok) { + throw new Error('Fehler beim Löschen'); + } + crimesList = crimesList.filter((i) => i.name !== tatort); + await invalidateAll(); + console.log('🗑️ Erfolgreich gelöscht:', path); + open = false; } catch (err) { + console.error('⚠️ Netzwerkfehler beim Speichern', err); isError = true; + } finally { inProgress = false; - console.error('⚠️ Netzwerkfehler beim Löschen:', err); } } @@ -129,7 +125,7 @@ Mit freundlichen Grüßen, } -{#if data.vorgang && data.crimesList} +{#if data.vorgang && crimesList}

    Vorgang {vorgangName}

    @@ -146,7 +142,7 @@ Mit freundlichen Grüßen, {#if isEmptyList} {:else} - {#each data.crimesList as item} + {#each crimesList as item (item.name)}
  • {#if admin} ({ - invalidateAll: vi.fn() -})); +vi.spyOn(nav, 'invalidateAll').mockResolvedValue(); +global.fetch = vi.fn().mockResolvedValue({ ok: true }); describe('Seite: Vorgangsansicht', () => { test.todo('Share Link disabled wenn Liste leer'); - describe('Szenario: Admin + Liste gefüllt', () => { + describe('Szenario: Admin + Liste gefüllt - Funktionalität', () => { test.todo('Share Link Link generierung richtig'); - it('ändert den Namen nach Speichern', async () => { - const testData = structuredClone(baseData); - const oldName = testData.crimesList[0].name; - const newName = 'Fall-B'; - const list = testData.crimesList - const vorgangToken = testData.vorgang.vorgangToken - // Minimaler fetch-Mock -global.fetch = vi.fn().mockResolvedValue({ ok: true }) as typeof fetch; - const { getAllByTestId } = render(TatortListPage, { props: { data: testData } }); + it('führt PUT-Request aus und aktualisiert UI nach onSave', async () => { + const data = structuredClone(baseData); + const oldName = data.crimesList[0].name; + const newName = 'Fall-C'; - const firstItem = getAllByTestId('test-list-item')[0]; - const editButton = within(firstItem).getByTestId('edit-button'); - await fireEvent.click(editButton); + render(TatortListPage, { props: { data } }); + const listItem = screen.getAllByTestId('test-list-item')[0]; + // teste ob alter Name angezeigt: + expect(listItem).toHaveTextContent(oldName); - const input = within(firstItem).getByTestId('test-input'); - expect(input).toHaveValue(oldName) - await fireEvent.input(input, { target: { value: newName } }); + // Editmodus + await fireEvent.click(within(listItem).getByTestId('edit-button')); + const input = within(listItem).getByTestId('test-input'); + await fireEvent.input(input, { target: { value: newName } }); - const commitButton = within(firstItem).getByTestId('commit-button'); - await fireEvent.click(commitButton); + // Commit + await fireEvent.click(within(listItem).getByTestId('commit-button')); + await Promise.resolve(); // wartet reaktive Updates ab - // const fetchMock = global.fetch as ReturnType; - // console.log('Fetch calls:', fetchMock.mock.calls); + // FETCH-CHECK + expect(global.fetch).toHaveBeenCalledWith( + `/api/list/${data.vorgang.vorgangToken}/${oldName}`, + expect.objectContaining({ + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + vorgangToken: data.vorgang.vorgangToken, + oldName, + newName + }) + }) + ); - // // Erwartung: fetch wurde aufgerufen - // expect(global.fetch).toHaveBeenCalledWith( - // expect.stringContaining(`/api/list/${vorgangToken}/${oldName}`), - // expect.objectContaining({ - // method: 'PUT', - // headers: { 'Content-Type': 'application/json' }, - // body: JSON.stringify({vorgangToken, oldName, newName}) - // }) - // ); + // INVALIDATE-CHECK + expect(nav.invalidateAll).toHaveBeenCalled(); - // expect(invalidateAll).toHaveBeenCalled(); - // Erwartung: neuer Name ist sofort im DOM sichtbar - // expect(within(firstItem).getByRole('textbox')).toHaveValue(newName); - // const editedLink = within(firstItem).getByRole('link'); - // const editedExpectedHref = `/view/${vorgangToken}/${newName}?pin=${testData.vorgang.vorgangPIN}`; - - // expect(editedLink).toBeInTheDocument(); - // expect(editedLink).toHaveAttribute('href', editedExpectedHref); - // expect(editedLink).toHaveAttribute('title', newName); + // UI-UPDATE + expect(within(listItem).getByText(newName)).toBeInTheDocument(); }); - // it('entfernt das Listenelement nach Löschen', async () => { - // const testData = structuredClone(baseData); - // testData.url = new URL('https://example.com/vorgang-1'); // Fix für Invalid URL - // const toDelete = testData.crimesList[0]; + it('führt DELETE-Request aus und entfernt Element aus UI', async () => { + const testData = structuredClone(baseData); + const oldName = testData.crimesList[0].name; - // global.fetch = vi.fn().mockResolvedValue({ ok: true }); + // Rendern und initiale Liste prüfen + render(TatortListPage, { props: { data: testData } }); + const initialItems = screen.getAllByTestId('test-list-item'); + expect(initialItems).toHaveLength(testData.crimesList.length); - // render(TatortListPage, { props: { data: testData } }); - // const deletedFirstItem = screen.getAllByTestId('test-list-item')[0]; - // const deletedLink = within(deletedFirstItem).getByRole('link'); - // const deletedExpectedHref = `/view/${testData.vorgang.vorgangToken}/${toDelete.name}?pin=${testData.vorgang.vorgangPIN}`; + const listItem = screen.getAllByTestId('test-list-item')[0]; + // teste ob alter Name angezeigt: + expect(listItem).toHaveTextContent(oldName); + // Delete-Button klicken + const del = within(listItem).getByTestId('delete-button'); + expect(del).toBeInTheDocument() + await fireEvent.click(within(listItem).getByTestId('delete-button')); + // auf reaktive Updates warten + await tick(); - // expect(deletedLink).toBeInTheDocument(); - // expect(deletedLink).toHaveAttribute('href', deletedExpectedHref); - // expect(deletedLink).toHaveAttribute('title', toDelete.name); - // await fireEvent.click(within(deletedFirstItem).getByTestId('delete-button')); + // FETCH-CHECK: URL & Payload + // entspricht: new URL(data.url).pathname + '/' + oldName + const expectedPath = new URL(testData.url).pathname; + expect(global.fetch).toHaveBeenCalledWith( + `/api${expectedPath}/${oldName}`, + expect.objectContaining({ + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + vorgangToken: testData.vorgang.vorgangToken, + tatort: oldName + }) + }) + ); + // INVALIDATE-CHECK + expect(nav.invalidateAll).toHaveBeenCalled(); - // // Erwartung: fetch wurde aufgerufen - // expect(global.fetch).toHaveBeenCalledWith( - // expect.stringContaining(`/api/vorgang-1/${toDelete.name}`), - // expect.any(Object) - // ); + // UI-UPDATE: Element entfernt + const updatedItems = screen.queryAllByTestId('test-list-item'); + expect(updatedItems).toHaveLength(testData.crimesList.length - 1); + expect(screen.queryByText(oldName)).toBeNull(); - // // Erwartung: Element ist nicht mehr im DOM - // expect(within(deletedFirstItem).getByRole('textbox')).toHaveValue(toDelete.name); - // }); + }); }); }); - - diff --git a/tests/fixtures.ts b/tests/fixtures.ts index cb40c9f..4126fb5 100644 --- a/tests/fixtures.ts +++ b/tests/fixtures.ts @@ -6,7 +6,7 @@ id: "admin", } const testCrimesList = [ { - name: 'modell-A', + name: 'Fall-A', lastModified: '2025-08-28T09:44:12.453Z', etag: '558f35716f6af953f9bb5d75f6d77e6a', size: 8947140, @@ -14,7 +14,7 @@ const testCrimesList = [ show_button: true }, { - name: 'Fall-A', + name: 'Fall-B', lastModified: '2025-08-28T10:37:20.142Z', etag: '43e3989c32c4682bee407baaf83b6fa0', size: 35788560, @@ -42,6 +42,6 @@ export const baseData = { vorgang: testVorgangsList[0], vorgangList: testVorgangsList, crimesList: testCrimesList, - url: new URL(`https://example.com/${testVorgangsList[0].vorgangToken}`), + url: `https://example.com/${testVorgangsList[0].vorgangToken}`, crimeNames: [ "modell-A", "Fall-A" ], } From 012f7ee3e891377e84ec60f7089fa87d1a431069 Mon Sep 17 00:00:00 2001 From: mina Date: Fri, 12 Sep 2025 10:23:47 +0200 Subject: [PATCH 06/12] =?UTF-8?q?implement=20testsNameItemEditor=20funktio?= =?UTF-8?q?nalit=C3=A4t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/NameItemEditor.svelte | 3 +- .../(token-based)/list/[vorgang]/+page.svelte | 2 - tests/ComponentNameItemEditor.test.ts | 144 ++++++++++++++++++ tests/ComponentNameItemEditor.view.test.ts | 43 ------ tests/TatortList.test.ts | 33 +--- 5 files changed, 152 insertions(+), 73 deletions(-) create mode 100644 tests/ComponentNameItemEditor.test.ts delete mode 100644 tests/ComponentNameItemEditor.view.test.ts diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index 58c4297..e39143e 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -11,11 +11,10 @@ } export let list: ListItem[] = []; - export let currentName: string = ''; + export let currentName: string; export let onSave: (n: string, o: string) => unknown = () => {}; export let onDelete: (n: string) => unknown = () => {}; - // lokaler State let localName = currentName; let isEditing = false; let inputRef: HTMLInputElement | null = null; diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index e22bc2c..6a429c3 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -26,7 +26,6 @@ // add other properties as needed } - // 2) Lokaler, reaktiver State mit $state let crimesList = $state(data.crimesList); let vorgangName: string = data.vorgang.vorgangName; const vorgangPIN: string = data.vorgang.vorgangPIN; @@ -38,7 +37,6 @@ let inProgress = $state(false); let isError = $state(false); - //Variable um nur admin UI anzuzeigen let admin = $state(data?.user?.admin); async function handleSave(newName: string, oldName: string) { diff --git a/tests/ComponentNameItemEditor.test.ts b/tests/ComponentNameItemEditor.test.ts new file mode 100644 index 0000000..ff1d504 --- /dev/null +++ b/tests/ComponentNameItemEditor.test.ts @@ -0,0 +1,144 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, test, vi } from "vitest"; +import NameItemEditor from '$lib/components/NameItemEditor.svelte'; +import { baseData } from './fixtures'; + + + +const testCrimesListIndex = 0; +const testItem = baseData.crimesList[testCrimesListIndex]; + const testCurrentName = testItem.name; + const testLocalName = 'Fall-C' + + +describe('NameItemEditor - Funktionalität', () => { + const onSave = vi.fn(); + const onDelete = vi.fn(); + const baseProps = { + list: baseData.crimesList, + currentName: testCurrentName, + onSave, + onDelete} + + test.todo('FocusIn nach Klick auf edit') + + it('zeigt initial Edit/Delete Buttons und aktuellen Namen', () => { + render(NameItemEditor, {props: baseProps}); + + expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId('delete-button')).toBeInTheDocument(); + expect(screen.queryByTestId('commit-button')).toBeNull(); + expect(screen.queryByTestId('cancel-button')).toBeNull(); + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + }); + + it('wechselt zu Commit/Cancel nach Klick auf Edit', async () => { + render(NameItemEditor, {props: baseProps}); + await fireEvent.click(screen.getByTestId('edit-button')); + const input = screen.getByTestId('test-input'); + + expect(screen.getByTestId('commit-button')).toBeInTheDocument(); + expect(screen.getByTestId('cancel-button')).toBeInTheDocument(); + expect(screen.queryByTestId('edit-button')).toBeNull(); + expect(screen.queryByTestId('delete-button')).toBeNull(); + expect(screen.getAllByRole('textbox')).toHaveLength(1); + expect(input).toHaveValue(testCurrentName); + }); + + it('zeigt Fehlermeldung bei leerem Namen', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: '' } }); + + expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('entfernt Fehlermeldung live beim nächsten gültigen Tastendruck', async () => { + render(NameItemEditor, { + props: { + list: baseData.crimesList, + currentName: baseData.crimesList[0].name, + onSave: vi.fn(), + onDelete: vi.fn() + } + }); + + await fireEvent.click(screen.getByTestId('edit-button')); + const input = screen.getByTestId('test-input'); + + await fireEvent.input(input, { target: { value: '' } }); + expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); + + await fireEvent.input(input, { target: { value: 'Fall-C' } }); + + expect(screen.queryByText('Name darf nicht leer sein.')).toBeNull(); +}); + + it('zeigt Fehlermeldung bei Duplikat', async () => { + const duplicateName = baseData.crimesList[1].name; + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: duplicateName } }); + + expect(screen.getByText('Name existiert bereits.')).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('ruft onSave korrekt auf bei gültigem Namen', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: testLocalName } }); + await fireEvent.click(screen.getByTestId('commit-button')); + + expect(onSave).toHaveBeenCalledWith(testLocalName, testCurrentName); + }); + + it('ruft onDelete korrekt auf', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('delete-button')); + + expect(onDelete).toHaveBeenCalledWith(testCurrentName); + }); + + it('setzt Zustand zurück bei Cancel', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'Zwischentext' } }); + await fireEvent.click(screen.getByTestId('cancel-button')); + + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + }); + + it('triggert Save bei Enter-Taste', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'ViaEnter' } }); + await fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onSave).toHaveBeenCalledWith('ViaEnter', testCurrentName); + }); + + it('bricht ab bei Escape-Taste', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'Zwischentext' } }); + await fireEvent.keyDown(input, { key: 'Escape' }); + + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/ComponentNameItemEditor.view.test.ts b/tests/ComponentNameItemEditor.view.test.ts deleted file mode 100644 index d25b7a9..0000000 --- a/tests/ComponentNameItemEditor.view.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { fireEvent, render } from '@testing-library/svelte'; -import { describe, expect, it, vi } from "vitest"; -import NameItemEditor from '$lib/components/NameItemEditor.svelte'; -import { baseData } from './fixtures'; - - - -const testCrimesListIndex = 0; -const testItem = baseData.crimesList[testCrimesListIndex]; - const editedName = baseData.crimeNames[testCrimesListIndex]; - const currentName = testItem.name; - const onSave = vi.fn(); - const onDelete = vi.fn(); - -describe('Button-Anzeige mit Icons', () => { - const baseProps = {list: baseData.crimesList, editedName, currentName, onDelete, onSave} - it('zeigt Edit/Delete Buttons initial', () => { - const { getByTestId, queryByTestId } = render(NameItemEditor, { - props: baseProps - - }); - - expect(getByTestId('edit-button')).toBeInTheDocument(); - expect(getByTestId('delete-button')).toBeInTheDocument(); - expect(queryByTestId('commit-button')).toBeNull(); - expect(queryByTestId('cancel-button')).toBeNull(); - }); - - it('zeigt Commit/Cancel Buttons nach Klick auf Edit', async () => { - const { getByTestId, queryByTestId } = render(NameItemEditor, { - props: baseProps - }); - - await fireEvent.click(getByTestId('edit-button')); - - expect(getByTestId('commit-button')).toBeInTheDocument(); - expect(getByTestId('cancel-button')).toBeInTheDocument(); - expect(queryByTestId('edit-button')).toBeNull(); - expect(queryByTestId('delete-button')).toBeNull(); - }); - - -}); diff --git a/tests/TatortList.test.ts b/tests/TatortList.test.ts index dfcf3c8..459499e 100644 --- a/tests/TatortList.test.ts +++ b/tests/TatortList.test.ts @@ -1,4 +1,3 @@ -// @vitest-environment jsdom import { render, fireEvent, screen, within } from '@testing-library/svelte'; import { describe, it, expect, vi, test } from 'vitest'; import * as nav from '$app/navigation'; @@ -6,7 +5,6 @@ import TatortListPage from '../src/routes/(token-based)/list/[vorgang]/+page.sve import { baseData } from './fixtures'; import { tick } from 'svelte'; -// Mock für invalidateAll vi.spyOn(nav, 'invalidateAll').mockResolvedValue(); global.fetch = vi.fn().mockResolvedValue({ ok: true }); @@ -16,7 +14,6 @@ describe('Seite: Vorgangsansicht', () => { describe('Szenario: Admin + Liste gefüllt - Funktionalität', () => { test.todo('Share Link Link generierung richtig'); - it('führt PUT-Request aus und aktualisiert UI nach onSave', async () => { const data = structuredClone(baseData); const oldName = data.crimesList[0].name; @@ -24,19 +21,15 @@ describe('Seite: Vorgangsansicht', () => { render(TatortListPage, { props: { data } }); const listItem = screen.getAllByTestId('test-list-item')[0]; - // teste ob alter Name angezeigt: expect(listItem).toHaveTextContent(oldName); - // Editmodus await fireEvent.click(within(listItem).getByTestId('edit-button')); const input = within(listItem).getByTestId('test-input'); await fireEvent.input(input, { target: { value: newName } }); - // Commit await fireEvent.click(within(listItem).getByTestId('commit-button')); - await Promise.resolve(); // wartet reaktive Updates ab + await tick(); - // FETCH-CHECK expect(global.fetch).toHaveBeenCalledWith( `/api/list/${data.vorgang.vorgangToken}/${oldName}`, expect.objectContaining({ @@ -50,10 +43,7 @@ describe('Seite: Vorgangsansicht', () => { }) ); - // INVALIDATE-CHECK expect(nav.invalidateAll).toHaveBeenCalled(); - - // UI-UPDATE expect(within(listItem).getByText(newName)).toBeInTheDocument(); }); @@ -61,26 +51,21 @@ describe('Seite: Vorgangsansicht', () => { const testData = structuredClone(baseData); const oldName = testData.crimesList[0].name; - // Rendern und initiale Liste prüfen render(TatortListPage, { props: { data: testData } }); const initialItems = screen.getAllByTestId('test-list-item'); expect(initialItems).toHaveLength(testData.crimesList.length); const listItem = screen.getAllByTestId('test-list-item')[0]; - // teste ob alter Name angezeigt: expect(listItem).toHaveTextContent(oldName); - // Delete-Button klicken const del = within(listItem).getByTestId('delete-button'); expect(del).toBeInTheDocument() await fireEvent.click(within(listItem).getByTestId('delete-button')); - // auf reaktive Updates warten await tick(); - // FETCH-CHECK: URL & Payload - // entspricht: new URL(data.url).pathname + '/' + oldName - const expectedPath = new URL(testData.url).pathname; + let expectedPath = new URL(testData.url).pathname; + expectedPath += `/${oldName}` expect(global.fetch).toHaveBeenCalledWith( - `/api${expectedPath}/${oldName}`, + `/api${expectedPath}`, expect.objectContaining({ method: 'DELETE', headers: { 'Content-Type': 'application/json' }, @@ -90,14 +75,10 @@ describe('Seite: Vorgangsansicht', () => { }) }) ); - // INVALIDATE-CHECK expect(nav.invalidateAll).toHaveBeenCalled(); - - // UI-UPDATE: Element entfernt - const updatedItems = screen.queryAllByTestId('test-list-item'); - expect(updatedItems).toHaveLength(testData.crimesList.length - 1); - expect(screen.queryByText(oldName)).toBeNull(); - + const updatedItems = screen.queryAllByTestId('test-list-item'); + expect(updatedItems).toHaveLength(testData.crimesList.length - 1); + expect(screen.queryByText(oldName)).toBeNull(); }); }); }); From 03835fe62488dae5ebad73666bc75418269f7bd1 Mon Sep 17 00:00:00 2001 From: mina Date: Mon, 22 Sep 2025 11:15:07 +0200 Subject: [PATCH 07/12] fixed crimesList aktualisiert sich nach dem Editing --- src/routes/(token-based)/list/[vorgang]/+page.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index 6a429c3..7c2bd6d 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -27,6 +27,7 @@ } let crimesList = $state(data.crimesList); + $inspect('debug Edit', crimesList); let vorgangName: string = data.vorgang.vorgangName; const vorgangPIN: string = data.vorgang.vorgangPIN; let vorgangToken: string = data.vorgang.vorgangToken; @@ -58,6 +59,7 @@ throw new Error('Fehler beim Speichern'); } await invalidateAll(); + crimesList = data.crimesList; open = false; } catch (err) { console.error('⚠️ Netzwerkfehler beim Speichern', err); From 5d2cfa6dfd0924af7b0237c9287f159c23cd5f44 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 23 Sep 2025 13:23:09 +0200 Subject: [PATCH 08/12] comment usage of props with old syntax --- src/lib/components/NameItemEditor.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/components/NameItemEditor.svelte b/src/lib/components/NameItemEditor.svelte index e39143e..71164c0 100644 --- a/src/lib/components/NameItemEditor.svelte +++ b/src/lib/components/NameItemEditor.svelte @@ -10,6 +10,7 @@ token?: string; } + // props, old syntax export let list: ListItem[] = []; export let currentName: string; export let onSave: (n: string, o: string) => unknown = () => {}; From b4591d044b955376a94b2e9e9ab2453a5ba14e73 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 24 Sep 2025 09:48:47 +0200 Subject: [PATCH 09/12] formatting: fixtures.ts file --- tests/fixtures.ts | 81 +++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/tests/fixtures.ts b/tests/fixtures.ts index 4126fb5..270a845 100644 --- a/tests/fixtures.ts +++ b/tests/fixtures.ts @@ -1,47 +1,46 @@ const testUser = { - admin: true, -exp: 1757067123, -iat: 1757063523, -id: "admin", -} + admin: true, + exp: 1757067123, + iat: 1757063523, + id: 'admin' +}; const testCrimesList = [ - { - name: 'Fall-A', - lastModified: '2025-08-28T09:44:12.453Z', - etag: '558f35716f6af953f9bb5d75f6d77e6a', - size: 8947140, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - }, - { - name: 'Fall-B', - lastModified: '2025-08-28T10:37:20.142Z', - etag: '43e3989c32c4682bee407baaf83b6fa0', - size: 35788560, - prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', - show_button: true - } - ]; + { + name: 'Fall-A', + lastModified: '2025-08-28T09:44:12.453Z', + etag: '558f35716f6af953f9bb5d75f6d77e6a', + size: 8947140, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + }, + { + name: 'Fall-B', + lastModified: '2025-08-28T10:37:20.142Z', + etag: '43e3989c32c4682bee407baaf83b6fa0', + size: 35788560, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + } +]; const testVorgangsList = [ - { -vorgangName: "vorgang-1", -vorgangPIN: "pin-123", -vorgangToken: "c322f26f-8c5e-4cb9-94b3-b5433bf5109e" - }, - { -vorgangName: "vorgang-2", -vorgangPIN: "pin-2", -vorgangToken: "cb0051bc-5f38-47b8-943c-9352d4d9c984" - } - -] + { + vorgangName: 'vorgang-1', + vorgangPIN: 'pin-123', + vorgangToken: 'c322f26f-8c5e-4cb9-94b3-b5433bf5109e' + }, + { + vorgangName: 'vorgang-2', + vorgangPIN: 'pin-2', + vorgangToken: 'cb0051bc-5f38-47b8-943c-9352d4d9c984' + } +]; export const baseData = { - user: testUser, - vorgang: testVorgangsList[0], - vorgangList: testVorgangsList, - crimesList: testCrimesList, - url: `https://example.com/${testVorgangsList[0].vorgangToken}`, - crimeNames: [ "modell-A", "Fall-A" ], -} + user: testUser, + vorgang: testVorgangsList[0], + vorgangList: testVorgangsList, + crimesList: testCrimesList, + url: `https://example.com/${testVorgangsList[0].vorgangToken}`, + crimeNames: ['modell-A', 'Fall-A'] +}; From 406e7f8e4d96f1e2ae10dbdf01192d5b1d166e62 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 24 Sep 2025 09:56:18 +0200 Subject: [PATCH 10/12] formatting: VorgangList.view.test.ts --- tests/VorgangList.view.test.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/VorgangList.view.test.ts b/tests/VorgangList.view.test.ts index 36334e2..41a8be1 100644 --- a/tests/VorgangList.view.test.ts +++ b/tests/VorgangList.view.test.ts @@ -1,21 +1,21 @@ import { render } from '@testing-library/svelte'; -import { describe, expect, it } from "vitest"; +import { describe, expect, it } from 'vitest'; import VorgangListPage from '../src/routes/(angemeldet)/list/+page.svelte'; import { baseData } from './fixtures'; -describe('Vorgänge Liste Page EmptyList-Komponente View', ()=>{ - it('zeigt EmptyList-Komponente an, wenn Liste leer ist', () => { - const testData = { ...baseData, vorgangList: [] }; - const { getByTestId } = render(VorgangListPage, {props:{data: testData}}); +describe('Vorgänge Liste Page EmptyList-Komponente View', () => { + it('zeigt EmptyList-Komponente an, wenn Liste leer ist', () => { + const testData = { ...baseData, vorgangList: [] }; + const { getByTestId } = render(VorgangListPage, { props: { data: testData } }); - expect(getByTestId('empty-list')).toBeInTheDocument(); - }); + expect(getByTestId('empty-list')).toBeInTheDocument(); + }); - it('zeigt Liste(mockData 2 Elemente) an, wenn Liste vorhanden ist', () => { - const testData = { ...baseData }; - const { getAllByTestId } = render(VorgangListPage, {props:{data: testData}}); -const items = getAllByTestId('test-list-item'); + it('zeigt Liste(mockData 2 Elemente) an, wenn Liste vorhanden ist', () => { + const testData = { ...baseData }; + const { getAllByTestId } = render(VorgangListPage, { props: { data: testData } }); + const items = getAllByTestId('test-list-item'); - expect(items.length).toBeGreaterThan(0); - }); -}) + expect(items.length).toBeGreaterThan(0); + }); +}); From 011c07b75364b7e03f159da116d3e403564a6d31 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 24 Sep 2025 11:01:45 +0200 Subject: [PATCH 11/12] formatting --- tests/ComponentNameItemEditor.test.ts | 242 +++++++++++++------------- 1 file changed, 120 insertions(+), 122 deletions(-) diff --git a/tests/ComponentNameItemEditor.test.ts b/tests/ComponentNameItemEditor.test.ts index ff1d504..b32cfbb 100644 --- a/tests/ComponentNameItemEditor.test.ts +++ b/tests/ComponentNameItemEditor.test.ts @@ -1,144 +1,142 @@ import { fireEvent, render, screen } from '@testing-library/svelte'; -import { describe, expect, it, test, vi } from "vitest"; +import { describe, expect, it, test, vi } from 'vitest'; import NameItemEditor from '$lib/components/NameItemEditor.svelte'; import { baseData } from './fixtures'; - - const testCrimesListIndex = 0; const testItem = baseData.crimesList[testCrimesListIndex]; - const testCurrentName = testItem.name; - const testLocalName = 'Fall-C' - +const testCurrentName = testItem.name; +const testLocalName = 'Fall-C'; describe('NameItemEditor - Funktionalität', () => { - const onSave = vi.fn(); - const onDelete = vi.fn(); - const baseProps = { - list: baseData.crimesList, - currentName: testCurrentName, - onSave, - onDelete} + const onSave = vi.fn(); + const onDelete = vi.fn(); + const baseProps = { + list: baseData.crimesList, + currentName: testCurrentName, + onSave, + onDelete + }; - test.todo('FocusIn nach Klick auf edit') + test.todo('FocusIn nach Klick auf edit'); - it('zeigt initial Edit/Delete Buttons und aktuellen Namen', () => { - render(NameItemEditor, {props: baseProps}); + it('zeigt initial Edit/Delete Buttons und aktuellen Namen', () => { + render(NameItemEditor, { props: baseProps }); - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); - expect(screen.getByTestId('delete-button')).toBeInTheDocument(); - expect(screen.queryByTestId('commit-button')).toBeNull(); - expect(screen.queryByTestId('cancel-button')).toBeNull(); - expect(screen.getByText(testCurrentName)).toBeInTheDocument(); - }); + expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + expect(screen.getByTestId('delete-button')).toBeInTheDocument(); + expect(screen.queryByTestId('commit-button')).toBeNull(); + expect(screen.queryByTestId('cancel-button')).toBeNull(); + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + }); - it('wechselt zu Commit/Cancel nach Klick auf Edit', async () => { - render(NameItemEditor, {props: baseProps}); - await fireEvent.click(screen.getByTestId('edit-button')); - const input = screen.getByTestId('test-input'); + it('wechselt zu Commit/Cancel nach Klick auf Edit', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + const input = screen.getByTestId('test-input'); - expect(screen.getByTestId('commit-button')).toBeInTheDocument(); - expect(screen.getByTestId('cancel-button')).toBeInTheDocument(); - expect(screen.queryByTestId('edit-button')).toBeNull(); - expect(screen.queryByTestId('delete-button')).toBeNull(); - expect(screen.getAllByRole('textbox')).toHaveLength(1); - expect(input).toHaveValue(testCurrentName); - }); + expect(screen.getByTestId('commit-button')).toBeInTheDocument(); + expect(screen.getByTestId('cancel-button')).toBeInTheDocument(); + expect(screen.queryByTestId('edit-button')).toBeNull(); + expect(screen.queryByTestId('delete-button')).toBeNull(); + expect(screen.getAllByRole('textbox')).toHaveLength(1); + expect(input).toHaveValue(testCurrentName); + }); - it('zeigt Fehlermeldung bei leerem Namen', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); + it('zeigt Fehlermeldung bei leerem Namen', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: '' } }); + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: '' } }); - expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); - expect(onSave).not.toHaveBeenCalled(); - }); + expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); - it('entfernt Fehlermeldung live beim nächsten gültigen Tastendruck', async () => { - render(NameItemEditor, { - props: { - list: baseData.crimesList, - currentName: baseData.crimesList[0].name, - onSave: vi.fn(), - onDelete: vi.fn() - } - }); + it('entfernt Fehlermeldung live beim nächsten gültigen Tastendruck', async () => { + render(NameItemEditor, { + props: { + list: baseData.crimesList, + currentName: baseData.crimesList[0].name, + onSave: vi.fn(), + onDelete: vi.fn() + } + }); - await fireEvent.click(screen.getByTestId('edit-button')); - const input = screen.getByTestId('test-input'); + await fireEvent.click(screen.getByTestId('edit-button')); + const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: '' } }); - expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); + await fireEvent.input(input, { target: { value: '' } }); + expect(screen.getByText('Name darf nicht leer sein.')).toBeInTheDocument(); - await fireEvent.input(input, { target: { value: 'Fall-C' } }); + await fireEvent.input(input, { target: { value: 'Fall-C' } }); - expect(screen.queryByText('Name darf nicht leer sein.')).toBeNull(); -}); - - it('zeigt Fehlermeldung bei Duplikat', async () => { - const duplicateName = baseData.crimesList[1].name; - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); - - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: duplicateName } }); - - expect(screen.getByText('Name existiert bereits.')).toBeInTheDocument(); - expect(onSave).not.toHaveBeenCalled(); - }); - - it('ruft onSave korrekt auf bei gültigem Namen', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); - - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: testLocalName } }); - await fireEvent.click(screen.getByTestId('commit-button')); - - expect(onSave).toHaveBeenCalledWith(testLocalName, testCurrentName); - }); - - it('ruft onDelete korrekt auf', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('delete-button')); - - expect(onDelete).toHaveBeenCalledWith(testCurrentName); - }); - - it('setzt Zustand zurück bei Cancel', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); - - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: 'Zwischentext' } }); - await fireEvent.click(screen.getByTestId('cancel-button')); - - expect(screen.getByText(testCurrentName)).toBeInTheDocument(); - expect(screen.getByTestId('edit-button')).toBeInTheDocument(); - }); - - it('triggert Save bei Enter-Taste', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); - - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: 'ViaEnter' } }); - await fireEvent.keyDown(input, { key: 'Enter' }); - - expect(onSave).toHaveBeenCalledWith('ViaEnter', testCurrentName); - }); - - it('bricht ab bei Escape-Taste', async () => { - render(NameItemEditor, { props: baseProps }); - await fireEvent.click(screen.getByTestId('edit-button')); - - const input = screen.getByTestId('test-input'); - await fireEvent.input(input, { target: { value: 'Zwischentext' } }); - await fireEvent.keyDown(input, { key: 'Escape' }); - - expect(screen.getByText(testCurrentName)).toBeInTheDocument(); - expect(onSave).not.toHaveBeenCalled(); - }); + expect(screen.queryByText('Name darf nicht leer sein.')).toBeNull(); + }); + + it('zeigt Fehlermeldung bei Duplikat', async () => { + const duplicateName = baseData.crimesList[1].name; + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: duplicateName } }); + + expect(screen.getByText('Name existiert bereits.')).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); + + it('ruft onSave korrekt auf bei gültigem Namen', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: testLocalName } }); + await fireEvent.click(screen.getByTestId('commit-button')); + + expect(onSave).toHaveBeenCalledWith(testLocalName, testCurrentName); + }); + + it('ruft onDelete korrekt auf', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('delete-button')); + + expect(onDelete).toHaveBeenCalledWith(testCurrentName); + }); + + it('setzt Zustand zurück bei Cancel', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'Zwischentext' } }); + await fireEvent.click(screen.getByTestId('cancel-button')); + + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + expect(screen.getByTestId('edit-button')).toBeInTheDocument(); + }); + + it('triggert Save bei Enter-Taste', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'ViaEnter' } }); + await fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onSave).toHaveBeenCalledWith('ViaEnter', testCurrentName); + }); + + it('bricht ab bei Escape-Taste', async () => { + render(NameItemEditor, { props: baseProps }); + await fireEvent.click(screen.getByTestId('edit-button')); + + const input = screen.getByTestId('test-input'); + await fireEvent.input(input, { target: { value: 'Zwischentext' } }); + await fireEvent.keyDown(input, { key: 'Escape' }); + + expect(screen.getByText(testCurrentName)).toBeInTheDocument(); + expect(onSave).not.toHaveBeenCalled(); + }); }); From 3a6b10e860cd13f96675e88753ce694cabbbdbd6 Mon Sep 17 00:00:00 2001 From: mina Date: Wed, 24 Sep 2025 12:37:14 +0200 Subject: [PATCH 12/12] deleted debug comments --- src/routes/(token-based)/list/[vorgang]/+page.svelte | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/routes/(token-based)/list/[vorgang]/+page.svelte b/src/routes/(token-based)/list/[vorgang]/+page.svelte index 7c2bd6d..ccbf121 100644 --- a/src/routes/(token-based)/list/[vorgang]/+page.svelte +++ b/src/routes/(token-based)/list/[vorgang]/+page.svelte @@ -27,7 +27,6 @@ } let crimesList = $state(data.crimesList); - $inspect('debug Edit', crimesList); let vorgangName: string = data.vorgang.vorgangName; const vorgangPIN: string = data.vorgang.vorgangPIN; let vorgangToken: string = data.vorgang.vorgangToken; @@ -38,14 +37,12 @@ let inProgress = $state(false); let isError = $state(false); - let admin = $state(data?.user?.admin); + let admin = data?.user?.admin; async function handleSave(newName: string, oldName: string) { open = true; inProgress = true; isError = false; - console.log('debug handleSave', newName, oldName); - try { const res = await fetch(`/api/list/${vorgangToken}/${oldName}`, { method: 'PUT',