Phase 9: Tests fuer die portierte Rechenlogik

41 weitere Tests fuer Statistik, Service-Prognose und Formatierung. Die
Faelle sind entlang der Regeln gewaehlt, die beim Portieren wichtig waren:
Wochenbeginn Montag, Nachtspanne ueber Mitternacht, mengengewichteter
Durchschnittspreis statt Mittel der Einzelpreise, Plausibilitaetsfenster beim
Langzeitverbrauch, Neustart der Oelprognose ab dem letzten Wechsel samt
Zeitlimit-Deckelung, BOM und Semikolon in der CSV-Ausgabe.

Ein Test war zunaechst falsch: er verglich das UTC-Datum gegen eine
Ortszeit-Erwartung. Der Code rechnet bewusst lokal - sonst begaenne der
Monat in unserer Zeitzone einen Tag zu frueh.

companion-app/README.md auf den erreichten Stand gebracht.

Gesamt: 90 Unit- und Rendertests plus 9 Pruefungen gegen die laufende
Home-Assistant-Instanz.
This commit is contained in:
Paul Nothaft
2026-08-11 11:21:56 +02:00
parent 571d12310f
commit a68817065b
4 changed files with 451 additions and 60 deletions
+103
View File
@@ -0,0 +1,103 @@
import { describe, expect, it } from "vitest"
import { alsCsv } from "./screens/csv"
import { kalenderDatei } from "./screens/kalender"
import { dauer, de, deOderStrich, eur, isoTag, schnittpreis, summe } from "./format"
describe("Zahlen im deutschen Format", () => {
it("setzt Punkt als Tausender- und Komma als Dezimaltrennzeichen", () => {
expect(de(48250)).toBe("48.250")
expect(de(1234.5, 1)).toBe("1.234,5")
expect(eur(89.1)).toBe("89,10")
})
it("macht aus fehlenden Werten eine Null, nicht NaN", () => {
expect(de(null)).toBe("0")
expect(de(undefined)).toBe("0")
})
it("unterscheidet mit deOderStrich zwischen null und unbekannt", () => {
expect(deOderStrich(0)).toBe("0")
expect(deOderStrich(null)).toBe("—")
})
})
describe("dauer", () => {
it("zeigt unter einer Stunde nur Minuten", () => {
expect(dauer(2700)).toBe("45 min")
})
it("zeigt darüber Stunden und Minuten zweistellig", () => {
expect(dauer(8040)).toBe("2 h 14 min")
})
it("zeigt einen Strich statt „0 min", () => {
expect(dauer(0)).toBe("—")
expect(dauer(null)).toBe("—")
})
})
describe("schnittpreis", () => {
it("gewichtet nach Menge, nicht nach Anzahl der Tankungen", () => {
// 10 l zu 2,00 € und 90 l zu 1,00 €: der Schnitt muss nahe 1,10 liegen,
// nicht bei 1,50 (dem Mittel der Einzelpreise).
const schnitt = schnittpreis([
{ liters: 10, fuel_total_eur: 20 },
{ liters: 90, fuel_total_eur: 90 },
])
expect(schnitt).toBeCloseTo(1.1, 2)
})
it("liefert 0 statt einer Division durch null", () => {
expect(schnittpreis([])).toBe(0)
expect(schnittpreis([{ liters: 0, fuel_total_eur: 10 }])).toBe(0)
})
})
describe("summe", () => {
it("überspringt fehlende Werte", () => {
expect(summe([{ a: 1 }, { a: null }, { a: 3 }], (e) => e.a ?? 0)).toBe(4)
})
})
describe("isoTag", () => {
it("gibt das lokale Datum aus, nicht das UTC-Datum", () => {
// 23:30 Ortszeit ist in UTC schon der Folgetag — der Tag darf nicht kippen.
const spaet = new Date(2026, 7, 11, 23, 30)
expect(isoTag(spaet)).toBe("2026-08-11")
})
})
describe("CSV-Ausgabe", () => {
it("trennt mit Semikolon und schreibt Dezimalkomma", () => {
const csv = alsCsv(["Strecke", "Ort"], [[42.5, "München"]])
expect(csv).toContain("Strecke;Ort")
expect(csv).toContain("42,5;München")
})
it("beginnt mit einem BOM, damit Excel die Umlaute richtig liest", () => {
expect(alsCsv(["a"], [])[0]).toBe("")
})
it("schützt Felder mit Semikolon oder Anführungszeichen", () => {
const csv = alsCsv(["Text"], [['Er sagte "hallo"; dann ging er']])
expect(csv).toContain('"Er sagte ""hallo""; dann ging er"')
})
})
describe("Kalenderdatei", () => {
it("erzeugt einen ganztägigen Termin mit Erinnerung zwei Tage vorher", () => {
const ics = kalenderDatei("Ölwechsel", "2027-03-14", "laut Prognose")
expect(ics).toContain("BEGIN:VCALENDAR")
expect(ics).toContain("DTSTART;VALUE=DATE:20270314")
expect(ics).toContain("SUMMARY:Ölwechsel")
expect(ics).toContain("TRIGGER:-P2D")
expect(ics.trimEnd().endsWith("END:VCALENDAR")).toBe(true)
})
it("schützt Sonderzeichen im Titel", () => {
expect(kalenderDatei("Service; Öl, Filter", "2027-01-01")).toContain(
"SUMMARY:Service\\; Öl\\, Filter",
)
})
})