Files
ObsiGate/tests/frontend/toolbar-order.test.mjs
T
bruno 943005328c
CI / lint (push) Successful in 2m5s
CI / security (push) Successful in 1m27s
CI / test (push) Successful in 4m10s
CI / build (push) Successful in 1m23s
CI / e2e (push) Successful in 13m55s
feat: barre d'outils de lecture epinglee, coloration syntaxique des fichiers de code et avatars predefinis (#115, #117, BUG-078)
2026-09-24 16:21:45 -04:00

143 lines
6.3 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:[^;]*;/);
});
// ── #115 : barre d'outils collante au défilement ─────────────────────────
test("viewer.js — action bar wrapped in a sticky .file-toolbar", () => {
assert.match(viewer, /class:\s*"file-toolbar"[\s\S]{0,120}file-actions/,
"the document action bar must live in a .file-toolbar wrapper");
});
test("popout.html — action bar wrapped in a .file-toolbar", () => {
assert.match(popout, /className\s*=\s*"file-toolbar"/,
"popout action bar must live in a .file-toolbar wrapper");
});
test("style.css — .file-toolbar is pinned to the top while scrolling", () => {
const m = css.match(/\.file-toolbar\s*\{([\s\S]*?)\}/);
assert.ok(m, ".file-toolbar rule missing");
assert.match(m[1], /position:\s*sticky/, ".file-toolbar must be sticky");
assert.match(m[1], /top:\s*0/, ".file-toolbar must pin to the top");
assert.match(m[1], /background:/, ".file-toolbar needs an opaque background");
});
test("style.css — reading mode hides the sticky toolbar", () => {
assert.match(css, /body\.reading-mode\s+\.file-toolbar\s*[,{]/,
"reading mode must hide .file-toolbar");
});
if (process.exitCode) {
console.error("\nToolbar order tests FAILED");
} else {
console.log("\nAll toolbar order tests passed.");
}