f086_Zusatz-Edit-der-Namen #34
@@ -1,90 +1,83 @@
|
||||
<script lang="ts">
|
||||
import Check from '$lib/icons/Check.svelte';
|
||||
import Edit from '$lib/icons/Edit.svelte';
|
||||
import Trash from '$lib/icons/Trash.svelte';
|
||||
import X from '$lib/icons/X.svelte';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
interface ListItem {
|
||||
name: string;
|
||||
token?: string;
|
||||
// add other properties as needed
|
||||
}
|
||||
let {
|
||||
list,
|
||||
editedName = $bindable(),
|
||||
currentName,
|
||||
onSave = () => {},
|
||||
onDelete = () => {}
|
||||
} = $props();
|
||||
|
||||
let localName = $state(currentName);
|
||||
let wasCancelled = $state(false);
|
||||
// props, old syntax
|
||||
export let list: ListItem[] = [];
|
||||
export let currentName: string;
|
||||
export let onSave: (n: string, o: string) => unknown = () => {};
|
||||
export let onDelete: (n: string) => unknown = () => {};
|
||||
|
||||
|
trachi93 marked this conversation as resolved
Outdated
|
||||
let error: string = $derived(validateName(localName));
|
||||
let isEditing = $state(false);
|
||||
let inputRef: HTMLInputElement;
|
||||
let localName = currentName;
|
||||
let isEditing = false;
|
||||
let inputRef: HTMLInputElement | null = null;
|
||||
|
||||
function validateName(name: string) {
|
||||
const trimmed = name.trim();
|
||||
$: error = validateName(localName);
|
||||
|
||||
if (!trimmed) {
|
||||
return 'Name darf nicht leer sein.';
|
||||
function validateName(name: string): string {
|
||||
const trimmed = name?.trim() ?? '';
|
||||
if (!trimmed) return 'Name darf nicht leer sein.';
|
||||
if (list.some((item) => item.name === trimmed && item.name !== currentName)) {
|
||||
return 'Name existiert bereits.';
|
||||
}
|
||||
|
||||
const duplicate = list.some(
|
||||
(item: ListItem) => item.name === trimmed && item.name !== currentName
|
||||
);
|
||||
|
||||
if (duplicate) return 'Name existiert bereits.';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function commitIfValid() {
|
||||
if (!error && !wasCancelled && localName != currentName) {
|
||||
const trimmedName: string = localName.trim();
|
||||
inputRef?.blur();
|
||||
isEditing = false;
|
||||
onSave(trimmedName, currentName);
|
||||
} else {
|
||||
localName = currentName;
|
||||
resetEdit();
|
||||
}
|
||||
}
|
||||
|
||||
function resetEdit() {
|
||||
wasCancelled = false;
|
||||
inputRef?.blur();
|
||||
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() {
|
||||
localName = currentName;
|
||||
isEditing = false;
|
||||
}
|
||||
|
||||
function commitEdit() {
|
||||
if (!error && localName != currentName) onSave(localName, currentName);
|
||||
|
||||
isEditing = false;
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') commitEdit();
|
||||
if (event.key === 'Escape') cancelEdit();
|
||||
}
|
||||
|
||||
function handleDeleteClick() {
|
||||
onDelete(currentName);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<input
|
||||
bind:this={inputRef}
|
||||
bind:value={localName}
|
||||
onblur={commitIfValid}
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<button onclick={startEdit}><Edit /></button>
|
||||
<button onclick={() => onDelete(currentName)}><Trash /></button>
|
||||
<div data-testid="test-nameItemEditor">
|
||||
{#if isEditing}
|
||||
<input
|
||||
data-testid="test-input"
|
||||
bind:this={inputRef}
|
||||
bind:value={localName}
|
||||
onkeydown={handleKeydown}
|
||||
/>
|
||||
<button
|
||||
data-testid="commit-button"
|
||||
disabled={!!error || localName === currentName}
|
||||
onclick={commitEdit}><Check /></button
|
||||
>
|
||||
<button data-testid="cancel-button" onclick={cancelEdit}><X /></button>
|
||||
{:else}
|
||||
<span>{localName}</span>
|
||||
<button data-testid="edit-button" onclick={startEdit}><Edit /></button>
|
||||
<button data-testid="delete-button" onclick={handleDeleteClick}><Trash /></button>
|
||||
{/if}
|
||||
{#if error}
|
||||
<p style="color: red;">{error}</p>
|
||||
<p class="text-red-500">{error}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
// add other properties as needed
|
||||
}
|
||||
|
||||
let crimesList = $state<ListItem[]>(data.crimesList);
|
||||
let vorgangName: string = data.vorgang.vorgangName;
|
||||
|
mina marked this conversation as resolved
Outdated
trachi93
commented
debug raus debug raus
|
||||
let crimesList: ListItem[] = $state(data.crimesList);
|
||||
const vorgangPIN: string = data.vorgang.vorgangPIN;
|
||||
let vorgangToken: string = data.vorgang.vorgangToken;
|
||||
let isEmptyList = $derived(crimesList.length === 0);
|
||||
@@ -37,12 +37,12 @@
|
||||
let inProgress = $state(false);
|
||||
let isError = $state(false);
|
||||
|
||||
//Variable um nur admin UI anzuzeigen
|
||||
let admin = data?.user?.admin;
|
||||
|
||||
|
mina marked this conversation as resolved
Outdated
trachi93
commented
ist dies notwendig? ist dies notwendig?
`admin` wird noch nicht dynamisch verwendet o. ä.
|
||||
async function handleSave(newName: string, oldName: string) {
|
||||
open = true;
|
||||
inProgress = true;
|
||||
isError = false;
|
||||
try {
|
||||
const res = await fetch(`/api/list/${vorgangToken}/${oldName}`, {
|
||||
|
mina marked this conversation as resolved
Outdated
trachi93
commented
debug raus debug raus
|
||||
method: 'PUT',
|
||||
@@ -50,29 +50,18 @@
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ vorgangToken, oldName, newName })
|
||||
})
|
||||
.then(() => {
|
||||
inProgress = 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;
|
||||
throw new Error('Fehler beim Speichern');
|
||||
}
|
||||
await invalidateAll();
|
||||
crimesList = data.crimesList;
|
||||
open = false;
|
||||
} catch (err) {
|
||||
console.error('⚠️ Netzwerkfehler:', err);
|
||||
console.error('⚠️ Netzwerkfehler beim Speichern', err);
|
||||
isError = true;
|
||||
} finally {
|
||||
inProgress = false;
|
||||
}
|
||||
}
|
||||
@@ -80,39 +69,31 @@
|
||||
async function handleDelete(tatort: string) {
|
||||
open = true;
|
||||
inProgress = true;
|
||||
let url = new URL(data.url);
|
||||
url.pathname += `/${tatort}`;
|
||||
console.log('Delete tatort: ', `/api${url.pathname}`, url.pathname);
|
||||
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;
|
||||
})
|
||||
.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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +122,7 @@ Mit freundlichen Grüßen,
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if data.vorgang && data.crimesList}
|
||||
{#if data.vorgang && crimesList}
|
||||
<div class="-z-10 bg-white">
|
||||
<div class="flex flex-col items-center justify-center w-full">
|
||||
<h1 class="text-xl">Vorgang {vorgangName}</h1>
|
||||
@@ -158,10 +139,11 @@ Mit freundlichen Grüßen,
|
||||
{#if isEmptyList}
|
||||
<EmptyList></EmptyList>
|
||||
{:else}
|
||||
{#each data.crimesList as item, crimeListItemIndex}
|
||||
{#each crimesList as item (item.name)}
|
||||
<li data-testid="test-list-item">
|
||||
<div class=" flex gap-x-4">
|
||||
<a
|
||||
data-testid="crime-link"
|
||||
href="/view/{vorgangToken}/{item.name}?pin={vorgangPIN}"
|
||||
class=" flex justify-between gap-x-6 py-5"
|
||||
aria-label="/view/{vorgangToken}/{item.name}?pin={vorgangPIN}"
|
||||
@@ -172,16 +154,18 @@ Mit freundlichen Grüßen,
|
||||
<div class="min-w-0 flex-auto">
|
||||
{#if admin}
|
||||
<NameItemEditor
|
||||
list={data.crimesList}
|
||||
editedName={data.crimeNames[crimeListItemIndex]}
|
||||
list={crimesList}
|
||||
currentName={item.name}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
></NameItemEditor>
|
||||
{:else}
|
||||
<span class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
|
||||
>{item.name}</span
|
||||
<p
|
||||
data-testid="test-nameItem-p"
|
||||
class="text-sm font-semibold leading-6 text-gray-900 inline-block min-w-1"
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
{/if}
|
||||
{#if item.size}
|
||||
<p class="mt-1 truncate text-xs leading-5 text-gray-500">
|
||||
|
||||
9
tests/ComponentEmptyList.view.test.ts
Normal file
9
tests/ComponentEmptyList.view.test.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import EmptyList from '$lib/components/EmptyList.svelte'
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('Komponente: EmptyList', () => {
|
||||
it('zeigt Hinweistext "Keine Einträge"', () => {
|
||||
render(EmptyList);
|
||||
expect(screen.getByText(/keine Einträge/i)).toBeInTheDocument(); });
|
||||
});
|
||||
142
tests/ComponentNameItemEditor.test.ts
Normal file
142
tests/ComponentNameItemEditor.test.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
84
tests/TatortList.test.ts
Normal file
84
tests/TatortList.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { render, fireEvent, screen, within } from '@testing-library/svelte';
|
||||
import { describe, it, expect, vi, test } from 'vitest';
|
||||
import * as nav from '$app/navigation';
|
||||
import TatortListPage from '../src/routes/(token-based)/list/[vorgang]/+page.svelte';
|
||||
import { baseData } from './fixtures';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
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 - 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;
|
||||
const newName = 'Fall-C';
|
||||
|
||||
render(TatortListPage, { props: { data } });
|
||||
const listItem = screen.getAllByTestId('test-list-item')[0];
|
||||
expect(listItem).toHaveTextContent(oldName);
|
||||
|
||||
await fireEvent.click(within(listItem).getByTestId('edit-button'));
|
||||
const input = within(listItem).getByTestId('test-input');
|
||||
await fireEvent.input(input, { target: { value: newName } });
|
||||
|
||||
await fireEvent.click(within(listItem).getByTestId('commit-button'));
|
||||
await tick();
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
expect(nav.invalidateAll).toHaveBeenCalled();
|
||||
expect(within(listItem).getByText(newName)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('führt DELETE-Request aus und entfernt Element aus UI', async () => {
|
||||
const testData = structuredClone(baseData);
|
||||
const oldName = testData.crimesList[0].name;
|
||||
|
||||
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];
|
||||
expect(listItem).toHaveTextContent(oldName);
|
||||
const del = within(listItem).getByTestId('delete-button');
|
||||
expect(del).toBeInTheDocument()
|
||||
await fireEvent.click(within(listItem).getByTestId('delete-button'));
|
||||
await tick();
|
||||
|
||||
let expectedPath = new URL(testData.url).pathname;
|
||||
expectedPath += `/${oldName}`
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
`/api${expectedPath}`,
|
||||
expect.objectContaining({
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
vorgangToken: testData.vorgang.vorgangToken,
|
||||
tatort: oldName
|
||||
})
|
||||
})
|
||||
);
|
||||
expect(nav.invalidateAll).toHaveBeenCalled();
|
||||
const updatedItems = screen.queryAllByTestId('test-list-item');
|
||||
expect(updatedItems).toHaveLength(testData.crimesList.length - 1);
|
||||
expect(screen.queryByText(oldName)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,67 +1,87 @@
|
||||
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';
|
||||
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
|
||||
}
|
||||
];
|
||||
describe('Seite: Vorgangsansicht', () => {
|
||||
test.todo('zeigt PIN und Share-Link, wenn Admin');
|
||||
test.todo('zeigt PIN und Share-Link disabeld, wenn Liste leer');
|
||||
|
||||
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'
|
||||
}
|
||||
];
|
||||
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 baseData = {
|
||||
user: testUser,
|
||||
vorgang: testVorgangsList[0],
|
||||
vorgangList: testVorgangsList,
|
||||
crimesList: testCrimesList,
|
||||
url: 'https://www.google.com',
|
||||
crimeNames: [testCrimesList[0].name]
|
||||
};
|
||||
expect(getByTestId('empty-list')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
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 } });
|
||||
it('zeigt keinen Listeneintrag', () => {
|
||||
const items = screen.queryAllByTestId('test-list-item');
|
||||
|
||||
expect(getByTestId('empty-list')).toBeInTheDocument();
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
describe('Szenario: Liste gefüllt (unabhängig von Rolle)', () => {
|
||||
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');
|
||||
|
||||
expect(items).toHaveLength(2);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
test.todo('testet zuletzt angezeigt, wenn item.lastModified');
|
||||
test.todo('zeigt Dateigröße, wenn item.size vorhanden ist');
|
||||
});
|
||||
|
||||
describe('Szenario: Admin + Liste gefüllt', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
test.todo('Modal testen, wenn open');
|
||||
});
|
||||
|
||||
describe('Szenario: Viewer + Liste gefüllt', () => {
|
||||
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');
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,52 +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', () => {
|
||||
@@ -56,11 +11,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);
|
||||
});
|
||||
});
|
||||
|
||||
46
tests/fixtures.ts
Normal file
46
tests/fixtures.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
const testUser = {
|
||||
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
|
||||
}
|
||||
];
|
||||
|
||||
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: `https://example.com/${testVorgangsList[0].vorgangToken}`,
|
||||
crimeNames: ['modell-A', 'Fall-A']
|
||||
};
|
||||
Reference in New Issue
Block a user
unnoetig? currentName ist auch nicht initiiert.