mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
[API] Finalise LinkConnector design, adding reroute logic (#817)
- Splits link connect logic out of `LinkConnector` to individual `RenderLink` classes - Add support for connecting / reconnecting reroutes in various configurations - Adds support for moving existing floating links from outputs / inputs - Fixes numerous corruption issues when reconnecting reroutes / moving links - Tests in separate PR #816
This commit is contained in:
@@ -5,54 +5,43 @@ import type { ISlotType } from "@/interfaces"
|
||||
import { describe, expect, test as baseTest, vi } from "vitest"
|
||||
|
||||
import { LinkConnector } from "@/canvas/LinkConnector"
|
||||
import { ToInputRenderLink } from "@/canvas/ToInputRenderLink"
|
||||
import { LGraph } from "@/LGraph"
|
||||
import { LGraphNode } from "@/LGraphNode"
|
||||
import { LLink } from "@/LLink"
|
||||
import { Reroute } from "@/Reroute"
|
||||
import { Reroute, type RerouteId } from "@/Reroute"
|
||||
import { LinkDirection } from "@/types/globalEnums"
|
||||
|
||||
type TestNetwork = LinkNetwork & { add(node: LGraphNode): void }
|
||||
|
||||
interface TestContext {
|
||||
network: TestNetwork
|
||||
network: LinkNetwork & { add(node: LGraphNode): void }
|
||||
connector: LinkConnector
|
||||
setConnectingLinks: ReturnType<typeof vi.fn>
|
||||
createTestNode: (id: number, slotType?: ISlotType) => LGraphNode
|
||||
createTestLink: (id: number, sourceId: number, targetId: number, slotType?: ISlotType) => LLink
|
||||
}
|
||||
|
||||
function createNetwork(): TestNetwork {
|
||||
const graph = new LGraph()
|
||||
const floatingLinks = new Map<number, LLink>()
|
||||
return {
|
||||
links: new Map<number, LLink>(),
|
||||
reroutes: new Map<number, Reroute>(),
|
||||
floatingLinks,
|
||||
getNodeById: (id: number) => graph.getNodeById(id),
|
||||
addFloatingLink: (link: LLink) => {
|
||||
floatingLinks.set(link.id, link)
|
||||
return link
|
||||
},
|
||||
removeReroute: () => true,
|
||||
add: (node: LGraphNode) => graph.add(node),
|
||||
}
|
||||
}
|
||||
|
||||
function createTestNode(id: number): LGraphNode {
|
||||
const node = new LGraphNode("test")
|
||||
node.id = id
|
||||
return node
|
||||
}
|
||||
|
||||
function createTestLink(id: number, sourceId: number, targetId: number, slotType: ISlotType = "number"): LLink {
|
||||
return new LLink(id, slotType, sourceId, 0, targetId, 0)
|
||||
}
|
||||
|
||||
const test = baseTest.extend<TestContext>({
|
||||
network: async ({}, use) => {
|
||||
const network = createNetwork()
|
||||
await use(network)
|
||||
const graph = new LGraph()
|
||||
const floatingLinks = new Map<number, LLink>()
|
||||
const reroutes = new Map<number, Reroute>()
|
||||
|
||||
await use({
|
||||
links: new Map<number, LLink>(),
|
||||
reroutes,
|
||||
floatingLinks,
|
||||
getNodeById: (id: number) => graph.getNodeById(id),
|
||||
addFloatingLink: (link: LLink) => {
|
||||
floatingLinks.set(link.id, link)
|
||||
return link
|
||||
},
|
||||
removeFloatingLink: (link: LLink) => floatingLinks.delete(link.id),
|
||||
getReroute: ((id: RerouteId | null | undefined) => id == null ? undefined : reroutes.get(id)) as LinkNetwork["getReroute"],
|
||||
removeReroute: (id: number) => reroutes.delete(id),
|
||||
add: (node: LGraphNode) => graph.add(node),
|
||||
})
|
||||
},
|
||||
|
||||
setConnectingLinks: async ({}, use: (mock: ReturnType<typeof vi.fn>) => Promise<void>) => {
|
||||
const mock = vi.fn()
|
||||
await use(mock)
|
||||
@@ -61,11 +50,26 @@ const test = baseTest.extend<TestContext>({
|
||||
const connector = new LinkConnector(setConnectingLinks)
|
||||
await use(connector)
|
||||
},
|
||||
createTestNode: async ({}, use) => {
|
||||
await use(createTestNode)
|
||||
|
||||
createTestNode: async ({ network }, use) => {
|
||||
await use((id: number): LGraphNode => {
|
||||
const node = new LGraphNode("test")
|
||||
node.id = id
|
||||
network.add(node)
|
||||
return node
|
||||
})
|
||||
},
|
||||
createTestLink: async ({}, use) => {
|
||||
await use(createTestLink)
|
||||
createTestLink: async ({ network }, use) => {
|
||||
await use((
|
||||
id: number,
|
||||
sourceId: number,
|
||||
targetId: number,
|
||||
slotType: ISlotType = "number",
|
||||
): LLink => {
|
||||
const link = new LLink(id, slotType, sourceId, 0, targetId, 0)
|
||||
network.links.set(link.id, link)
|
||||
return link
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -90,8 +94,6 @@ describe("LinkConnector", () => {
|
||||
const slotType: ISlotType = "number"
|
||||
sourceNode.addOutput("out", slotType)
|
||||
targetNode.addInput("in", slotType)
|
||||
network.add(sourceNode)
|
||||
network.add(targetNode)
|
||||
|
||||
const link = new LLink(1, slotType, 1, 0, 2, 0)
|
||||
network.links.set(link.id, link)
|
||||
@@ -122,8 +124,6 @@ describe("LinkConnector", () => {
|
||||
const slotType: ISlotType = "number"
|
||||
sourceNode.addOutput("out", slotType)
|
||||
targetNode.addInput("in", slotType)
|
||||
network.add(sourceNode)
|
||||
network.add(targetNode)
|
||||
|
||||
const link = new LLink(1, slotType, 1, 0, 2, 0)
|
||||
network.links.set(link.id, link)
|
||||
@@ -173,6 +173,36 @@ describe("LinkConnector", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Dragging from reroutes", () => {
|
||||
test("should handle dragging from reroutes", ({ network, connector, createTestNode, createTestLink }) => {
|
||||
const originNode = createTestNode(1)
|
||||
const targetNode = createTestNode(2)
|
||||
|
||||
const output = originNode.addOutput("out", "number")
|
||||
targetNode.addInput("in", "number")
|
||||
|
||||
const link = createTestLink(1, 1, 2)
|
||||
const reroute = new Reroute(1, network, [0, 0], undefined, [link.id])
|
||||
network.reroutes.set(reroute.id, reroute)
|
||||
link.parentId = reroute.id
|
||||
|
||||
connector.dragFromReroute(network, reroute)
|
||||
|
||||
expect(connector.state.connectingTo).toBe("input")
|
||||
expect(connector.state.draggingExistingLinks).toBe(false)
|
||||
expect(connector.renderLinks.length).toBe(1)
|
||||
|
||||
const renderLink = connector.renderLinks[0]
|
||||
expect(renderLink instanceof ToInputRenderLink).toBe(true)
|
||||
expect(renderLink.toType).toEqual("input")
|
||||
expect(renderLink.node).toEqual(originNode)
|
||||
expect(renderLink.fromSlot).toEqual(output)
|
||||
expect(renderLink.fromReroute).toEqual(reroute)
|
||||
expect(renderLink.fromDirection).toEqual(LinkDirection.NONE)
|
||||
expect(renderLink.network).toEqual(network)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Reset", () => {
|
||||
test("should reset state and clear links", ({ network, connector }) => {
|
||||
connector.state.connectingTo = "input"
|
||||
|
||||
Reference in New Issue
Block a user