From 9e64bf72787d3527843488cbc3587afde2058432 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Thu, 20 Mar 2025 22:20:11 +1100 Subject: [PATCH] [Test] Add LinkConnector tests (#808) --- test/LinkConnector.test.ts | 249 +++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 test/LinkConnector.test.ts diff --git a/test/LinkConnector.test.ts b/test/LinkConnector.test.ts new file mode 100644 index 0000000000..6b30126c5c --- /dev/null +++ b/test/LinkConnector.test.ts @@ -0,0 +1,249 @@ +import type { MovingRenderLink } from "@/canvas/MovingRenderLink" +import type { LinkNetwork } from "@/interfaces" +import type { ISlotType } from "@/interfaces" + +import { describe, expect, test as baseTest, vi } from "vitest" + +import { LinkConnector } from "@/canvas/LinkConnector" +import { LGraph } from "@/LGraph" +import { LGraphNode } from "@/LGraphNode" +import { LLink } from "@/LLink" +import { Reroute } from "@/Reroute" +import { LinkDirection } from "@/types/globalEnums" + +type TestNetwork = LinkNetwork & { add(node: LGraphNode): void } + +interface TestContext { + network: TestNetwork + connector: LinkConnector + setConnectingLinks: ReturnType + 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() + return { + links: new Map(), + reroutes: new Map(), + 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({ + network: async ({}, use) => { + const network = createNetwork() + await use(network) + }, + setConnectingLinks: async ({}, use: (mock: ReturnType) => Promise) => { + const mock = vi.fn() + await use(mock) + }, + connector: async ({ setConnectingLinks }, use) => { + const connector = new LinkConnector(setConnectingLinks) + await use(connector) + }, + createTestNode: async ({}, use) => { + await use(createTestNode) + }, + createTestLink: async ({}, use) => { + await use(createTestLink) + }, +}) + +describe("LinkConnector", () => { + test("should initialize with default state", ({ connector }) => { + expect(connector.state).toEqual({ + connectingTo: undefined, + multi: false, + draggingExistingLinks: false, + }) + expect(connector.renderLinks).toEqual([]) + expect(connector.inputLinks).toEqual([]) + expect(connector.outputLinks).toEqual([]) + expect(connector.hiddenReroutes.size).toBe(0) + }) + + describe("Moving Input Links", () => { + test("should handle moving input links", ({ network, connector, createTestNode }) => { + const sourceNode = createTestNode(1) + const targetNode = createTestNode(2) + + 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) + targetNode.inputs[0].link = link.id + + connector.moveInputLink(network, targetNode.inputs[0]) + + expect(connector.state.connectingTo).toBe("input") + expect(connector.state.draggingExistingLinks).toBe(true) + expect(connector.inputLinks).toContain(link) + expect(link._dragging).toBe(true) + }) + + test("should not move input link if already connecting", ({ connector, network }) => { + connector.state.connectingTo = "input" + + expect(() => { + connector.moveInputLink(network, { link: 1 } as any) + }).toThrow("Already dragging links.") + }) + }) + + describe("Moving Output Links", () => { + test("should handle moving output links", ({ network, connector, createTestNode }) => { + const sourceNode = createTestNode(1) + const targetNode = createTestNode(2) + + 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) + sourceNode.outputs[0].links = [link.id] + + connector.moveOutputLink(network, sourceNode.outputs[0]) + + expect(connector.state.connectingTo).toBe("output") + expect(connector.state.draggingExistingLinks).toBe(true) + expect(connector.state.multi).toBe(true) + expect(connector.outputLinks).toContain(link) + expect(link._dragging).toBe(true) + }) + + test("should not move output link if already connecting", ({ connector, network }) => { + connector.state.connectingTo = "output" + + expect(() => { + connector.moveOutputLink(network, { links: [1] } as any) + }).toThrow("Already dragging links.") + }) + }) + + describe("Dragging New Links", () => { + test("should handle dragging new link from output", ({ network, connector, createTestNode }) => { + const sourceNode = createTestNode(1) + const slotType: ISlotType = "number" + sourceNode.addOutput("out", slotType) + + connector.dragNewFromOutput(network, sourceNode, sourceNode.outputs[0]) + + expect(connector.state.connectingTo).toBe("input") + expect(connector.renderLinks.length).toBe(1) + expect(connector.state.draggingExistingLinks).toBe(false) + }) + + test("should handle dragging new link from input", ({ network, connector, createTestNode }) => { + const targetNode = createTestNode(1) + const slotType: ISlotType = "number" + targetNode.addInput("in", slotType) + + connector.dragNewFromInput(network, targetNode, targetNode.inputs[0]) + + expect(connector.state.connectingTo).toBe("output") + expect(connector.renderLinks.length).toBe(1) + expect(connector.state.draggingExistingLinks).toBe(false) + }) + }) + + describe("Reset", () => { + test("should reset state and clear links", ({ network, connector }) => { + connector.state.connectingTo = "input" + connector.state.multi = true + connector.state.draggingExistingLinks = true + + const link = new LLink(1, "number", 1, 0, 2, 0) + link._dragging = true + connector.inputLinks.push(link) + + const reroute = new Reroute(1, network) + reroute.pos = [0, 0] + reroute._dragging = true + connector.hiddenReroutes.add(reroute) + + connector.reset() + + expect(connector.state).toEqual({ + connectingTo: undefined, + multi: false, + draggingExistingLinks: false, + }) + expect(connector.renderLinks).toEqual([]) + expect(connector.inputLinks).toEqual([]) + expect(connector.outputLinks).toEqual([]) + expect(connector.hiddenReroutes.size).toBe(0) + expect(link._dragging).toBeUndefined() + expect(reroute._dragging).toBeUndefined() + }) + }) + + describe("Event Handling", () => { + test("should handle event listeners until reset", ({ connector, createTestNode }) => { + const listener = vi.fn() + connector.listenUntilReset("input-moved", listener) + + const sourceNode = createTestNode(1) + + const mockRenderLink = { + node: sourceNode, + fromSlot: { name: "out", type: "number" }, + fromPos: [0, 0], + fromDirection: LinkDirection.RIGHT, + toType: "input", + link: new LLink(1, "number", 1, 0, 2, 0), + } as MovingRenderLink + + connector.events.dispatch("input-moved", mockRenderLink) + expect(listener).toHaveBeenCalled() + + connector.reset() + connector.events.dispatch("input-moved", mockRenderLink) + expect(listener).toHaveBeenCalledTimes(1) + }) + }) + + describe("Export", () => { + test("should export current state", ({ network, connector }) => { + connector.state.connectingTo = "input" + connector.state.multi = true + + const link = new LLink(1, "number", 1, 0, 2, 0) + connector.inputLinks.push(link) + + const exported = connector.export(network) + + expect(exported.state).toEqual(connector.state) + expect(exported.inputLinks).toEqual(connector.inputLinks) + expect(exported.outputLinks).toEqual(connector.outputLinks) + expect(exported.renderLinks).toEqual(connector.renderLinks) + expect(exported.network).toBe(network) + }) + }) +})