My-Weekly-ToDo-List/tests/unit/format.test.ts
mARTin-B78 5c5f2a284e
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run
feat: 63 Unit-Tests fuer die ausgelagerten Funktionen
Nodes eingebauter Test-Runner mit --experimental-strip-types: keine neue
Abhaengigkeit, keine Browser, keine Datenbank, Laufzeit rund 170 ms.
npm test fuehrt sie aus, npm run test:e2e startet weiterhin Playwright.

Die Tests haben sofort zwei echte Fehler gefunden, die ich beim Auslagern
in v1.117.7 selbst eingebaut hatte:

- isAllDayEvent rief parseCalendarDate ohne Import auf
- resolveViewDays nutzte clampViewDays ohne Import

Beides haette zur Laufzeit einen ReferenceError gegeben. Der Build meldete
nichts, weil typescript.ignoreBuildErrors noch aktiv ist -- genau der Fall,
gegen den C1 laeuft. isAllDayEvent liegt jetzt in date-utils.ts, wo es
thematisch hingehoert; clampViewDays und MAX_VIEW_DAYS in
device-settings.ts, DaysSelector re-exportiert sie.

Geprueft werden ISO-Kalenderwochen gegen bekannte Kalenderdaten, die
Wochenstart-Berechnung inklusive Sonntag-Sonderfall, das Zeitraster, die
12/24-Stunden-Anzeige und der Round-Trip formatDateToISO <-> parseCalendarDate
-- die Bruecke zwischen Datenbank und Anzeige, aus der Ein-Tag-Verschiebungen
entstehen.

Mit npm run build verifiziert (Exit 0).

v1.118.0

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

134 lines
4.6 KiB
TypeScript

import { test, describe } from "node:test";
import assert from "node:assert/strict";
import {
stripHtml, invertColor, getSomedaySlotCount, DEFAULT_SOMEDAY_SLOT_COUNT,
} from "../../src/lib/weekly/format.ts";
import { isAllDayEvent } from "../../src/lib/weekly/date-utils.ts";
import { clampViewDays, MAX_VIEW_DAYS } from "../../src/lib/weekly/device-settings.ts";
import type { Task, CalendarEvent } from "../../src/lib/weekly/types.ts";
/**
* Unit-Tests der Darstellungs-Helfer und der Spaltenbegrenzung.
* Läuft ohne Browser und ohne Datenbank: `npm run test:unit`.
*/
// Minimale Aufgabe — nur die Felder, die die getestete Funktion liest.
const task = (somedaySlotIndex: number | null): Task => ({
id: "t", title: "t", completed: false, updatedAt: new Date(), order: 0,
userId: "u", somedaySlotIndex,
});
describe("stripHtml", () => {
test("entfernt Tags, behält den Text", () => {
assert.equal(stripHtml("<p>Hallo <b>Welt</b></p>"), "Hallo Welt");
});
test("schneidet umgebende Leerzeichen ab", () => {
assert.equal(stripHtml(" <p> Text </p> "), "Text");
});
test("lässt reinen Text unverändert", () => {
assert.equal(stripHtml("Einkaufen gehen"), "Einkaufen gehen");
});
test("kommt mit leerer Eingabe zurecht", () => {
assert.equal(stripHtml(""), "");
});
});
describe("invertColor", () => {
test("invertiert Weiß zu Schwarz", () => {
assert.equal(invertColor("#ffffff"), "#000000");
});
test("invertiert Schwarz zu Weiß", () => {
assert.equal(invertColor("#000000"), "#ffffff");
});
test("Kurzform wird ausgeschrieben", () => {
assert.equal(invertColor("#fff"), "#000000");
});
test("Rautezeichen ist optional", () => {
assert.equal(invertColor("ffffff"), "#000000");
});
test("zweimal invertieren ergibt die Ausgangsfarbe", () => {
assert.equal(invertColor(invertColor("#3b82f6")), "#3b82f6");
});
test("ungültige Eingaben werden unverändert zurückgegeben", () => {
assert.equal(invertColor("keine-farbe"), "keine-farbe");
assert.equal(invertColor(""), "");
});
});
describe("getSomedaySlotCount", () => {
test("leere Liste ergibt die Grundanzahl", () => {
assert.equal(getSomedaySlotCount([]), DEFAULT_SOMEDAY_SLOT_COUNT);
});
test("hält unter der Grundanzahl immer die Grundanzahl", () => {
assert.equal(getSomedaySlotCount([task(0), task(1)]), DEFAULT_SOMEDAY_SLOT_COUNT);
});
test("wächst mit, sobald der letzte Platz belegt ist", () => {
// Anspruch laut Kommentar im Code: unten bleibt immer ein freier Platz.
assert.equal(getSomedaySlotCount([task(5)]), 7);
});
test("Aufgaben ohne Platznummer verändern nichts", () => {
assert.equal(getSomedaySlotCount([task(null), task(null)]), DEFAULT_SOMEDAY_SLOT_COUNT);
});
});
describe("isAllDayEvent", () => {
// Diese Funktion rief parseCalendarDate auf, ohne es zu importieren — beim Auslagern
// aus WeeklyView.tsx übersehen. Der Build meldete nichts, weil ignoreBuildErrors
// aktiv war; zur Laufzeit hätte es einen ReferenceError gegeben. Dieser Test hätte
// den Fehler sofort gefunden.
const ev = (startTime: string, endTime: string): CalendarEvent =>
({ id: "e", title: "e", startTime, endTime, source: "google" });
test("reines Datum ohne Uhrzeit gilt als ganztägig", () => {
assert.equal(isAllDayEvent(ev("2026-09-07", "2026-09-08")), true);
});
test("Termin mit Uhrzeit über eine Stunde ist nicht ganztägig", () => {
assert.equal(isAllDayEvent(ev("2026-09-07T10:00:00", "2026-09-07T11:00:00")), false);
});
test("Mitternacht bis Mitternacht gilt als ganztägig", () => {
assert.equal(isAllDayEvent(ev("2026-09-07T00:00:00", "2026-09-08T00:00:00")), true);
});
test("ohne Startzeit nicht ganztägig", () => {
assert.equal(isAllDayEvent(ev("", "")), false);
});
});
describe("clampViewDays", () => {
test("gültige Werte bleiben unverändert", () => {
for (const n of [1, 7, 14, 21, 31]) assert.equal(clampViewDays(n), n);
});
test("begrenzt nach oben", () => {
assert.equal(clampViewDays(999), MAX_VIEW_DAYS);
});
test("begrenzt nach unten", () => {
assert.equal(clampViewDays(0), 1);
assert.equal(clampViewDays(-5), 1);
});
test("rundet Kommazahlen", () => {
assert.equal(clampViewDays(7.4), 7);
assert.equal(clampViewDays(7.6), 8);
});
test("fällt bei Unsinn auf 7 zurück", () => {
assert.equal(clampViewDays(NaN), 7);
assert.equal(clampViewDays(Infinity), 7);
});
});