Files
mission-control-v2/frontend/src/views/Handy.test.tsx
T
HitonabiandClaude Opus 5.5 241e91c03f Später · 19 Handy-Ansicht und installierbare App (ohne Web-Push)
- /handy: Ampel, „Braucht Aufmerksamkeit“ (Rückfragen mit Ja/Nein, rote vor gelben Hinweisen, Geräte ohne Antwort,
  bereite Updates), vier Wege weiter, Knopf „Als App auf dieses Gerät legen“, wo Chrome es anbietet
- Manifest: startet in der Handy-Ansicht, Sprache, Beschreibung, Kurzwege
- Seitenleiste: Handy-Ansicht nur in der Handy-Schublade; Strg+K kennt sie
- BEDIENUNG.md: App aufs Handy legen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:39:30 +02:00

78 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ReactNode } from "react"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { render, screen } from "@testing-library/react"
import { api } from "@/lib/api"
import type { Hinweis, Rueckfrage } from "@/lib/typen"
import { HandySeite } from "./Handy"
vi.mock("@/lib/api", async (original) => ({ ...(await original<typeof import("@/lib/api")>()), api: vi.fn(), post: vi.fn() }))
vi.mock("@tanstack/react-router", async (original) => ({
...(await original<typeof import("@tanstack/react-router")>()),
Link: ({ to, hash, children, className }: { to: string; hash?: string; children: ReactNode; className?: string }) => (
<a href={hash ? `${to}#${hash}` : to} className={className}>{children}</a>
),
}))
// Handy-Startansicht (Ausbauplan „Später“, Punkt 19, 26.09.2026): Ampel und „Braucht Aufmerksamkeit“.
const jetzt = Math.floor(Date.now() / 1000)
const hinweis = (id: string, stufe: "rot" | "gelb", titel: string, zustand: Hinweis["zustand"] = "neu"): Hinweis => ({
id, stufe, titel, text: "", quelle: "x", seit: jetzt - 60, zuletzt: jetzt, aktionen: [], zustand,
})
const frage: Rueckfrage = {
nr: 7, zeit: jetzt - 60, frage: "Soll ich Gitea 1.27.4 einspielen?", text: "", pfad: "/x", von: "wochenbericht",
stufe: "normal", ablauf: jetzt + 3600, status: "offen", befund: null,
}
function zeigen(d: { box?: Hinweis[]; lab?: Hinweis[]; fragen?: Rueckfrage[]; wartung?: boolean; stumm?: boolean }) {
const daten: Record<string, unknown> = {
"/api/instanz": { rolle: "box", instanz: "Box-Wart", partner: { eingerichtet: true, name: "Homelab" } },
"/api/partner": { eingerichtet: true, name: "Homelab", erreichbar: true },
"/api/ziele": { ziele: [] },
"/api/homelab/ziele": {
ziele: [{ id: "ct-104", name: "Gitea", art: "container", instanz: "homelab", erreichbar: !d.stumm, bausteine: [],
geprueft: null, rueckweg: null, zaehlung: {}, offen: 0 }],
bericht: null, ausfuehrer: { verbunden: true, zuletzt: jetzt },
wartung: { laeuft: !!d.wartung, fenster: "So 03:00–04:15" },
},
"/api/start": { hinweise: d.box ?? [], updates: { gelesen: true, bausteine: [], unklar: [], festgehalten: [] } },
"/api/homelab/hinweise": { hinweise: d.lab ?? [] },
"/api/rueckfragen": { offen: d.fragen ?? [], zuletzt: [] },
}
vi.mocked(api).mockImplementation((async (pfad: string) => {
if (pfad in daten) return daten[pfad]
throw new Error(`unerwartete Abfrage ${pfad}`)
}) as typeof api)
render(<QueryClientProvider client={new QueryClient()}><HandySeite /></QueryClientProvider>)
}
describe("Handy-Startansicht", () => {
beforeEach(() => vi.clearAllMocks())
it("alles ruhig: grüne Ampel, nichts braucht dich", async () => {
zeigen({ lab: [hinweis("a", "gelb", "Stummer Hinweis", "stumm")] })
expect(await screen.findByText("Nichts — alles läuft, und nichts wartet auf dich.")).toBeInTheDocument()
expect(screen.getByRole("heading", { level: 2, name: "Alles läuft" })).toBeInTheDocument()
})
it("rote Hinweise zuerst, dazu stumme Geräte", async () => {
zeigen({ box: [hinweis("g", "gelb", "Job „Daily News Report“ mit Fehlern")], lab: [hinweis("r", "rot", "Gitea antwortet nicht")], stumm: true })
expect(await screen.findByText("Gitea antwortet nicht")).toBeInTheDocument()
const eintraege = (await screen.findAllByRole("link")).map((l) => l.textContent)
expect(eintraege.indexOf("Gitea antwortet nichtHomelab")).toBeLessThan(eintraege.indexOf("Job „Daily News Report“ mit FehlernKI-Box"))
expect(screen.getByRole("heading", { level: 2, name: "Störung" })).toBeInTheDocument()
})
it("Rückfragen stehen oben mit Ja und Nein", async () => {
zeigen({ fragen: [frage] })
expect(await screen.findByRole("heading", { level: 2, name: "Lucy wartet auf dich" })).toBeInTheDocument()
expect(screen.getByText("Soll ich Gitea 1.27.4 einspielen?")).toBeInTheDocument()
expect(screen.getByRole("button", { name: "Ja, ausführen" })).toBeInTheDocument()
})
it("im Wartungsfenster: Wartung statt Störung-Farbe", async () => {
zeigen({ wartung: true })
expect(await screen.findByRole("heading", { level: 2, name: "Wartung läuft" })).toBeInTheDocument()
expect(screen.getByText("Fenster So 03:00–04:15 — Meldung am Ende.")).toBeInTheDocument()
})
})