import { describe, expect, it, vi } from "vitest" import { befehle, suchtext, type BefehlsKontext } from "./register" import { NAV } from "@/nav" function kontext(ueberschreiben: Partial = {}): BefehlsKontext { return { text: "", navigate: vi.fn(), schublade: vi.fn(), melden: vi.fn(), api: vi.fn(async () => ({}) as never), ...ueberschreiben, } } describe("Befehls-Register", () => { it("führt jedes Navigations-Ziel auf — genau einmal", () => { const ziele = befehle("").filter((b) => b.gruppe === "gehe") expect(ziele).toHaveLength(NAV.length) expect(new Set(ziele.map((z) => z.id)).size).toBe(NAV.length) }) it("zeigt Suche und „An Lucy“ erst, wenn etwas getippt ist", () => { const leer = befehle("") expect(leer.some((b) => b.gruppe === "finden")).toBe(false) expect(leer.some((b) => b.gruppe === "lucy")).toBe(false) const getippt = befehle("Wissensgraph-Ansicht bauen") expect(getippt.filter((b) => b.gruppe === "finden")).toHaveLength(2) expect(getippt.filter((b) => b.gruppe === "lucy")).toHaveLength(1) }) // Unter acht Zeichen ist es kein Auftrag, sondern ein Tippfehler auf dem Weg zu // einem Sprungziel. Sonst böte die Palette bei „mod" an, daraus eine Idee zu machen. it("bietet kurze Eingaben NICHT als Idee an", () => { expect(befehle("mod").some((b) => b.gruppe === "lucy")).toBe(false) expect(befehle("modelle").some((b) => b.gruppe === "lucy")).toBe(false) expect(befehle("modellwechsel").some((b) => b.gruppe === "lucy")).toBe(true) }) // ── Die Sicherheitsregel ────────────────────────────────────────────────── // Die Palette ist schnell genug, dass „Strg+K, mot, Enter" einen Dienst neu startet, // bevor man es zu Ende gedacht hat. Alles Eingreifende MUSS deshalb `rueckfrage` // tragen — die Oberfläche hält dann an und sagt erst, was passieren wird. it("verlangt für jeden eingreifenden Befehl eine Rückfrage", () => { const eingreifend = /neu starten|löschen|loeschen|deploy|zurücksetzen|reboot|entfernen/i for (const b of befehle("etwas getipptes")) { if (eingreifend.test(b.label)) { expect(b.rueckfrage, `„${b.label}" muss nachfragen`).toBeTruthy() } } }) it("nennt in der Rückfrage die Folge, nicht nur die Aktion", () => { const motor = befehle("").find((b) => b.id === "tun:engine-neustart")! expect(motor.rueckfrage).toBeTruthy() // Der Text muss sagen, was der Nutzer DANACH merkt. expect(motor.rueckfrage).toMatch(/Modelle/) expect(motor.rueckfrage).toMatch(/aufwärmen|abbrechen|abbrechen/i) }) it("lässt harmlose Befehle ohne Rückfrage durch", () => { const wartung = befehle("").find((b) => b.id === "tun:wartung")! expect(wartung.rueckfrage).toBeUndefined() }) // ── Verhalten einzelner Befehle ─────────────────────────────────────────── it("öffnet die Schublade über die URL, nicht über ein Ereignis", () => { const ctx = kontext() befehle("").find((b) => b.id === "tun:logs")!.ausfuehren(ctx) expect(ctx.schublade).toHaveBeenCalledWith("logs") }) it("erfasst eine Idee zum Durchdenken — und startet keinen Bau", async () => { const ctx = kontext({ text: "Wissensgraph-Ansicht bauen" }) await befehle(ctx.text).find((b) => b.gruppe === "lucy")!.ausfuehren(ctx) expect(ctx.api).toHaveBeenCalledTimes(1) const [pfad, init] = (ctx.api as ReturnType).mock.calls[0] expect(pfad).toBe("/api/ideen") const rumpf = JSON.parse((init as RequestInit).body as string) // `art: "idee"` heisst durchdenken. `"projekt"` wuerde sofort ein Repo anlegen — // das darf die Palette niemals aus einem Tastendruck heraus tun. expect(rumpf.art).toBe("idee") expect(rumpf.titel).toBe("Wissensgraph-Ansicht bauen") }) it("kürzt einen überlangen Ideen-Titel und hebt den vollen Text in die Notiz", async () => { const lang = "a".repeat(200) const ctx = kontext({ text: lang }) await befehle(lang).find((b) => b.gruppe === "lucy")!.ausfuehren(ctx) const rumpf = JSON.parse( ((ctx.api as ReturnType).mock.calls[0][1] as RequestInit).body as string, ) expect(rumpf.titel.length).toBeLessThanOrEqual(160) expect(rumpf.notiz).toBe(lang) }) it("packt Suchwörter in den Suchtext, damit cmdk sie findet", () => { const wartung = befehle("").find((b) => b.id === "tun:wartung")! expect(suchtext(wartung)).toMatch(/neustart/) // steht nur in den Stichworten expect(suchtext(wartung)).toMatch(/System-Wartung/) }) })