feat(viewer): #90 regroupement fonctionnel des boutons de la barre d'actions + spacers
Ordre: [TOC][pop-out][Bookmark] | [Editer][Source][.md][Forge] | [Copier][PDF][Export] | [Partager] - viewer.js: construction par groupes nav/edit/export/share, spacer .action-sep entre groupes non vides; Pretty ancre au groupe edition - popout.html: meme assemblage groupe, ordre aligne - style.css: regle .file-actions .action-sep (1px, var(--border-md)) - tests: tests/frontend/toolbar-order.test.mjs (9) branche au CI
This commit is contained in:
@@ -51,6 +51,7 @@ jobs:
|
|||||||
node mobile-editor.test.mjs
|
node mobile-editor.test.mjs
|
||||||
node semantic-search.test.mjs
|
node semantic-search.test.mjs
|
||||||
node desktop.test.mjs
|
node desktop.test.mjs
|
||||||
|
node toolbar-order.test.mjs
|
||||||
else
|
else
|
||||||
echo "tests/frontend/node_modules missing - installing jsdom"
|
echo "tests/frontend/node_modules missing - installing jsdom"
|
||||||
npm install --no-audit --no-fund --silent
|
npm install --no-audit --no-fund --silent
|
||||||
@@ -63,6 +64,7 @@ jobs:
|
|||||||
node mobile-editor.test.mjs
|
node mobile-editor.test.mjs
|
||||||
node semantic-search.test.mjs
|
node semantic-search.test.mjs
|
||||||
node desktop.test.mjs
|
node desktop.test.mjs
|
||||||
|
node toolbar-order.test.mjs
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Tests ─────────────────────────────────────────────────────────
|
# ── Tests ─────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
utilisateur. Le prompt Général est enrichi d'un bloc « Contexte applicatif
|
utilisateur. Le prompt Général est enrichi d'un bloc « Contexte applicatif
|
||||||
actuel » (`build_general_system_prompt(vaults, app_context, recent_files)`).
|
actuel » (`build_general_system_prompt(vaults, app_context, recent_files)`).
|
||||||
Fiche : [docs/features/ai-app-context.md](./docs/features/ai-app-context.md).
|
Fiche : [docs/features/ai-app-context.md](./docs/features/ai-app-context.md).
|
||||||
|
- **#90 Barre d'actions du document — regroupement fonctionnel** : les boutons
|
||||||
|
en haut du document sont réordonnés en quatre groupes séparés par des
|
||||||
|
barres verticales (`span.action-sep`) :
|
||||||
|
`[TOC] [pop-out] [Bookmark] | [Editer] [Source] [.md] [Forge] | [Copier] [PDF] [Export] | [Partager]`.
|
||||||
|
Le bouton « Pretty » (fichiers texte) rejoint le groupe édition, les
|
||||||
|
boutons non applicables à un type de fichier sont omis et les spacers d'un
|
||||||
|
groupe vide ne sont pas rendus. La fenêtre pop-out (`popout.html`) est
|
||||||
|
alignée sur le même ordre (groupes nav / édition / export / partage).
|
||||||
|
Tests : `tests/frontend/toolbar-order.test.mjs` (9).
|
||||||
|
|
||||||
### Corrigé
|
### Corrigé
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -135,6 +135,7 @@
|
|||||||
|
|
||||||
| # | Domaine / fonctionnalité | Version | Détails |
|
| # | Domaine / fonctionnalité | Version | Détails |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|
| 90 | Barre d'actions du document — regroupement fonctionnel + spacers | 2.3.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||||
| 89 | Drag & drop complet de fichiers/dossiers & intégration Assistant IA | 2.3.0 | [features/drag-and-drop-ai.md](./features/drag-and-drop-ai.md) |
|
| 89 | Drag & drop complet de fichiers/dossiers & intégration Assistant IA | 2.3.0 | [features/drag-and-drop-ai.md](./features/drag-and-drop-ai.md) |
|
||||||
| 1–8 | Fondations (FastAPI, TF-IDF + stemming, watchdog, SPA, sécurité, path traversal, gzip, PWA) | 1.0.0→1.4.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
| 1–8 | Fondations (FastAPI, TF-IDF + stemming, watchdog, SPA, sécurité, path traversal, gzip, PWA) | 1.0.0→1.4.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||||
| 9–20 | UX & productivité (share, webhooks, dashboard, conflits, backlinks, redaction, backups, graphe, header, aperçu) | 1.5.0→1.6.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
| 9–20 | UX & productivité (share, webhooks, dashboard, conflits, backlinks, redaction, backups, graphe, header, aperçu) | 1.5.0→1.6.0 | [archive](./archive/COMPLETED_v1-v2.md) |
|
||||||
@@ -177,7 +178,7 @@
|
|||||||
|
|
||||||
| Priorité | Items | Effort total estimé |
|
| Priorité | Items | Effort total estimé |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ✅ Complété | #1 → #59, #61–72, #74–76, #78–84, #88 | ~107 jours réalisés |
|
| ✅ Complété | #1 → #59, #61–72, #74–76, #78–84, #88–90 | ~107 jours réalisés |
|
||||||
| 🔵 P2 restant | #77 Desktop : signature de code (non retenue), 6 tests E2E **manuels** ([protocole](./DESKTOP_E2E_CHECKLIST.md)) | ~0,5-1 jour |
|
| 🔵 P2 restant | #77 Desktop : signature de code (non retenue), 6 tests E2E **manuels** ([protocole](./DESKTOP_E2E_CHECKLIST.md)) | ~0,5-1 jour |
|
||||||
| ⚪ P4 restant | #73 Sync (6-8j) | 6-8 jours |
|
| ⚪ P4 restant | #73 Sync (6-8j) | 6-8 jours |
|
||||||
| ⚪ P0/P1 restant | #85-87 Refonte architecturale, performance, CI/CD (issues BUG-035 → BUG-040) | ~15-23 jours |
|
| ⚪ P0/P1 restant | #85-87 Refonte architecturale, performance, CI/CD (issues BUG-035 → BUG-040) | ~15-23 jours |
|
||||||
|
|||||||
@@ -362,6 +362,24 @@ La barre de mise en forme flottante du mode édition mobile est remplacée par u
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## #90 — Barre d'actions du document : regroupement fonctionnel ✅ TERMINÉ
|
||||||
|
|
||||||
|
Réordonnancement des boutons du haut du document en quatre groupes
|
||||||
|
séparés par des barres verticales (`span.action-sep`) :
|
||||||
|
|
||||||
|
`[TOC] [pop-out] [Bookmark] | [Editer] [Source] [.md] [Forge] | [Copier] [PDF] [Export] | [Partager]`
|
||||||
|
|
||||||
|
- **Viewer principal** (`frontend/js/viewer.js`, `renderFile`) : construction par
|
||||||
|
groupes `navBtns` / `editBtns` / `exportBtns` / `shareBtns` ; « Pretty »
|
||||||
|
(fichiers texte) ancré au groupe édition ; les spacers d'un groupe vide
|
||||||
|
(ex. fichier image ou binaire) ne sont pas rendus.
|
||||||
|
- **Pop-out** (`frontend/popout.html`) : même assemblage groupé, ordre aligné.
|
||||||
|
- **CSS** (`frontend/style.css`) : règle `.file-actions .action-sep`
|
||||||
|
(trait 1 px, `var(--border-md)`, `align-self: stretch`).
|
||||||
|
- **Tests** : `tests/frontend/toolbar-order.test.mjs` (9, statiques).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Grosses fonctionnalités — fiches dédiées
|
## Grosses fonctionnalités — fiches dédiées
|
||||||
|
|
||||||
| # | Feature | Version | Fiche |
|
| # | Feature | Version | Fiche |
|
||||||
|
|||||||
+22
-6
@@ -879,12 +879,28 @@ export function renderFile(data) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileActions = [copyBtn, sourceBtn];
|
// Separateur visuel entre les groupes d'actions (#90)
|
||||||
if (!data.is_image) fileActions.push(mdBtn);
|
const actionSep = () => el("span", { class: "action-sep" });
|
||||||
if (data.is_markdown) fileActions.push(pdfBtn, exportWrap, forgeBtn, tocBtn);
|
// Ordre des boutons par groupes fonctionnels (#90) :
|
||||||
if (!data.unsupported) fileActions.push(editBtn);
|
// [TOC][pop-out][Bookmark] | [Editer][Source][.md][Forge] | [Copier][PDF][Export] | [Partager]
|
||||||
if (prettyBtn) fileActions.push(prettyBtn);
|
// Boutons non affiches selon le type de fichier (Pretty, PDF/Export hors
|
||||||
fileActions.push(openNewWindowBtn, shareBtn, bookmarkBtn);
|
// markdown, ...) restent ancres a leur groupe ; spacer de groupe vide retire.
|
||||||
|
const navBtns = [tocBtn, openNewWindowBtn, bookmarkBtn];
|
||||||
|
const editBtns = [];
|
||||||
|
if (!data.unsupported) editBtns.push(editBtn);
|
||||||
|
editBtns.push(sourceBtn);
|
||||||
|
if (!data.is_image) editBtns.push(mdBtn);
|
||||||
|
if (data.is_markdown) editBtns.push(forgeBtn);
|
||||||
|
if (prettyBtn) editBtns.push(prettyBtn);
|
||||||
|
const exportBtns = [copyBtn];
|
||||||
|
if (data.is_markdown) exportBtns.push(pdfBtn, exportWrap);
|
||||||
|
const shareBtns = [shareBtn];
|
||||||
|
const fileActions = [];
|
||||||
|
[navBtns, editBtns, exportBtns, shareBtns].forEach((group) => {
|
||||||
|
if (group.length === 0) return;
|
||||||
|
if (fileActions.length > 0) fileActions.push(actionSep());
|
||||||
|
group.forEach((b) => fileActions.push(b));
|
||||||
|
});
|
||||||
|
|
||||||
// Assemble
|
// Assemble
|
||||||
area.innerHTML = "";
|
area.innerHTML = "";
|
||||||
|
|||||||
+15
-10
@@ -734,9 +734,14 @@ const RightSidebarManager = {
|
|||||||
header.appendChild(tagsDiv);
|
header.appendChild(tagsDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons — memes groupes/spacers que le viewer principal (#90)
|
||||||
const actionsDiv = document.createElement("div");
|
const actionsDiv = document.createElement("div");
|
||||||
actionsDiv.className = "file-actions";
|
actionsDiv.className = "file-actions";
|
||||||
|
const actionSep = () => {
|
||||||
|
const s = document.createElement("span");
|
||||||
|
s.className = "action-sep";
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
const copyBtn = document.createElement("button");
|
const copyBtn = document.createElement("button");
|
||||||
copyBtn.className = "btn-action";
|
copyBtn.className = "btn-action";
|
||||||
@@ -752,7 +757,6 @@ const RightSidebarManager = {
|
|||||||
if (window.lucide) window.lucide.createIcons();
|
if (window.lucide) window.lucide.createIcons();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(copyBtn);
|
|
||||||
|
|
||||||
const sourceBtn = document.createElement("button");
|
const sourceBtn = document.createElement("button");
|
||||||
sourceBtn.className = "btn-action";
|
sourceBtn.className = "btn-action";
|
||||||
@@ -782,7 +786,6 @@ const RightSidebarManager = {
|
|||||||
raw.style.display = "none";
|
raw.style.display = "none";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(sourceBtn);
|
|
||||||
|
|
||||||
const dlBtn = document.createElement("button");
|
const dlBtn = document.createElement("button");
|
||||||
dlBtn.className = "btn-action";
|
dlBtn.className = "btn-action";
|
||||||
@@ -796,7 +799,6 @@ const RightSidebarManager = {
|
|||||||
a.click();
|
a.click();
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(dlBtn);
|
|
||||||
|
|
||||||
const pdfBtn = document.createElement("button");
|
const pdfBtn = document.createElement("button");
|
||||||
pdfBtn.className = "btn-action";
|
pdfBtn.className = "btn-action";
|
||||||
@@ -805,7 +807,6 @@ const RightSidebarManager = {
|
|||||||
pdfBtn.addEventListener("click", () => {
|
pdfBtn.addEventListener("click", () => {
|
||||||
window.open(`/api/file/${encodeURIComponent(vault)}/pdf?path=${encodeURIComponent(path)}`, "_blank");
|
window.open(`/api/file/${encodeURIComponent(vault)}/pdf?path=${encodeURIComponent(path)}`, "_blank");
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(pdfBtn);
|
|
||||||
|
|
||||||
const editBtn = document.createElement("button");
|
const editBtn = document.createElement("button");
|
||||||
editBtn.className = "btn-action";
|
editBtn.className = "btn-action";
|
||||||
@@ -814,7 +815,6 @@ const RightSidebarManager = {
|
|||||||
editBtn.addEventListener("click", () => {
|
editBtn.addEventListener("click", () => {
|
||||||
openEditor(vault, path);
|
openEditor(vault, path);
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(editBtn);
|
|
||||||
|
|
||||||
const tocBtn = document.createElement("button");
|
const tocBtn = document.createElement("button");
|
||||||
tocBtn.className = "btn-action";
|
tocBtn.className = "btn-action";
|
||||||
@@ -822,7 +822,6 @@ const RightSidebarManager = {
|
|||||||
tocBtn.title = "Afficher/Masquer le sommaire";
|
tocBtn.title = "Afficher/Masquer le sommaire";
|
||||||
tocBtn.innerHTML = '<i data-lucide="list" style="width:14px;height:14px"></i> TOC';
|
tocBtn.innerHTML = '<i data-lucide="list" style="width:14px;height:14px"></i> TOC';
|
||||||
tocBtn.addEventListener("click", () => { RightSidebarManager.toggle(); });
|
tocBtn.addEventListener("click", () => { RightSidebarManager.toggle(); });
|
||||||
actionsDiv.appendChild(tocBtn);
|
|
||||||
|
|
||||||
// Share button
|
// Share button
|
||||||
const shareBtn = document.createElement("button");
|
const shareBtn = document.createElement("button");
|
||||||
@@ -844,7 +843,6 @@ const RightSidebarManager = {
|
|||||||
alert("Lien copié : " + url);
|
alert("Lien copié : " + url);
|
||||||
} catch (err) { alert("Erreur: " + err.message); }
|
} catch (err) { alert("Erreur: " + err.message); }
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(shareBtn);
|
|
||||||
|
|
||||||
// Bookmark button
|
// Bookmark button
|
||||||
const bookmarkBtn = document.createElement("button");
|
const bookmarkBtn = document.createElement("button");
|
||||||
@@ -864,8 +862,15 @@ const RightSidebarManager = {
|
|||||||
bookmarkBtn.classList.toggle("active", bm.bookmarked);
|
bookmarkBtn.classList.toggle("active", bm.bookmarked);
|
||||||
} catch (err) { alert("Erreur: " + err.message); }
|
} catch (err) { alert("Erreur: " + err.message); }
|
||||||
});
|
});
|
||||||
actionsDiv.appendChild(bookmarkBtn);
|
|
||||||
|
// Assemble par groupes (ordre aligne sur le viewer principal, #90) :
|
||||||
|
// [TOC][Bookmark] | [Editer][Source][.md] | [Copier][PDF] | [Partager]
|
||||||
|
[[tocBtn, bookmarkBtn], [editBtn, sourceBtn, dlBtn], [copyBtn, pdfBtn], [shareBtn]]
|
||||||
|
.forEach(function (group) {
|
||||||
|
if (actionsDiv.childNodes.length > 0) actionsDiv.appendChild(actionSep());
|
||||||
|
group.forEach(function (b) { actionsDiv.appendChild(b); });
|
||||||
|
});
|
||||||
|
|
||||||
header.appendChild(actionsDiv);
|
header.appendChild(actionsDiv);
|
||||||
area.appendChild(header);
|
area.appendChild(header);
|
||||||
|
|
||||||
|
|||||||
@@ -1798,6 +1798,15 @@ select {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
/* Separateur vertical entre groupes d'actions (#90) */
|
||||||
|
.file-actions .action-sep {
|
||||||
|
align-self: stretch;
|
||||||
|
width: 1px;
|
||||||
|
min-height: 20px;
|
||||||
|
margin: 2px 4px;
|
||||||
|
background: var(--border-md, var(--border));
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.btn-action {
|
.btn-action {
|
||||||
font-family: "JetBrains Mono", monospace;
|
font-family: "JetBrains Mono", monospace;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
#!/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, pop-out, bookmark]", () => {
|
||||||
|
assert.match(
|
||||||
|
viewer,
|
||||||
|
/const navBtns = \[\s*tocBtn,\s*openNewWindowBtn,\s*bookmarkBtn\s*\];/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("viewer.js — group edit = [edit, source, .md, forge] (pretty appended)", () => {
|
||||||
|
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 mdBtn 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)"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("viewer.js — group export = [copier, pdf, export]", () => {
|
||||||
|
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], /exportBtns\.push\(pdfBtn,\s*exportWrap\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user