first
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createRequire } from "node:module";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
const html = process.env.GRAPH_COMMUNITY_WASH_HTML || "";
|
||||
assert.notEqual(html, "", "GRAPH_COMMUNITY_WASH_HTML must point at generated HTML");
|
||||
|
||||
const executablePath = process.env.GRAPH_COMMUNITY_WASH_CHROME_EXECUTABLE || "";
|
||||
const browser = await chromium.launch(executablePath ? { executablePath } : {});
|
||||
|
||||
try {
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } });
|
||||
await page.goto(pathToFileURL(html).href);
|
||||
await waitForSigmaGlobal(page);
|
||||
|
||||
const initial = await sigmaSnapshot(page);
|
||||
assert.equal(initial.route, "sigma-global", "offline global route should use Sigma");
|
||||
assert.equal(initial.oldDomNodeCount, 0, "Sigma global should not render the old DOM full graph");
|
||||
assert.ok(initial.communityRegionCount >= 1, "Sigma global should expose community regions");
|
||||
assert.ok(initial.communityLabelCount >= 1 && initial.communityLabelCount <= 8, "Sigma global should expose passive map labels");
|
||||
assert.equal(initial.circularCommunityButtonCount, 0, "Sigma global should not expose circular community buttons");
|
||||
assert.equal(initial.aggregationButtonCount, 0, "Sigma global should not expose aggregation buttons");
|
||||
|
||||
const communityId = await firstCommunityRegionId(page);
|
||||
const regionPoint = await findCommunityRegionPoint(page, communityId);
|
||||
await page.mouse.click(regionPoint.x, regionPoint.y);
|
||||
await waitForCommunitySelection(page, communityId);
|
||||
const selected = await sigmaSnapshot(page);
|
||||
assert.equal(selected.focus, "", "clicking a community region should not enter a DOM community route");
|
||||
assert.equal(selected.selectedCommunityId, communityId, "clicking a community region should select that community");
|
||||
assert.equal(selected.oldDomNodeCount, 0, "community selection should keep the Sigma global route mounted");
|
||||
|
||||
await setSearchQuery(page, "节点A");
|
||||
await page.waitForSelector('.sigma-global-node-hit-target[data-node-id="A"]');
|
||||
await page.locator('.sigma-global-node-hit-target[data-node-id="A"]').click({ force: true });
|
||||
await page.waitForFunction(() => (
|
||||
document.querySelector('.sigma-global-node-hit-target[data-node-id="A"]')?.getAttribute("data-selected") === "true"
|
||||
));
|
||||
const nodeSelected = await sigmaSnapshot(page);
|
||||
assert.equal(nodeSelected.selectedNodeId, "A", "node hit target should win over the community region beneath it");
|
||||
assert.equal(nodeSelected.route, "sigma-global", "node hit target should keep the Sigma global route active");
|
||||
|
||||
const labelPoint = await communityLabelCenter(page, communityId);
|
||||
await page.mouse.click(labelPoint.x, labelPoint.y);
|
||||
await waitForCommunitySelection(page, communityId);
|
||||
const labelClick = await sigmaSnapshot(page);
|
||||
assert.equal(labelClick.selectedCommunityId, communityId, "passive label area should fall through to the community region");
|
||||
assert.equal(labelClick.labelPointerEvents, "none", "community labels should stay passive");
|
||||
|
||||
console.log(JSON.stringify({
|
||||
communityId,
|
||||
initial,
|
||||
regionPoint,
|
||||
selected,
|
||||
nodeSelected,
|
||||
labelClick
|
||||
}, null, 2));
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
async function waitForSigmaGlobal(page) {
|
||||
await page.waitForSelector(".sigma-global-route[data-route='sigma-global']");
|
||||
await page.waitForSelector(".sigma-global-renderer[data-renderer='sigma-global']");
|
||||
await page.waitForSelector(".sigma-global-community-region[data-community-id]");
|
||||
}
|
||||
|
||||
async function sigmaSnapshot(page) {
|
||||
return page.evaluate(() => {
|
||||
const selectedRegion = document.querySelector('.sigma-global-community-region[data-selected="true"]');
|
||||
const selectedNode = document.querySelector('.sigma-global-node-hit-target[data-selected="true"]');
|
||||
const firstLabel = document.querySelector(".sigma-global-community-label");
|
||||
return {
|
||||
route: document.querySelector(".sigma-global-route")?.getAttribute("data-route") || "",
|
||||
renderer: document.querySelector(".sigma-global-renderer")?.getAttribute("data-renderer") || "",
|
||||
focus: document.querySelector("[data-testid='offline-graph-root']")?.getAttribute("data-llm-wiki-graph-focus")
|
||||
|| document.querySelector("[data-llm-wiki-graph-root='true']")?.getAttribute("data-focus")
|
||||
|| "",
|
||||
oldDomNodeCount: document.querySelectorAll(".node").length,
|
||||
oldCommunityWashCount: document.querySelectorAll(".community-wash").length,
|
||||
communityRegionCount: document.querySelectorAll(".sigma-global-community-region").length,
|
||||
communityLabelCount: document.querySelectorAll(".sigma-global-community-label").length,
|
||||
circularCommunityButtonCount: document.querySelectorAll(".sigma-global-community-wash, button.sigma-global-community-label, [role='button'].sigma-global-community-label").length,
|
||||
aggregationButtonCount: document.querySelectorAll(".sigma-global-aggregation-container").length,
|
||||
selectedCommunityId: selectedRegion?.getAttribute("data-community-id") || "",
|
||||
selectedNodeId: selectedNode?.getAttribute("data-node-id") || "",
|
||||
labelPointerEvents: firstLabel ? window.getComputedStyle(firstLabel).pointerEvents : ""
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function firstCommunityRegionId(page) {
|
||||
return page.evaluate(() => {
|
||||
const region = document.querySelector(".sigma-global-community-region[data-community-id]:not([data-community-id='_none'])")
|
||||
|| document.querySelector(".sigma-global-community-region[data-community-id]");
|
||||
const id = region?.getAttribute("data-community-id") || "";
|
||||
if (!id) throw new Error("Missing Sigma community region id");
|
||||
return id;
|
||||
});
|
||||
}
|
||||
|
||||
async function findCommunityRegionPoint(page, communityId) {
|
||||
return page.evaluate((communityId) => {
|
||||
const region = document.querySelector(`.sigma-global-community-region[data-community-id="${CSS.escape(communityId)}"]`);
|
||||
if (!region) throw new Error(`Missing Sigma community region ${communityId}`);
|
||||
const rect = region.getBoundingClientRect();
|
||||
const candidates = [
|
||||
[0.5, 0.18],
|
||||
[0.2, 0.5],
|
||||
[0.8, 0.5],
|
||||
[0.5, 0.82],
|
||||
[0.32, 0.32],
|
||||
[0.68, 0.68],
|
||||
[0.5, 0.5]
|
||||
];
|
||||
for (const [rx, ry] of candidates) {
|
||||
const x = rect.left + rect.width * rx;
|
||||
const y = rect.top + rect.height * ry;
|
||||
const hit = document.elementFromPoint(x, y);
|
||||
if (hit?.closest?.(".sigma-global-node-hit-target")) continue;
|
||||
if (hit?.closest?.(".sigma-global-community-region") === region) {
|
||||
return { x, y, communityId };
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find exposed Sigma community region point: ${JSON.stringify({
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
})}`);
|
||||
}, communityId);
|
||||
}
|
||||
|
||||
async function communityLabelCenter(page, communityId) {
|
||||
return page.evaluate((communityId) => {
|
||||
const label = document.querySelector(`.sigma-global-community-label[data-community-id="${CSS.escape(communityId)}"]`);
|
||||
if (!label) throw new Error(`Missing Sigma community label ${communityId}`);
|
||||
const rect = label.getBoundingClientRect();
|
||||
return {
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2
|
||||
};
|
||||
}, communityId);
|
||||
}
|
||||
|
||||
async function waitForCommunitySelection(page, communityId) {
|
||||
await page.waitForFunction((communityId) => (
|
||||
document.querySelector(`.sigma-global-community-region[data-community-id="${CSS.escape(communityId)}"]`)?.getAttribute("data-selected") === "true"
|
||||
), communityId);
|
||||
}
|
||||
|
||||
async function setSearchQuery(page, query) {
|
||||
await page.locator(".graph-search-input").focus();
|
||||
await page.evaluate((query) => {
|
||||
const input = document.querySelector(".graph-search[data-state='open'] .graph-search-input");
|
||||
if (!(input instanceof HTMLInputElement)) throw new Error("Graph search input is not open");
|
||||
input.value = query;
|
||||
input.dispatchEvent(new InputEvent("input", {
|
||||
bubbles: true,
|
||||
inputType: "insertText",
|
||||
data: query
|
||||
}));
|
||||
}, query);
|
||||
await page.waitForFunction((query) => (
|
||||
document.querySelector(".graph-search-input")?.value === query
|
||||
), query);
|
||||
}
|
||||
Reference in New Issue
Block a user