Files
ObsiGate/tests/frontend/toolbar-order.test.mjs
T
bruno 140a8f6efe
CI / lint (push) Successful in 1m18s
CI / security (push) Successful in 53s
CI / test (push) Successful in 2m30s
CI / build (push) Successful in 50s
CI / e2e (push) Successful in 10m27s
feat(viewer): #90 toolbar groupes aussi pour les fichiers texte non markdown
[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)
2026-09-15 08:24:22 -04:00

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.");
}