content-explorer/ui/components/both-dataspaces.test.tsx
tegwick 55f6180b8e
All checks were successful
synthetic / suite (push) Successful in 1m2s
Add a Wikidata export connector for a pinned CC0 computing field.
The connector maps native Q-ids to aliases under urn:ce ids, publishes only through the existing rights and reference gate, and leaves the real-user pilot open.

Assistant: grok
Assistant-Session: 01a15378-f601-7fe2-a143-8e1500a1d59c
2026-10-19 11:38:04 +02:00

128 lines
5 KiB
TypeScript

// @vitest-environment happy-dom
import { createRoot, type Root } from "react-dom/client";
import { act } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { afterEach, describe, expect, it } from "vitest";
import { EntityDetail } from "@/components/EntityDetail";
import { RecordBrowser } from "@/components/RecordBrowser";
import { findDataspace, findEntity, findKind, listNavigation } from "@/lib/catalog";
import { applyTableView } from "@/lib/table-view";
import type { ViewHistory } from "@/lib/table-view";
declare global {
var IS_REACT_ACT_ENVIRONMENT: boolean;
}
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
const SPACES = ["systems-thinking", "software-architecture"] as const;
let root: Root | null = null;
afterEach(() => {
act(() => root?.unmount());
root = null;
document.body.innerHTML = "";
});
function memoryHistory(): ViewHistory {
let current = "";
return {
read: () => (current ? `?${current}` : ""),
push: (search) => {
current = search;
},
subscribe: () => () => undefined,
};
}
function names(): string[] {
return [...document.querySelectorAll("tbody tr td:first-child")].map((cell) => cell.textContent ?? "");
}
function buttonNamed(label: string): HTMLButtonElement {
const node = [...document.querySelectorAll("button")].find((item) => item.textContent === label);
if (!(node instanceof HTMLButtonElement)) throw new Error(`missing ${label}`);
return node;
}
describe("both dataspaces", () => {
it("browses, searches, filters, sorts, and opens a detail for each field", async () => {
const ids = listNavigation().map((item) => item.id);
for (const id of SPACES) expect(ids).toContain(id);
for (const id of SPACES) {
const space = findDataspace(id);
const kind = findKind(id, "concept");
expect(space?.title.length).toBeGreaterThan(0);
expect(kind?.rows.length).toBeGreaterThan(1);
expect(kind?.rows.every((row) => row.route.startsWith(`/${id}/concept/`))).toBe(true);
const sample = kind!.rows[0];
const tag = sample.facets.tags[0];
const searched = applyTableView(kind!.rows, { q: sample.name, facets: {}, sort: { key: "name", dir: 1 } });
expect(searched.map((row) => row.name)).toEqual([sample.name]);
const filtered = applyTableView(kind!.rows, { q: "", facets: { tags: [tag] }, sort: { key: "name", dir: 1 } });
expect(filtered.length).toBeGreaterThan(0);
expect(filtered.length).toBeLessThan(kind!.rows.length);
expect(filtered.every((row) => row.facets.tags.includes(tag))).toBe(true);
const descending = applyTableView(kind!.rows, { q: "", facets: {}, sort: { key: "name", dir: -1 } });
expect(descending.map((row) => row.name)).toEqual(
[...kind!.rows.map((row) => row.name)].sort((a, b) => b.localeCompare(a)),
);
const host = document.createElement("div");
document.body.innerHTML = "";
document.body.appendChild(host);
root = createRoot(host);
await act(async () => {
root?.render(<RecordBrowser rows={kind!.rows} columns={kind!.columns} history={memoryHistory()} />);
});
expect(names().length).toBe(kind!.rows.length);
const input = document.querySelector("input[aria-label='Search records']");
if (!(input instanceof HTMLInputElement)) throw new Error("missing search");
await act(async () => {
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
setValue?.call(input, sample.name);
input.dispatchEvent(new Event("input", { bubbles: true }));
input.form?.requestSubmit();
});
expect(names()).toEqual([sample.name]);
await act(async () => {
const setValue = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
setValue?.call(input, "");
input.dispatchEvent(new Event("input", { bubbles: true }));
input.form?.requestSubmit();
});
expect(names().length).toBe(kind!.rows.length);
await act(async () => {
buttonNamed(tag).click();
});
expect(names().length).toBe(filtered.length);
expect(document.body.textContent).toContain(tag);
await act(async () => {
buttonNamed(tag).click();
});
expect(names().length).toBe(kind!.rows.length);
const sortName = [...document.querySelectorAll("button")].find((node) => node.textContent?.includes("Name"));
if (!(sortName instanceof HTMLButtonElement)) throw new Error("missing sort");
await act(async () => {
sortName.click();
});
expect(names()).toEqual(descending.map((row) => row.name));
const slug = sample.route.split("/").filter(Boolean).at(-1) ?? "";
const detail = findEntity(id, "concept", decodeURIComponent(slug));
expect(detail?.title).toBe(sample.name);
const html = renderToStaticMarkup(<EntityDetail entity={detail!} />);
expect(html).toContain(`>${detail!.title}<`);
expect(html).toContain(detail!.tldr);
expect(html).toContain("Sources");
act(() => root?.unmount());
root = null;
}
});
});