test(frontend): Vitest-Unit-Tests + CI-Integration, CLAUDE.md ignoriert
16 Tests ohne Browser-Abhängigkeit (jsdom): useIsDesktop (matchMedia-Reaktion), client.ts (ApiError, Popup-Blocker-Fix bei ladeDokumentAnsehen), CommandPalette (Debounce/Escape/Material-Treffer-Hinweis), ObjektListPage (Standort- Gruppierung, Kacheln-Umschalter, localStorage-Persistenz, Suche). Vitest separat von vite.config.ts (eigene vitest.config.ts), e2e/*.spec.ts (Playwright) explizit ausgeschlossen. CI führt `npm run test` jetzt vor dem Build aus. Nebenbei: CLAUDE.md ins .gitignore aufgenommen (bleibt lokal, nicht Teil des Repos). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import * as client from "../api/client";
|
||||
import { ObjektListPage } from "./ObjektListPage";
|
||||
|
||||
const OBJEKTE = [
|
||||
{ id: 1, code: "RTW-1", name: "RTW 1", standort_id: 10, status: "aktiv", letzte_kontrolle_am: null },
|
||||
{ id: 2, code: "KTW-1", name: "KTW 1", standort_id: 20, status: "aktiv", letzte_kontrolle_am: null },
|
||||
];
|
||||
const STANDORTE = [
|
||||
{ id: 10, name: "Wache Nord" },
|
||||
{ id: 20, name: "Wache Süd" },
|
||||
];
|
||||
|
||||
function renderSeite() {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<ObjektListPage />
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
vi.spyOn(client, "apiRequest").mockImplementation((pfad: string) => {
|
||||
if (pfad === "/objekte") return Promise.resolve(OBJEKTE);
|
||||
if (pfad === "/standorte") return Promise.resolve(STANDORTE);
|
||||
return Promise.reject(new Error(`unerwarteter Pfad: ${pfad}`));
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("ObjektListPage", () => {
|
||||
it("gruppiert Objekte standardmäßig nach Standort", async () => {
|
||||
renderSeite();
|
||||
await waitFor(() => expect(screen.getByText("Wache Nord", { exact: false })).toBeInTheDocument());
|
||||
expect(screen.getByText("Wache Süd", { exact: false })).toBeInTheDocument();
|
||||
expect(screen.getByText("RTW 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("KTW 1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Umschalter wechselt auf flache Kachel-Ansicht ohne Standort-Gruppen", async () => {
|
||||
const nutzer = userEvent.setup();
|
||||
renderSeite();
|
||||
await waitFor(() => expect(screen.getByText("RTW 1")).toBeInTheDocument());
|
||||
|
||||
await nutzer.click(screen.getByRole("button", { name: "Kacheln" }));
|
||||
|
||||
expect(screen.queryByText("Wache Nord", { exact: false })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("RTW 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("KTW 1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("merkt die gewählte Ansicht über localStorage", async () => {
|
||||
const nutzer = userEvent.setup();
|
||||
renderSeite();
|
||||
await waitFor(() => expect(screen.getByText("RTW 1")).toBeInTheDocument());
|
||||
await nutzer.click(screen.getByRole("button", { name: "Kacheln" }));
|
||||
|
||||
expect(localStorage.getItem("mabea-objekte-ansicht")).toBe("kacheln");
|
||||
});
|
||||
|
||||
it("Suchfeld filtert nach Name und Code", async () => {
|
||||
const nutzer = userEvent.setup();
|
||||
renderSeite();
|
||||
await waitFor(() => expect(screen.getByText("RTW 1")).toBeInTheDocument());
|
||||
|
||||
await nutzer.type(screen.getByPlaceholderText("Suche oder Code…"), "KTW");
|
||||
|
||||
expect(screen.queryByText("RTW 1")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("KTW 1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user