Adding the follow-up kind turned one of these tests green-for-the-wrong- reason and one red: both had the three kinds written out by hand, so "all of them are selected" no longer meant what the name said. That is the failure mode a hand-copied list has — it does not break loudly, it drifts. The list now comes from ANSTEHEND_ARTEN, and the two cases that depend on completeness build their input from it. I committed the previous change with this test red. That was wrong; it should have blocked the commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
109 lines
4.9 KiB
TypeScript
109 lines
4.9 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { AnstehendFilter } from "@/components/dashboard/AnstehendFilter";
|
|
import { ANSTEHEND_ARTEN, type AnstehendArt } from "@/lib/dashboard-filter";
|
|
|
|
// Die Auswahl muss in die Adresse, nicht in den Browser-Zustand: die
|
|
// Übersicht wird auf dem Server gebaut, und ein längerer Zeitraum bringt
|
|
// Zeilen ins Spiel, die im Browser gar nicht liegen. Geprüft wird deshalb
|
|
// genau das — was in der Adresse landet.
|
|
|
|
const replace = vi.fn();
|
|
let suchString = "";
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
useRouter: () => ({ replace }),
|
|
usePathname: () => "/",
|
|
useSearchParams: () => new URLSearchParams(suchString),
|
|
}));
|
|
|
|
// Aus dem Verzeichnis abgeleitet, nicht abgeschrieben: eine neue Art hätte
|
|
// diesen Test sonst stillschweigend am falschen Fall prüfen lassen — genau das
|
|
// ist beim Hinzufügen der Wiedervorlage passiert.
|
|
const ALLE: AnstehendArt[] = ANSTEHEND_ARTEN.map((a) => a.value);
|
|
|
|
beforeEach(() => {
|
|
replace.mockClear();
|
|
suchString = "";
|
|
});
|
|
|
|
describe("AnstehendFilter", () => {
|
|
it("schreibt einen gewählten Zeitraum in die Adresse", async () => {
|
|
const user = userEvent.setup();
|
|
render(<AnstehendFilter zeitraum={60} arten={[...ALLE]} />);
|
|
await user.click(screen.getByRole("button", { name: "90 Tage" }));
|
|
expect(replace).toHaveBeenCalledWith("/?tage=90", { scroll: false });
|
|
});
|
|
|
|
it("lässt den Standard aus der Adresse verschwinden statt ihn hineinzuschreiben", async () => {
|
|
// Sonst trüge jede geteilte Adresse einen Filter, den niemand gesetzt hat.
|
|
const user = userEvent.setup();
|
|
suchString = "tage=90";
|
|
render(<AnstehendFilter zeitraum={90} arten={[...ALLE]} />);
|
|
await user.click(screen.getByRole("button", { name: "60 Tage" }));
|
|
expect(replace).toHaveBeenCalledWith("/", { scroll: false });
|
|
});
|
|
|
|
it("nimmt eine abgewählte Art aus der Auswahl", async () => {
|
|
const user = userEvent.setup();
|
|
render(<AnstehendFilter zeitraum={60} arten={[...ALLE]} />);
|
|
await user.click(screen.getByRole("button", { name: "Austritt" }));
|
|
const erwartet = encodeURIComponent(ALLE.filter((a) => a !== "exit").join(","));
|
|
expect(replace).toHaveBeenCalledWith(`/?arten=${erwartet}`, { scroll: false });
|
|
});
|
|
|
|
it("nimmt eine wieder hinzugewählte Art auf", async () => {
|
|
const user = userEvent.setup();
|
|
suchString = "arten=hire";
|
|
render(<AnstehendFilter zeitraum={60} arten={["hire"]} />);
|
|
await user.click(screen.getByRole("button", { name: "Rückkehr" }));
|
|
expect(replace).toHaveBeenCalledWith("/?arten=hire%2Creturn", { scroll: false });
|
|
});
|
|
|
|
it("führt die letzte abgewählte Art auf alle zurück, nicht auf keine", async () => {
|
|
// Eine leere Karte ist keine Antwort, und der Weg zurück wäre sonst ein
|
|
// Klick zu weit.
|
|
const user = userEvent.setup();
|
|
suchString = "arten=hire";
|
|
render(<AnstehendFilter zeitraum={60} arten={["hire"]} />);
|
|
await user.click(screen.getByRole("button", { name: "Eintritt" }));
|
|
expect(replace).toHaveBeenCalledWith("/", { scroll: false });
|
|
});
|
|
|
|
it("lässt vollständige Auswahl gar nicht erst in der Adresse stehen", async () => {
|
|
const user = userEvent.setup();
|
|
// Die letzte fehlende Art wieder dazu: dann ist wieder alles gewählt, und
|
|
// das gehört nicht in die Adresse. Bewusst über ALLE gebildet statt
|
|
// abgeschrieben — sonst prüft dieser Test nach der nächsten neuen Art die
|
|
// falsche Bedingung.
|
|
const bisAufEine = ALLE.slice(0, -1);
|
|
suchString = `arten=${bisAufEine.join(",")}`;
|
|
render(<AnstehendFilter zeitraum={60} arten={[...bisAufEine]} />);
|
|
await user.click(screen.getByRole("button", { name: "Wiedervorlage" }));
|
|
expect(replace).toHaveBeenCalledWith("/", { scroll: false });
|
|
});
|
|
|
|
it("behält andere Angaben in der Adresse bei", async () => {
|
|
const user = userEvent.setup();
|
|
suchString = "arten=hire";
|
|
render(<AnstehendFilter zeitraum={60} arten={["hire"]} />);
|
|
await user.click(screen.getByRole("button", { name: "30 Tage" }));
|
|
expect(replace).toHaveBeenCalledWith("/?arten=hire&tage=30", { scroll: false });
|
|
});
|
|
|
|
it("zeigt am Knopf an, was gerade gewählt ist", () => {
|
|
render(<AnstehendFilter zeitraum={60} arten={["hire"]} />);
|
|
expect(screen.getByRole("button", { name: "Eintritt" })).toHaveAttribute("aria-pressed", "true");
|
|
expect(screen.getByRole("button", { name: "Austritt" })).toHaveAttribute("aria-pressed", "false");
|
|
});
|
|
|
|
it("springt beim Filtern nicht an den Seitenanfang", async () => {
|
|
// Die Karte steht in der unteren Hälfte; dorthin sieht gerade, wer klickt.
|
|
const user = userEvent.setup();
|
|
render(<AnstehendFilter zeitraum={60} arten={[...ALLE]} />);
|
|
await user.click(screen.getByRole("button", { name: "30 Tage" }));
|
|
expect(replace.mock.calls[0][1]).toEqual({ scroll: false });
|
|
});
|
|
});
|