test(frontend): Vitest-Unit-Tests + CI-Integration, CLAUDE.md ignoriert
CI / backend-tests (push) Failing after 2m18s
CI / frontend-build (push) Failing after 17s

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:
2026-09-08 09:46:35 +02:00
co-authored by Claude Sonnet 5
parent 7ce644ac51
commit 28d8b2957d
10 changed files with 1558 additions and 2 deletions
@@ -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();
});
});