Files
zemion 6ad7c6e0ef
Module Package Release / publish-packages (push) Successful in 11s
Release govoplan-search v0.1.20: centralize filtering and refresh behavior
2026-09-08 01:32:53 +02:00

103 lines
6.4 KiB
JavaScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import test from "node:test";
const requireCore = createRequire(new URL("../../../govoplan-core/webui/package.json", import.meta.url));
const ts = requireCore("typescript");
function loadTypeScript(relativePath, dependencies = {}) {
const source = readFileSync(new URL(relativePath, import.meta.url), "utf8");
const { outputText } = ts.transpileModule(source, { compilerOptions: {
target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS,
} });
const module = { exports: {} };
new Function("require", "module", "exports", outputText)((id) => {
if (id in dependencies) return dependencies[id];
throw new Error(`Unexpected runtime dependency: ${id}`);
}, module, module.exports);
return module.exports;
}
const { readSearchFilter, writeSearchFilter, effectiveSearchFilters, searchFilterOptions } = loadTypeScript("../src/components/searchFilters.ts");
test("legacy missing filters remain all; explicit none wins over repeated values", () => {
assert.equal(readSearchFilter(new URLSearchParams("q=permit"), "module"), null);
assert.deepEqual(readSearchFilter(new URLSearchParams("module=files&module=cases&module=files"), "module"), ["cases", "files"]);
assert.deepEqual(readSearchFilter(new URLSearchParams("module=cases&module_none=1"), "module"), []);
assert.deepEqual(readSearchFilter(new URLSearchParams("resource_type=case&resource_type_none=1"), "resource_type"), []);
});
test("filter URL roundtrips preserve query, context, language and the other filter", () => {
const original = new URLSearchParams("q=permit&context=cases.current&language=german&resource_type=case&other=keep");
const selected = writeSearchFilter(original, "module", ["files", "cases", "files"]);
assert.deepEqual(selected.getAll("module"), ["cases", "files"]);
assert.equal(original.has("module"), false);
const none = writeSearchFilter(selected, "module", []);
assert.equal(none.get("module_none"), "1");
assert.equal(none.has("module"), false);
assert.deepEqual(readSearchFilter(none, "module"), []);
const all = writeSearchFilter(none, "module", null);
assert.equal(all.toString(), original.toString());
assert.equal(readSearchFilter(all, "module"), null);
});
test("global all, none and subset selections keep distinct effective requests", () => {
assert.deepEqual(effectiveSearchFilters(null, null), { modules: undefined, resourceTypes: undefined, matchNone: false });
assert.equal(effectiveSearchFilters([], null).matchNone, true);
assert.equal(effectiveSearchFilters(null, []).matchNone, true);
assert.deepEqual(effectiveSearchFilters(["cases", "files"], ["case", "file"]), {
modules: ["cases", "files"], resourceTypes: ["case", "file"], matchNone: false,
});
});
test("context bounds never broaden an empty resource-type intersection", () => {
const context = { id: "files.context", moduleId: "files", label: "Files", pathPrefixes: ["/files"], resourceTypes: ["file", "folder"] };
assert.deepEqual(effectiveSearchFilters(null, null, context), { modules: ["files"], resourceTypes: ["file", "folder"], matchNone: false });
assert.deepEqual(effectiveSearchFilters(["cases"], ["case", "file"], context), { modules: ["files"], resourceTypes: ["file"], matchNone: false });
assert.deepEqual(effectiveSearchFilters(null, ["case"], context), { modules: ["files"], resourceTypes: [], matchNone: true });
assert.equal(effectiveSearchFilters(null, [], context).matchNone, true);
// The hidden global module selection must not override the current context.
assert.equal(effectiveSearchFilters([], null, context).matchNone, false);
assert.equal(effectiveSearchFilters(null, null, { ...context, resourceTypes: [] }).matchNone, false);
});
const catalogue = [
{ provider_id: "files.source", module_id: "files", resource_type: "file", label: "Files", order: 1 },
{ provider_id: "files.source", module_id: "files", resource_type: "folder", label: "Folders", order: 2 },
{ provider_id: "cases.source", module_id: "cases", resource_type: "case", label: "Cases", order: 3 },
];
const observed = [{ module_id: "mail", resource_type: "message" }];
const labels = new Map([["files", "Dateien"], ["cases", "Fälle"]]);
test("filter choices combine catalogue, observed values and removable stale selections", () => {
const all = searchFilterOptions(catalogue, observed, labels, ["files", "retired-source"], ["unknown-type"]);
assert.deepEqual(new Set(all.modules.map((item) => item.value)), new Set(["files", "cases", "mail", "retired-source"]));
assert.equal(all.modules.find((item) => item.value === "files").label, "Dateien");
assert.deepEqual(new Set(all.resourceTypes.map((item) => item.value)), new Set(["file", "folder", "unknown-type"]));
assert.deepEqual(searchFilterOptions(catalogue, observed, labels, [], null).resourceTypes, []);
const context = { id: "files.context", moduleId: "files", label: "Files", pathPrefixes: ["/files"], resourceTypes: ["file"] };
assert.deepEqual(searchFilterOptions(catalogue, observed, labels, null, null, context).resourceTypes, [{ value: "file", label: "Files" }]);
});
test("explicit none is a frontend-only empty response; legacy API empty arrays still request all", async () => {
const paths = [], calls = [];
const response = { query: "permit", results: [], diagnostics: [], next_cursor: null, has_more: false };
const { search } = loadTypeScript("../src/api/search.ts", { "@govoplan/core-webui": {
apiPath: (path, parameters) => { paths.push({ path, parameters }); return path; },
apiFetch: async (...args) => { calls.push(args); return response; },
} });
const settings = { apiBaseUrl: "", apiKey: "", accessToken: "" };
assert.deepEqual(await search(settings, { query: "permit", matchNone: true, modules: [] }), response);
assert.equal(calls.length, 0);
assert.equal(paths.length, 0);
await search(settings, { query: "permit", modules: [], resourceTypes: [] });
assert.equal(calls.length, 1);
assert.deepEqual(paths[0].parameters.module, []);
assert.deepEqual(paths[0].parameters.resource_type, []);
assert.equal("matchNone" in paths[0].parameters, false);
await search(settings, { query: "permit", modules: ["cases", "files"], resourceTypes: ["case", "file"] });
assert.deepEqual(paths[1].parameters.module, ["cases", "files"]);
assert.deepEqual(paths[1].parameters.resource_type, ["case", "file"]);
});