Files
semantica/explorer/tests/graphColorLegend.e2e.ts
T
Wei TaoandClaude Code 9d93a80840 fix(explorer): label the graph view control "Focus" (#1552)
* fix(explorer): label the graph view control "Focus"

The control read "Focused" before it was ever activated, which describes a
state the selection had already reached rather than the action available.
The view mode value, tooltip, enablement and active styling are unchanged.

The legend e2e drove this button by its accessible name, so the selector
moves with the label; it now also asserts the visible text, the
selection-dependent enablement and the active state.

Closes #1551

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(explorer): name the control in the grouped-selection hint, and trim the test

"Activate Focused mode" instructed the reader to press a control that no
longer carries that name. The surrounding strings describe the mode itself,
which is still called focused, so they stay.

Drop the label and active-state assertions from the colour-legend test: the
getByRole locator already fails when the accessible name is wrong, and the
rest belonged to the control's contract rather than to the legend's.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-10 09:42:17 +05:30

115 lines
6.0 KiB
TypeScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { existsSync } from "node:fs";
import { setTimeout as delay } from "node:timers/promises";
import test from "node:test";
import { chromium, type Page } from "playwright";
const BASE_URL = "http://127.0.0.1:4175";
const initialNodes = [
{ id: "alice", type: "Person", content: "Alice", properties: {} },
{ id: "bob", type: "Person", content: "Bob", properties: {} },
{ id: "acme", type: "Organization", content: "Acme", properties: {} },
{ id: "london", type: "Location", content: "London", properties: {} },
{ id: "research", type: "Project", content: "Research", properties: {} },
{ id: "report", type: "Document", content: "Report", properties: {} },
];
const edges = [
["alice", "acme", "WORKS_AT"], ["bob", "acme", "WORKS_AT"],
["acme", "london", "LOCATED_IN"], ["alice", "research", "LEADS"],
["bob", "report", "AUTHORED"], ["report", "research", "DESCRIBES"],
].map(([source, target, type], i) => ({
id: `edge_${i}`, familyId: `edge_${i}`, source, target, type, weight: 1, properties: {},
}));
async function assertLegendMatchesGraph(page: Page, nodeIds?: string[]) {
const result = await page.evaluate(async (includedNodeIds) => {
const storePath = "/src/store/graphStore.ts";
const { graph } = await import(storePath);
const colors: Record<string, string> = {};
graph.forEachNode((id: string, attrs: { semanticGroup: string; baseColor: string }) => {
if (includedNodeIds && !includedNodeIds.includes(id)) return;
const hex = attrs.baseColor.replace("#", "");
colors[attrs.semanticGroup] = `rgb(${[0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16)).join(", ")})`;
});
const items = [...document.querySelectorAll(".explore-color-legend-item")].map((item) => ({
group: item.querySelector(".explore-color-legend-name")?.textContent,
color: getComputedStyle(item.querySelector(".explore-color-legend-mark")!).backgroundColor,
}));
return { colors, items };
}, nodeIds);
assert.equal(result.items.length, Object.keys(result.colors).length);
for (const item of result.items) {
assert.equal(item.color, result.colors[item.group!], `Swatch for ${item.group} must match the loaded canvas color`);
}
}
test("visible legend follows loaded data, reloads, focused views, and distance mode", async (t) => {
const server = spawn("npm", ["run", "dev", "--", "--host", "127.0.0.1", "--port", "4175", "--strictPort"], { stdio: "ignore" });
t.after(() => { server.kill(); });
let ready = false;
for (let i = 0; i < 100; i += 1) {
try { if ((await fetch(BASE_URL)).ok) { ready = true; break; } } catch { /* Starting Vite. */ }
await delay(100);
}
assert.ok(ready, "Vite must start");
const browser = await chromium.launch({
headless: true,
executablePath:
process.env.CHROMIUM_PATH || (existsSync("/usr/bin/chromium") ? "/usr/bin/chromium" : undefined),
});
t.after(() => browser.close());
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
page.setDefaultTimeout(10_000);
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
page.on("console", (message) => { if (message.type() === "error") errors.push(message.text()); });
let nodes = initialNodes;
await page.routeWebSocket("**/ws/graph-updates", () => {});
await page.route("**/api/**", async (route) => {
const path = new URL(route.request().url()).pathname;
let json: unknown = {};
if (path === "/api/info") json = { capabilities: { agent_memory: false } };
if (path === "/api/graph/stats") json = { node_count: nodes.length, edge_count: edges.length };
if (path === "/api/graph/nodes") json = { nodes, total: nodes.length, next_cursor: null };
if (path === "/api/graph/edges") json = { edges, total: edges.length, next_cursor: null };
if (path === "/api/temporal/bounds") json = { min: null, max: null };
if (path === "/api/temporal/snapshot") json = { active_node_ids: nodes.map((n) => n.id), active_node_count: nodes.length };
if (path === "/api/graph/search") json = { results: [{ node: nodes[0], score: 1 }] };
await route.fulfill({ json });
});
await page.goto(BASE_URL);
await page.getByRole("button", { name: "Open Semantica Explorer" }).click();
const legend = page.getByRole("group", { name: "Node colors" });
await legend.waitFor();
await page.locator("canvas").first().waitFor({ state: "visible" });
await assertLegendMatchesGraph(page);
assert.equal(await legend.getByText("Person", { exact: true }).count(), 1);
assert.equal(await legend.getByText("Biomolecule", { exact: true }).count(), 0);
nodes = initialNodes.map((node) => ({ ...node, type: node.type === "Person" ? "Researcher" : node.type }));
await page.getByRole("button", { name: "Reload graph data" }).click();
await legend.getByText("Researcher", { exact: true }).waitFor();
assert.equal(await legend.getByText("Person", { exact: true }).count(), 0);
await assertLegendMatchesGraph(page);
await page.getByPlaceholder("Search command, node, or concept").fill("Alice");
await page.getByRole("option").filter({ hasText: "Alice" }).click();
const heatmap = page.getByRole("button", { name: "Heatmap", exact: true });
await heatmap.click();
await legend.waitFor({ state: "hidden" });
await heatmap.click();
await legend.waitFor();
await assertLegendMatchesGraph(page);
const focusButton = page.getByRole("button", { name: "Focus", exact: true });
assert.equal(await focusButton.isDisabled(), false, "Focus is enabled once a node is selected");
await focusButton.click();
await legend.getByText("Document", { exact: true }).waitFor({ state: "hidden" });
await assertLegendMatchesGraph(page, ["alice", "acme", "research"]);
assert.equal(await legend.getByText("Researcher", { exact: true }).count(), 1);
await page.getByRole("button", { name: "Full Graph", exact: true }).click();
await legend.getByText("Document", { exact: true }).waitFor();
await assertLegendMatchesGraph(page);
assert.deepEqual(errors, []);
});