feat(viewer): mode pretty print avec coloration syntaxique et numeros de ligne pour fichiers texte (#89)
CI / lint (push) Successful in 1m17s
CI / security (push) Successful in 56s
CI / test (push) Successful in 2m25s
CI / build (push) Successful in 48s
CI / e2e (push) Successful in 10m35s

This commit is contained in:
2026-09-14 19:12:43 -04:00
parent 96bb1cbcae
commit d3f7a04411
6 changed files with 170 additions and 0 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* Frontend tests for Pretty print helpers (#89).
* detectPrettyLang / buildPrettyHtml are pure functions extracted from
* frontend/js/viewer.js; they are evaluated in a sandbox to avoid importing
* the full viewer module chain (browser-only dependencies).
*/
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import vm from "node:vm";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const src = readFileSync(path.join(__dirname, "..", "..", "frontend", "js", "viewer.js"), "utf8");
function extractFn(name) {
const start = src.indexOf(`function ${name}(`);
assert.ok(start !== -1, `function ${name} not found in viewer.js`);
let depth = 0;
let i = src.indexOf("{", start);
const begin = i;
for (; i < src.length; i++) {
if (src[i] === "{") depth++;
else if (src[i] === "}") {
depth--;
if (depth === 0) break;
}
}
return src.slice(start, i + 1);
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
const sandbox = { escapeHtml };
vm.createContext(sandbox);
vm.runInContext(`${extractFn("detectPrettyLang")}\n${extractFn("buildPrettyHtml")}\nglobalThis.detectPrettyLang = detectPrettyLang; globalThis.buildPrettyHtml = buildPrettyHtml;`, sandbox);
const { detectPrettyLang, buildPrettyHtml } = sandbox;
console.log("Testing detectPrettyLang...");
assert.equal(detectPrettyLang({ is_json: true }), "json");
assert.equal(detectPrettyLang({ extension: ".json" }), "json");
assert.equal(detectPrettyLang({ extension: ".py", html: '<pre><code class="language-python">x</code></pre>' }), "python");
assert.equal(detectPrettyLang({ extension: ".sh", html: '<pre><code class="language-bash">x</code></pre>' }), "bash");
assert.equal(detectPrettyLang({ extension: ".txt", html: "<pre><code>x</code></pre>" }), "plaintext");
console.log("Testing buildPrettyHtml (json reformat)...");
const jsonOut = buildPrettyHtml({ is_json: true }, '{"a":1,"b":[2,3]}');
assert.ok(jsonOut.includes("pretty-view"), "wrapper class present");
assert.ok(jsonOut.includes("pp-gutter"), "gutter present");
assert.ok(jsonOut.includes('class="language-json"'), "language class present");
// 4 formatted lines: {, "a": 1, "b": [...], } -> gutter must show 4 numbers at least
assert.ok(/<span>4<\/span>/.test(jsonOut), "line numbers computed on formatted text");
assert.ok(jsonOut.includes("&quot;a&quot;"), "HTML-escaped quotes");
console.log("Testing buildPrettyHtml (invalid json falls back to raw)...");
const bad = buildPrettyHtml({ is_json: true }, "{not valid json}");
assert.ok(bad.includes("{not valid json}"), "raw content displayed when JSON invalid");
console.log("Testing buildPrettyHtml (xss escaped)...");
const xss = buildPrettyHtml({ extension: ".py", html: "" }, 'print("<script>alert(1)</script>")');
assert.ok(!xss.includes("<script>"), "no raw script tag injected");
assert.ok(xss.includes("&lt;script&gt;"), "script tag escaped");
console.log("✅ All pretty-print tests passed successfully!");