132 lines
7.6 KiB
JavaScript
132 lines
7.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* ObsiGate — Configurations modal mobile usability non-regression tests (BUG-071).
|
|
*
|
|
* Static checks (no jsdom needed — runs in the "Frontend unit tests" CI step):
|
|
* - BUG-071a: #config-nav shared the .help-nav rule that hides it below
|
|
* 768px, but the config modal had no toggle (the help modal has
|
|
* #help-hamburger) → the table of contents was unreachable on mobile.
|
|
* The header must carry #config-hamburger (same .help-hamburger
|
|
* treatment: hidden on desktop, visible on mobile) wired in config.js.
|
|
* - BUG-071b: the TOC links were bare anchors with no JS — no active state,
|
|
* no auto-collapse on mobile, unreliable scrolling inside the modal.
|
|
* config.js must smooth-scroll to the section, mark it active and collapse
|
|
* the nav on mobile, and reset the nav on open.
|
|
* - BUG-071c: two-column grids (.ai-default-grid, .ai-provider-fields),
|
|
* fixed-width add-rows (.config-add-row, 180/140/100px inline widths) and
|
|
* single-line webhook/token/share items overflowed a 360px viewport.
|
|
* style.css must stack/wrap them below 768px with 44px touch targets.
|
|
* - BUG-071d: every #config-nav link target must exist (dead-anchor guard,
|
|
* same class of bug as BUG-067 for the help modal).
|
|
* - BUG-071e: data-i18n-attr supports several "attr:key" pairs (";"-
|
|
* separated) so the toggle carries translated title AND aria-label.
|
|
*
|
|
* Usage: node tests/frontend/config-mobile.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 indexHtml = readFileSync(path.join(ROOT, "frontend", "index.html"), "utf8");
|
|
const configJs = readFileSync(path.join(ROOT, "frontend", "js", "config.js"), "utf8");
|
|
const i18nJs = readFileSync(path.join(ROOT, "frontend", "js", "i18n.js"), "utf8");
|
|
const css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8");
|
|
const fr = JSON.parse(readFileSync(path.join(ROOT, "frontend", "locales", "fr.json"), "utf8"));
|
|
const en = JSON.parse(readFileSync(path.join(ROOT, "frontend", "locales", "en.json"), "utf8"));
|
|
|
|
function test(label, fn) {
|
|
try {
|
|
fn();
|
|
console.log(" ✓ " + label);
|
|
} catch (err) {
|
|
console.error(" ✗ " + label + "\n " + err.message);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
// ── BUG-071a: header TOC toggle ─────────────────────────────────────────────
|
|
test("index.html — #config-hamburger exists in the config modal header", () => {
|
|
const modal = indexHtml.match(/<div class="editor-modal" id="config-modal">([\s\S]*?)<div class="editor-body help-body" id="config-body">/);
|
|
assert.ok(modal, "#config-modal with #config-body not found");
|
|
const header = modal[1].match(/<div class="editor-header">([\s\S]*?)<\/div>\s*<\/div>/);
|
|
assert.ok(header, "config modal .editor-header not found");
|
|
assert.match(header[1], /id="config-hamburger"/, "no #config-hamburger in the config header — TOC unreachable on mobile");
|
|
assert.match(header[1], /help-hamburger/, "the toggle must reuse .help-hamburger (desktop-hidden, mobile-visible)");
|
|
assert.match(header[1], /aria-label/, "the toggle needs an accessible label");
|
|
assert.match(header[1], /config\.toc_toggle/, "the toggle label must use the i18n key config.toc_toggle");
|
|
});
|
|
|
|
test("i18n — config.toc_toggle exists in FR and EN", () => {
|
|
assert.ok(fr["config.toc_toggle"], "fr.json missing config.toc_toggle");
|
|
assert.ok(en["config.toc_toggle"], "en.json missing config.toc_toggle");
|
|
assert.notEqual(fr["config.toc_toggle"], "config.toc_toggle", "FR value must be translated");
|
|
assert.notEqual(en["config.toc_toggle"], "config.toc_toggle", "EN value must be translated");
|
|
});
|
|
|
|
// ── BUG-071b: TOC behaviour in config.js ────────────────────────────────────
|
|
test("config.js — hamburger toggles #config-nav", () => {
|
|
assert.match(configJs, /getElementById\("config-hamburger"\)/, "no binding on #config-hamburger");
|
|
assert.match(configJs, /_setConfigNav\(/, "TOC open/close helper missing");
|
|
});
|
|
|
|
test("config.js — TOC links smooth-scroll, mark active, collapse on mobile", () => {
|
|
assert.match(configJs, /#config-nav[\s\S]{0,400}?help-nav-link/, "no handler on the #config-nav links");
|
|
assert.match(configJs, /scrollIntoView/, "section scroll must use scrollIntoView inside the modal");
|
|
assert.match(configJs, /innerWidth <= 768/, "the nav must auto-collapse on mobile viewports only");
|
|
});
|
|
|
|
test("config.js — TOC display reset when the modal opens", () => {
|
|
assert.match(configJs, /configNavOnOpen[\s\S]{0,120}?style\.display = ''/, "stale inline display would stick across sessions");
|
|
});
|
|
|
|
// ── BUG-071c: mobile CSS ────────────────────────────────────────────────────
|
|
test("style.css — config TOC becomes a capped top block on mobile", () => {
|
|
assert.match(css, /#config-modal #config-nav/, "no mobile rule scoped to #config-modal #config-nav");
|
|
assert.match(css, /#config-modal #config-nav[\s\S]{0,400}?max-height/, "the opened TOC must be height-capped so content stays reachable");
|
|
});
|
|
|
|
test("style.css — two-column config grids stack on mobile", () => {
|
|
assert.match(css, /#config-modal \.ai-default-grid/, ".ai-default-grid still 2 columns on mobile");
|
|
assert.match(css, /#config-modal \.ai-provider-fields/, ".ai-provider-fields still 3fr/2fr on mobile");
|
|
assert.match(css, /grid-template-columns: 1fr;/, "mobile grids must collapse to a single column");
|
|
});
|
|
|
|
test("style.css — add-rows wrap and fixed inline widths are neutralised", () => {
|
|
assert.match(css, /#config-modal \.config-add-row/, "no mobile rule for .config-add-row (token/webhook rows overflow)");
|
|
assert.match(css, /width: auto !important/, "fixed inline widths (180/140/100px) must be overridden on mobile");
|
|
assert.match(css, /min-height: 44px/, "mobile action controls need 44px touch targets");
|
|
});
|
|
|
|
test("style.css — webhook/token/share rows wrap on mobile", () => {
|
|
for (const cls of ["webhook-item", "token-item", "share-item"]) {
|
|
assert.match(css, new RegExp("#config-modal \\." + cls), `.${cls} has no mobile wrap rule`);
|
|
}
|
|
});
|
|
|
|
// ── BUG-071d: dead-anchor guard ─────────────────────────────────────────────
|
|
test("index.html — every #config-nav link resolves to an element id", () => {
|
|
const nav = indexHtml.match(/<nav class="help-nav" id="config-nav">([\s\S]*?)<\/nav>/);
|
|
assert.ok(nav, "#config-nav not found");
|
|
const hrefs = [...nav[1].matchAll(/href="(#[^"]+)"/g)].map((m) => m[1].slice(1));
|
|
assert.ok(hrefs.length > 0, "no links in #config-nav");
|
|
const missing = hrefs.filter((id) => !indexHtml.includes(`id="${id}"`));
|
|
assert.deepEqual(missing, [], `dead TOC anchors (cf. BUG-067): ${missing.join(", ")}`);
|
|
});
|
|
|
|
// ── BUG-071e: multi-pair data-i18n-attr ─────────────────────────────────────
|
|
test("i18n.js — data-i18n-attr supports several attr:key pairs", () => {
|
|
assert.match(i18nJs, /split\(['"];/, "pairs must be split on ';'");
|
|
assert.match(i18nJs, /el\.setAttribute\(attr, t\(key\)\)/, "each pair must set its attribute");
|
|
});
|
|
|
|
if (process.exitCode) {
|
|
console.error("\nConfig mobile tests FAILED");
|
|
} else {
|
|
console.log("\nAll config mobile tests passed.");
|
|
}
|