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:
@@ -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",
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user