Files
plainleaf/plugs/object-graph/ui/components/visibility.test.ts
T

137 lines
3.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { Edge, Filters, ObjectNode } from "../../src/model.ts";
import { computeVisible, type NodeState } from "./visibility.ts";
const node = (
ref: string,
tags: string[] = [],
rootTag: string | null = "page",
): ObjectNode => ({
kind: "page",
ref,
title: ref,
rootTag,
primaryTag: tags[0] ?? null,
tags,
dangling: false,
attributes: {},
});
const ns = (ref: string, tags: string[] = []): NodeState => ({
node: node(ref, tags),
status: "expanded",
});
const edge = (source: string, target: string, label: string): Edge => ({
source,
target,
label,
kind: "mention",
refs: [],
undirected: false,
});
const filters = (overrides: Partial<Filters> = {}): Filters => ({
hiddenTags: [],
hiddenLabels: [],
hideEdgeLabels: false,
hideOrphans: true,
...overrides,
});
describe("computeVisible", () => {
it("keeps the root visible even with no edges", () => {
const r = computeVisible(
[ns("Root"), ns("B"), ns("C")],
[],
filters(),
"Root",
);
expect(r.visibleNodes.map((n) => n.node.ref)).toEqual(["Root"]);
expect(r.visibleEdges).toEqual([]);
});
it("hides orphans (non-root with no labeled edge)", () => {
const r = computeVisible(
[ns("Root"), ns("B"), ns("Orphan")],
[edge("Root", "B", "mention")],
filters(),
"Root",
);
const refs = r.visibleNodes.map((n) => n.node.ref).sort();
expect(refs).toEqual(["B", "Root"]);
});
it("hideOrphans=false keeps everyone visible regardless of edges", () => {
const r = computeVisible(
[ns("Root"), ns("B"), ns("Orphan")],
[edge("Root", "B", "mention")],
filters({ hideOrphans: false }),
"Root",
);
expect(r.visibleNodes).toHaveLength(3);
});
it("drops edges with hidden labels and re-evaluates orphan status", () => {
const r = computeVisible(
[ns("Root"), ns("X")],
[edge("Root", "X", "co-mention")],
filters({ hiddenLabels: ["co-mention"] }),
"Root",
);
// X is now an orphan because its only edge was filtered out.
expect(r.visibleNodes.map((n) => n.node.ref)).toEqual(["Root"]);
expect(r.visibleEdges).toEqual([]);
});
it("applies the tag filter uniformly (root excepted)", () => {
const r = computeVisible(
[ns("Root", []), ns("Driver", ["driver"]), ns("Engineer", ["engineer"])],
[edge("Root", "Driver", "mention"), edge("Root", "Engineer", "mention")],
filters({ hiddenTags: ["engineer"] }),
"Root",
);
const refs = r.visibleNodes.map((n) => n.node.ref).sort();
expect(refs).toEqual(["Driver", "Root"]);
expect(r.visibleEdges).toHaveLength(1);
expect(r.visibleEdges[0].target).toBe("Driver");
});
it("treats untagged nodes as a separate hideable bucket", () => {
const r = computeVisible(
[ns("Root"), ns("Untagged", [])],
[edge("Root", "Untagged", "mention")],
filters({ hiddenTags: ["(untagged)"] }),
"Root",
);
expect(r.visibleNodes.map((n) => n.node.ref)).toEqual(["Root"]);
});
it("drops edges that touch a tag-filtered endpoint", () => {
const r = computeVisible(
[ns("Root"), ns("Visible", ["driver"]), ns("Hidden", ["engineer"])],
[
edge("Visible", "Hidden", "mention"),
edge("Root", "Visible", "mention"),
],
filters({ hiddenTags: ["engineer"] }),
"Root",
);
expect(r.visibleEdges).toHaveLength(1);
expect(r.visibleEdges[0].source).toBe("Root");
});
it("keeps an edge if any tag on a node passes (multi-tag union)", () => {
const r = computeVisible(
[ns("Root"), ns("Both", ["driver", "person"])],
[edge("Root", "Both", "mention")],
filters({ hiddenTags: ["driver"] }),
"Root",
);
expect(r.visibleNodes.map((n) => n.node.ref).sort()).toEqual([
"Both",
"Root",
]);
});
});