import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; const NODE_NAME = "LegacyWidgetWidthFix"; async function loadExtension() { let extension; const animationFrames = []; class LGraphNode { constructor() { this.widgets = []; this.size = [200, 100]; } addWidget(type, name, value, callback, options) { const widget = { type, name, value, callback, options, width: 240 }; this.widgets.push(widget); return widget; } addDOMWidget(name, type, element, options) { const widget = { name, type, element, options, width: 250 }; this.widgets.push(widget); return widget; } addCustomWidget(widget) { this.widgets.push(widget); return widget; } } const graph = { _nodes: [], dirtyCalls: 0, setDirtyCanvas() { this.dirtyCalls += 1; }, }; const app = { graph, registerExtension(value) { extension = value; }, }; globalThis.LGraphNode = LGraphNode; globalThis.LiteGraph = { vueNodesMode: false }; globalThis.__lwwfTestApp = app; globalThis.requestAnimationFrame = (callback) => { animationFrames.push(callback); }; const sourceUrl = new URL("../web/bugfix.js", import.meta.url); const source = (await readFile(sourceUrl, "utf8")).replace( 'import { app } from "../../scripts/app.js";', "const app = globalThis.__lwwfTestApp;", ); const moduleUrl = `data:text/javascript;base64,${Buffer.from(source).toString("base64")}`; await import(moduleUrl); return { animationFrames, app, extension, graph, LGraphNode }; } test("repairs existing and newly-created nodes without re-adding the fix node", async () => { const { animationFrames, extension, graph, LGraphNode } = await loadExtension(); class FixNode extends LGraphNode {} FixNode.comfyClass = NODE_NAME; extension.beforeRegisterNodeDef(FixNode, { name: NODE_NAME }); const existingNode = new LGraphNode(); existingNode.widgets.push({ name: "existing", width: 180 }); graph._nodes.push(existingNode); const fixNode = new FixNode(); extension.nodeCreated(fixNode); graph._nodes.push(fixNode); fixNode.onAdded(); assert.equal(existingNode.widgets[0].width, undefined); const repairButton = fixNode.widgets.find( (widget) => widget.name === "Repair widget widths now", ); assert.ok(repairButton, "the fix node should expose a manual repair button"); const newNode = new LGraphNode(); const normalWidget = newNode.addWidget("number", "new", 0, null, {}); assert.equal(normalWidget.width, undefined); const directWidget = { name: "direct", width: 300 }; newNode.widgets.push(directWidget); extension.nodeCreated(newNode); assert.equal(directWidget.width, undefined); const deferredWidget = { name: "deferred", width: 320 }; newNode.widgets.push(deferredWidget); graph._nodes.push(newNode); animationFrames.splice(0).forEach((callback) => callback()); assert.equal(deferredWidget.width, undefined); const unusualWidget = { name: "unusual", width: 500 }; existingNode.widgets.push(unusualWidget); repairButton.callback(); assert.equal(unusualWidget.width, undefined); directWidget.width = 410; assert.equal(directWidget.width, undefined, "LiteGraph writes remain blocked"); globalThis.LiteGraph.vueNodesMode = true; directWidget.width = 410; assert.equal(directWidget.width, 410, "Vue mode writes remain untouched"); fixNode.onRemoved(); directWidget.width = 430; assert.equal(directWidget.width, 430, "removal restores stock width writes"); });