feat(tasks): wiederkehrende Aufgaben
Taeglich, woechentlich, alle zwei Wochen oder monatlich. Wandert die Aufgabe nach ERLEDIGT, entsteht die naechste von selbst. Die erledigte bleibt stehen - sonst ginge die Historie verloren. Gerechnet wird vom Faelligkeitsdatum, nicht vom Abhaken: eine woechentliche Aufgabe bleibt auf ihrem Montag, auch wenn sie einmal erst am Mittwoch erledigt wurde. Lange Liegengebliebenes rutscht so weit vor, bis der Termin in der Zukunft liegt; eine Aufgabe, die schon bei ihrer Entstehung ueberfaellig ist, hilft niemandem. Beim Monatsintervall bleibt der Tag im Monat erhalten. setMonth allein rollt ueber: der 31. Januar wuerde zum 3. Maerz statt zum 28. Februar. Ausgeloest wird an beiden Stellen, an denen eine Aufgabe erledigt werden kann - im Dialog und durch Ziehen ins Brett. Nur der Uebergang zaehlt: eine bereits erledigte Aufgabe erneut zu speichern erzeugt keine zweite Fassung. Bewusst grob gehalten. Feinere Regeln wie "jeden zweiten Dienstag im Monat" braucht ein Projektdashboard nicht, und sie waeren ohne Kalenderansicht kaum nachvollziehbar. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+55
-1
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { TASK_STATUS_META, TASK_STATUS_ORDER, progressFromTasks } from "./tasks";
|
||||
import {
|
||||
TASK_STATUS_META,
|
||||
TASK_STATUS_ORDER,
|
||||
nextDueDate,
|
||||
progressFromTasks,
|
||||
} from "./tasks";
|
||||
|
||||
describe("progressFromTasks", () => {
|
||||
it("rechnet den Anteil erledigter Aufgaben in Prozent um", () => {
|
||||
@@ -34,3 +39,52 @@ describe("Spaltenreihenfolge", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("nextDueDate", () => {
|
||||
const abhaken = (iso: string) => new Date(iso);
|
||||
|
||||
it("rechnet vom bisherigen Termin, nicht vom Abhaken", () => {
|
||||
// Woechentlich, faellig Montag, erledigt erst Mittwoch: der naechste
|
||||
// Termin bleibt der Montag darauf, nicht der uebernaechste Mittwoch.
|
||||
const next = nextDueDate("WEEKLY", new Date("2026-07-06T12:00:00Z"), abhaken("2026-07-08T09:00:00Z"));
|
||||
expect(next.toISOString().slice(0, 10)).toBe("2026-07-13");
|
||||
});
|
||||
|
||||
it("nimmt ohne bisherigen Termin den Zeitpunkt des Abhakens", () => {
|
||||
const next = nextDueDate("DAILY", null, abhaken("2026-07-26T09:00:00Z"));
|
||||
expect(next.toISOString().slice(0, 10)).toBe("2026-07-27");
|
||||
});
|
||||
|
||||
it("schiebt so lange weiter, bis der Termin in der Zukunft liegt", () => {
|
||||
// Ein halbes Jahr liegengeblieben: die naechste Fassung darf nicht
|
||||
// schon bei ihrer Entstehung ueberfaellig sein.
|
||||
const next = nextDueDate("WEEKLY", new Date("2026-01-05T12:00:00Z"), abhaken("2026-07-26T09:00:00Z"));
|
||||
expect(next.getTime()).toBeGreaterThan(abhaken("2026-07-26T09:00:00Z").getTime());
|
||||
// Und es bleibt ein Montag.
|
||||
expect(next.getUTCDay()).toBe(1);
|
||||
});
|
||||
|
||||
it("haelt beim Monatsintervall den Tag im Monat", () => {
|
||||
const next = nextDueDate("MONTHLY", new Date("2026-03-15T12:00:00Z"), abhaken("2026-03-16T09:00:00Z"));
|
||||
expect(next.toISOString().slice(0, 10)).toBe("2026-04-15");
|
||||
});
|
||||
|
||||
it("rollt beim Monatsintervall nicht ueber kurze Monate hinweg", () => {
|
||||
// Der 31. Januar plus ein Monat ist der 28. Februar, nicht der 3. Maerz.
|
||||
const next = nextDueDate("MONTHLY", new Date("2026-01-31T12:00:00Z"), abhaken("2026-02-01T09:00:00Z"));
|
||||
expect(next.toISOString().slice(0, 10)).toBe("2026-02-28");
|
||||
});
|
||||
|
||||
it("kennt zwei Wochen", () => {
|
||||
const next = nextDueDate("BIWEEKLY", new Date("2026-07-01T12:00:00Z"), abhaken("2026-07-02T09:00:00Z"));
|
||||
expect(next.toISOString().slice(0, 10)).toBe("2026-07-15");
|
||||
});
|
||||
|
||||
it("endet auch bei einem absurd alten Termin", () => {
|
||||
// Ohne Deckel liefe die Schleife bei einem taeglichen Intervall und
|
||||
// einem Datum von 1970 sehr lange.
|
||||
const next = nextDueDate("DAILY", new Date("1970-01-01T00:00:00Z"), abhaken("2026-07-26T09:00:00Z"));
|
||||
expect(next instanceof Date).toBe(true);
|
||||
expect(Number.isNaN(next.getTime())).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user