Archiv-Navigator: Baum nur Ordner + flache Inhaltsliste (Zeilen: öffnen/umbenennen/löschen), Ordner/Dokument anlegen (Dateiname ohne Stempel, Datum im Frontmatter), Write-back geöffneter Archiv-Dokumente über Quell-Verknüpfung; Ablösen übergibt aktiven Tab
This commit is contained in:
+181
-82
@@ -1,15 +1,29 @@
|
||||
// Wiki-Ansicht: Übersichts-/Schlagwort-Seiten, Dokumentseite, Leerzustände.
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
// Wiki-Ansicht: Ordner-Baum links, flache Inhaltsliste rechts, Zeilen-Aktionen
|
||||
// (öffnen, umbenennen, löschen), Ordner-Umbenennen, Leerzustände.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { initWikiView } from "./wiki-view";
|
||||
|
||||
function setup() {
|
||||
document.body.innerHTML = `<div id="w"></div>`;
|
||||
const onLink = vi.fn();
|
||||
const view = initWikiView(document.getElementById("w")!, onLink);
|
||||
return { view, onLink };
|
||||
const openDoc = vi.fn();
|
||||
const openWiki = vi.fn();
|
||||
const actions = {
|
||||
rename: vi.fn(),
|
||||
remove: vi.fn(),
|
||||
moveFolder: vi.fn(),
|
||||
createFolder: vi.fn(),
|
||||
createDoc: vi.fn(),
|
||||
};
|
||||
const view = initWikiView(document.getElementById("w")!, {
|
||||
openDoc,
|
||||
openWiki,
|
||||
actions,
|
||||
});
|
||||
return { view, openDoc, openWiki, actions };
|
||||
}
|
||||
|
||||
const doc = (name: string, extra: object = {}) => ({
|
||||
const doc = (relpath: string, name: string, extra: object = {}) => ({
|
||||
relpath,
|
||||
name,
|
||||
title: name.toUpperCase(),
|
||||
description: null,
|
||||
@@ -24,67 +38,185 @@ const page = JSON.stringify({
|
||||
home: "/tmp/archiv",
|
||||
tag: null,
|
||||
total: 3,
|
||||
tags: [
|
||||
{ name: "adr", count: 2 },
|
||||
{ name: "infra", count: 1 },
|
||||
],
|
||||
recent: [doc("neu")],
|
||||
tags: [],
|
||||
folders: [
|
||||
{ name: "", docs: [doc("wurzel-doc", { backlinks: 2, tags: ["adr"] })] },
|
||||
{ name: "konzepte", docs: [doc("neu"), doc("alt")] },
|
||||
{
|
||||
name: "",
|
||||
docs: [
|
||||
doc("2026-07-19_1000-wurzel-doc.md", "wurzel-doc", {
|
||||
backlinks: 2,
|
||||
description: "Beschreibung",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{ name: "konzepte", docs: [doc("konzepte/2026-07-19_1005-neu.md", "neu")] },
|
||||
{
|
||||
name: "konzepte/panel",
|
||||
docs: [doc("konzepte/panel/2026-07-19_1010-alt.md", "alt")],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
describe("initWikiView — Seiten", () => {
|
||||
it("rendert Kopf, Chips, Zuletzt- und Ordner-Sektionen", () => {
|
||||
describe("initWikiView — Baum und Liste", () => {
|
||||
it("Baum zeigt nur Ordner, Wurzel ist gewählt und listet ihren Inhalt", () => {
|
||||
const { view } = setup();
|
||||
view.set(page);
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("Archiv");
|
||||
expect(document.querySelector(".wiki-head-right")!.textContent).toBe("3 Dokumente");
|
||||
const chips = [...document.querySelectorAll(".wiki-chips .wiki-chip")].map(
|
||||
(c) => c.textContent,
|
||||
);
|
||||
expect(chips).toEqual(["Alle", "#adr 2", "#infra 1"]);
|
||||
const eyebrows = [...document.querySelectorAll(".wiki-folder")].map(
|
||||
// Baum: Wurzel-Knopf + Ordner (keine Dokumentzeilen im Baum).
|
||||
expect(document.querySelector(".wiki-tree-root")!.className).toContain("active");
|
||||
const folders = [...document.querySelectorAll(".wiki-tree .wiki-tree-name")].map(
|
||||
(e) => e.textContent,
|
||||
);
|
||||
expect(eyebrows).toEqual(["Zuletzt", "Wurzel", "konzepte/"]);
|
||||
expect(folders).toEqual(["konzepte", "panel"]);
|
||||
// Liste rechts: nur der direkte Wurzel-Inhalt.
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("Archiv");
|
||||
expect(document.querySelector(".wiki-head-right")!.textContent).toBe("1 Dokument");
|
||||
const rows = [...document.querySelectorAll(".wiki-doc .wiki-doc-title")].map(
|
||||
(e) => e.textContent,
|
||||
);
|
||||
expect(rows).toEqual(["WURZEL-DOC"]);
|
||||
expect(document.querySelector(".wiki-doc-back")!.textContent).toBe("↩ 2");
|
||||
expect(document.querySelector(".wiki-doc-desc")!.textContent).toBe("Beschreibung");
|
||||
});
|
||||
|
||||
it("Dokumentzeile öffnet das Dokument, Tag-Chip die Tag-Seite", () => {
|
||||
const { view, onLink } = setup();
|
||||
view.set(page);
|
||||
const rows = document.querySelectorAll<HTMLElement>(".wiki-doc");
|
||||
rows[1].click(); // Wurzel-Dokument (nach der Zuletzt-Zeile)
|
||||
expect(onLink).toHaveBeenCalledWith("wurzel-doc");
|
||||
rows[1].querySelector<HTMLElement>(".wiki-doc-tags .wiki-chip")!.click();
|
||||
expect(onLink).toHaveBeenCalledWith("tag:adr");
|
||||
document.querySelector<HTMLElement>(".wiki-chips .wiki-chip")!.click();
|
||||
expect(onLink).toHaveBeenCalledWith("tag:");
|
||||
});
|
||||
|
||||
it("Tag-Seite: Titel, aktiver Chip, Leerfall", () => {
|
||||
it("Ordner-Klick wählt aus und zeigt dessen flache Liste", () => {
|
||||
const { view } = setup();
|
||||
view.set(page);
|
||||
const names = document.querySelectorAll<HTMLElement>(".wiki-tree .wiki-tree-name");
|
||||
names[0].click(); // konzepte
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("konzepte");
|
||||
expect(document.querySelector(".wiki-head-sub")!.textContent).toBe(
|
||||
"/tmp/archiv/konzepte",
|
||||
);
|
||||
const rows = [...document.querySelectorAll(".wiki-doc .wiki-doc-title")].map(
|
||||
(e) => e.textContent,
|
||||
);
|
||||
// Nur direkter Inhalt — das Dokument aus konzepte/panel erscheint nicht.
|
||||
expect(rows).toEqual(["NEU"]);
|
||||
expect(
|
||||
document.querySelector(".wiki-tree summary.active .wiki-tree-name")!.textContent,
|
||||
).toBe("konzepte");
|
||||
});
|
||||
|
||||
it("Dokument-Klick öffnet über den absoluten Pfad", () => {
|
||||
const { view, openDoc } = setup();
|
||||
view.set(page);
|
||||
document.querySelector<HTMLElement>(".wiki-doc")!.click();
|
||||
expect(openDoc).toHaveBeenCalledWith("/tmp/archiv/2026-07-19_1000-wurzel-doc.md");
|
||||
});
|
||||
|
||||
it("Auswahl übersteht ein Puffer-Update, verschwundener Ordner fällt zur Wurzel", () => {
|
||||
const { view } = setup();
|
||||
view.set(page);
|
||||
document.querySelectorAll<HTMLElement>(".wiki-tree .wiki-tree-name")[0].click();
|
||||
view.set(page); // Update: Auswahl bleibt
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("konzepte");
|
||||
view.set(
|
||||
JSON.stringify({
|
||||
kind: "page",
|
||||
home: "/a",
|
||||
tag: "adr",
|
||||
total: 0,
|
||||
tags: [{ name: "adr", count: 1 }],
|
||||
recent: [],
|
||||
folders: [],
|
||||
home: "/tmp/archiv",
|
||||
tag: null,
|
||||
total: 1,
|
||||
tags: [],
|
||||
folders: [{ name: "", docs: [doc("2026-07-19_1000-wurzel-doc.md", "wurzel-doc")] }],
|
||||
}),
|
||||
);
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("#adr");
|
||||
const active = document.querySelector(".wiki-chip.active")!;
|
||||
expect(active.textContent).toBe("#adr 1");
|
||||
expect(document.querySelector(".wiki-empty strong")!.textContent).toBe(
|
||||
"Keine Dokumente mit #adr.",
|
||||
expect(document.querySelector(".wiki-head-title")!.textContent).toBe("Archiv");
|
||||
});
|
||||
});
|
||||
|
||||
describe("initWikiView — Aktionen", () => {
|
||||
it("Stift an der Zeile: Inline-Umbenennen mit Enter, ohne das Dokument zu öffnen", () => {
|
||||
const { view, actions, openDoc } = setup();
|
||||
view.set(page);
|
||||
const row = document.querySelector<HTMLElement>(".wiki-doc")!;
|
||||
row.querySelector<HTMLElement>(".cmd-edit")!.click();
|
||||
const input = row.querySelector<HTMLInputElement>(".wiki-tree-input")!;
|
||||
expect(input.value).toBe("wurzel-doc");
|
||||
input.value = "Neuer Name";
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
expect(actions.rename).toHaveBeenCalledWith(
|
||||
"2026-07-19_1000-wurzel-doc.md",
|
||||
"Neuer Name",
|
||||
);
|
||||
expect(openDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Escape stellt die Zeile wieder her", () => {
|
||||
const { view, actions } = setup();
|
||||
view.set(page);
|
||||
document.querySelector<HTMLElement>(".wiki-doc .cmd-edit")!.click();
|
||||
const input = document.querySelector<HTMLInputElement>(".wiki-doc .wiki-tree-input")!;
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
||||
expect(document.querySelector(".wiki-doc .wiki-doc-title")!.textContent).toBe(
|
||||
"WURZEL-DOC",
|
||||
);
|
||||
expect(actions.rename).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Löschen meldet mit relpath, ohne das Dokument zu öffnen", () => {
|
||||
const { view, actions, openDoc } = setup();
|
||||
view.set(page);
|
||||
document.querySelector<HTMLElement>(".wiki-doc .cmd-del")!.click();
|
||||
expect(actions.remove).toHaveBeenCalledWith("2026-07-19_1000-wurzel-doc.md");
|
||||
expect(openDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Plus im Listenkopf: Formular legt Dokument im gewählten Ordner an", () => {
|
||||
const { view, actions, openDoc } = setup();
|
||||
view.set(page);
|
||||
document.querySelectorAll<HTMLElement>(".wiki-tree .wiki-tree-name")[0].click();
|
||||
document.querySelector<HTMLElement>(".wiki-head .wiki-add")!.click();
|
||||
const form = document.querySelector<HTMLElement>(".wiki-form")!;
|
||||
expect(form.querySelector(".wiki-form-title")!.textContent).toBe("Neues Dokument");
|
||||
const input = form.querySelector<HTMLInputElement>("input")!;
|
||||
input.value = "Deploy Notiz";
|
||||
form.querySelector<HTMLElement>(".wiki-form-submit")!.click();
|
||||
expect(actions.createDoc).toHaveBeenCalledWith("konzepte", "Deploy Notiz");
|
||||
expect(document.querySelector(".wiki-form")).toBeNull();
|
||||
expect(openDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Abbrechen und Escape schließen das Formular ohne Aktion", () => {
|
||||
const { view, actions } = setup();
|
||||
view.set(page);
|
||||
document.querySelector<HTMLElement>(".wiki-head .wiki-add")!.click();
|
||||
document.querySelector<HTMLElement>(".wiki-form-cancel")!.click();
|
||||
expect(document.querySelector(".wiki-form")).toBeNull();
|
||||
|
||||
document.querySelector<HTMLElement>(".wiki-head .wiki-add")!.click();
|
||||
document
|
||||
.querySelector<HTMLElement>(".wiki-form")!
|
||||
.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true }));
|
||||
expect(document.querySelector(".wiki-form")).toBeNull();
|
||||
expect(actions.createDoc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Plus im Baum: Formular legt Ordner an, Pfad mit gewähltem Ordner vorbefüllt", () => {
|
||||
const { view, actions } = setup();
|
||||
view.set(page);
|
||||
document.querySelectorAll<HTMLElement>(".wiki-tree .wiki-tree-name")[0].click();
|
||||
document.querySelector<HTMLElement>(".wiki-tree-head .wiki-add")!.click();
|
||||
const input = document.querySelector<HTMLInputElement>(".wiki-form input")!;
|
||||
expect(input.value).toBe("konzepte/");
|
||||
input.value = "konzepte/neu";
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
expect(actions.createFolder).toHaveBeenCalledWith("konzepte/neu");
|
||||
});
|
||||
|
||||
it("Ordner-Stift: Inline-Eingabe des Pfads, Enter meldet moveFolder", () => {
|
||||
const { view, actions } = setup();
|
||||
view.set(page);
|
||||
const summary = document.querySelector<HTMLElement>(".wiki-tree summary")!;
|
||||
summary.querySelector<HTMLElement>(".cmd-edit")!.click();
|
||||
const input = summary.querySelector<HTMLInputElement>(".wiki-tree-input")!;
|
||||
expect(input.value).toBe("konzepte");
|
||||
input.value = "notizen/2026";
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
|
||||
expect(actions.moveFolder).toHaveBeenCalledWith("konzepte", "notizen/2026");
|
||||
});
|
||||
});
|
||||
|
||||
describe("initWikiView — Leerzustände", () => {
|
||||
it("leeres Archiv erklärt das Archivieren", () => {
|
||||
const { view } = setup();
|
||||
view.set(
|
||||
@@ -94,7 +226,6 @@ describe("initWikiView — Seiten", () => {
|
||||
tag: null,
|
||||
total: 0,
|
||||
tags: [],
|
||||
recent: [],
|
||||
folders: [],
|
||||
}),
|
||||
);
|
||||
@@ -102,45 +233,13 @@ describe("initWikiView — Seiten", () => {
|
||||
"Das Archiv ist leer.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initWikiView — Dokument und Leerzustand", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
it("rendert Markdown, Rücksprung, Tags und Backlinks", () => {
|
||||
const { view, onLink } = setup();
|
||||
view.set(
|
||||
JSON.stringify({
|
||||
kind: "doc",
|
||||
home: "/a",
|
||||
relpath: "konzepte/2026-07-19_1000-x.md",
|
||||
name: "x",
|
||||
title: "X",
|
||||
tags: ["adr"],
|
||||
backlinks: ["anderes-doc"],
|
||||
markdown: "# Überschrift\n\nText mit [[ziel|Label]].\n",
|
||||
}),
|
||||
);
|
||||
expect(document.querySelector(".wiki-body h1")!.textContent).toBe("Überschrift");
|
||||
const wiki = document.querySelector<HTMLElement>(".wiki-body a.wiki")!;
|
||||
expect(wiki.textContent).toBe("Label");
|
||||
wiki.click();
|
||||
expect(onLink).toHaveBeenCalledWith("ziel");
|
||||
document.querySelector<HTMLElement>(".wiki-back")!.click();
|
||||
expect(onLink).toHaveBeenCalledWith("tag:");
|
||||
const back = document.querySelector<HTMLElement>(".wiki-backlinks a")!;
|
||||
back.click();
|
||||
expect(onLink).toHaveBeenCalledWith("anderes-doc");
|
||||
});
|
||||
|
||||
it("leerer Puffer bietet den Übersichts-Einstieg", () => {
|
||||
const { view, onLink } = setup();
|
||||
const { view, openWiki } = setup();
|
||||
view.set("");
|
||||
expect(view.empty()).toBe(true);
|
||||
document.querySelector<HTMLElement>(".wiki-empty .wiki-chip")!.click();
|
||||
expect(onLink).toHaveBeenCalledWith("tag:");
|
||||
expect(openWiki).toHaveBeenCalledWith("tag:");
|
||||
view.set(page);
|
||||
expect(view.empty()).toBe(false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user