|
|
|
// @ts-check
|
|
|
|
/// <reference path="../node_modules/@types/jest/index.d.ts" />
|
|
|
|
const { start } = require("../utils");
|
|
|
|
const lg = require("../utils/litegraph");
|
|
|
|
|
|
|
|
describe("extensions", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
lg.setup(global);
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
lg.teardown(global);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("calls each extension hook", async () => {
|
|
|
|
const mockExtension = {
|
|
|
|
name: "TestExtension",
|
|
|
|
init: jest.fn(),
|
|
|
|
setup: jest.fn(),
|
|
|
|
addCustomNodeDefs: jest.fn(),
|
|
|
|
getCustomWidgets: jest.fn(),
|
|
|
|
beforeRegisterNodeDef: jest.fn(),
|
|
|
|
registerCustomNodes: jest.fn(),
|
|
|
|
loadedGraphNode: jest.fn(),
|
|
|
|
nodeCreated: jest.fn(),
|
|
|
|
beforeConfigureGraph: jest.fn(),
|
|
|
|
afterConfigureGraph: jest.fn(),
|
|
|
|
};
|
|
|
|
|
|
|
|
const { app, ez, graph } = await start({
|
|
|
|
async preSetup(app) {
|
|
|
|
app.registerExtension(mockExtension);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
// Basic initialisation hooks should be called once, with app
|
|
|
|
expect(mockExtension.init).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.init).toHaveBeenCalledWith(app);
|
|
|
|
|
|
|
|
// Adding custom node defs should be passed the full list of nodes
|
|
|
|
expect(mockExtension.addCustomNodeDefs).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.addCustomNodeDefs.mock.calls[0][1]).toStrictEqual(app);
|
|
|
|
const defs = mockExtension.addCustomNodeDefs.mock.calls[0][0];
|
|
|
|
expect(defs).toHaveProperty("KSampler");
|
|
|
|
expect(defs).toHaveProperty("LoadImage");
|
|
|
|
|
|
|
|
// Get custom widgets is called once and should return new widget types
|
|
|
|
expect(mockExtension.getCustomWidgets).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.getCustomWidgets).toHaveBeenCalledWith(app);
|
|
|
|
|
|
|
|
// Before register node def will be called once per node type
|
|
|
|
const nodeNames = Object.keys(defs);
|
|
|
|
const nodeCount = nodeNames.length;
|
|
|
|
expect(mockExtension.beforeRegisterNodeDef).toHaveBeenCalledTimes(nodeCount);
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
// It should be send the JS class and the original JSON definition
|
|
|
|
const nodeClass = mockExtension.beforeRegisterNodeDef.mock.calls[i][0];
|
|
|
|
const nodeDef = mockExtension.beforeRegisterNodeDef.mock.calls[i][1];
|
|
|
|
|
|
|
|
expect(nodeClass.name).toBe("ComfyNode");
|
|
|
|
expect(nodeClass.comfyClass).toBe(nodeNames[i]);
|
|
|
|
expect(nodeDef.name).toBe(nodeNames[i]);
|
|
|
|
expect(nodeDef).toHaveProperty("input");
|
|
|
|
expect(nodeDef).toHaveProperty("output");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register custom nodes is called once after registerNode defs to allow adding other frontend nodes
|
|
|
|
expect(mockExtension.registerCustomNodes).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
// Before configure graph will be called here as the default graph is being loaded
|
|
|
|
expect(mockExtension.beforeConfigureGraph).toHaveBeenCalledTimes(1);
|
|
|
|
// it gets sent the graph data that is going to be loaded
|
|
|
|
const graphData = mockExtension.beforeConfigureGraph.mock.calls[0][0];
|
|
|
|
|
|
|
|
// A node created is fired for each node constructor that is called
|
|
|
|
expect(mockExtension.nodeCreated).toHaveBeenCalledTimes(graphData.nodes.length);
|
|
|
|
for (let i = 0; i < graphData.nodes.length; i++) {
|
|
|
|
expect(mockExtension.nodeCreated.mock.calls[i][0].type).toBe(graphData.nodes[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Each node then calls loadedGraphNode to allow them to be updated
|
|
|
|
expect(mockExtension.loadedGraphNode).toHaveBeenCalledTimes(graphData.nodes.length);
|
|
|
|
for (let i = 0; i < graphData.nodes.length; i++) {
|
|
|
|
expect(mockExtension.loadedGraphNode.mock.calls[i][0].type).toBe(graphData.nodes[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// After configure is then called once all the setup is done
|
|
|
|
expect(mockExtension.afterConfigureGraph).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
expect(mockExtension.setup).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.setup).toHaveBeenCalledWith(app);
|
|
|
|
|
|
|
|
// Ensure hooks are called in the correct order
|
|
|
|
const callOrder = [
|
|
|
|
"init",
|
|
|
|
"addCustomNodeDefs",
|
|
|
|
"getCustomWidgets",
|
|
|
|
"beforeRegisterNodeDef",
|
|
|
|
"registerCustomNodes",
|
|
|
|
"beforeConfigureGraph",
|
|
|
|
"nodeCreated",
|
|
|
|
"loadedGraphNode",
|
|
|
|
"afterConfigureGraph",
|
|
|
|
"setup",
|
|
|
|
];
|
|
|
|
for (let i = 1; i < callOrder.length; i++) {
|
|
|
|
const fn1 = mockExtension[callOrder[i - 1]];
|
|
|
|
const fn2 = mockExtension[callOrder[i]];
|
|
|
|
expect(fn1.mock.invocationCallOrder[0]).toBeLessThan(fn2.mock.invocationCallOrder[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
graph.clear();
|
|
|
|
|
|
|
|
// Ensure adding a new node calls the correct callback
|
|
|
|
ez.LoadImage();
|
|
|
|
expect(mockExtension.loadedGraphNode).toHaveBeenCalledTimes(graphData.nodes.length);
|
|
|
|
expect(mockExtension.nodeCreated).toHaveBeenCalledTimes(graphData.nodes.length + 1);
|
|
|
|
expect(mockExtension.nodeCreated.mock.lastCall[0].type).toBe("LoadImage");
|
|
|
|
|
|
|
|
// Reload the graph to ensure correct hooks are fired
|
|
|
|
await graph.reload();
|
|
|
|
|
|
|
|
// These hooks should not be fired again
|
|
|
|
expect(mockExtension.init).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.addCustomNodeDefs).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.getCustomWidgets).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.registerCustomNodes).toHaveBeenCalledTimes(1);
|
|
|
|
expect(mockExtension.beforeRegisterNodeDef).toHaveBeenCalledTimes(nodeCount);
|
|
|
|
expect(mockExtension.setup).toHaveBeenCalledTimes(1);
|
|
|
|
|
|
|
|
// These should be called again
|
|
|
|
expect(mockExtension.beforeConfigureGraph).toHaveBeenCalledTimes(2);
|
|
|
|
expect(mockExtension.nodeCreated).toHaveBeenCalledTimes(graphData.nodes.length + 2);
|
|
|
|
expect(mockExtension.loadedGraphNode).toHaveBeenCalledTimes(graphData.nodes.length + 1);
|
|
|
|
expect(mockExtension.afterConfigureGraph).toHaveBeenCalledTimes(2);
|
|
|
|
}, 15000);
|
|
|
|
|
|
|
|
it("allows custom nodeDefs and widgets to be registered", async () => {
|
|
|
|
const widgetMock = jest.fn((node, inputName, inputData, app) => {
|
|
|
|
expect(node.constructor.comfyClass).toBe("TestNode");
|
|
|
|
expect(inputName).toBe("test_input");
|
|
|
|
expect(inputData[0]).toBe("CUSTOMWIDGET");
|
|
|
|
expect(inputData[1]?.hello).toBe("world");
|
|
|
|
expect(app).toStrictEqual(app);
|
|
|
|
|
|
|
|
return {
|
|
|
|
widget: node.addWidget("button", inputName, "hello", () => {}),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register our extension that adds a custom node + widget type
|
|
|
|
const mockExtension = {
|
|
|
|
name: "TestExtension",
|
|
|
|
addCustomNodeDefs: (nodeDefs) => {
|
|
|
|
nodeDefs["TestNode"] = {
|
|
|
|
output: [],
|
|
|
|
output_name: [],
|
|
|
|
output_is_list: [],
|
|
|
|
name: "TestNode",
|
|
|
|
display_name: "TestNode",
|
|
|
|
category: "Test",
|
|
|
|
input: {
|
|
|
|
required: {
|
|
|
|
test_input: ["CUSTOMWIDGET", { hello: "world" }],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
getCustomWidgets: jest.fn(() => {
|
|
|
|
return {
|
|
|
|
CUSTOMWIDGET: widgetMock,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
const { graph, ez } = await start({
|
|
|
|
async preSetup(app) {
|
|
|
|
app.registerExtension(mockExtension);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(mockExtension.getCustomWidgets).toBeCalledTimes(1);
|
|
|
|
|
|
|
|
graph.clear();
|
|
|
|
expect(widgetMock).toBeCalledTimes(0);
|
|
|
|
const node = ez.TestNode();
|
|
|
|
expect(widgetMock).toBeCalledTimes(1);
|
|
|
|
|
|
|
|
// Ensure our custom widget is created
|
|
|
|
expect(node.inputs.length).toBe(0);
|
|
|
|
expect(node.widgets.length).toBe(1);
|
|
|
|
const w = node.widgets[0].widget;
|
|
|
|
expect(w.name).toBe("test_input");
|
|
|
|
expect(w.type).toBe("button");
|
|
|
|
});
|
|
|
|
});
|