CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Neue additive status-Spalte (open/in_progress/done) auf tasks,
status.go (SetStatus/ByStatus/GetStatus/ListAll) haelt completed_at
synchron fuer TSK-03s escalation.Store. Neuer HTTP-Layer taskapi
(GET /tasks, PATCH /tasks/{id}/status), Tenant-Kontext-Durchsetzung
wie backchannelapi (VOR-06). Neue Next.js-Oberflaeche
web/workflow-tasks (Liste + Kanban, Drag&Drop + Tastaturbedienung).
tasks-a11y-fixture fuer den echten Browser-Test, kein Produktions-
binary vorhanden das geaendert werden musste.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
113 lines
5.6 KiB
TypeScript
113 lines
5.6 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from "vitest";
|
|
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
|
|
import { ThemeProvider, I18nProvider, ToastProvider } from "@nexarch/shl";
|
|
import { TaskBoard } from "./TaskBoard";
|
|
import type { Task } from "../lib/api";
|
|
|
|
function renderWithProviders(ui: React.ReactElement) {
|
|
return render(
|
|
<ThemeProvider>
|
|
<I18nProvider initialLocale="de">
|
|
<ToastProvider>{ui}</ToastProvider>
|
|
</I18nProvider>
|
|
</ThemeProvider>
|
|
);
|
|
}
|
|
|
|
const past = new Date(Date.now() - 86400000).toISOString();
|
|
const future = new Date(Date.now() + 86400000 * 3).toISOString();
|
|
|
|
function makeTasks(): Task[] {
|
|
return [
|
|
{ ID: "t1", Title: "Ueberfaellig-Aufgabe", AssigneeType: "person", AssigneeID: "u1", Priority: "high", References: null, DueDate: past, Status: "open" },
|
|
{ ID: "t2", Title: "Zukunfts-Aufgabe", AssigneeType: "person", AssigneeID: "u1", Priority: "low", References: null, DueDate: future, Status: "open" },
|
|
{ ID: "t3", Title: "Erledigte-Aufgabe", AssigneeType: "person", AssigneeID: "u1", Priority: "normal", References: null, DueDate: past, Status: "done" },
|
|
];
|
|
}
|
|
|
|
describe("TaskBoard", () => {
|
|
const originalFetch = global.fetch;
|
|
afterEach(() => {
|
|
global.fetch = originalFetch;
|
|
});
|
|
|
|
// AK1/Pruefung2: ueberfaellige Aufgaben sind klar gekennzeichnet;
|
|
// erledigte Aufgaben gelten nie als ueberfaellig, auch mit vergangener
|
|
// Faelligkeit (siehe t3).
|
|
it("kennzeichnet ueberfaellige, nicht erledigte Aufgaben", async () => {
|
|
global.fetch = vi.fn(async () => new Response(JSON.stringify(makeTasks()), { status: 200 })) as typeof fetch;
|
|
renderWithProviders(<TaskBoard userId="alice" tenantSlug="acme" />);
|
|
|
|
await waitFor(() => expect(screen.getByTestId("task-list")).toBeInTheDocument());
|
|
const list = screen.getByTestId("task-list");
|
|
expect(within(list).getByText(/Ueberfaellig-Aufgabe/)).toHaveTextContent("(ueberfaellig)");
|
|
expect(within(list).getByText(/Zukunfts-Aufgabe/)).not.toHaveTextContent("(ueberfaellig)");
|
|
expect(within(list).getByText(/Erledigte-Aufgabe/)).not.toHaveTextContent("(ueberfaellig)");
|
|
});
|
|
|
|
// AK2/Pruefung1: Filterkombination (Prioritaet + nur ueberfaellige).
|
|
it("filtert nach Prioritaet und ueberfaellig-Status kombiniert", async () => {
|
|
global.fetch = vi.fn(async () => new Response(JSON.stringify(makeTasks()), { status: 200 })) as typeof fetch;
|
|
renderWithProviders(<TaskBoard userId="alice" tenantSlug="acme" />);
|
|
|
|
await waitFor(() => expect(screen.getByTestId("task-list")).toBeInTheDocument());
|
|
|
|
fireEvent.change(screen.getByLabelText("Prioritaet"), { target: { value: "high" } });
|
|
fireEvent.click(screen.getByLabelText(/Nur/));
|
|
|
|
const list = screen.getByTestId("task-list");
|
|
expect(within(list).getByText(/Ueberfaellig-Aufgabe/)).toBeInTheDocument();
|
|
expect(within(list).queryByText(/Zukunfts-Aufgabe/)).not.toBeInTheDocument();
|
|
expect(within(list).queryByText(/Erledigte-Aufgabe/)).not.toBeInTheDocument();
|
|
});
|
|
|
|
// AK4/Pruefung4: Statuswechsel in der Kanban-Ansicht schreibt ueber
|
|
// TSK-01 zurueck (hier: Tastaturaktion statt HTML5-DnD, gleichwertig,
|
|
// siehe Komponentenkommentar -- realer DnD-Event ist in jsdom nicht
|
|
// sinnvoll simulierbar).
|
|
it("verschiebt eine Aufgabe in der Kanban-Ansicht und schreibt den Status zurueck", async () => {
|
|
const statusCalls: { id: string; body: string }[] = [];
|
|
global.fetch = vi.fn(async (url: string | URL, init?: RequestInit) => {
|
|
const u = url.toString();
|
|
if (u.includes("/status")) {
|
|
const idMatch = u.match(/tasks\/([^/?]+)\/status/);
|
|
statusCalls.push({ id: idMatch ? idMatch[1] : "", body: init?.body as string });
|
|
return new Response(null, { status: 204 });
|
|
}
|
|
return new Response(JSON.stringify(makeTasks()), { status: 200 });
|
|
}) as typeof fetch;
|
|
|
|
renderWithProviders(<TaskBoard userId="alice" tenantSlug="acme" />);
|
|
await waitFor(() => expect(screen.getByTestId("task-list")).toBeInTheDocument());
|
|
|
|
fireEvent.click(screen.getByText("Kanban"));
|
|
await waitFor(() => expect(screen.getByRole("group", { name: "Spalte Offen" })).toBeInTheDocument());
|
|
|
|
fireEvent.click(screen.getByLabelText("Ueberfaellig-Aufgabe nach Erledigt verschieben"));
|
|
|
|
await waitFor(() => expect(statusCalls).toHaveLength(1));
|
|
expect(statusCalls[0].id).toBe("t1");
|
|
expect(JSON.parse(statusCalls[0].body)).toEqual({ status: "done" });
|
|
});
|
|
|
|
// AK5: Backend liefert bereits nur Aufgaben des aktuellen Mandanten
|
|
// (server-seitiger Filter in taskapi, siehe workflow/internal/taskapi
|
|
// Tests) -- hier nur geprueft, dass die Komponente die Backend-Liste
|
|
// unveraendert uebernimmt, keine eigene, umgehbare Client-Filterung
|
|
// dazwischenschaltet.
|
|
it("zeigt genau die vom Backend gelieferte Aufgabenliste ohne eigene Mandantenfilterung", async () => {
|
|
global.fetch = vi.fn(async () => new Response(JSON.stringify(makeTasks()), { status: 200 })) as typeof fetch;
|
|
renderWithProviders(<TaskBoard userId="alice" tenantSlug="acme" />);
|
|
|
|
await waitFor(() => expect(screen.getByTestId("task-list")).toBeInTheDocument());
|
|
const list = screen.getByTestId("task-list");
|
|
expect(within(list).getAllByRole("listitem")).toHaveLength(3);
|
|
});
|
|
|
|
it("zeigt eine Fehlermeldung, wenn der Mandant abgelehnt wird", async () => {
|
|
global.fetch = vi.fn(async () => new Response(JSON.stringify({ error: "nicht berechtigt" }), { status: 403 })) as typeof fetch;
|
|
renderWithProviders(<TaskBoard userId="alice" tenantSlug="fremd" />);
|
|
await waitFor(() => expect(screen.getByRole("alert")).toBeInTheDocument());
|
|
});
|
|
});
|