62f6592ef6
The viewer threads the model's doc through normalizeModel into both composite view states — appended to INFO.B (collapsed box) and INFO.C (expanded cluster frame) — and shows the root composite's doc as a muted header line (#rootdoc span in GRAPH_HEAD, empty when absent; the DOM population sits in the browser-only block, the same boundary as the breadcrumb). viewer_tooltip.mjs pins the doc'd cases beside the byte-exact un-doc'd pins; sample-model.json gains the additive doc key; e2e drives aura graph over a nested-doc blueprint into the emitted page. closes #125
176 lines
6.6 KiB
JavaScript
176 lines
6.6 KiB
JavaScript
// Headless tooltip guard for the `aura graph` viewer (graph-viewer.js).
|
|
//
|
|
// Property protected: the INFO maps genDot populates (INFO.B for a node body,
|
|
// INFO.C for an expanded composite's cluster frame, INFO.P for a port) and the
|
|
// md() markdown-to-HTML step that turns an INFO entry into the #tip div's
|
|
// innerHTML all keep their CURRENT exact string shape. Before this guard the DOT
|
|
// half of the viewer (normalizeModel/genDot output) was covered by the sibling
|
|
// viewer_*.mjs suite, but the INFO population (addInfo, the composite-cluster
|
|
// caption) and md() were asserted by no test — a silent wording/markup change
|
|
// there would reach the browser tooltip undetected.
|
|
//
|
|
// It loads the real viewer module and drives the exported pure `genDot` (for
|
|
// INFO.B/INFO.C/INFO.P) and `md` (for the markdown step). `node` is REQUIRED: a
|
|
// skipped guard is not a guard.
|
|
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
|
|
// One composite ("sub", holding a primitive "double") + one root-level
|
|
// primitive ("gauge"), wired as: root.nodes = { "0": <sub composite ref>, "1":
|
|
// <gauge primitive> }. Small enough that every INFO entry below is traceable by
|
|
// hand; big enough to cover collapsed-composite, expanded-composite, and
|
|
// primitive INFO shapes in one fixture.
|
|
const model = {
|
|
root: {
|
|
nodes: {
|
|
"0": { comp: "sub" },
|
|
"1": { prim: {
|
|
name: "g", type: "Mul2", role: "node",
|
|
params: [["k", "f64"]],
|
|
bound: [],
|
|
ins: [["f64", "any", "a"]],
|
|
outs: [["y", "f64"]],
|
|
} },
|
|
},
|
|
edges: [],
|
|
},
|
|
composites: {
|
|
sub: {
|
|
inputs: [["f64", "any", "x"]],
|
|
outputs: [["y", "f64"]],
|
|
nodes: {
|
|
"0": { prim: {
|
|
name: "double", type: "Mul2", role: "node",
|
|
params: [], bound: [],
|
|
ins: [["f64", "any", "a"]],
|
|
outs: [["y", "f64"]],
|
|
} },
|
|
},
|
|
edges: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
// COMP (used by genDot to resolve a composite reference by name) is captured at
|
|
// require time from window.AURA_MODEL — mirrors the browser bootstrap and the
|
|
// sibling viewer_nested_depth.mjs guard.
|
|
global.window = { AURA_MODEL: model };
|
|
const { normalizeModel, genDot, md } = require(join(here, "..", "assets", "graph-viewer.js"));
|
|
const ROOT = normalizeModel(model).root;
|
|
|
|
const fail = (msg) => { console.error(msg); process.exit(1); };
|
|
const expect = (actual, expected, msg) => {
|
|
if (actual !== expected) {
|
|
fail(`${msg}\n--- expected ---\n${JSON.stringify(expected)}\n--- actual ---\n${JSON.stringify(actual)}`);
|
|
}
|
|
};
|
|
|
|
// --- Property 1: collapsed composite — INFO.B carries type/role + params line.
|
|
{
|
|
const { info } = genDot(ROOT, "root", new Set(), false);
|
|
expect(
|
|
info.B["n0"],
|
|
"**sub** <span class='dim'>· composite</span>\nparams: <span class='dim'>none</span>",
|
|
"collapsed composite INFO.B[n0] shape changed"
|
|
);
|
|
}
|
|
|
|
// --- Property 2: expanded composite — INFO.C carries the cluster caption.
|
|
{
|
|
const { info } = genDot(ROOT, "root", new Set(["n0"]), false);
|
|
expect(
|
|
info.C["clust_n0"],
|
|
"**sub** <span class='dim'>· composite · click frame to collapse</span>",
|
|
"expanded composite INFO.C[clust_n0] shape changed"
|
|
);
|
|
}
|
|
|
|
// --- Property 3: primitive node — INFO.B body line + INFO.P port entries.
|
|
{
|
|
const { info } = genDot(ROOT, "root", new Set(), false);
|
|
expect(
|
|
info.B["n1"],
|
|
"**Mul2** <span class='dim'>· node</span>\nparams: `k:f64`",
|
|
"primitive INFO.B[n1] shape changed"
|
|
);
|
|
expect(info.P["P_n1_i0"], "**a** · `f64`", "primitive input port INFO.P[P_n1_i0] shape changed");
|
|
expect(info.P["P_n1_o0"], "**y** · `f64`", "primitive output port INFO.P[P_n1_o0] shape changed");
|
|
}
|
|
|
|
// --- Property 4: md() — the markdown-to-HTML step the #tip div consumes.
|
|
{
|
|
expect(md("**x**\n`y`"), "<b>x</b><br><code>y</code>", "md() markdown conversion changed");
|
|
// End-to-end: md() applied to the real INFO.B[n1] string from Property 3 —
|
|
// bold the type, wrap the param signature in <code>, turn the newline into
|
|
// <br>, and leave the hand-authored <span class='dim'> markup untouched
|
|
// (md() only rewrites **, `` ` ``, and \n — it does not touch raw HTML).
|
|
const { info } = genDot(ROOT, "root", new Set(), false);
|
|
expect(
|
|
md(info.B["n1"]),
|
|
"<b>Mul2</b> <span class='dim'>· node</span><br>params: <code>k:f64</code>",
|
|
"md() applied to a real INFO.B entry changed shape"
|
|
);
|
|
}
|
|
|
|
// --- #125: doc'd composite + root -------------------------------------------
|
|
// A minimal model whose composite def and root both carry a "doc"; pins that
|
|
// the doc reaches INFO.B (collapsed), INFO.C (expanded), and root.doc.
|
|
//
|
|
// genDot resolves a nested composite reference via the module-scope COMP
|
|
// captured once at require time from window.AURA_MODEL (see the file-top
|
|
// comment above the `model`/`ROOT` constants and the sibling
|
|
// viewer_nested_depth.mjs guard's identical note) — reusing the
|
|
// already-required module's genDot would resolve "sub" back to THIS file's
|
|
// original doc-less "sub" composite of the same name, not docModel's. Fresh-
|
|
// require the module against docModel so its own COMP reflects the doc'd
|
|
// composite.
|
|
const docModel = {
|
|
root: {
|
|
nodes: { "0": { comp: "sub" } },
|
|
edges: [],
|
|
doc: "root rationale line",
|
|
},
|
|
composites: {
|
|
sub: {
|
|
inputs: [["f64", "any", "a"]],
|
|
outputs: [["y", "f64"]],
|
|
nodes: {},
|
|
edges: [],
|
|
doc: "why this sub-graph is wired this way",
|
|
},
|
|
},
|
|
};
|
|
const viewerPath = join(here, "..", "assets", "graph-viewer.js");
|
|
delete require.cache[require.resolve(viewerPath)];
|
|
global.window = { AURA_MODEL: docModel };
|
|
const { normalizeModel: normalizeModelDoc, genDot: genDotDoc } = require(viewerPath);
|
|
const docNorm = normalizeModelDoc(docModel);
|
|
expect(docNorm.root.doc, "root rationale line", "root doc carried");
|
|
expect(docNorm.composites.sub.doc, "why this sub-graph is wired this way", "composite doc carried");
|
|
|
|
{
|
|
const { info } = genDotDoc(docNorm.root, "root", new Set(), false);
|
|
expect(
|
|
info.B["n0"],
|
|
"**sub** <span class='dim'>· composite</span>\nparams: <span class='dim'>none</span>\nwhy this sub-graph is wired this way",
|
|
"collapsed tooltip carries the doc paragraph"
|
|
);
|
|
}
|
|
|
|
{
|
|
const { info } = genDotDoc(docNorm.root, "root", new Set(["n0"]), false);
|
|
expect(
|
|
info.C["clust_n0"],
|
|
"**sub** <span class='dim'>· composite · click frame to collapse</span>\nwhy this sub-graph is wired this way",
|
|
"expanded cluster tooltip carries the doc paragraph"
|
|
);
|
|
}
|
|
|
|
console.log("OK — INFO.B/INFO.C/INFO.P tooltip shapes and md() conversion pinned.");
|
|
process.exit(0);
|