f086_Zusatz-Edit-der-Namen #34

Merged
mina merged 13 commits from f086_Zusatz-Edit-der-Namen into development 2025-09-24 12:40:12 +02:00
5 changed files with 152 additions and 73 deletions
Showing only changes of commit 012f7ee3e8 - Show all commits

View File

@@ -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;

View File

@@ -26,7 +26,6 @@
// add other properties as needed
}
// 2) Lokaler, reaktiver State mit $state
let crimesList = $state<ListItem[]>(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) {

View File

@@ -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();
});
});

View File

@@ -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();
});
});

View File

@@ -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();
});
});
});