b127269e89
- 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.
108 lines
3.7 KiB
TypeScript
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");
|
|
});
|
|
});
|