diff --git a/src/LGraph.ts b/src/LGraph.ts index 60c866efb..5c457959c 100644 --- a/src/LGraph.ts +++ b/src/LGraph.ts @@ -900,6 +900,13 @@ export class LGraph implements LinkNetwork, Serialisable { } } + // Floating links + for (const link of this.floatingLinks.values()) { + if (link.origin_id === node.id || link.target_id === node.id) { + this.removeFloatingLink(link) + } + } + // callback node.onRemoved?.() @@ -1401,6 +1408,10 @@ export class LGraph implements LinkNetwork, Serialisable { if (reroute.floatingLinkIds.size === 0) { delete reroute.floating } + + if (reroute.floatingLinkIds.size === 0 && reroute.linkIds.size === 0) { + this.removeReroute(reroute.id) + } } } diff --git a/test/LGraph.test.ts b/test/LGraph.test.ts index dd393f300..4b3675bf5 100644 --- a/test/LGraph.test.ts +++ b/test/LGraph.test.ts @@ -32,6 +32,18 @@ describe("LGraph", () => { const fromOldSchema = new LGraph(oldSchemaGraph) expect(fromOldSchema).toMatchSnapshot("oldSchemaGraph") }) + + describe("Reroutes", () => { + test("Floating reroute should be removed when node and link are removed", ({ expect, floatingLinkGraph }) => { + const graph = new LGraph(floatingLinkGraph) + expect(graph.nodes.length).toBe(1) + graph.remove(graph.nodes[0]) + expect(graph.nodes.length).toBe(0) + expect(graph.links.size).toBe(0) + expect(graph.floatingLinks.size).toBe(0) + expect(graph.reroutes.size).toBe(0) + }) + }) }) describe("Legacy LGraph Compatibility Layer", () => { diff --git a/test/assets/floatingLink.json b/test/assets/floatingLink.json new file mode 100644 index 000000000..b10ee8b42 --- /dev/null +++ b/test/assets/floatingLink.json @@ -0,0 +1,68 @@ +{ + "id": "d175890f-716a-4ece-ba33-1d17a513b7be", + "revision": 0, + "last_node_id": 2, + "last_link_id": 1, + "nodes": [ + { + "id": 2, + "type": "VAEDecode", + "pos": [63.44815444946289, 178.71633911132812], + "size": [210, 46], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": null + }, + { + "name": "vae", + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [] + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + }, + "widgets_values": [] + } + ], + "links": [], + "floatingLinks": [ + { + "id": 4, + "origin_id": 2, + "origin_slot": 0, + "target_id": -1, + "target_slot": -1, + "type": "IMAGE", + "parentId": 1 + } + ], + "groups": [], + "config": {}, + "extra": { + "linkExtensions": [], + "reroutes": [ + { + "id": 1, + "pos": [393.2383117675781, 194.61941528320312], + "linkIds": [], + "floating": { + "slotType": "output" + } + } + ] + }, + "version": 0.4 +} diff --git a/test/testExtensions.ts b/test/testExtensions.ts index 21784f913..c70f1bb6f 100644 --- a/test/testExtensions.ts +++ b/test/testExtensions.ts @@ -5,12 +5,14 @@ import { test as baseTest } from "vitest" import { LGraph } from "@/LGraph" import { LiteGraph } from "@/litegraph" +import floatingLink from "./assets/floatingLink.json" import { basicSerialisableGraph, minimalSerialisableGraph, oldSchemaGraph } from "./assets/testGraphs" interface LitegraphFixtures { minimalGraph: LGraph minimalSerialisableGraph: SerialisableGraph oldSchemaGraph: ISerialisedGraph + floatingLinkGraph: ISerialisedGraph } /** These fixtures alter global state, and are difficult to reset. Relies on a single test per-file to reset state. */ @@ -29,6 +31,7 @@ export const test = baseTest.extend({ }, minimalSerialisableGraph: structuredClone(minimalSerialisableGraph), oldSchemaGraph: structuredClone(oldSchemaGraph), + floatingLinkGraph: structuredClone(floatingLink as unknown as ISerialisedGraph), }) /** Test that use {@link DirtyFixtures}. One test per file. */ diff --git a/test/tsconfig.json b/test/tsconfig.json index b6f8c45b6..4c8bef17a 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -5,7 +5,8 @@ "noEmit": true, "strict": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "resolveJsonModule": true }, "include": ["**/*"] }