Spaces:
Sleeping
Sleeping
// @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"); | |
}); | |
}); | |