Files
fusion/packages/dashboard/app/components/nodes/__tests__/node-help.test.ts
gsxdsm be7c23693c feat(FN-6880): author optional-group container in the node editor (U4)
Render and author optional-group as a third group-container kind beside
foreach/loop: register the node type (OptionalGroupNode) so it renders with a
header + defaultOn badge and parentId template children, treat it as a group
everywhere in workflow-flow-mapping (irToFlow children, flowToIr template
reassembly, cascade-delete, condition-editable), add a palette entry, and an
inspector defaultOn toggle. Includes a node-help entry and round-trip / toggle /
cascade-delete tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:29:58 -07:00

109 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { effectiveNodeKind, nodeHelpFor, nodeHelpForData } from "../node-help";
import type { WorkflowFlowNodeData } from "../WorkflowNodeTypes";
/** All editor kinds plus the graph-only IR kinds the help registry must cover.
* Kept inline (not imported from core) so a missing entry fails loudly here. */
const EDITOR_KINDS = [
"start",
"end",
"prompt",
"script",
"gate",
"merge",
"hold",
"split",
"join",
"foreach",
"loop",
"optional-group",
"step-review",
"parse-steps",
"code",
"notify",
] as const;
const GRAPH_ONLY_KINDS = [
"merge-gate",
"merge-attempt",
"manual-merge-hold",
"retry-backoff",
"recovery-router",
"branch-group-member-integration",
"branch-group-promotion",
"pr-create",
"pr-respond",
"pr-merge",
] as const;
describe("nodeHelpFor", () => {
it("returns help for every editor node kind", () => {
for (const kind of EDITOR_KINDS) {
const help = nodeHelpFor(kind);
expect(help, `missing help for editor kind ${kind}`).not.toBeNull();
// Every node documents what it does and its I/O + edges.
expect(help!.title).toBeTruthy();
expect(help!.summary).toBeTruthy();
expect(help!.inputs).toBeTruthy();
expect(help!.outputs).toBeTruthy();
expect(help!.edges).toBeTruthy();
}
});
it("returns help for every graph-only policy node kind, flagged engine-managed", () => {
for (const kind of GRAPH_ONLY_KINDS) {
const help = nodeHelpFor(kind);
expect(help, `missing help for graph-only kind ${kind}`).not.toBeNull();
expect(help!.graphOnly).toBe(true);
}
});
it("editor kinds are not flagged engine-managed", () => {
for (const kind of EDITOR_KINDS) {
expect(nodeHelpFor(kind)!.graphOnly).toBeFalsy();
}
});
it("returns null for an unknown kind", () => {
expect(nodeHelpFor("not-a-kind")).toBeNull();
});
it("describes branch-group promotion's single-managed-PR idempotency", () => {
const help = nodeHelpFor("branch-group-promotion")!;
expect(help.summary).toMatch(/single managed PR/i);
expect(help.summary).toMatch(/never creates a second PR/i);
expect(help.edges).toMatch(/merge attempt/i);
});
it("distinguishes member integration (off-switch exempt) from promotion (gated)", () => {
expect(nodeHelpFor("branch-group-member-integration")!.summary).toMatch(/even when global auto-merge is off/i);
expect(nodeHelpFor("branch-group-promotion")!.summary).toMatch(/[Gg]ated by group\/global auto-merge/);
});
it("merge gate documents its auto-on / auto-off routing", () => {
const help = nodeHelpFor("merge-gate")!;
expect(help.edges).toMatch(/auto-on/);
expect(help.edges).toMatch(/auto-off/);
});
});
describe("effectiveNodeKind / nodeHelpForData", () => {
function data(kind: WorkflowFlowNodeData["kind"], irKind?: string): WorkflowFlowNodeData {
return { kind, label: kind, ...(irKind ? { irKind } : {}) };
}
it("prefers the preserved IR kind over the collapsed editor kind", () => {
// A branch-group-promotion node renders as a generic "merge" shape but
// preserves its IR kind so the help stays specific.
const d = data("merge", "branch-group-promotion");
expect(effectiveNodeKind(d)).toBe("branch-group-promotion");
expect(nodeHelpForData(d)!.title).toBe("Branch group · promotion");
});
it("falls back to the editor kind when no IR kind is preserved", () => {
const d = data("merge");
expect(effectiveNodeKind(d)).toBe("merge");
expect(nodeHelpForData(d)!.title).toBe("Merge boundary");
});
});