Files
alpenwerk-hr/tests/unit/colors.test.ts
Andrei Laas 7a33e493b5
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 10m59s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m0s
Die Historie bekommt ihre eigenen Farben
In "Letzte Aktivitaeten" stand "Eintritt" weiter auf Rosa, waehrend die Karte
daneben ihn laengst gruen zeigte. Dieselbe Ursache wie bei den
Anstehend-Chips, nur eine Ecke weiter: die Uebersicht zeigt Ereignisse aus
employee_history, holte ihre Farbe aber aus ACTION_CATEGORY — und das ist die
Sprache des Protokolls. Dort heisst es "Neueinstellung" und
"Wiedereinstellung", in der Historie "Eintritt" und "Wiedereintritt". Genau
diese zwei von elf standen nicht darin und fielen auf den neutralen Chip
zurueck; die uebrigen neun trafen zufaellig.

EVENT_CATEGORY ist jetzt die Zuordnung fuer die Historie, als
Record<HistoryEventType, …> und damit vollzaehlig: ein zwoelftes Ereignis
laesst der Typpruefer nicht durch, ohne dass jemand eine Farbe dafuer
bestimmt. Ein Nachschlagen mit Rueckfall haette auch dann wieder still etwas
Plausibles geliefert.

Betroffen war nicht nur die Uebersicht — der Historie-Reiter in der
Personalakte faerbte seine Chips und seine Filterknoepfe aus derselben
falschen Tabelle. Auch die sind umgestellt.

Die Punkte vor den Zeilen lagen in einer zweiten Tabelle in page.tsx und
sagten fuer "Eintritt" bereits gruen — Punkt und Chip derselben Zeile kamen
also aus zwei Verzeichnissen, von denen eines das falsche war. Beide leiten
jetzt aus EVENT_CATEGORY ab.

Die Rueckkehr ist dabei violett geworden, auch in der Historie: auf der
Uebersicht steht sie neben dem Eintritt, und zwei Gruentoene nebeneinander
sind keine zwei Dinge. ANSTEHEND_STYLES leitet fuer Eintritt, Austritt und
Rueckkehr aus derselben Tabelle ab — die beiden Karten koennen nicht mehr
auseinanderlaufen.

ACTION_CATEGORY behaelt seinen Rueckfall, und das bleibt richtig: die
Aktionen schreiben die SQL-Funktionen als freien Text, eine neue kann
jederzeit dazukommen, und ihr neutraler Chip ist dann eine ehrliche Aussage.
Fuer eine geschlossene Aufzaehlung war derselbe Rueckfall ein Fehler.

Acht Tests, aus EVENT_TYPE_LABELS abgeleitet statt abgeschrieben: dass jedes
Ereignis eine Farbe hat, dass keines den neutralen Chip bekommt, dass Punkt
und Chip derselben Zeile zusammenpassen und dass die beiden Karten der
Uebersicht dasselbe meinen.

Lint, Typen, Schemaabgleich, 575 Tests und der Build sind sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:48:54 +02:00

118 lines
4.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
actionBadgeStyle,
ANSTEHEND_STYLES,
avatarColorFor,
CATEGORY_STYLES,
EVENT_CATEGORY,
eventBadgeStyle,
eventDotStyle,
STATUS_STYLES,
} from "@/lib/colors";
import { EVENT_TYPE_LABELS } from "@/lib/reports";
import type { HistoryEventType } from "@/lib/types";
describe("avatarColorFor", () => {
it("is deterministic for the same seed", () => {
expect(avatarColorFor("Maria Gruber")).toBe(avatarColorFor("Maria Gruber"));
});
it("returns a value from the fixed palette (a hex color)", () => {
expect(avatarColorFor("Anna Huber")).toMatch(/^#[0-9a-f]{6}$/i);
});
});
describe("STATUS_STYLES", () => {
it("has an entry for every employment status", () => {
expect(Object.keys(STATUS_STYLES).sort()).toEqual(["Aktiv", "Ausgetreten", "Geplant", "Karenz"].sort());
});
});
describe("actionBadgeStyle", () => {
it("maps known audit actions to their category style", () => {
expect(actionBadgeStyle("Austritt")).toBe(STATUS_STYLES.Ausgetreten);
expect(actionBadgeStyle("Neueinstellung")).toBe(STATUS_STYLES.Aktiv);
});
// Gegen CATEGORY_STYLES.brand geprüft und nicht gegen einen abgetippten
// Klassennamen: welche Farbe der Chip ohne eigene Bedeutung trägt, ist eine
// Frage der Palette und hat sich mit der Umstellung auf das Manner-CD schon
// einmal geändert. Der Test soll die Zuordnung sichern, nicht den Farbton.
it("falls back to the neutral category style for an unknown action", () => {
expect(actionBadgeStyle("Irgendwas Unbekanntes")).toBe(CATEGORY_STYLES.brand);
});
});
// ═══ Historie: die Sprache von employee_history, nicht die des Protokolls ═══
//
// Die Uebersicht und der Historie-Reiter zeigen Ereignisse der Personalakte,
// holten ihre Farbe aber aus der Tabelle der Protokoll-Aktionen. Die kennt
// „Neueinstellung" und „Wiedereinstellung"; die Historie sagt „Eintritt" und
// „Wiedereintritt". Genau diese zwei standen dort nicht und bekamen den
// neutralen Chip — in „Letzte Aktivitaeten" war „Eintritt" rosa, waehrend die
// Karte daneben ihn laengst gruen zeigte.
describe("eventBadgeStyle", () => {
// Aus EVENT_TYPE_LABELS abgeleitet und nicht abgeschrieben: kaeme ein
// zwoelftes Ereignis dazu, prueft dieser Test es mit.
const ALLE = Object.keys(EVENT_TYPE_LABELS) as HistoryEventType[];
it("kennt jedes Ereignis der Personalakte", () => {
expect(ALLE.length).toBeGreaterThan(0);
for (const typ of ALLE) {
expect(EVENT_CATEGORY[typ], typ).toBeDefined();
}
});
it("gibt keinem Ereignis den neutralen Chip", () => {
// Der neutrale Chip heisst „keine eigene Bedeutung". Fuer ein Ereignis
// der Personalakte ist das nie die Wahrheit, sondern immer ein
// Nachschlagen, das danebengegriffen hat.
for (const typ of ALLE) {
expect(eventBadgeStyle(typ), typ).not.toBe(CATEGORY_STYLES.brand);
}
});
it("faerbt Eintritt und Wiedereintritt gruen", () => {
expect(eventBadgeStyle("Eintritt")).toBe(CATEGORY_STYLES.success);
expect(eventBadgeStyle("Wiedereintritt")).toBe(CATEGORY_STYLES.success);
});
it("faerbt den Austritt rot", () => {
expect(eventBadgeStyle("Austritt")).toBe(CATEGORY_STYLES.danger);
});
it("gibt Punkt und Chip derselben Zeile dieselbe Kategorie", () => {
// Beide leiten aus EVENT_CATEGORY ab; vorher lagen sie in zwei
// getrennten Verzeichnissen und eines davon war das falsche.
for (const typ of ALLE) {
expect(eventDotStyle(typ), typ).toContain(
eventBadgeStyle(typ).match(/text-([a-z]+)-text/)?.[1] ?? "###"
);
}
});
});
describe("Uebersicht: beide Karten meinen dasselbe", () => {
// Anstehend und Letzte Aktivitaeten stehen nebeneinander. Ein Eintritt darf
// links nicht anders aussehen als rechts — genau das war zu sehen.
it("Eintritt, Austritt und Rueckkehr stimmen ueberein", () => {
expect(ANSTEHEND_STYLES.hire).toBe(eventBadgeStyle("Eintritt"));
expect(ANSTEHEND_STYLES.exit).toBe(eventBadgeStyle("Austritt"));
expect(ANSTEHEND_STYLES.return).toBe(eventBadgeStyle("Rückkehr"));
});
it("haelt die vier Arten voneinander getrennt", () => {
const werte = Object.values(ANSTEHEND_STYLES);
expect(new Set(werte).size).toBe(werte.length);
});
});
// Das Protokoll behaelt seinen Rueckfall, und das ist richtig: die Aktionen
// schreiben die SQL-Funktionen als freien Text, eine neue kann jederzeit
// dazukommen. Ihr neutraler Chip ist dann eine ehrliche Aussage.
describe("actionBadgeStyle bleibt offen", () => {
it("faengt eine unbekannte Aktion mit dem neutralen Chip auf", () => {
expect(actionBadgeStyle("Eine neue Aktion aus einer Migration")).toBe(CATEGORY_STYLES.brand);
});
});