Files
DemonGatanjieuandClaude Opus 5.5 7b09909e5b test: publish the plugin's tests, one command to run them
tests/ (Python backend tests, .mjs frontend rule tests) is no longer
ignored. tools/run_tests.mjs runs the structure check, every
tests/*.mjs and the Python tests with $PYTHON, the portable
python_embeded or python. docs/guides/testing.md says how to run and
write them; README links it. Developer notes no longer point at 18 test
files that were removed earlier (AGENTS.md table, backend, frontend and
update-guide notes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:47:35 +08:00

107 lines
4.5 KiB
JavaScript

// node --experimental-vm-modules tests/canvas_undo.mjs — undo from the activity log.
import assert from 'node:assert/strict';
import { snapshotGraph } from '../web/modules/activity_diff.js';
import { canUndo, keepUndo, undoEntry } from '../web/modules/canvas_undo.js';
globalThis.LiteGraph = { registered_node_types: {} };
function makeNode(id, type, widgets, inputs = []) {
return {
id, type, title: type, inputs, outputs: [],
widgets: Object.entries(widgets).map(([name, value]) => ({ name, value })),
};
}
function makeGraph(nodes) {
const graph = {
_nodes: nodes,
getNodeById: id => graph._nodes.find(node => String(node.id) === String(id)) || null,
remove: node => { graph._nodes = graph._nodes.filter(item => item !== node); },
steps: 0,
};
return graph;
}
function makeApp(graph, key = 'wf-1') {
const tracker = { captureCanvasState() { graph.steps += 1; } };
return { graph, canvas: { graph }, extensionManager: { workflow: { activeWorkflow: { key, changeTracker: tracker } } } };
}
// A value change and an added unconnected node: undone together, one Ctrl+Z step.
{
const sampler = makeNode(3, 'KSampler', { seed: 5, steps: 20, sampler_name: 'euler' });
const graph = makeGraph([sampler]);
const app = makeApp(graph);
const before = snapshotGraph(graph);
sampler.widgets[0].value = 6; // a seed changed meanwhile stays
sampler.widgets[1].value = 30;
sampler.widgets[2].value = 'dpmpp_2m';
graph._nodes.push(makeNode(7, 'CLIPTextEncode', { text: 'a cat' }));
keepUndo('e1', 'wf-1', before, snapshotGraph(graph));
assert.equal(canUndo(app, { id: 'e1' }), true);
undoEntry(app, { id: 'e1' });
assert.equal(sampler.widgets[0].value, 6);
assert.equal(sampler.widgets[1].value, 20);
assert.equal(typeof sampler.widgets[1].value, 'number');
assert.equal(sampler.widgets[2].value, 'euler');
assert.equal(graph.getNodeById(7), null);
assert.equal(graph.steps, 1);
assert.equal(canUndo(app, { id: 'e1' }), false, 'an entry is undone once');
}
// Changed again since, another workflow open, or the added node wired up: no undo.
{
const sampler = makeNode(3, 'Steps', { steps: 20 });
const graph = makeGraph([sampler]);
const before = snapshotGraph(graph);
sampler.widgets[0].value = 30;
keepUndo('e2', 'wf-1', before, snapshotGraph(graph));
assert.equal(canUndo(makeApp(graph, 'wf-2'), { id: 'e2' }), false);
sampler.widgets[0].value = 31;
assert.equal(canUndo(makeApp(graph), { id: 'e2' }), false);
assert.throws(() => undoEntry(makeApp(graph), { id: 'e2' }), /activityUndoGone/);
const graph2 = makeGraph([]);
const before2 = snapshotGraph(graph2);
const added = makeNode(5, 'LoraLoader', { lora_name: 'x' });
graph2._nodes.push(added);
keepUndo('e3', 'wf-1', before2, snapshotGraph(graph2));
added.outputs = [{ links: [11] }];
assert.equal(canUndo(makeApp(graph2), { id: 'e3' }), false);
}
// Removed nodes or rewired inputs are never kept; nor is an entry without an id.
{
const a = makeNode(1, 'A', { v: 1 });
const b = makeNode(2, 'B', { v: 1 }, [{ link: null }]);
const graph = makeGraph([a, b]);
const before = snapshotGraph(graph);
graph._nodes = [b];
keepUndo('e4', 'wf-1', before, snapshotGraph(graph));
assert.equal(canUndo(makeApp(graph), { id: 'e4' }), false);
const graph2 = makeGraph([makeNode(1, 'A', { v: 1 }), b]);
const before2 = snapshotGraph(graph2);
b.inputs[0].link = 4;
b.widgets[0].value = 2;
keepUndo('e5', 'wf-1', before2, snapshotGraph(graph2));
assert.equal(canUndo(makeApp(graph2), { id: 'e5' }), false);
assert.equal(canUndo(makeApp(graph2), {}), false);
}
// A choice this computer no longer has, or only a seed changed: nothing to offer.
{
const loader = makeNode(4, 'CheckpointLoaderSimple', { ckpt_name: 'a.safetensors' });
loader.widgets[0].options = { values: ['b.safetensors'] };
const graph = makeGraph([loader]);
const before = snapshotGraph(graph);
loader.widgets[0].value = 'b.safetensors';
keepUndo('e6', 'wf-1', before, snapshotGraph(graph));
assert.equal(canUndo(makeApp(graph), { id: 'e6' }), false);
const seeded = makeNode(8, 'Noise', { noise_seed: 1 });
const graph2 = makeGraph([seeded]);
const before2 = snapshotGraph(graph2);
seeded.widgets[0].value = 2;
keepUndo('e7', 'wf-1', before2, snapshotGraph(graph2));
assert.equal(canUndo(makeApp(graph2), { id: 'e7' }), false);
}
console.log('canvas_undo: ok');