test_org_click_through_smoke.js
6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Org Graph 三节点条点击可达详情 (2026-08-06 SDD) smoke test. Same
// no-framework/no-fixture convention as frontend/test_graph_console_smoke.js
// -- loads app.js in a bare vm.Context and asserts on generated HTML
// strings / regex matches / plain data for the new top-level functions:
// - renderDashboardOrgStripHtml / renderTaskDetailOrgStripHtml: both
// org-strip render locations now carry data-org-node on every node
// (root cause from the BRD: renderOrgStrip() had zero data-* attrs).
// - ORG_NODE_DETAIL_RE: the new #/tasks/:task_id/org/:org_node route --
// legal three-way org_node enum, illegal node name, missing segment.
// - getLatestRunForOrgNode: "取最新 run" filter -- must return the
// started_at-latest (array-last) entry for a given org_node, not the
// first, and must not merge two runs' nodes together.
// - renderOrgComboPanelContent: empty-state (no run yet) and populated
// (with/without artifacts) cases, always via the existing simple-list
// markup pattern -- no separate artifact-content renderer invented here.
//
// Run: node frontend/test_org_click_through_smoke.js
"use strict";
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const assert = require("assert");
function makeFakeDiv() {
let text = "";
return {
set textContent(v) { text = v == null ? "" : String(v); },
get textContent() { return text; },
get innerHTML() {
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
},
};
}
const sandbox = {
document: {
createElement: () => makeFakeDiv(),
getElementById: () => null,
querySelectorAll: () => [],
addEventListener: () => {},
},
window: {
location: { protocol: "http:", host: "localhost", hash: "" },
addEventListener: () => {},
},
localStorage: { getItem: () => null, setItem: () => {} },
console,
};
vm.createContext(sandbox);
const src = fs.readFileSync(path.join(__dirname, "app.js"), "utf8");
// Top-level `const` regexes stay lexically scoped to the script and never
// reach the sandbox object (same Annex B quirk test_graph_console_smoke.js
// already documents for ARTIFACT_DETAIL_RE) -- re-export what this test needs.
const augmentedSrc = src + "\nglobalThis.ORG_NODE_DETAIL_RE = ORG_NODE_DETAIL_RE;\n";
new vm.Script(augmentedSrc, { filename: "app.js" }).runInContext(sandbox);
let assertionCount = 0;
function check(condition, message) {
assertionCount++;
assert.ok(condition, message);
}
// ---- 1. data-org-node on both org-strip render locations ----
{
const dashboardHtml = sandbox.renderDashboardOrgStripHtml("coder", { planner: "done", coder: "running" });
for (const node of ["planner", "coder", "validator"]) {
check(dashboardHtml.includes('data-org-node="' + node + '"'), "dashboard org-strip must carry data-org-node for " + node);
}
check(dashboardHtml.includes('class="org-node active" data-org-node="coder"'), "active node keeps its existing .active class alongside the new attribute");
const activeSet = new Set(["planner"]);
const taskDetailHtml = sandbox.renderTaskDetailOrgStripHtml(activeSet);
for (const node of ["planner", "coder", "validator"]) {
check(taskDetailHtml.includes('data-org-node="' + node + '"'), "v_task_detail inline org-strip must carry data-org-node for " + node);
}
}
// ---- 2. ORG_NODE_DETAIL_RE: happy / boundary / failure ----
{
const legal = "#/tasks/abc-123/org/coder".match(sandbox.ORG_NODE_DETAIL_RE);
check(legal, "legal task_id + coder must match");
check(legal[1] === "abc-123" && legal[2] === "coder", "captured groups must be task_id and org_node");
for (const node of ["planner", "coder", "validator"]) {
check(("#/tasks/t1/org/" + node).match(sandbox.ORG_NODE_DETAIL_RE), "all three org_node enum values must match: " + node);
}
check(!"#/tasks/t1/org/reviewer".match(sandbox.ORG_NODE_DETAIL_RE), "illegal org_node (not in the 3-way enum) must not match");
check(!"#/tasks/t1/org/".match(sandbox.ORG_NODE_DETAIL_RE), "missing org_node segment must not match");
check(!"#/tasks/t1".match(sandbox.ORG_NODE_DETAIL_RE), "plain #/tasks/:id (no /org/ suffix) must not match this regex");
}
// ---- 3. getLatestRunForOrgNode: latest-wins, no cross-run merging ----
{
const task = {
work_graph_runs: [
{ run_id: "run-old", org_node: "planner", status: "done", started_at: "2026-08-01 00:00:00" },
{ run_id: "run-new", org_node: "planner", status: "done", started_at: "2026-08-02 00:00:00" },
{ run_id: "run-coder", org_node: "coder", status: "running", started_at: "2026-08-02 01:00:00" },
],
};
const latestPlanner = sandbox.getLatestRunForOrgNode(task, "planner");
check(latestPlanner.run_id === "run-new", "must return the array-last (time-latest) run for a repeated org_node, not the first");
const latestCoder = sandbox.getLatestRunForOrgNode(task, "coder");
check(latestCoder.run_id === "run-coder", "single-run org_node resolves to that one run");
const noValidator = sandbox.getLatestRunForOrgNode(task, "validator");
check(noValidator === null, "org_node with zero runs must resolve to null, not throw or return undefined-shaped data");
}
// ---- 4. renderOrgComboPanelContent: empty state + artifact filtering ----
{
const emptyTask = { work_graph_runs: [], artifacts: [] };
const emptyHtml = sandbox.renderOrgComboPanelContent(emptyTask, "validator");
check(emptyHtml.includes("尚未开始"), "no-run org_node must render an explicit empty state, not blank/undefined");
const taskWithRun = {
work_graph_runs: [
{ run_id: "run-1", org_node: "coder", status: "done", graph_manifest: { nodes: [{ node_id: "n1", depends_on: [] }] } },
],
artifacts: [
{ artifact_id: "art-1", produced_by_run_id: "run-1", file_path: "src/x.py", artifact_type: "code_diff", validation_status: "passed" },
{ artifact_id: "art-2", produced_by_run_id: "run-other", file_path: "src/y.py", artifact_type: "code_diff", validation_status: "passed" },
],
};
const populatedHtml = sandbox.renderOrgComboPanelContent(taskWithRun, "coder");
check(populatedHtml.includes("run-1"), "combo content must name the resolved run_id");
check(populatedHtml.includes('data-artifact-id="art-1"'), "artifact produced by the resolved run must be listed");
check(!populatedHtml.includes('data-artifact-id="art-2"'), "artifact from a different run_id must be filtered out");
const taskNoArtifacts = {
work_graph_runs: [{ run_id: "run-2", org_node: "coder", status: "failed", graph_manifest: null }],
artifacts: [],
};
const noArtifactsHtml = sandbox.renderOrgComboPanelContent(taskNoArtifacts, "coder");
check(noArtifactsHtml.includes("暂无 Artifact"), "run with zero artifacts must show the existing empty-state text, not blank/error");
}
console.log(
"frontend/test_org_click_through_smoke.js: all " + assertionCount + " assertions passed"
);