Files
ObsiGate/tests/frontend/mobile-toolbar.test.mjs
T
bruno 34428ea7b3
CI / lint (push) Successful in 2m45s
CI / security (push) Successful in 1m58s
CI / test (push) Successful in 4m41s
CI / build (push) Successful in 1m52s
CI / e2e (push) Successful in 16m32s
test: mobileBlockContaining ignore les commentaires CSS (CI #842)
- le helper choisissait le premier bloc media contenant la chaine 'mobile-toolbar' ; un commentaire citant ce selecteur (correctif BUG-103) le faisait retomber sur le mauvais bloc, sans la clearance .main-body (BUG-073)
- les commentaires sont maintenant retires avant la recherche de bloc
2026-10-02 15:46:05 -04:00

137 lines
5.5 KiB
JavaScript

#!/usr/bin/env node
/**
* ObsiGate — Mobile bottom toolbar clearance non-regression tests (BUG-073).
*
* Static checks (no jsdom — runs in the "Frontend unit tests" CI step):
* - BUG-073: the fixed bottom toolbar (#mobile-toolbar, 64px + safe-area)
* covered the last pixels of every document/page because the clearance
* rule in the ≤768px media block targeted `.main-layout` — a class that
* does not exist in index.html (the real layout container is
* `.main-body`). The dead selector reserved no space, so #content-area
* extended behind the bar and the end of the content could never be
* scrolled clear of it.
* - The clearance must live on `.main-body` inside the mobile media block
* (desktop keeps no padding), the dead `.main-layout` rules must be gone,
* reading mode (toolbar hidden) must reset the padding, and the
* now-playing rule must not double-count the toolbar zone.
*
* Usage: node tests/frontend/mobile-toolbar.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 css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8");
const indexHtml = readFileSync(path.join(ROOT, "frontend", "index.html"), "utf8");
function test(label, fn) {
try {
fn();
console.log(" \u2713 " + label);
} catch (err) {
console.error(" \u2717 " + label + "\n " + err.message);
process.exitCode = 1;
}
}
/** Body of the first @media (max-width: 768px) block whose body contains `needle`. */
function mobileBlockContaining(needle) {
const marker = "@media (max-width: 768px)";
// Les commentaires sont ignorés : un sélecteur cité dans une annotation
// (ex. « .mobile-toolbar » dans un commentaire de style.css) ne doit pas
// faire rétrograder ce helper sur le mauvais bloc mobile (BUG-103/CI #842).
const source = css.replace(/\/\*[\s\S]*?\*\//g, "");
let idx = 0;
while ((idx = source.indexOf(marker, idx)) !== -1) {
const open = source.indexOf("{", idx);
let depth = 0;
let end = open;
for (; end < source.length; end++) {
if (source[end] === "{") depth++;
else if (source[end] === "}") {
depth--;
if (depth === 0) break;
}
}
const body = source.slice(open + 1, end);
if (body.includes(needle)) return body;
idx = end + 1;
}
return null;
}
// ── The layout container the clearance must target exists in the DOM ───────
test("index.html — layout container .main-body and #mobile-toolbar both exist", () => {
assert.match(indexHtml, /class="main-body"/, "index.html must define the .main-body layout container");
assert.match(indexHtml, /class="mobile-toolbar"/, "index.html must define the .mobile-toolbar bar");
});
// ── BUG-073: clearance inside the mobile media block ────────────────────────
const toolbarBlock = mobileBlockContaining(".mobile-toolbar");
test("style.css — a ≤768px media block styles .mobile-toolbar", () => {
assert.ok(toolbarBlock, "no @media (max-width: 768px) block contains .mobile-toolbar");
});
test("style.css — mobile clearance targets .main-body (64px + safe-area)", () => {
assert.ok(toolbarBlock, "toolbar media block not found");
assert.match(
toolbarBlock,
/\.main-body \{[^}]*padding-bottom:[^}]*calc\(64px \+ env\(safe-area-inset-bottom/,
"the toolbar clearance must pad .main-body inside the mobile block (BUG-073: .main-layout does not exist in index.html)",
);
});
test("style.css — dead .main-layout clearance rules are gone", () => {
assert.ok(toolbarBlock, "toolbar media block not found");
assert.doesNotMatch(
toolbarBlock,
/\.main-layout \{[^}]*padding-bottom/,
".main-layout clearance is a dead selector in index.html (BUG-073)",
);
assert.doesNotMatch(
toolbarBlock,
/\.editor-modal\.active ~ \.main-layout/,
"the sibling clearance reset targeted a selector that never matches (BUG-073)",
);
});
test("style.css — desktop .main-body carries no toolbar padding", () => {
const base = css.match(/\.main-body \{[^}]*\}/);
assert.ok(base, ".main-body base rule not found");
assert.doesNotMatch(base[0], /padding-bottom/, "toolbar clearance must stay mobile-only");
});
// ── Companion rules ─────────────────────────────────────────────────────────
test("style.css — reading mode resets the clearance (toolbar hidden)", () => {
assert.match(
css,
/body\.reading-mode \.main-body \{[^}]*padding-bottom: 0/,
"reading mode hides #mobile-toolbar: .main-body must release the padding",
);
});
test("style.css — np-active keeps only the dock zone (no toolbar double-count)", () => {
assert.match(
css,
/body\.np-active \.content-area \{[^}]*padding-bottom: 76px/,
"toolbar clearance now comes from .main-body: np-active must pad the dock zone only (76px)",
);
assert.doesNotMatch(
css,
/body\.np-active \.content-area \{[^}]*calc\(64px \+ env\(safe-area-inset-bottom[^}]*\+ 76px/,
"the old formula double-counted the toolbar zone already cleared by .main-body",
);
});
if (process.exitCode) {
console.error("\nMobile toolbar tests FAILED");
} else {
console.log("\nAll mobile toolbar tests passed.");
}