Files
planer/src/lib/aufgabe-anzeige.test.ts
T
Florian WildeandClaude Opus 5 63c3c15a3c feat(aufgaben): Datenmodell und Regeln für Aufgaben und Projekte
Aufgabe mit Typ, Status, Priorität, Aufwand, Fälligkeit und Herkunft;
Projekt als eigene Klammer darum.

Zwei Entwurfsentscheidungen:
- Die Inbox ist kein eigener Zustand, sondern schlicht eine Aufgabe ohne
  Bereich. Einsortieren heißt: einen Bereich setzen. Ein zusätzliches
  Feld könnte mit der Zuordnung auseinanderlaufen.
- Projekt ist ein eigenes Modell, kein Aufgabentyp. Ein Projekt hat
  selbst weder Aufwand noch Fälligkeit, sondern leitet beides aus dem
  ab, was drinsteckt. ARBEITSPAKET und MEILENSTEIN sind Typen der
  Aufgaben *innerhalb* eines Projekts.

@@unique([quelle, quelleRef]) ist die Grundlage für die Integrationen ab
M6: sie upserten darüber, statt zu doppeln. Von Hand erfasste Aufgaben
tragen quelleRef = null; PostgreSQL sieht NULL-Werte als verschieden an,
sie behindern sich also nicht.

Verworfene Aufgaben zählen bewusst gar nicht in den Fortschritt eines
Projekts — sonst sähe es fertig aus, weil die Hälfte gestrichen wurde.

Fälligkeit wird in Kalendertagen in Europe/Berlin gerechnet, nicht in
24-Stunden-Schritten: was heute um 23:00 fällig ist, ist heute fällig.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:17:41 +02:00

120 lines
3.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { faelligkeitFarbe, fuerAnzeige, type AufgabeRoh } from './aufgabe-anzeige'
// Dienstag, 17. März 2026, 10:00 Uhr Berliner Zeit.
const jetzt = new Date('2026-03-17T09:00:00Z')
function roh(teil: Partial<AufgabeRoh> = {}): AufgabeRoh {
return {
id: 'a1',
titel: 'Angebot schreiben',
notiz: null,
typ: 'AUFGABE',
status: 'OFFEN',
prioritaet: 3,
aufwandMinuten: 60,
faelligAm: null,
quelle: 'MANUELL',
quelleUrl: null,
bereich: null,
projekt: null,
...teil,
}
}
describe('fuerAnzeige', () => {
it('lässt den Datumswert leer, wenn kein Termin gesetzt ist', () => {
const anzeige = fuerAnzeige(roh(), jetzt)
expect(anzeige.faelligWert).toBe('')
expect(anzeige.faelligDatum).toBeNull()
expect(anzeige.faelligText).toBe('ohne Termin')
expect(anzeige.ueberfaellig).toBe(false)
})
it('gibt den Datumswert im Format des Eingabefelds zurück', () => {
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-20T12:00:00Z') }),
jetzt,
)
expect(anzeige.faelligWert).toBe('2026-03-20')
})
it('nimmt den Berliner Kalendertag, nicht den UTC-Tag', () => {
// 23:30 UTC am 19. ist in Berlin bereits der 20. um 00:30.
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-19T23:30:00Z') }),
jetzt,
)
expect(anzeige.faelligWert).toBe('2026-03-20')
})
it('markiert eine offene Aufgabe von gestern als überfällig', () => {
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-16T09:00:00Z') }),
jetzt,
)
expect(anzeige.ueberfaellig).toBe(true)
expect(anzeige.faelligText).toBe('seit gestern überfällig')
})
it('markiert eine erledigte Aufgabe von gestern nicht als überfällig', () => {
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-16T09:00:00Z'), status: 'ERLEDIGT' }),
jetzt,
)
expect(anzeige.ueberfaellig).toBe(false)
})
it('schreibt das Datum ausgeschrieben für den Hinweis', () => {
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-20T12:00:00Z') }),
jetzt,
)
expect(anzeige.faelligDatum).toBe('Fr., 20.03.2026')
})
it('reicht Bereich und Projekt unverändert durch', () => {
const anzeige = fuerAnzeige(
roh({
bereich: { id: 'b1', name: 'Gewerbe', farbe: '#7c3aed' },
projekt: { id: 'p1', name: 'Website neu' },
}),
jetzt,
)
expect(anzeige.bereich?.name).toBe('Gewerbe')
expect(anzeige.projekt?.name).toBe('Website neu')
})
})
describe('faelligkeitFarbe', () => {
it('warnt rot bei Überfälligkeit', () => {
const anzeige = fuerAnzeige(
roh({ faelligAm: new Date('2026-03-15T09:00:00Z') }),
jetzt,
)
expect(faelligkeitFarbe(anzeige)).toBe('gefahr')
})
it('warnt gelb für heute und morgen', () => {
expect(
faelligkeitFarbe(
fuerAnzeige(roh({ faelligAm: new Date('2026-03-17T15:00:00Z') }), jetzt),
),
).toBe('warnung')
expect(
faelligkeitFarbe(
fuerAnzeige(roh({ faelligAm: new Date('2026-03-18T15:00:00Z') }), jetzt),
),
).toBe('warnung')
})
it('bleibt bei allem Weiteren unauffällig', () => {
expect(
faelligkeitFarbe(
fuerAnzeige(roh({ faelligAm: new Date('2026-04-01T09:00:00Z') }), jetzt),
),
).toBe('neutral')
expect(faelligkeitFarbe(fuerAnzeige(roh(), jetzt))).toBe('neutral')
})
})