mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-26 13:34:08 -03:00
ComfyUI frontend 1.53 turned LGraphNode.mode into a prototype accessor backed by node._state, and serialize() now reads that state directly. Redefining mode on the instance shadowed the setter, so bypass/mute never reached the serialized workflow and silently reverted to Always on save/reload or workflow tab switch. Add interceptModeChange() in web/comfyui/utils.js: it delegates to the prototype accessor when one exists (observing changes only), and falls back to the legacy closure accessor on older frontends. Use it in lora_loader.js and in the Vue widgets' setupModeChangeHandler, which covers the LoRA provider/aggregator nodes with the same latent bug.
100 lines
2.6 KiB
JavaScript
100 lines
2.6 KiB
JavaScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { APP_MODULE, UTILS_MODULE } = vi.hoisted(() => ({
|
|
APP_MODULE: new URL("../../../scripts/app.js", import.meta.url).pathname,
|
|
UTILS_MODULE: new URL("../../../web/comfyui/utils.js", import.meta.url).pathname,
|
|
}));
|
|
|
|
vi.mock(APP_MODULE, () => ({
|
|
app: {
|
|
graph: null,
|
|
registerExtension: vi.fn(),
|
|
ui: {
|
|
settings: {
|
|
getSettingValue: vi.fn(),
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
describe("interceptModeChange", () => {
|
|
let interceptModeChange;
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
({ interceptModeChange } = await import(UTILS_MODULE));
|
|
});
|
|
|
|
describe("legacy frontend (mode as plain data property)", () => {
|
|
it("reads and writes the mode through the installed accessor", () => {
|
|
const node = { mode: 0 };
|
|
interceptModeChange(node, vi.fn());
|
|
|
|
node.mode = 4;
|
|
expect(node.mode).toBe(4);
|
|
});
|
|
|
|
it("invokes the callback only when the mode actually changes", () => {
|
|
const node = { mode: 0 };
|
|
const onModeChange = vi.fn();
|
|
interceptModeChange(node, onModeChange);
|
|
|
|
node.mode = 0;
|
|
expect(onModeChange).not.toHaveBeenCalled();
|
|
|
|
node.mode = 4;
|
|
expect(onModeChange).toHaveBeenCalledWith(4, 0);
|
|
});
|
|
});
|
|
|
|
describe("ECS frontend (mode as prototype accessor backed by shell state)", () => {
|
|
function createEcsNode() {
|
|
class LGraphNode {
|
|
constructor() {
|
|
this._state = { mode: 0 };
|
|
}
|
|
get mode() {
|
|
return this._state.mode;
|
|
}
|
|
set mode(value) {
|
|
this._state.mode = value;
|
|
}
|
|
}
|
|
return new LGraphNode();
|
|
}
|
|
|
|
it("keeps writes flowing into the shell state so serialization stays correct", () => {
|
|
const node = createEcsNode();
|
|
interceptModeChange(node, vi.fn());
|
|
|
|
node.mode = 4;
|
|
|
|
expect(node._state.mode).toBe(4);
|
|
expect(node.mode).toBe(4);
|
|
});
|
|
|
|
it("invokes the callback with new and old mode on change", () => {
|
|
const node = createEcsNode();
|
|
const onModeChange = vi.fn();
|
|
interceptModeChange(node, onModeChange);
|
|
|
|
node.mode = 4;
|
|
expect(onModeChange).toHaveBeenCalledWith(4, 0);
|
|
|
|
node.mode = 4;
|
|
expect(onModeChange).toHaveBeenCalledTimes(1);
|
|
|
|
node.mode = 0;
|
|
expect(onModeChange).toHaveBeenCalledWith(0, 4);
|
|
});
|
|
|
|
it("keeps the installed accessor configurable so it can be redefined", () => {
|
|
const node = createEcsNode();
|
|
interceptModeChange(node, vi.fn());
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(node, "mode");
|
|
expect(descriptor.configurable).toBe(true);
|
|
});
|
|
});
|
|
});
|