Files
alpenwerk-hr/tests/components/VertragTab.test.tsx
Maximilian Stubhan f5ace8af2e Make the part-time arrangement a state you can report on
Last step moved the four part-time arrangements out of the absence list
and recorded the reason in the history text. That answered "what
happened" but not "who is in one right now", and the profile showed
nothing at all. So it becomes a real field: teilzeit_art, with an
optional end date.

The objection I raised then still holds — a state goes stale, because
nobody goes back to note when a Bildungsteilzeit ended. teilzeit_bis is
the answer to it: with an end date a report decides for itself what is
still running instead of trusting that someone maintained the row. Left
empty it means "open end", which is an honest thing to say.

It runs through the ordinary change machinery rather than beside it. It
sits in app_feld_karte, so it shows up in the history as a field with
before and after, and can be corrected there like any other. The
description suffix from last step is gone — writing the same thing twice
is how two versions start disagreeing.

Reporting: filter by variant, by "in one at all", and by when it ends;
group headcount by variant, where the absence of one reads "Keine"
rather than a dash, because in a report that is an answer and not a gap.
Plus columns in the export and the import.

One gap found while rehearsing, and only because the probe happened to
pick a return date in the future: a scheduled return carries its payload
through pending_org_changes, and that payload did not include the
variant. Someone would have come back on reduced hours in April with the
reason gone. The daily run now carries it too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:07:41 +02:00

148 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { VertragTab } from "@/components/employees/tabs/VertragTab";
import type { Database } from "@/lib/supabase/types";
type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"];
// Auf dem Vertragsblatt stand ein Feld „Merkmale" mit allem Zutreffenden,
// durch Kommas getrennt — und einem Gedankenstrich, wenn nichts zutraf.
// Daraus liess sich nicht ablesen, ob jemand keinen Dienstwagen hat oder ob
// die Frage nie beantwortet wurde. Und beim Dienstwagen fehlte, worum es
// eigentlich geht: ob Verbrenner oder E-KFZ.
function mitarbeiter(teil: Partial<EmployeeRow> = {}): EmployeeRow {
return {
id: "e1",
personnel_number: 3038,
first_name: "Hannah",
last_name: "Winkler",
gender: "w",
birth_date: "1990-04-01",
sv_nummer: null,
nationality: "Österreich",
address: null,
address_country: null,
postal_code: null,
city: null,
email: null,
phone: null,
job_title: "IT-Security-Spezialist:in",
location_id: "l1",
employment_type: "Vollzeit",
weekly_hours: 38.5,
monthly_salary_gross: null,
contract_type: "unbefristet",
contract_end_date: null,
paygrade: "C",
source: "Extern",
status: "Aktiv",
entry_date: "2017-07-15",
exit_date: null,
exit_reason: null,
karenz_start_date: null,
karenz_return_date: null,
absence_type: null,
avatar_color: null,
worker_type: "Angestellte:r",
collective_agreement: "Handel",
work_days: ["Mo", "Di", "Mi", "Do", "Fr"],
is_betriebsrat: false,
has_dienstwagen: false,
dienstwagen_art: null,
has_kuendigungsschutz: false,
kuendigungsschutz_bis: null,
teilzeit_art: null,
teilzeit_bis: null,
is_laterale_fuehrung: false,
is_c_level: false,
title_prefix: [],
title_suffix: [],
emergency_contact_name: null,
emergency_contact_phone: null,
emergency_contact_relation: null,
created_at: "2017-07-15T00:00:00.000Z",
updated_at: "2017-07-15T00:00:00.000Z",
...teil,
} as EmployeeRow;
}
/** Der Wert, der unter einer Beschriftung steht. */
function wertZu(label: string): string {
return screen.getByText(label).parentElement!.querySelector("dd")!.textContent ?? "";
}
describe("VertragTab — Merkmale", () => {
it("führt jedes Merkmal als eigene Zeile, nicht als Aufzählung", () => {
render(<VertragTab employee={mitarbeiter()} />);
for (const label of ["Dienstwagen", "Betriebsrat", "Laterale Führung", "C-Level"]) {
expect(screen.getByText(label)).toBeInTheDocument();
}
expect(screen.queryByText("Merkmale")).not.toBeInTheDocument();
});
it("sagt Nein, statt das Feld leer zu lassen", () => {
// „–" liesse offen, ob niemand die Frage beantwortet hat.
render(<VertragTab employee={mitarbeiter()} />);
expect(wertZu("Dienstwagen")).toBe("Nein");
expect(wertZu("Betriebsrat")).toBe("Nein");
expect(wertZu("C-Level")).toBe("Nein");
});
it("nennt beim Dienstwagen die Antriebsart statt eines blossen Ja", () => {
render(<VertragTab employee={mitarbeiter({ has_dienstwagen: true, dienstwagen_art: "Elektro" })} />);
expect(wertZu("Dienstwagen")).toBe("E-KFZ");
});
it("unterscheidet den Verbrenner davon", () => {
render(<VertragTab employee={mitarbeiter({ has_dienstwagen: true, dienstwagen_art: "Verbrenner" })} />);
expect(wertZu("Dienstwagen")).toBe("Verbrenner");
});
it("zeigt gesetzte Merkmale als Ja", () => {
render(<VertragTab employee={mitarbeiter({ is_betriebsrat: true, is_c_level: true })} />);
expect(wertZu("Betriebsrat")).toBe("Ja");
expect(wertZu("C-Level")).toBe("Ja");
expect(wertZu("Laterale Führung")).toBe("Nein");
});
});
describe("VertragTab — Kündigungsschutz", () => {
it("nennt das Enddatum statt eines blossen Ja", () => {
// „Ja" allein liesse offen, ob der Schutz noch läuft — und genau danach
// fragt jemand, der auf dieses Blatt schaut.
render(<VertragTab employee={mitarbeiter({ has_kuendigungsschutz: true, kuendigungsschutz_bis: "2027-06-30" })} />);
expect(wertZu("Besonderer Kündigungsschutz")).toBe("bis 30.06.2027");
});
it("sagt es auch, wenn das Ende offen ist", () => {
render(<VertragTab employee={mitarbeiter({ has_kuendigungsschutz: true, kuendigungsschutz_bis: null })} />);
expect(wertZu("Besonderer Kündigungsschutz")).toBe("Ja (Ende offen)");
});
it("steht ohne Schutz auf Nein", () => {
render(<VertragTab employee={mitarbeiter()} />);
expect(wertZu("Besonderer Kündigungsschutz")).toBe("Nein");
});
});
// Die Teilzeitvarianten sind keine Abwesenheiten, sondern ein Zustand mit
// weniger Stunden. Damit darüber berichtet werden kann, steht die Variante an
// der Person — und dann muss sie auch am Profil zu sehen sein.
describe("VertragTab — Teilzeitvariante", () => {
it("nennt Variante und Ende", () => {
render(<VertragTab employee={mitarbeiter({ teilzeit_art: "Bildungsteilzeit", teilzeit_bis: "2027-06-30" })} />);
expect(wertZu("Teilzeitvariante")).toBe("Bildungsteilzeit bis 30.06.2027");
});
it("sagt es auch, wenn das Ende offen ist", () => {
render(<VertragTab employee={mitarbeiter({ teilzeit_art: "Elternteilzeit", teilzeit_bis: null })} />);
expect(wertZu("Teilzeitvariante")).toBe("Elternteilzeit (Ende offen)");
});
it("bleibt leer, wo keine besteht", () => {
render(<VertragTab employee={mitarbeiter()} />);
expect(wertZu("Teilzeitvariante")).toBe("–");
});
});