// 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, """); }, }; } 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_nodes_involved: ["planner"], status: "done", started_at: "2026-08-01 00:00:00" }, { run_id: "run-new", org_nodes_involved: ["planner"], status: "done", started_at: "2026-08-02 00:00:00" }, { run_id: "run-coder", org_nodes_involved: ["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_nodes_involved: ["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_nodes_involved: ["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" );