Files
ApiCore/ApiCore-Frontend/src/lib/system/labels.spec.ts
T
Nick SzaunigandClaude Opus 5.5 ce922c8569
Backend Image / build-and-push (push) Successful in 5m31s
Frontend Image / build-and-push (push) Failing after 18m45s
feat: Sprachnotiz in der Durchsicht, Info-Seite und Zähler (T7 L5 Tasks 20, 22)
Task 20: /inspections/new?voice=<id> zeigt die Sprachnotiz über dem
Formular (Abgeschriebenes, Unklares, „Alle übernehmen“ mit Rückfrage)
und Vorschläge unter den Feldern mit wörtlichem Zitat; nichts wird ohne
Tipp übernommen, die Volkskarte wird nie angehakt. Bei ausgeschalteter
Auswertung ist der Text offen zum Abtippen. Speichern schickt
voiceCaptureId; bei 409 VOICE_CAPTURE_GONE genau ein neuer Versuch ohne
Notiz mit demselben Idempotency-Key (online und aus der Warteschlange);
VOICE_CAPTURE_COLONY_MISMATCH nie automatisch, sondern „Ohne
Sprachnotiz speichern/senden“. Skalen aus lib/inspections/scales.ts mit
Paritätstest zum Backend.

Task 22: /settings/voice mit Modell und Fristen (bei ausgeschalteter
Auswertung ausdrücklich: nichts geht an einen KI-Dienst), „Alle meine
Sprachnotizen löschen“ auf Server und Gerät mit Anzahl; Systemkarte
„Sprachnotizen“ (nur system:manage) mit Zählern und Kosten der letzten
7 Tage (Ruling 15); Service Worker cacht /api/v1/voice-captures nie,
/voice und /voice/new offline erreichbar.

Gates: Frontend lint, typecheck, 3106 Tests, build.

Refs #27

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-16 13:03:09 +02:00

96 lines
3.8 KiB
TypeScript

import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import {
DOMAIN_EVENT_LABELS,
JOB_LABELS,
JOB_RUN_STATUS_LABELS,
QUEUE_STALE_WARNING_SECONDS,
WEBHOOK_DELIVERY_STATUS_LABELS,
attemptsLabel,
formatAge,
} from "./labels";
import { JOB_NAMES } from "./types";
describe("system labels", () => {
it("labels every job", () => {
expect(JOB_NAMES.every((job) => JOB_LABELS[job].length > 0)).toBe(true);
expect(JOB_LABELS.cleanup).toBe("Aufräumen");
});
it("labels the voice transcribe job and both voice events", () => {
expect(JOB_LABELS["voice-transcribe"]).toBe("Sprachnotizen abschreiben");
expect(DOMAIN_EVENT_LABELS.VoiceCaptureReady).toBe("Sprachnotiz fertig");
expect(DOMAIN_EVENT_LABELS.VoiceCaptureFailed).toBe("Sprachnotiz fehlgeschlagen");
});
it("labels the run and delivery states", () => {
expect(JOB_RUN_STATUS_LABELS.FAILED).toBe("Fehlgeschlagen");
expect(WEBHOOK_DELIVERY_STATUS_LABELS.DELIVERED).toBe("Zugestellt");
expect(WEBHOOK_DELIVERY_STATUS_LABELS.PENDING).toBe("Offen");
expect(DOMAIN_EVENT_LABELS.InspectionRecorded).toBe("Durchsicht erfasst");
});
it("DOMAIN_EVENT_LABELS names ColonySplit, QueenAssigned, the extraction, honey batch and phenology events and SeasonPhaseChanged", () => {
expect(DOMAIN_EVENT_LABELS).toMatchObject({
ColonySplit: "Ableger angelegt",
QueenAssigned: "Königin zugewiesen",
ExtractionRecorded: "Schleuderung erfasst",
ExtractionCorrected: "Schleuderung korrigiert",
HoneyBatchRecorded: "Honigcharge angelegt",
HoneyBatchCorrected: "Honigcharge korrigiert",
PhenologyReported: "Pflanzenmeldung erfasst",
PhenologyCorrected: "Pflanzenmeldung korrigiert",
SeasonPhaseChanged: "Saisonabschnitt gewechselt",
});
});
it("warns after ten minutes", () => {
expect(QUEUE_STALE_WARNING_SECONDS).toBe(600);
});
it("pluralises attempts", () => {
expect(attemptsLabel(1)).toBe("1 Versuch");
expect(attemptsLabel(0)).toBe("0 Versuche");
expect(attemptsLabel(8)).toBe("8 Versuche");
});
it("formats ages in German", () => {
expect(formatAge(5)).toBe("vor wenigen Sekunden");
expect(formatAge(180)).toBe("vor 3 Min.");
expect(formatAge(7300)).toBe("vor 2 Std.");
expect(formatAge(2 * 86400 + 10)).toBe("vor 2 Tagen");
expect(formatAge(86400)).toBe("vor 1 Tag");
});
});
describe("DOMAIN_EVENT_LABELS completeness", () => {
it("names PhenologyRecomputeRequested and WinterCheckRecorded", () => {
expect(DOMAIN_EVENT_LABELS.PhenologyRecomputeRequested).toBe("Saison neu berechnen angefordert");
expect(DOMAIN_EVENT_LABELS.WinterCheckRecorded).toBe("Winterkontrolle erfasst");
});
it("has a German label for every backend domain event type", () => {
const catalog = resolve(process.cwd(), "../ApiCore-Backend/src/domain-events/domain-event-types.ts");
if (!existsSync(catalog)) return; // frontend checked out alone
const source = readFileSync(catalog, "utf8");
const block = source.slice(source.indexOf("const CATALOG"), source.indexOf("DOMAIN_EVENT_TYPES"));
const types = [...block.matchAll(/^\s+([A-Z][A-Za-z]+): true,/gm)].map((match) => match[1]);
expect(types.length).toBeGreaterThan(20);
expect(types.filter((type) => !(type in DOMAIN_EVENT_LABELS))).toEqual([]);
});
it("DOMAIN_EVENT_LABELS names ColonyCreated, ColonyMoved, ApiaryChanged and TskReferenceDayReached", () => {
expect(DOMAIN_EVENT_LABELS).toMatchObject({
ColonyCreated: "Volk angelegt",
ColonyMoved: "Volk umgesetzt",
ApiaryChanged: "Stand geändert",
TskReferenceDayReached: "TSK-Stichtag erreicht",
});
});
it("names HiveChanged", () => {
expect(DOMAIN_EVENT_LABELS.HiveChanged).toBe("Beute geändert");
});
});