180 lines
5.6 KiB
JavaScript
180 lines
5.6 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",
|
|
);
|
|
});
|
|
|
|
await test("facets panel collapses and restores via the toggle", async () => {
|
|
localStorage.removeItem("obsigate_facets_collapsed");
|
|
renderAdvancedSearchResults(PAYLOAD, "python", null);
|
|
const panel = document.querySelector(".search-facets");
|
|
const toggle = panel.querySelector(".search-facets__toggle");
|
|
assert.ok(toggle, "collapse toggle rendered");
|
|
assert.equal(panel.getAttribute("data-collapsed"), "false", "expanded by default");
|
|
assert.ok(panel.querySelector(".search-facets__body"), "groups live in the body");
|
|
|
|
toggle.click();
|
|
assert.equal(panel.getAttribute("data-collapsed"), "true", "click collapses");
|
|
assert.equal(
|
|
localStorage.getItem("obsigate_facets_collapsed"),
|
|
"1",
|
|
"collapsed state persisted",
|
|
);
|
|
|
|
renderAdvancedSearchResults(PAYLOAD, "python", null);
|
|
const panel2 = document.querySelector(".search-facets");
|
|
assert.equal(
|
|
panel2.getAttribute("data-collapsed"),
|
|
"true",
|
|
"collapsed state survives a re-render",
|
|
);
|
|
panel2.querySelector(".search-facets__toggle").click();
|
|
assert.equal(panel2.getAttribute("data-collapsed"), "false", "toggle restores");
|
|
localStorage.removeItem("obsigate_facets_collapsed");
|
|
});
|
|
|
|
// ── Summary ──
|
|
console.log(`\n${passCount}/${testCount} tests passed`);
|
|
if (passCount !== testCount) {
|
|
process.exit(1);
|
|
}
|