[pop-out][Bookmark] | [Editer][Source][.ext][Pretty] | [Copier] | [Partager] - TOC ancre uniquement au markdown (plus de bouton TOC sur .sh/.py/...) - PDF/Export deja ancres au markdown: groupe export = [Copier] seul sur texte - tests toolbar-order mis a jour (conditions is_markdown sur nav/export)
119 lines
5.1 KiB
JavaScript
119 lines
5.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ObsiGate — Toolbar action grouping tests (#90).
|
|
*
|
|
* Static non-regression checks on the source of the document action bar:
|
|
* the buttons must be assembled in functional groups separated by spacers:
|
|
* [TOC][pop-out][Bookmark] | [Editer][Source][.md][Forge] | [Copier][PDF][Export] | [Partager]
|
|
*
|
|
* Covers frontend/js/viewer.js (main app), frontend/popout.html (popout
|
|
* window, kept aligned) and frontend/style.css (.action-sep rule).
|
|
*
|
|
* Usage: node tests/frontend/toolbar-order.test.mjs
|
|
*/
|
|
|
|
import { strict as assert } from "node:assert";
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
|
|
const viewer = readFileSync(path.join(ROOT, "frontend", "js", "viewer.js"), "utf8");
|
|
const popout = readFileSync(path.join(ROOT, "frontend", "popout.html"), "utf8");
|
|
const css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8");
|
|
|
|
function test(label, fn) {
|
|
try {
|
|
fn();
|
|
console.log(" \u2713 " + label);
|
|
} catch (err) {
|
|
console.error(" \u2717 " + label + "\n " + err.message);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
// ── viewer.js : définition des groupes ───────────────────────────────────
|
|
test("viewer.js — group nav: TOC markdown-only, then pop-out, bookmark", () => {
|
|
const m = viewer.match(/const navBtns = \[\];([\s\S]*?)const editBtns/);
|
|
assert.ok(m, "navBtns block not found");
|
|
assert.match(m[1], /if \(data\.is_markdown\) navBtns\.push\(tocBtn\);/);
|
|
assert.match(m[1], /navBtns\.push\(openNewWindowBtn,\s*bookmarkBtn\);/);
|
|
});
|
|
|
|
test("viewer.js — group edit = [edit, source, .ext, forge?, pretty?] in order", () => {
|
|
const m = viewer.match(
|
|
/const editBtns = \[\];([\s\S]*?)const exportBtns/,
|
|
);
|
|
assert.ok(m, "editBtns block not found");
|
|
const block = m[1];
|
|
const order = [];
|
|
const re = /editBtns\.push\((\w+)\)/g;
|
|
let g;
|
|
while ((g = re.exec(block))) order.push(g[1]);
|
|
// editBtn conditional, then sourceBtn, then ext-download conditional, then forge conditional
|
|
assert.deepEqual(order, ["editBtn", "sourceBtn", "mdBtn", "forgeBtn", "prettyBtn"]);
|
|
assert.ok(block.includes("if (!data.unsupported) editBtns.push(editBtn)"));
|
|
assert.ok(block.includes("if (!data.is_image) editBtns.push(mdBtn)"));
|
|
assert.ok(block.includes("if (data.is_markdown) editBtns.push(forgeBtn)"));
|
|
// Pretty (text files) must land in the EDIT group, after the extension button
|
|
assert.ok(block.indexOf("editBtns.push(prettyBtn)") > block.indexOf("editBtns.push(mdBtn)"));
|
|
});
|
|
|
|
test("viewer.js — export group = [copier] + [pdf, export] markdown-only", () => {
|
|
const m = viewer.match(/const exportBtns = \[([\s\S]*?)\];([\s\S]*?)const shareBtns/);
|
|
assert.ok(m, "exportBtns block not found");
|
|
assert.ok(m[1].includes("copyBtn"), "copyBtn must be first in export group");
|
|
assert.match(m[2], /if \(data\.is_markdown\) exportBtns\.push\(pdfBtn,\s*exportWrap\)/,
|
|
"PDF/Export must be markdown-only (text files: Copier alone)");
|
|
});
|
|
|
|
test("viewer.js — group share = [partager]", () => {
|
|
assert.match(viewer, /const shareBtns = \[\s*shareBtn\s*\];/);
|
|
});
|
|
|
|
test("viewer.js — assembly inserts an action-sep between non-empty groups", () => {
|
|
const m = viewer.match(
|
|
/\[navBtns,\s*editBtns,\s*exportBtns,\s*shareBtns\]\.forEach\(\(group\)\s*=>\s*\{([\s\S]*?)\}\);/,
|
|
);
|
|
assert.ok(m, "group assembly loop not found");
|
|
const body = m[1];
|
|
assert.match(body, /if \(group\.length === 0\) return;/, "empty group must be skipped");
|
|
assert.match(body, /if \(fileActions\.length > 0\) fileActions\.push\(actionSep\(\)\);/,
|
|
"spacer must be inserted between groups");
|
|
});
|
|
|
|
test("viewer.js — no legacy flat fileActions.push order remains", () => {
|
|
assert.doesNotMatch(
|
|
viewer,
|
|
/const fileActions = \[\s*copyBtn,\s*sourceBtn\s*\];/,
|
|
"old flat construction should be gone",
|
|
);
|
|
});
|
|
|
|
// ── popout.html : alignement ───────────────────────────────────────────────
|
|
test("popout.html — grouped assembly matches viewer order", () => {
|
|
assert.match(
|
|
popout,
|
|
/\[\[tocBtn,\s*bookmarkBtn\],\s*\[editBtn,\s*sourceBtn,\s*dlBtn\],\s*\[copyBtn,\s*pdfBtn\],\s*\[shareBtn\]\]/,
|
|
);
|
|
assert.match(popout, /if \(actionsDiv\.childNodes\.length > 0\) actionsDiv\.appendChild\(actionSep\(\)\);/);
|
|
});
|
|
|
|
test("popout.html — no leftover sequential appends remain", () => {
|
|
assert.doesNotMatch(popout, /actionsDiv\.appendChild\((copyBtn|sourceBtn|dlBtn|pdfBtn|editBtn|tocBtn|shareBtn|bookmarkBtn)\);/,
|
|
"buttons must be appended only via the grouped loop");
|
|
});
|
|
|
|
// ── style.css : règle du séparateur ──────────────────────────────────────
|
|
test("style.css — .action-sep rule exists", () => {
|
|
assert.match(css, /\.file-actions \.action-sep\s*\{[^}]*background:[^;]*;/);
|
|
});
|
|
|
|
if (process.exitCode) {
|
|
console.error("\nToolbar order tests FAILED");
|
|
} else {
|
|
console.log("\nAll toolbar order tests passed.");
|
|
}
|