Files
llm-wiki/tests/graph-html-density.regression-1.sh
T
2026-07-12 21:26:08 +08:00

229 lines
8.4 KiB
Bash
Executable File

#!/bin/bash
# Regression: engine graph HTML should keep density contracts for larger graphs
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
source "$REPO_ROOT/tests/lib/graph-html-engine-helpers.sh"
write_density_fixture() {
local output="$1"
local count="$2"
node - <<'NODE' "$output" "$count"
const fs = require("fs");
const output = process.argv[2];
const count = Number(process.argv[3]);
const nodes = Array.from({ length: count }, (_, index) => {
const type = index % 8 === 0 ? "source" : index % 3 === 0 ? "topic" : "entity";
const community = String(index % 6);
return {
id: `node-${index}`,
label: `Density Node ${index}`,
type,
community,
confidence: "EXTRACTED",
content: `# Density Node ${index}\n\n这是第 ${index} 个密度测试节点,用来验证大量节点时不会全部铺成大卡片。\n\n关联到 [[node-${Math.max(0, index - 1)}|前一个节点]]。`
};
});
const edges = [];
for (let index = 1; index < count; index++) {
edges.push({
id: `edge-${index}`,
from: `node-${index - 1}`,
to: `node-${index}`,
type: index % 5 === 0 ? "INFERRED" : "EXTRACTED",
weight: index % 5 === 0 ? 0.6 : 0.9
});
}
const communities = Array.from({ length: 6 }, (_, index) => ({
id: String(index),
label: `社区 ${index}`,
node_count: nodes.filter((node) => node.community === String(index)).length,
source_count: 1,
is_primary: index === 0,
recommended_start_node_id: `node-${index}`
}));
const graph = {
meta: {
wiki_title: "密度测试知识库",
build_date: "2026-04-27",
total_nodes: nodes.length,
total_edges: edges.length
},
nodes,
edges,
learning: {
entry: { recommended_start_node_id: "node-0" },
views: {
path: { enabled: true, degraded: false, node_ids: ["node-0", "node-1", "node-2"] },
community: { enabled: true, degraded: false, node_ids: nodes.filter((node) => node.community === "0").map((node) => node.id) },
global: { enabled: true, degraded: false, node_ids: nodes.map((node) => node.id) }
},
communities
},
insights: {
surprising_connections: [],
isolated_nodes: [],
bridge_nodes: [],
sparse_communities: [],
meta: { degraded: false }
}
};
fs.writeFileSync(output, JSON.stringify(graph, null, 2));
NODE
}
test_graph_engine_exports_density_rules() {
local tmp_dir html
tmp_dir="$(mktemp -d)"
build_graph_html_fixture "$tmp_dir"
html="$tmp_dir/wiki/knowledge-graph.html"
assert_file_contains "$html" "e.getAtlasDensityMode="
assert_file_contains "$html" "screenEffectiveDensityMode"
assert_file_contains "$html" "nodeDisplayModeForDensity"
assert_file_contains "$html" "e.resolveAtlasVisibleSnapshot="
assert_file_contains "$html" "e.edgeStrokeWidth="
assert_file_contains "$html" "e.cardDims="
assert_file_contains "$html" ".node.is-overview"
assert_file_contains "$html" ".node[data-visual-role=\"map-pin\"]"
assert_file_contains "$html" "dataset.densityMode"
assert_file_contains "$html" "dataset.effectiveDensity"
rm -rf "$tmp_dir"
}
test_graph_html_builds_large_density_fixture_as_single_file() {
local tmp_dir output_dir html
tmp_dir="$(mktemp -d)"
output_dir="$tmp_dir/wiki"
mkdir -p "$output_dir"
write_density_fixture "$output_dir/graph-data.json" 200
ensure_graph_engine_dist
bash "$REPO_ROOT/scripts/build-graph-html.sh" "$tmp_dir" > /dev/null 2>&1 \
|| fail "build-graph-html.sh should succeed on 200-node density fixture"
html="$output_dir/knowledge-graph.html"
assert_single_file_engine_output "$output_dir"
assert_file_contains "$html" "密度测试知识库"
assert_file_contains "$html" "Density Node 199"
assert_file_contains "$html" "200 节点"
assert_file_contains "$html" "199 关联"
rm -rf "$tmp_dir"
}
test_graph_html_density_preview_for_compact_and_point_nodes() {
local tmp_dir output_dir html playwright_node_path
tmp_dir="$(mktemp -d)"
output_dir="$tmp_dir/wiki"
mkdir -p "$output_dir"
write_density_fixture "$output_dir/graph-data.json" 240
ensure_graph_engine_dist
bash "$REPO_ROOT/scripts/build-graph-html.sh" "$tmp_dir" > /dev/null 2>&1 \
|| fail "build-graph-html.sh should succeed on density preview fixture"
html="$output_dir/knowledge-graph.html"
playwright_node_path="$(
npx --yes -p playwright -c 'node -e "const path=require(\"path\"); console.log(path.dirname(process.env.PATH.split(\":\")[0]))"'
)"
GRAPH_DENSITY_PREVIEW_HTML="$html" NODE_PATH="$playwright_node_path" node "$REPO_ROOT/tests/browser/graph-density-preview.mjs" \
|| fail "density preview browser regression should pass"
rm -rf "$tmp_dir"
}
test_graph_density_thresholds_and_budgets() {
ensure_graph_engine_dist
node --input-type=module - <<'NODE' "$REPO_ROOT" || fail "density thresholds and budgets should hold"
import assert from "node:assert/strict";
import { pathToFileURL } from "node:url";
import path from "node:path";
const repoRoot = process.argv[2];
const engine = await import(pathToFileURL(path.join(repoRoot, "packages/graph-engine/dist/engine.esm.js")).href);
const {
buildAtlasModel,
deriveAtlasLayout,
resolveAtlasVisibleSnapshot,
nodeDisplayModeForDensity,
screenEffectiveDensityMode
} = engine;
function makeGraph(count, edgeCount) {
const nodes = Array.from({ length: count }, (_, index) => ({
id: `node-${index}`,
label: `Density Node ${index}`,
type: index % 8 === 0 ? "source" : index % 3 === 0 ? "topic" : "entity",
community: String(index % 6),
confidence: "EXTRACTED",
content: `# Density Node ${index}\n\n节点 ${index}。`
}));
const edges = Array.from({ length: edgeCount }, (_, index) => ({
id: `edge-${index}`,
from: `node-${index % count}`,
to: `node-${(index * 7 + 1) % count}`,
type: index % 5 === 0 ? "INFERRED" : "EXTRACTED",
weight: index % 9 === 0 ? 0.4 : 0.9
})).filter((edge) => edge.from !== edge.to);
return {
meta: { wiki_title: "密度预算测试" },
nodes,
edges,
learning: { entry: { recommended_start_node_id: "node-0" } }
};
}
function snapshotFor(count, edgeCount, selectedNodeId) {
const model = buildAtlasModel(makeGraph(count, edgeCount));
const layout = deriveAtlasLayout(model);
return resolveAtlasVisibleSnapshot(model, layout, {
activeCommunityId: "all",
focusMode: "all",
query: "",
selectedNodeId,
filters: { EXTRACTED: true, INFERRED: true, AMBIGUOUS: true, UNVERIFIED: true }
});
}
const pointSnapshot = snapshotFor(201, 900, "node-200");
assert.equal(pointSnapshot.densityMode, "point-plus-focus");
assert.ok(Object.keys(pointSnapshot.labelNodeIds).length <= 61);
assert.ok(pointSnapshot.labelNodeIds["node-200"]);
assert.ok(pointSnapshot.importantNodeIds["node-0"]);
assert.ok(pointSnapshot.edges.length <= 800);
const overviewSnapshot = snapshotFor(501, 1500, "node-500");
assert.equal(overviewSnapshot.densityMode, "overview");
assert.ok(Object.keys(overviewSnapshot.labelNodeIds).length <= 41);
assert.ok(overviewSnapshot.labelNodeIds["node-500"]);
assert.ok(overviewSnapshot.importantNodeIds["node-0"]);
assert.ok(overviewSnapshot.edges.length <= 1000);
assert.equal(screenEffectiveDensityMode(120, 1), "compact-card");
assert.equal(screenEffectiveDensityMode(120, 2), "card");
assert.equal(screenEffectiveDensityMode(120, 0.5), "point-plus-focus");
assert.equal(nodeDisplayModeForDensity({ selected: false, labelVisible: false, visualRole: "map-pin" }, "card"), "card");
assert.equal(nodeDisplayModeForDensity({ selected: false, labelVisible: false, visualRole: "map-pin" }, "compact-card"), "compact-card");
assert.equal(nodeDisplayModeForDensity({ selected: false, labelVisible: false, visualRole: "map-pin" }, "point-plus-focus"), "point");
assert.equal(nodeDisplayModeForDensity({ selected: true, labelVisible: false, visualRole: "map-pin" }, "overview"), "card");
NODE
}
main() {
npm run build -w @llm-wiki/graph-engine > /dev/null 2>&1 \
|| fail "graph-engine build should succeed before density regression"
test_graph_engine_exports_density_rules
test_graph_html_builds_large_density_fixture_as_single_file
test_graph_html_density_preview_for_compact_and_point_nodes
test_graph_density_thresholds_and_budgets
[ -f "$REPO_ROOT/tests/fixtures/graph-interactive-dense/wiki/graph-data.json" ] || fail "dense fixture should exist"
echo "PASS: graph HTML density regression coverage"
}
main "$@"