Files
ObsiGate/tests/frontend/search-facets.test.mjs
T
bruno 2813d546a7
CI / lint (push) Successful in 2m41s
CI / security (push) Successful in 2m0s
CI / test (push) Successful in 4m45s
CI / build (push) Successful in 3m4s
CI / e2e (push) Successful in 15m36s
feat: facette Extensions dans les facettes de recherche #157
2026-10-01 20:35:21 -04:00

151 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
/**
* ObsiGate - JSDOM tests for the search results facets panel (#157):
* vault, tag and file-extension facets rendered by
* `renderAdvancedSearchResults()` (frontend/js/search.js).
*
* Covers:
* - the three facet groups are rendered (Vaults / Tags / Extensions)
* - the extensions facet appears even when tags and vaults are empty
* - clicking an extension replaces any `ext:` operator in the search
* input and triggers a new search
*
* Usage: node tests/frontend/search-facets.test.mjs
*/
import { strict as assert } from "node:assert";
import { JSDOM } from "jsdom";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const JS_DIR = path.resolve(__dirname, "..", "..", "frontend", "js");
const dom = new JSDOM(
`<!DOCTYPE html>
<html><body>
<input id="search-input" value="" />
<select id="vault-filter"><option value="all">all</option></select>
<div id="results-bar" style="display:none"></div>
<span id="rb-count"></span>
<div id="content-area"></div>
</body></html>`,
{ url: "http://localhost/", pretendToBeVisual: true },
);
const w = dom.window;
globalThis.window = w;
globalThis.document = w.document;
globalThis.HTMLElement = w.HTMLElement;
globalThis.Element = w.Element;
globalThis.Node = w.Node;
globalThis.Event = w.Event;
globalThis.CustomEvent = w.CustomEvent;
globalThis.KeyboardEvent = w.KeyboardEvent;
globalThis.localStorage = w.localStorage;
globalThis.sessionStorage = w.sessionStorage;
Object.defineProperty(globalThis, "navigator", {
value: w.navigator,
configurable: true,
writable: true,
});
globalThis.MutationObserver = w.MutationObserver;
globalThis.getComputedStyle = w.getComputedStyle.bind(w);
globalThis.requestAnimationFrame = (cb) => setTimeout(cb, 0);
globalThis.cancelAnimationFrame = (id) => clearTimeout(id);
let testCount = 0;
let passCount = 0;
async function test(name, fn) {
testCount++;
try {
await fn();
passCount++;
console.log(` ✓ ${name}`);
} catch (err) {
console.error(` ✗ ${name}\n ${err.message}`);
process.exitCode = 1;
}
}
// Backend payload shape for /api/search/advanced (facets only matter here).
const PAYLOAD = {
results: [],
total: 0,
offset: 0,
limit: 50,
query_time_ms: 1,
facets: {
vaults: { IT: 4, Recettes: 2 },
tags: { linux: 3 },
extensions: { md: 5, pdf: 2, xlsx: 1 },
},
};
globalThis.fetch = async () => ({
ok: true,
status: 200,
json: async () => PAYLOAD,
});
const { renderAdvancedSearchResults } = await import(
pathToFileURL(path.join(JS_DIR, "search.js")).href
);
const facetGroups = () =>
Array.from(document.querySelectorAll(".search-facets__group"));
const extGroup = () =>
document.querySelector('.search-facets__group[data-facet="extensions"]');
const extItems = () =>
Array.from(extGroup()?.querySelectorAll(".search-facets__item") || []);
console.log("Search facets (#157) — Vaults / Tags / Extensions");
await test("renders the three facet groups", async () => {
renderAdvancedSearchResults(PAYLOAD, "python", null);
assert.equal(facetGroups().length, 3, "vault, tag and extension groups");
const labels = facetGroups().map((g) =>
g.querySelector(".search-facets__label").textContent,
);
assert.deepEqual(labels, ["Vaults", "Tags", "Extensions"]);
});
await test("lists extensions with their counts", async () => {
const texts = extItems().map((i) => i.textContent.trim());
assert.deepEqual(texts, [".md 5", ".pdf 2", ".xlsx 1"]);
assert.equal(
extItems()[0].querySelector(".facet-count").textContent,
"5",
"count lives in .facet-count",
);
});
await test("extension facet shows even with empty tags and vaults", async () => {
renderAdvancedSearchResults(
{ ...PAYLOAD, facets: { vaults: {}, tags: {}, extensions: { md: 1 } } },
"",
null,
);
assert.ok(extGroup(), "extensions group rendered alone");
assert.equal(extItems().length, 1);
});
await test("clicking an extension adds the ext: operator", async () => {
document.getElementById("search-input").value = "python ext:md";
renderAdvancedSearchResults(PAYLOAD, "python", null);
extItems()[1].click(); // .pdf
assert.equal(
document.getElementById("search-input").value,
"python ext:pdf",
"existing ext: operator replaced, not duplicated",
);
});
// ── Summary ──
console.log(`\n${passCount}/${testCount} tests passed`);
if (passCount !== testCount) {
process.exit(1);
}