Let the truncated Anstehend list open
"… und 5 weitere Ereignisse in diesem Zeitraum" was a sentence to read. It is now a button: click it and the rest unfolds in place, click again and the card goes back to eight rows. Until now the only way to see the remaining entries was to narrow the period or the kinds — which changes the question rather than the answer. This happens in the browser, not through the URL, unlike the period and kind filters. Those go through the address because a longer period brings rows into play that were never loaded; here every entry in the period is already on the page and the eight was purely presentational. A round trip would mean waiting for data that is already there, plus a history entry for something nobody wants to go back to. The list moved into its own component so the state has somewhere to live. KIND_LABEL and the item type moved with it, since they only describe this list. The button only appears when there is something to unfold, and once open it offers the way back — otherwise the card stays long for the rest of the session because somebody looked once. Verified: 10 tests, five mutation-checked (ignoring the state, dropping the way back, showing the button with nothing to unfold, losing the singular, dropping aria-expanded each turn them red). 487 tests, typecheck and build clean. Not seen in a browser — login goes through the company account and the database is unreachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
101
tests/components/AnstehendListe.test.tsx
Normal file
101
tests/components/AnstehendListe.test.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AnstehendListe, VORSCHAU, type AnstehendEintrag } from "@/components/dashboard/AnstehendListe";
|
||||
|
||||
// Die Karte zeigt acht Zeilen und darunter, wie viele es insgesamt sind.
|
||||
// Diese Zeile war lange ein Satz zum Lesen; jetzt ist sie ein Knopf. Wer
|
||||
// alle sehen will, soll nicht den Zeitraum weiter stellen müssen — das
|
||||
// änderte die Frage, nicht nur die Antwort.
|
||||
|
||||
const HEUTE = "2026-09-09";
|
||||
|
||||
function eintrag(nr: number, teil: Partial<AnstehendEintrag> = {}): AnstehendEintrag {
|
||||
return {
|
||||
id: `i${nr}`,
|
||||
employeeId: `e${nr}`,
|
||||
label: `Person ${nr}`,
|
||||
date: `2026-10-${String(nr).padStart(2, "0")}`,
|
||||
kind: "hire",
|
||||
...teil,
|
||||
};
|
||||
}
|
||||
|
||||
const viele = Array.from({ length: 13 }, (_, i) => eintrag(i + 1));
|
||||
|
||||
function zeige(eintraege: AnstehendEintrag[]) {
|
||||
return render(<AnstehendListe eintraege={eintraege} today={HEUTE} leerText="Nichts steht an." />);
|
||||
}
|
||||
|
||||
describe("AnstehendListe", () => {
|
||||
it("zeigt zunächst nur die Vorschau", () => {
|
||||
zeige(viele);
|
||||
expect(screen.getAllByRole("listitem")).toHaveLength(VORSCHAU);
|
||||
expect(screen.getByText("Person 8")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Person 9")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("nennt im Knopf, wie viele noch kommen", () => {
|
||||
zeige(viele);
|
||||
expect(screen.getByRole("button", { name: /und 5 weitere Ereignisse anzeigen/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("klappt auf Klick alle auf", async () => {
|
||||
const user = userEvent.setup();
|
||||
zeige(viele);
|
||||
await user.click(screen.getByRole("button", { name: /anzeigen/i }));
|
||||
|
||||
expect(screen.getAllByRole("listitem")).toHaveLength(13);
|
||||
expect(screen.getByText("Person 13")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("führt wieder zurück", async () => {
|
||||
// Ohne den Weg zurück bliebe die Karte für den Rest der Sitzung lang,
|
||||
// auch wenn jemand nur einmal nachsehen wollte.
|
||||
const user = userEvent.setup();
|
||||
zeige(viele);
|
||||
await user.click(screen.getByRole("button", { name: /anzeigen/i }));
|
||||
await user.click(screen.getByRole("button", { name: /Weniger anzeigen/i }));
|
||||
|
||||
expect(screen.getAllByRole("listitem")).toHaveLength(VORSCHAU);
|
||||
});
|
||||
|
||||
it("meldet den Zustand an Hilfsmittel", async () => {
|
||||
const user = userEvent.setup();
|
||||
zeige(viele);
|
||||
const knopf = screen.getByRole("button", { name: /anzeigen/i });
|
||||
expect(knopf).toHaveAttribute("aria-expanded", "false");
|
||||
|
||||
await user.click(knopf);
|
||||
expect(screen.getByRole("button", { name: /Weniger/i })).toHaveAttribute("aria-expanded", "true");
|
||||
});
|
||||
|
||||
it("zeigt keinen Knopf, wenn ohnehin alles zu sehen ist", () => {
|
||||
// Ein Knopf, der nichts aufklappt, ist ein Versprechen ohne Inhalt.
|
||||
zeige(viele.slice(0, VORSCHAU));
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sagt bei einem einzelnen Ereignis Einzahl", () => {
|
||||
zeige(viele.slice(0, VORSCHAU + 1));
|
||||
expect(screen.getByRole("button", { name: /und 1 weiteres Ereignis anzeigen/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("zeigt den übergebenen Text, wenn nichts ansteht", () => {
|
||||
zeige([]);
|
||||
expect(screen.getByText("Nichts steht an.")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("kennzeichnet überfällige Wiedervorlagen", () => {
|
||||
// Nur Wiedervorlagen können überfällig sein: die anderen Arten haben
|
||||
// eine untere Grenze, eine offene Aufgabe nicht.
|
||||
zeige([eintrag(1, { kind: "note", date: "2026-08-01", hinweis: "Dienstzettel" })]);
|
||||
expect(screen.getByText(/überfällig/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("verlinkt auf die Akte, nicht auf die Notiz", () => {
|
||||
zeige([eintrag(1, { kind: "note", employeeId: "e42" })]);
|
||||
expect(screen.getByRole("link")).toHaveAttribute("href", "/employees/e42");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user