Files
ai-control/src/commands-view.test.ts
T
marcusH b127269e89 Bugfix-Runde Panel/Archiv: Datenverlust, Suche, Löschen, Markdown
- ProjectConfig reicht unbekannte Keys durch (serde flatten). Bisher verlor
  jeder read-modify-write die Felder neuerer Versionen; so ging archiveHome
  bei der Pool-Zuweisung durch einen älteren Build verloren.
- Archiv-Suche: Nutzereingabe wird in einen FTS5-Ausdruck übersetzt, der nicht
  scheitern kann. ai-control, C++ oder eine halbe Phrase warfen bisher einen
  Syntaxfehler — bei der Live-Suche in jedem Tippzwischenstand.
- commands_delete: Befehlstext als Identität, Indizes werden geprüft. Bisher
  Panik oder Löschen der falschen Kachel, wenn die Panel-Indizes der Datei
  hinterherhingen.
- Archivieren überschreibt keine Datei mehr: Der Stempel hat Minutenauflösung,
  bei Kollision wird -2, -3, ... angehängt.
- archive_panel prüft erst auf leeres Panel, dann wird das Archiv-Home gesetzt;
  bisher blieb ein umkonfiguriertes Home trotz Meldung "nicht archiviert".
- Kaputte .claude.json eines Pools wird nicht mehr durch eine frische ersetzt;
  nur eine fehlende Datei rechtfertigt das.
- Archiv-Formular leert beim Schließen — sonst galten Ordner und Schlagwörter
  des vorigen Dokuments unbemerkt fürs nächste.
- Markdown fürs Panel entschärft (src/markdown.ts): rohes HTML wird als Text
  ausgegeben, Link- und Bildziele auf harmlose Schemata beschränkt. Das Panel
  teilt sich den Webview mit dem Terminal, dessen term_write in die PTY
  schreibt; ohne CSP war das ein Weg von Fremdinhalt zu Befehlsausführung.

Regressionstests je Fix: 73 Rust-Tests, 35 Frontend-Tests.
2026-07-19 23:04:17 +02:00

108 lines
3.7 KiB
TypeScript

// Modus-Logik (Tabs) und Kachel-Ansicht der Befehls-History.
import { beforeEach, describe, expect, it, vi } from "vitest";
import { initCommandsView, initPanelMode } from "./commands-view";
function modeSetup() {
document.body.innerHTML = `
<div id="tabs">
<button data-mode="commands">Befehle</button>
<button data-mode="draft">Dokument</button>
<button data-mode="wiki">Wiki</button>
<button data-mode="search">Suche</button>
</div>
<div id="title">Mein Titel</div>
<div id="draft-el"></div>
<div id="cc" hidden></div>
<div id="sc" hidden></div>
<div id="wc" hidden></div>`;
const el = (id: string) => document.getElementById(id)!;
const flush = vi.fn();
const mode = initPanelMode({
tabsEl: el("tabs"),
draftEls: [el("draft-el")],
commandsContent: el("cc"),
searchContent: el("sc"),
wikiContent: el("wc"),
titleEl: el("title"),
flush,
});
const tab = (m: string) =>
document.querySelector<HTMLElement>(`[data-mode="${m}"]`)!;
return { mode, flush, el, tab };
}
describe("initPanelMode", () => {
it("startet im Dokument-Modus mit aktivem Tab", () => {
const { el, tab } = modeSetup();
expect(el("draft-el").hidden).toBe(false);
expect(el("cc").hidden).toBe(true);
expect(tab("draft").classList.contains("active")).toBe(true);
});
it("wechselt Sichtbarkeit, Titel und aktive Markierung", () => {
const { mode, flush, el, tab } = modeSetup();
mode.to("commands");
expect(flush).toHaveBeenCalledOnce();
expect(el("cc").hidden).toBe(false);
expect(el("draft-el").hidden).toBe(true);
expect(el("title").textContent).toBe("Befehle");
expect(tab("commands").classList.contains("active")).toBe(true);
expect(tab("draft").classList.contains("active")).toBe(false);
mode.to("draft");
expect(el("title").textContent).toBe("Mein Titel");
});
it("Tab-Klick wechselt den Modus", () => {
const { el, tab } = modeSetup();
tab("wiki").click();
expect(el("wc").hidden).toBe(false);
expect(el("draft-el").hidden).toBe(true);
});
it("clear hebt die Auswahl auf; danach wählt to() wieder aus", () => {
const { mode, el, tab } = modeSetup();
mode.clear();
expect(document.querySelectorAll(".active").length).toBe(0);
expect(mode.current()).toBe(null);
mode.to("draft");
expect(tab("draft").classList.contains("active")).toBe(true);
expect(el("title").textContent).toBe("Mein Titel");
});
});
describe("initCommandsView", () => {
beforeEach(() => {
document.body.innerHTML = `<div id="c"></div>`;
});
const jsonl = [
JSON.stringify({ ts: 1700000000, session: true }),
JSON.stringify({
ts: 1700000100,
commands: [{ cmd: "ls -la", note: "Liste" }, { cmd: "pwd" }],
}),
].join("\n");
it("rendert Kacheln, Session-Trenner und meldet empty korrekt", () => {
const view = initCommandsView(document.getElementById("c")!, () => {});
view.set(jsonl);
expect(view.empty()).toBe(false);
expect(document.querySelectorAll(".cmd-tile").length).toBe(2);
expect(document.querySelectorAll(".cmd-sep").length).toBe(1);
expect(document.querySelector(".cmd-text")!.textContent).toBe("ls -la");
view.set("");
expect(view.empty()).toBe(true);
});
/// Der Befehlstext geht mit, weil die Indizes der Datei hinterherhinken
/// können; das Backend gleicht damit ab, bevor es löscht.
it("meldet Löschen mit Record-Index, Eintrags-Index und Befehl", () => {
const onDelete = vi.fn();
const view = initCommandsView(document.getElementById("c")!, onDelete);
view.set(jsonl);
const dels = document.querySelectorAll<HTMLElement>(".cmd-del");
dels[1].click(); // zweite Kachel im Record 1
expect(onDelete).toHaveBeenCalledWith(1, 1, "pwd");
});
});