Files
AEmotionStudio-ComfyUI-Magn…/tests/unit/NodeDataExtractor.test.ts
T
Æmotion StudioandClaude Opus 5.5 7852897c1d Add Inputs and Outputs links to the pop-out viewer
Each slot lists the nodes it links to as chips that jump to them, and flags
required inputs with nothing connected. Links to a subgraph's own input or
output show as plain chips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:14:49 -08:00

80 lines
4.6 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { getSlotLinks } from '../../src/info-panel/NodeDataExtractor';
describe('getSlotLinks', () => {
// Nodes and links as LiteGraph keeps them; getInputNode follows an input's link to its origin
const makeGraph = () => {
const graph: any = { _nodes: [], links: new Map() };
graph.getNodeById = (id: number) => graph._nodes.find((n: any) => n.id === id) ?? null;
return graph;
};
const addNode = (graph: any, id: number, title: string, inputs: any[] = [], outputs: any[] = [], required: string[] = []) => {
class Node {
static nodeData = { input: { required: Object.fromEntries(required.map(name => [name, ['IMAGE']])) } };
id = id;
title = title;
graph = graph;
inputs = inputs;
outputs = outputs;
getInputNode(slot: number) {
const link = graph.links.get(this.inputs[slot].link);
return link ? graph.getNodeById(link.origin_id) : null;
}
}
const node = new Node();
graph._nodes.push(node);
return node;
};
const link = (graph: any, id: number, origin: any, originSlot: number, target: any, targetSlot: number) => {
graph.links.set(id, { origin_id: origin.id, origin_slot: originSlot, target_id: target.id, target_slot: targetSlot });
(origin.outputs[originSlot].links ??= []).push(id);
target.inputs[targetSlot].link = id;
};
it('names the nodes at the other end of each link, flags required inputs with nothing connected, and leaves out unlinked widget inputs', () => {
const graph = makeGraph();
const loader = addNode(graph, 1, 'Load Checkpoint', [], [
{ name: 'MODEL', type: 'MODEL', links: null },
{ name: 'CLIP', type: 'CLIP', localized_name: 'CLIP', links: null }
]);
const sampler = addNode(graph, 2, 'KSampler', [
{ name: 'model', type: 'MODEL', link: null },
{ name: 'positive', type: 'CONDITIONING', label: 'Positive prompt', link: null },
{ name: 'latent_image', type: 'LATENT', link: null },
{ name: 'seed', type: 'INT', widget: { name: 'seed' }, link: null },
{ name: 'cfg', type: 'FLOAT', widget: { name: 'cfg' }, link: null }
], [], ['model', 'positive', 'latent_image']);
const encode = addNode(graph, 3, '', [{ name: 'clip', type: 'CLIP', link: null }]);
const prim = addNode(graph, 4, 'CFG', [], [{ name: 'FLOAT', type: 'FLOAT', links: null }]);
link(graph, 10, loader, 0, sampler, 0);
link(graph, 11, loader, 1, encode, 0);
link(graph, 12, prim, 0, sampler, 4);
// A subgraph's own input and output nodes are not in graph._nodes
graph.inputs = [{ name: 'positive', label: 'Prompt', type: 'CONDITIONING' }];
graph.outputs = [{ name: 'LATENT', type: 'LATENT' }];
sampler.inputs[1].link = 13;
graph.links.set(13, { origin_id: -10, origin_slot: 0, target_id: 2, target_slot: 1 });
expect(getSlotLinks(sampler as any)).toEqual({
inputs: [
{ name: 'model', type: 'MODEL', linked: [{ id: 1, title: 'Load Checkpoint', slot: 'MODEL' }], missing: false },
{ name: 'Positive prompt', type: 'CONDITIONING', linked: [{ title: 'Subgraph input', slot: 'Prompt' }], missing: false },
{ name: 'latent_image', type: 'LATENT', linked: [], missing: true },
{ name: 'cfg', type: 'FLOAT', linked: [{ id: 4, title: 'CFG', slot: 'FLOAT' }], missing: false }
],
outputs: []
});
link(graph, 14, loader, 0, addNode(graph, 5, 'Upscale', [{ name: 'model', label: 'Model in', type: 'MODEL', link: null }]), 0);
loader.outputs[0].links.push(15);
graph.links.set(15, { origin_id: 1, origin_slot: 0, target_id: -20, target_slot: 0 });
expect(getSlotLinks(loader as any).outputs).toEqual([
{ name: 'MODEL', type: 'MODEL', linked: [{ id: 2, title: 'KSampler', slot: 'model' }, { id: 5, title: 'Upscale', slot: 'Model in' }, { title: 'Subgraph output', slot: 'LATENT' }] },
{ name: 'CLIP', type: 'CLIP', linked: [{ id: 3, title: 'Untitled', slot: 'clip' }] }
]);
// Without a node definition, such as a subgraph node, nothing is required
const subgraphNode = { id: 6, title: 'Subgraph', graph, inputs: [{ name: 'image', type: 'IMAGE', link: null }], outputs: [], getInputNode: () => null };
expect(getSlotLinks(subgraphNode as any).inputs).toEqual([{ name: 'image', type: 'IMAGE', linked: [], missing: false }]);
});
});