126 lines
5.7 KiB
JavaScript
126 lines
5.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
import vm from "node:vm";
|
|
|
|
const source = await readFile(
|
|
new URL("../web/nodes/ksamplers/FL_KsamplerContextWindow.js", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
test("context-window progress uses stable string node keys", () => {
|
|
assert.match(source, /const nodeKey = \(value\) => String\(value\);/);
|
|
assert.match(source, /INSTANCES\.set\(nodeKey\(node\.id\), inst\);/);
|
|
assert.match(source, /INSTANCES\.get\(nodeKey\(detail\.node\)\)/);
|
|
assert.doesNotMatch(source, /parseInt\(detail\.node/);
|
|
});
|
|
|
|
function harness() {
|
|
class Element {
|
|
constructor() { this.style = {}; this.children = []; this.roles = new Map(); this.events = {}; }
|
|
appendChild(child) { this.children.push(child); }
|
|
querySelector(selector) {
|
|
if (!this.roles.has(selector)) this.roles.set(selector, new Element());
|
|
return this.roles.get(selector);
|
|
}
|
|
addEventListener(name, callback) { this.events[name] = callback; }
|
|
setAttribute(name, value) { this[name] = value; }
|
|
remove() { this.removed = true; }
|
|
}
|
|
const handlers = new Map();
|
|
const timers = new Map();
|
|
const app = { registerExtension(extension) { this.extension = extension; } };
|
|
const context = vm.createContext({
|
|
app,
|
|
api: { addEventListener: (name, callback) => handlers.set(name, callback) },
|
|
document: { createElement: () => new Element(), getElementById: () => null, head: new Element() },
|
|
setTimeout(callback) { const id = timers.size + 1; timers.set(id, callback); return id; },
|
|
clearTimeout(id) { timers.delete(id); },
|
|
});
|
|
vm.runInContext(source.replace(/^import .*;\r?$/gm, "").replace(/^export /gm, ""), context);
|
|
const defaults = vm.runInContext("({...ADVANCED_DEFAULTS})", context);
|
|
const widgets = Object.entries({ seed: 7, steps: 20, context_length: 81, context_overlap: 30, ...defaults }).map(([name, value]) => ({
|
|
name, value, type: "number", computeSize: () => [200, 20], options: {},
|
|
}));
|
|
const node = {
|
|
id: "subgraph:7", constructor: { comfyClass: "FL_KsamplerContextWindow" }, properties: {},
|
|
widgets, inputs: widgets.map((w) => ({name: w.name, link: null, widget: {name: w.name}})), size: [330, 730], setDirtyCanvas() {}, graph: { change() {} },
|
|
addDOMWidget(name, type, container, options) {
|
|
const widget = { name, type, container, options };
|
|
this.widgets.push(widget);
|
|
return widget;
|
|
},
|
|
computeSize() { return [330, 100 + this.widgets.filter((w) => !w.hidden).length * 24]; },
|
|
setSize(size) { this.size = size; },
|
|
};
|
|
app.extension.nodeCreated(node);
|
|
const init = () => { for (const [id, callback] of timers) { timers.delete(id); callback(); } };
|
|
const widget = (name) => widgets.find((w) => w.name === name);
|
|
const role = (name) => widget("context_progress").container.children[0].querySelector(`[data-role="${name}"]`);
|
|
return { context, node, widget, role, init, handlers, timers };
|
|
}
|
|
|
|
test("advanced collapse preserves widget values and serialization order", () => {
|
|
const h = harness();
|
|
const before = h.node.widgets.map((w) => [w.name, w.value]);
|
|
h.init();
|
|
const compactHeight = h.node.size[1];
|
|
assert.equal(h.widget("temporal_unit").hidden, true);
|
|
assert.equal(h.widget("context_length").hidden, undefined);
|
|
h.role("advanced").events.click();
|
|
assert.equal(h.widget("temporal_unit").type, "number");
|
|
assert.equal(h.widget("temporal_unit").hidden, undefined);
|
|
assert.equal(h.widget("context_stride").hidden, true);
|
|
assert.ok(h.node.size[1] > compactHeight);
|
|
h.role("advanced").events.click();
|
|
assert.deepEqual(h.node.widgets.map((w) => [w.name, w.value]), before);
|
|
assert.equal(h.node.size[1], compactHeight);
|
|
assert.ok(compactHeight < 730);
|
|
});
|
|
|
|
test("schedule-dependent controls follow the actual ComfyUI enum values", () => {
|
|
const h = harness();
|
|
h.init();
|
|
h.role("advanced").events.click();
|
|
const schedule = h.widget("context_schedule");
|
|
schedule.value = "standard_uniform";
|
|
schedule.callback();
|
|
assert.equal(h.widget("context_stride").hidden, undefined);
|
|
assert.equal(h.widget("closed_loop").hidden, true);
|
|
schedule.value = "looped_uniform";
|
|
schedule.callback();
|
|
assert.equal(h.widget("closed_loop").hidden, undefined);
|
|
schedule.value = "batched";
|
|
schedule.callback();
|
|
assert.equal(h.widget("context_overlap").hidden, true);
|
|
assert.equal(h.widget("context_overlap").value, 30);
|
|
});
|
|
|
|
test("saved legacy values survive configure and connected advanced sockets stay present", () => {
|
|
const h = harness();
|
|
h.init();
|
|
h.node.id = "nested:42";
|
|
h.widget("temporal_unit").value = "video_frames_4n_plus_1";
|
|
h.node.inputs.find((input) => input.name === "context_stride").link = 3;
|
|
h.node.onConfigure();
|
|
assert.equal(h.widget("temporal_unit").value, "video_frames_4n_plus_1");
|
|
assert.equal(h.node.inputs.find((input) => input.name === "context_stride").link, 3);
|
|
assert.match(h.role("advanced").textContent, /2 overrides/);
|
|
h.handlers.get("fl_context_window_progress")({ detail: { node: "nested:42", status: "resolved", profile: "ltx", video_frames: 81, latent_length: 11 } });
|
|
assert.match(h.role("model").textContent, /LTX.*81 frames \/ 11 latents/);
|
|
h.node.onConnectionsChange();
|
|
assert.match(h.role("model").textContent, /Settings changed/);
|
|
});
|
|
|
|
test("progress accepts string execution IDs and removal cancels pending initialization", () => {
|
|
const h = harness();
|
|
h.init();
|
|
h.handlers.get("executing")({ detail: "subgraph:7" });
|
|
assert.equal(h.role("percent").textContent, "0%");
|
|
h.handlers.get("execution_error")({ detail: { node_id: "subgraph:7", exception_message: "Invalid overlap" } });
|
|
assert.equal(h.role("percent").textContent, "error");
|
|
const pending = harness();
|
|
pending.widget("context_progress").onRemove();
|
|
assert.equal(pending.timers.size, 0);
|
|
});
|