Every workflow tab in a page shares one server connection, and ComfyUI files a node's results by node id. A run from one tab that finished while another tab was open landed on the open tab's node with the same id. On Video Crop + Rotate + Pad -> Clip that was a second video under the buttons that grew the node over the one below; Show Text and Seed also saved the other workflow's text and seed into this one, and Save Video and Compare replaced their own result. - The three Crop + Rotate + Pad nodes stand down ComfyUI's image and video previews (they send no picture of their own), in both renderers. - suppressCoreVideoPreview sets hideOutputImages, so Nodes 2.0 no longer draws Save Video's clip a second time under its player. - shared/prompt_scope.mjs notes which graph queued each run (the queue call is wrapped for the prompt id). For a result from another tab, the AusBoss node here gets this tab's own stored result back, every pack onExecuted handler returns early, and the result is handed to the owner tab's AusBoss nodes when that tab is open again. Core nodes are left alone. Tests: prompt_scope (scope rules plus a policy check that every onExecuted handler asks isForeignRun first) and core_preview (every transform kind suppresses both previews; video suppression sets hideOutputImages) fail on main and pass here.
116 lines
4.0 KiB
JavaScript
116 lines
4.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
CORE_IMAGE_PREVIEW_WIDGET,
|
|
CORE_VIDEO_PREVIEW_WIDGET,
|
|
hideCanvasWidget,
|
|
suppressCoreImagePreview,
|
|
suppressCoreVideoPreview,
|
|
} from "../js/shared/core_preview.mjs";
|
|
|
|
function fakeNode() {
|
|
const node = {
|
|
widgets: [],
|
|
addCustomWidget(widget) {
|
|
this.widgets.push(widget);
|
|
return widget;
|
|
},
|
|
addDOMWidget(name, type, element) {
|
|
const widget = { name, type, element };
|
|
this.widgets.push(widget);
|
|
return widget;
|
|
},
|
|
};
|
|
return node;
|
|
}
|
|
|
|
const isHidden = (widget) =>
|
|
widget.hidden === true && widget.computeSize()[1] === -4 &&
|
|
widget.computeLayoutSize().minHeight === 0;
|
|
|
|
test("a widget the frontend adds later is hidden on arrival", () => {
|
|
const node = fakeNode();
|
|
suppressCoreImagePreview(node);
|
|
const widget = node.addCustomWidget({ name: CORE_IMAGE_PREVIEW_WIDGET });
|
|
assert.ok(isHidden(widget));
|
|
});
|
|
|
|
test("a widget already on the node when we arrive is swept up", () => {
|
|
const node = fakeNode();
|
|
const widget = { name: CORE_IMAGE_PREVIEW_WIDGET };
|
|
node.widgets.push(widget);
|
|
suppressCoreImagePreview(node);
|
|
assert.ok(isHidden(widget));
|
|
});
|
|
|
|
test("only the core preview widget is touched", () => {
|
|
const node = fakeNode();
|
|
suppressCoreImagePreview(node);
|
|
const mine = node.addCustomWidget({ name: "ausboss_input_preview" });
|
|
assert.equal(mine.hidden, undefined);
|
|
assert.equal(typeof mine.computeSize, "undefined");
|
|
});
|
|
|
|
test("the image and video suppressors do not stand in for each other", () => {
|
|
// They wrap different methods and are flagged separately, so a node that
|
|
// draws both kinds can ask for both.
|
|
const node = fakeNode();
|
|
suppressCoreImagePreview(node);
|
|
const video = node.addDOMWidget(CORE_VIDEO_PREVIEW_WIDGET, "video", {
|
|
style: {},
|
|
});
|
|
assert.equal(video.hidden, undefined, "image suppression must not hide the video widget");
|
|
|
|
suppressCoreVideoPreview(node);
|
|
const second = node.addDOMWidget(CORE_VIDEO_PREVIEW_WIDGET, "video", { style: {} });
|
|
assert.ok(isHidden(second));
|
|
// ...and the one added before suppression is swept on the way in.
|
|
assert.ok(isHidden(video));
|
|
});
|
|
|
|
test("suppressing twice keeps one wrapper, not a growing chain", () => {
|
|
const node = fakeNode();
|
|
const original = node.addCustomWidget;
|
|
suppressCoreImagePreview(node);
|
|
const wrapped = node.addCustomWidget;
|
|
suppressCoreImagePreview(node);
|
|
assert.notEqual(wrapped, original);
|
|
assert.equal(node.addCustomWidget, wrapped);
|
|
});
|
|
|
|
test("hiding is idempotent and never re-stashes a live method", () => {
|
|
const widget = { name: CORE_IMAGE_PREVIEW_WIDGET };
|
|
hideCanvasWidget(widget);
|
|
const hiddenDraw = widget.draw;
|
|
hideCanvasWidget(widget);
|
|
assert.equal(widget.draw, hiddenDraw);
|
|
});
|
|
|
|
test("the DOM widget's element is hidden along with it", () => {
|
|
const node = fakeNode();
|
|
suppressCoreVideoPreview(node);
|
|
const element = { style: {} };
|
|
node.addDOMWidget(CORE_VIDEO_PREVIEW_WIDGET, "video", element);
|
|
assert.equal(element.style.display, "none");
|
|
});
|
|
|
|
// The Crop + Rotate + Pad nodes send no picture of their own, so a core
|
|
// preview on one can only be another node's result: in practice a run from
|
|
// another workflow tab, filed under the same node id. All three kinds stand
|
|
// both core previews down, not just the image node.
|
|
test("every transform node stands down both core previews", async () => {
|
|
const { readFileSync } = await import("node:fs");
|
|
const source = readFileSync(new URL("../js/shared/transform_editor.mjs", import.meta.url), "utf-8");
|
|
const install = source.slice(source.indexOf("export function installTransformNode"), source.indexOf("function installVideoDrop"));
|
|
assert.match(install, /\n\s*suppressCoreImagePreview\(node\);/);
|
|
assert.match(install, /\n\s*suppressCoreVideoPreview\(node\);/);
|
|
assert.doesNotMatch(install, /if \(kind === "image"\) suppressCoreImagePreview/);
|
|
});
|
|
|
|
test("the video suppression also stands down Nodes 2.0's own output copy", () => {
|
|
const node = fakeNode();
|
|
suppressCoreVideoPreview(node);
|
|
assert.equal(node.hideOutputImages, true);
|
|
});
|