Frame Chooser, mostly. A pause did not survive a workflow load: LiteGraph clears a graph by removing every node, so undo, a tab switch, Clear Workflow and opening another file all fired the teardown a deleted node uses to release its pause, and killed a run that was still going. That now only fires for a real deletion. Answering "keep all" wrote the batch out as 1,2,...,N into pick_list, so the next run dropped whatever the enumeration did not reach or failed outright on a shorter clip; it writes the empty answer it was given. And pick_list is only written back under "keep last selection" - it pre-answers the node, so filling it in automatically meant a chooser left at the default paused exactly once. Refine Mask: smooth applies only the change it made to the jaggy edge rather than binarizing everything, so an already-soft matte keeps its alpha (binary masks stay bit-identical), and blur now reaches the matting solve instead of being thresholded back out of the trimap. Frame Interpolate's copy path honours batch_size; it gathered every copied frame at once, which on a long clip at an integer multiple was a multi-gigabyte allocation on the frames device that no setting bounded. Subgraph execution ids are resolved by walking the chain instead of stripping the prefix, which could match an unrelated node with the same number. Status and runtime badges appear inside subgraphs again, and a subgraph pause renders on the node that actually paused. Also: the Custom colour scheme no longer strips every node's colour when its stored colour is unreadable; Drop Shadow and Pad Image name their own widget for an unreadable colour instead of blaming Transform; a non-ASCII token and a non-object JSON body answer 400 rather than throwing a 500 and stranding the pause; pick_list rejects Unicode digits, which used to raise during validation or silently read as a frame nobody typed. Tooling: validate_nodes parses NODE_MODULES rather than matching it anywhere in the text, and an unregistered module is an error - it used to exit 0 printing nothing. It also refuses mapping keys declared outside nodes/, which is how the registry fixtures came to be advertised to ComfyUI-Manager as installable nodes; they are .py.txt now. And the video test no longer leaves ComfyUI on sys.path, which was flipping every fail-soft seam for whatever ran next.
68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { nodeByExecutionId } from "../js/shared/graph_ids.mjs";
|
|
|
|
// A graph whose ids repeat inside a subgraph, which is the normal case: node
|
|
// ids are small integers numbered independently per graph.
|
|
function makeGraph() {
|
|
const inner3 = { id: 3, title: "inner chooser" };
|
|
const inner1 = { id: 1, title: "inner loader" };
|
|
const subgraph = {
|
|
getNodeById: (id) => [inner1, inner3].find((node) => node.id === id) ?? null,
|
|
};
|
|
const root3 = { id: 3, title: "root chooser" };
|
|
const holder12 = { id: 12, title: "subgraph node", subgraph };
|
|
const deeper = {
|
|
id: 5,
|
|
title: "nested holder",
|
|
subgraph: {
|
|
getNodeById: (id) => (id === 3 ? { id: 3, title: "deep chooser" } : null),
|
|
},
|
|
};
|
|
const rootGraph = {
|
|
getNodeById: (id) =>
|
|
[root3, holder12, deeper].find((node) => node.id === id) ?? null,
|
|
};
|
|
return { rootGraph, root3, inner3 };
|
|
}
|
|
|
|
test("a top-level id resolves on the root graph", () => {
|
|
const { rootGraph, root3 } = makeGraph();
|
|
assert.equal(nodeByExecutionId(rootGraph, "3"), root3);
|
|
assert.equal(nodeByExecutionId(rootGraph, 3), root3);
|
|
});
|
|
|
|
test("a subgraph id resolves inside its own graph, not the root", () => {
|
|
// The bug this replaces: stripping the prefix and looking "3" up on the
|
|
// root returned the unrelated top-level node with the same number, so one
|
|
// node's panel rendered on another's face and its writeback landed there.
|
|
const { rootGraph, root3, inner3 } = makeGraph();
|
|
const found = nodeByExecutionId(rootGraph, "12:3");
|
|
assert.equal(found, inner3);
|
|
assert.notEqual(found, root3);
|
|
assert.equal(found.title, "inner chooser");
|
|
});
|
|
|
|
test("nesting walks the whole chain", () => {
|
|
const { rootGraph } = makeGraph();
|
|
assert.equal(nodeByExecutionId(rootGraph, "5:3").title, "deep chooser");
|
|
});
|
|
|
|
test("an id that does not resolve returns null rather than a guess", () => {
|
|
const { rootGraph } = makeGraph();
|
|
assert.equal(nodeByExecutionId(rootGraph, "99:3"), null); // no such holder
|
|
assert.equal(nodeByExecutionId(rootGraph, "12:99"), null); // no such inner id
|
|
assert.equal(nodeByExecutionId(rootGraph, "3:3"), null); // holder has no subgraph
|
|
assert.equal(nodeByExecutionId(rootGraph, ""), null);
|
|
assert.equal(nodeByExecutionId(rootGraph, null), null);
|
|
assert.equal(nodeByExecutionId(null, "3"), null);
|
|
assert.equal(nodeByExecutionId({}, "3"), null);
|
|
});
|
|
|
|
test("string-keyed graphs still resolve", () => {
|
|
// Some frontends key nodes by string; both spellings are tried.
|
|
const rootGraph = { getNodeById: (id) => (id === "7" ? { id: "7" } : null) };
|
|
assert.equal(nodeByExecutionId(rootGraph, "7").id, "7");
|
|
});
|