Der Papierkorb neben dem Plus, im Organigramm wie in der Liste, und nur dort, wo nichts darunter haengt. Ein Knopf, der beim Klick eine Absage erteilt, ist schlechter als kein Knopf -- die Datenbank prueft es trotzdem, denn sie sieht auch geschlossene Planstellen, die im Baum gar nicht gezeichnet sind. Abgewiesen wird mit Grund: untergeordnete Einheiten, Planstellen (geschlossene zaehlen mit), Kostenstellen, die darauf verweisen, und die Wurzel selbst. Geloescht und nicht geschlossen, und das ist eine bewusste Einschraenkung. delete_position macht seit dem OM-Umbau den Unterschied vor: besetzt gewesen → valid_to, nie besetzt gewesen → weg. Fuer Einheiten laesst sich davon heute nur die zweite Haelfte umsetzen, weil ein valid_to an einer Einheit zwar eingetragen, aber nirgends gelesen wuerde -- weder Organigramm noch orgMapsAbfragen, Berichte, Druck oder die Auswahl beim Anlegen einer Planstelle schraenken org_units auf den Stichtag ein. Die geschlossene Einheit staende ueberall weiter da, nur mit einem Datum, das niemand sieht. Das Schliessen zum Stichtag kommt, wenn org_units gegen den Stichtag gelesen wird -- dieselbe Arbeit, die auch das Verschieben braucht.
94 lines
3.9 KiB
TypeScript
94 lines
3.9 KiB
TypeScript
import { render as rtlRender, screen } from "@testing-library/react";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { buildUnitTree, PositionTree } from "@/components/orgchart/PositionTree";
|
|
import type { OrgEmployee, OrgUnitNode, OrgVacancy } from "@/components/orgchart/types";
|
|
import { ToastProvider } from "@/components/ui/Toast";
|
|
|
|
vi.mock("@/actions/org", () => ({
|
|
createOrgUnit: vi.fn(async () => ({ success: true })),
|
|
deleteOrgUnit: vi.fn(async () => ({ success: true })),
|
|
}));
|
|
vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh: vi.fn(), push: vi.fn() }) }));
|
|
|
|
function render(ui: React.ReactElement) {
|
|
return rtlRender(<ToastProvider>{ui}</ToastProvider>);
|
|
}
|
|
|
|
// Der Knopf „Einheit darunter anlegen" hängt allein daran, dass ein Knoten
|
|
// eine `unitId` trägt. Verschwindet die still — etwa weil jemand den Baum
|
|
// umbaut —, bleibt die Oberfläche fehlerfrei und die Handlung ist weg.
|
|
|
|
const UNITS: OrgUnitNode[] = [
|
|
{ id: "u1", org_number: "OE-0001", name: "Gesellschaft", parent_id: null, unit_type: "Gesellschaft" },
|
|
{ id: "u2", org_number: "OE-0010", name: "Produktion", parent_id: "u1", unit_type: "Bereich" },
|
|
];
|
|
|
|
const EMPLOYEES: OrgEmployee[] = [
|
|
{
|
|
id: "p1",
|
|
personnel_number: 1,
|
|
first_name: "Anna",
|
|
last_name: "Huber",
|
|
job_title: "Maschinenbedienung",
|
|
manager_id: null,
|
|
formal_manager_id: null,
|
|
absent: false,
|
|
absence_type: null,
|
|
org_unit_id: "u2",
|
|
is_chief: false,
|
|
position_id: "s1",
|
|
position_number: "S-0001",
|
|
},
|
|
];
|
|
|
|
const VACANCIES: OrgVacancy[] = [
|
|
{ position_id: "s2", position_number: "S-0002", job_title: "Schlosser:in", org_unit_id: "u2", is_chief: false },
|
|
];
|
|
|
|
describe("buildUnitTree", () => {
|
|
const baum = buildUnitTree(UNITS, EMPLOYEES, VACANCIES);
|
|
|
|
it("trägt die Einheit an jedem Einheitenknoten", () => {
|
|
expect(baum[0].unitId).toBe("u1");
|
|
expect(baum[0].children[0].unitId).toBe("u2");
|
|
});
|
|
|
|
it("trägt sie an nichts anderem", () => {
|
|
// Eine Person, eine Tätigkeitsgruppe oder eine Vakanz ist keine Einheit —
|
|
// ein Plus daran würde etwas anlegen, das woanders hinge.
|
|
const bereich = baum[0].children[0];
|
|
for (const kind of bereich.children.filter((k) => k.kind !== "role")) {
|
|
expect(kind.unitId, `„${kind.label}" darf keine Einheit tragen`).toBeUndefined();
|
|
for (const enkel of kind.children) expect(enkel.unitId).toBeUndefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("PositionTree", () => {
|
|
it("bietet je Einheit einen Knopf zum Anlegen an", async () => {
|
|
render(<PositionTree employees={EMPLOYEES} units={UNITS} vacancies={VACANCIES} />);
|
|
// Die Wurzel ist aufgeklappt; ihr Kind ist damit sichtbar.
|
|
expect(await screen.findByRole("button", { name: /Einheit unter .*Gesellschaft anlegen/ })).toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: /Einheit unter .*Produktion anlegen/ })).toBeInTheDocument();
|
|
});
|
|
|
|
it("bietet ihn nicht an einer Person an", () => {
|
|
render(<PositionTree employees={EMPLOYEES} units={UNITS} vacancies={VACANCIES} />);
|
|
expect(screen.queryByRole("button", { name: /Einheit unter Huber/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("bietet den Papierkorb nur an, wo nichts darunter hängt", () => {
|
|
// „Produktion" trägt eine Person und eine Vakanz, die Gesellschaft trägt
|
|
// „Produktion" — entfernbar ist keine von beiden. Ein Knopf, der beim
|
|
// Klick eine Absage erteilt, ist schlechter als kein Knopf.
|
|
render(<PositionTree employees={EMPLOYEES} units={UNITS} vacancies={VACANCIES} />);
|
|
expect(screen.queryByRole("button", { name: /Einheit .* entfernen/ })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("bietet ihn an einer leeren Einheit an", () => {
|
|
const leer: OrgUnitNode = { id: "u3", org_number: "OE-0411", name: "Neu", parent_id: "u1", unit_type: "Bereich" };
|
|
render(<PositionTree employees={EMPLOYEES} units={[...UNITS, leer]} vacancies={VACANCIES} />);
|
|
expect(screen.getByRole("button", { name: /Einheit .*OE-0411.* entfernen/ })).toBeInTheDocument();
|
|
});
|
|
});
|