feat: barre d'outils de lecture epinglee, coloration syntaxique des fichiers de code et avatars predefinis (#115, #117, BUG-078)
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { strict as assert } from "node:assert";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -136,11 +136,46 @@ test("style.css — preview circle and sidebar image rules exist", () => {
|
||||
assert.match(css, /\.sidebar-user-avatar-img\s*\{/, "no .sidebar-user-avatar-img rule");
|
||||
});
|
||||
|
||||
// ── #117: preset avatar gallery ─────────────────────────────────────────────
|
||||
test("index.html — #cfg-profile exposes 12 bundled preset avatars", () => {
|
||||
const section = indexHtml.match(/<section[^>]*id="cfg-profile"[\s\S]*?<\/section>/);
|
||||
assert.ok(section, "#cfg-profile section not found");
|
||||
const html = section[0];
|
||||
assert.match(html, /id="profile-avatar-presets"/, "preset gallery container missing");
|
||||
const buttons = [...html.matchAll(/class="profile-avatar-preset"[^>]*data-avatar="([^"]+)"/g)];
|
||||
assert.equal(buttons.length, 12, `expected 12 preset avatars, found ${buttons.length}`);
|
||||
const names = buttons.map((m) => m[1]);
|
||||
assert.equal(new Set(names).size, 12, "preset avatar names must be unique");
|
||||
for (const name of names) {
|
||||
const rel = path.join(ROOT, "frontend", "icons", "avatar", name);
|
||||
assert.ok(existsSync(rel), `missing avatar file: ${rel}`);
|
||||
assert.ok(html.includes(`/static/icons/avatar/${name}`),
|
||||
`#${name} must be served from /static/icons/avatar/`);
|
||||
}
|
||||
});
|
||||
|
||||
test("config.js — preset avatars reuse the upload pipeline", () => {
|
||||
assert.match(configJs, /profile-avatar-presets/, "preset container lookup missing");
|
||||
assert.match(configJs, /\.profile-avatar-preset/, "preset button selector missing");
|
||||
assert.match(configJs, /\/static\/icons\/avatar\//, "preset image base URL missing");
|
||||
assert.match(configJs, /_resizeAvatarFile\(blob\)/, "preset must go through the resize pipeline");
|
||||
assert.match(configJs, /_patchProfileAvatar\(dataUrl\)/, "preset must be persisted via PATCH");
|
||||
assert.match(configJs, /obsigate-avatar-preset/, "active preset marker missing");
|
||||
assert.match(configJs, /markActivePreset/, "active preset highlight missing");
|
||||
});
|
||||
|
||||
test("style.css — preset avatar rules exist", () => {
|
||||
assert.match(css, /\.profile-avatar-presets\s*\{/, "no .profile-avatar-presets rule");
|
||||
assert.match(css, /\.profile-avatar-preset\s*\{/, "no .profile-avatar-preset rule");
|
||||
assert.match(css, /\.profile-avatar-preset\.active\s*\{/, "no active preset rule");
|
||||
});
|
||||
|
||||
// ── #113c: i18n + backend ───────────────────────────────────────────────────
|
||||
test("i18n — avatar keys exist in FR and EN", () => {
|
||||
const keys = [
|
||||
"config.avatar_label",
|
||||
"config.avatar_hint",
|
||||
"config.avatar_presets_label",
|
||||
"config.avatar_choose",
|
||||
"config.avatar_remove",
|
||||
"config.avatar_updated",
|
||||
|
||||
@@ -111,6 +111,30 @@ 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 {
|
||||
|
||||
@@ -284,6 +284,28 @@ function testSidebarUserSection() {
|
||||
console.log(' ✓ header trimmed + sidebar account section (#112)');
|
||||
}
|
||||
|
||||
// ── Test syntax highlighting follows the theme mode (BUG-078) ──────────────
|
||||
function testSyntaxHighlightTheme() {
|
||||
const themes = readFileSync(join(JS_DIR, 'themes.js'), 'utf-8');
|
||||
const ui = readFileSync(join(JS_DIR, 'ui.js'), 'utf-8');
|
||||
|
||||
// themes.js must swap the highlight.js stylesheets based on the *mode*.
|
||||
const applyTheme = themes.match(/function applyTheme\(themeKey, mode\)\s*\{([\s\S]*?)\n\}/);
|
||||
assert.ok(applyTheme, 'themes.js applyTheme not found');
|
||||
assert.match(applyTheme[1], /hljs-theme-dark/, 'applyTheme must toggle the dark hljs sheet');
|
||||
assert.match(applyTheme[1], /hljs-theme-light/, 'applyTheme must toggle the light hljs sheet');
|
||||
assert.match(applyTheme[1], /mode === 'dark'|mode === "dark"/, 'sheet choice must key on the mode');
|
||||
|
||||
// ui.js must resolve the persisted *mode*, never treat the theme key as one.
|
||||
assert.match(ui, /obsigate-theme-mode/, 'ui.js must read the persisted theme mode');
|
||||
assert.doesNotMatch(
|
||||
ui,
|
||||
/darkSheet\.disabled\s*=\s*theme !== "dark"[\s\S]{0,120}lightSheet\.disabled\s*=\s*theme !== "light"/,
|
||||
'ui.js must not compare the theme key to "dark"/"light" (BUG-078 regression)',
|
||||
);
|
||||
console.log(' ✓ syntax highlighting follows the theme mode (BUG-078)');
|
||||
}
|
||||
|
||||
// ── Run all tests ──────────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
let passed = 0, failed = 0;
|
||||
@@ -300,6 +322,7 @@ async function main() {
|
||||
['ai-fab module exports', testAIFabModuleExports],
|
||||
['config TOC icons', testConfigTocIcons],
|
||||
['sidebar user section', testSidebarUserSection],
|
||||
['syntax highlight theme', testSyntaxHighlightTheme],
|
||||
];
|
||||
|
||||
for (const [name, fn] of tests) {
|
||||
|
||||
Reference in New Issue
Block a user