Files
alpenwerk-hr/tests/components/VertragTab.test.tsx
Maximilian Stubhan d2f4a7aab7 Ask for a residence permit only where one is needed
Employees from outside the EU, the EEA and Switzerland need a residence
permit, and HR needs to know when it expires — about eighty people in
the current data, across Türkei, Serbien and Bosnien. Two columns, the
same shape as the dismissal protection: a flag and a date that only
means anything with it. The date is optional, because an open-ended
permit has none and a mandatory field would force an invented one.

The nationality coupling deliberately stays out of the database. Putting
it there would mean keeping the country list in two places — SQL and
lib/countries.ts, where the picker needs it anyway — so an EU accession
would become a migration instead of a line in a list. Worse, correcting
somebody's nationality would fail the constraint while the old permit
was still attached, which is exactly the moment someone is fixing a
mistake. The UI decides whether the fields appear, and clears them when
the nationality moves into the free-movement area.

So the list is the load-bearing part, and it is tested: 31 entries, all
of them values the picker can actually produce, no duplicates, no third
countries. A missing nationality reads as "no permit required" — an
unanswered question is a reason to record it, not to demand papers.

The permit shows on the Stammdaten tab only for the nationalities it
applies to. A line reading "Aufenthaltstitel: Nein" under an Austrian
citizenship would look like information rather than a question that does
not arise.

Filter by it and by when it expires — the question behind that being
"whose permit runs out next quarter" — plus columns in the export.

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

150 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",
hat_aufenthaltstitel: false,
aufenthaltstitel_bis: null,
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("–");
});
});