From e229016ef9cdf2869b02ee2e6f4374f71c6203d8 Mon Sep 17 00:00:00 2001 From: DrJKL Date: Thu, 14 May 2026 16:16:44 -0700 Subject: [PATCH] test(subgraph): rewrite paste migration/auto-expose tests to assert behavior - Drop mock-only WidgetActions demote test; e2e covers the real path - Drop redundant negative paste-migration test - Rewrite the two paste-time hook-wiring tests in LGraphCanvas.clipboard.test.ts to wire the real flushProxyWidgetMigration / autoExposeKnownPreviewNodes helpers and assert observable post-paste state (proxyWidgets cleared, PreviewExposureStore exposures populated) instead of pinning hook invocations on static mocks. Amp-Thread-ID: https://ampcode.com/threads/T-019e2812-d683-710e-946f-9ddb9018ff5a Co-authored-by: Amp --- .../parameters/WidgetActions.test.ts | 56 +------ .../src/LGraphCanvas.clipboard.test.ts | 150 ++++++++---------- 2 files changed, 64 insertions(+), 142 deletions(-) diff --git a/src/components/rightSidePanel/parameters/WidgetActions.test.ts b/src/components/rightSidePanel/parameters/WidgetActions.test.ts index ed471d4ada..a47ca53905 100644 --- a/src/components/rightSidePanel/parameters/WidgetActions.test.ts +++ b/src/components/rightSidePanel/parameters/WidgetActions.test.ts @@ -9,7 +9,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' -import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import WidgetActions from './WidgetActions.vue' @@ -17,14 +16,9 @@ const { mockGetInputSpecForWidget } = vi.hoisted(() => ({ mockGetInputSpecForWidget: vi.fn() })) -const { mockDemoteWidget, mockPromoteWidget } = vi.hoisted(() => ({ - mockDemoteWidget: vi.fn(), - mockPromoteWidget: vi.fn() -})) - vi.mock('@/core/graph/subgraph/promotionUtils', () => ({ - demoteWidget: mockDemoteWidget, - promoteWidget: mockPromoteWidget, + demoteWidget: vi.fn(), + promoteWidget: vi.fn(), isLinkedPromotion: vi.fn(() => false) })) @@ -211,50 +205,4 @@ describe('WidgetActions', () => { expect(onResetToDefault).toHaveBeenCalledWith('option1') }) - - it('demotes promoted widget per parent with computed sourceNodeId', async () => { - const sourceNodeId = '7' - const widget = { - name: 'seed', - type: 'number', - value: 1, - label: 'Seed', - options: {}, - y: 0, - sourceNodeId, - sourceWidgetName: 'seed' - } as IBaseWidget - const node = fromAny({ - id: 5, - type: 'SubgraphNode', - title: 'Subgraph', - rootGraph: { id: 'graph-test' }, - computeSize: vi.fn(), - size: [200, 100], - isSubgraphNode: () => true - }) - const parent = fromAny({ id: 5 }) - const otherParent = fromAny({ id: 8 }) - - const { user } = renderWidgetActions(widget, node, { - parents: [parent, otherParent], - isShownOnParents: true - }) - - await user.click(screen.getByRole('button', { name: /Hide/ })) - - expect(mockDemoteWidget).toHaveBeenCalledTimes(2) - expect(mockDemoteWidget).toHaveBeenNthCalledWith( - 1, - expect.objectContaining({ id: sourceNodeId }), - widget, - [parent] - ) - expect(mockDemoteWidget).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ id: String(node.id) }), - widget, - [otherParent] - ) - }) }) diff --git a/src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts b/src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts index 747498bd65..0bc1129c13 100644 --- a/src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts +++ b/src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts @@ -2,6 +2,8 @@ import { createTestingPinia } from '@pinia/testing' import { setActivePinia } from 'pinia' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { flushProxyWidgetMigration } from '@/core/graph/subgraph/migration/proxyWidgetMigration' +import { autoExposeKnownPreviewNodes } from '@/core/graph/subgraph/promotionUtils' import type { Subgraph } from '@/lib/litegraph/src/litegraph' import { LGraph, @@ -17,6 +19,7 @@ import type { ExportedSubgraph, ISerialisedNode } from '@/lib/litegraph/src/types/serialisation' +import { usePreviewExposureStore } from '@/stores/previewExposureStore' vi.mock('@/renderer/core/canvas/canvasStore', () => ({ useCanvasStore: () => ({}) @@ -179,7 +182,7 @@ describe('remapClipboardSubgraphNodeIds', () => { }) }) -describe('_deserializeItems proxyWidgets migration', () => { +describe('_deserializeItems paste-time migration & auto-expose', () => { let originalFlush: typeof LGraph.proxyWidgetMigrationFlush let originalAutoExpose: typeof LGraph.autoExposePreviewNodes const registeredTypesToCleanup: string[] = [] @@ -199,6 +202,29 @@ describe('_deserializeItems proxyWidgets migration', () => { registeredTypesToCleanup.length = 0 }) + function registerSubgraphNodeTypeOnCreate(rootGraph: LGraph): void { + rootGraph.events.addEventListener('subgraph-created', (e) => { + const { subgraph } = e.detail + class TestSubgraphNode extends SubgraphNode { + constructor() { + super(rootGraph, subgraph as Subgraph, { + id: -1, + type: subgraph.id, + pos: [0, 0], + size: [100, 100], + inputs: [], + outputs: [], + flags: {}, + order: 0, + mode: 0 + }) + } + } + LiteGraph.registerNodeType(subgraph.id, TestSubgraphNode) + registeredTypesToCleanup.push(subgraph.id) + }) + } + function createCanvas(graph: LGraph): LGraphCanvas { const el = document.createElement('canvas') el.width = 800 @@ -234,31 +260,15 @@ describe('_deserializeItems proxyWidgets migration', () => { return new LGraphCanvas(el, graph, { skip_render: true }) } - it('invokes the migration hook for top-level pasted SubgraphNodes carrying legacy proxyWidgets', () => { - const flush = vi.fn() - LGraph.proxyWidgetMigrationFlush = flush + it('clears legacy proxyWidgets on a pasted SubgraphNode and applies host widget values', () => { + LGraph.proxyWidgetMigrationFlush = (hostNode, nodeData) => + flushProxyWidgetMigration({ + hostNode, + hostWidgetValues: nodeData?.widgets_values + }) const rootGraph = new LGraph() - rootGraph.events.addEventListener('subgraph-created', (e) => { - const { subgraph } = e.detail - class TestSubgraphNode extends SubgraphNode { - constructor() { - super(rootGraph, subgraph as Subgraph, { - id: -1, - type: subgraph.id, - pos: [0, 0], - size: [100, 100], - inputs: [], - outputs: [], - flags: {}, - order: 0, - mode: 0 - }) - } - } - LiteGraph.registerNodeType(subgraph.id, TestSubgraphNode) - registeredTypesToCleanup.push(subgraph.id) - }) + registerSubgraphNodeTypeOnCreate(rootGraph) const canvas = createCanvas(rootGraph) const subgraphId = createUuidv4() @@ -322,79 +332,29 @@ describe('_deserializeItems proxyWidgets migration', () => { canvas._deserializeItems(parsed, {}) - expect(flush).toHaveBeenCalledTimes(1) - const [hostNode, infoArg] = flush.mock.calls[0] - expect(hostNode).toBeInstanceOf(SubgraphNode) - expect(infoArg?.widgets_values).toStrictEqual([42]) + const pastedHosts = rootGraph.nodes.filter( + (n): n is SubgraphNode => n instanceof SubgraphNode + ) + expect(pastedHosts).toHaveLength(1) + expect(pastedHosts[0].properties.proxyWidgets).toBeUndefined() }) - it('does not invoke the migration hook for plain pasted nodes', () => { - const flush = vi.fn() - LGraph.proxyWidgetMigrationFlush = flush + it('auto-exposes preview nodes for pasted subgraphs that lack previewExposures', () => { + LGraph.autoExposePreviewNodes = (hostNode) => + autoExposeKnownPreviewNodes(hostNode) const rootGraph = new LGraph() - const canvas = createCanvas(rootGraph) - - const parsed: ClipboardItems = { - nodes: [ - { - id: 1, - type: 'test/plain', - pos: [0, 0], - size: [140, 80], - flags: {}, - order: 0, - mode: 0, - inputs: [], - outputs: [], - properties: {} - } - ], - groups: [], - reroutes: [], - links: [], - subgraphs: [] - } - - canvas._deserializeItems(parsed, {}) - - expect(flush).not.toHaveBeenCalled() - }) - - it('invokes the auto-expose hook for every pasted SubgraphNode (older clipboard data without previewExposures)', () => { - const autoExpose = vi.fn() - LGraph.autoExposePreviewNodes = autoExpose - - const rootGraph = new LGraph() - rootGraph.events.addEventListener('subgraph-created', (e) => { - const { subgraph } = e.detail - class TestSubgraphNode extends SubgraphNode { - constructor() { - super(rootGraph, subgraph as Subgraph, { - id: -1, - type: subgraph.id, - pos: [0, 0], - size: [100, 100], - inputs: [], - outputs: [], - flags: {}, - order: 0, - mode: 0 - }) - } - } - LiteGraph.registerNodeType(subgraph.id, TestSubgraphNode) - registeredTypesToCleanup.push(subgraph.id) - }) + registerSubgraphNodeTypeOnCreate(rootGraph) const canvas = createCanvas(rootGraph) const subgraphId = createUuidv4() + const interiorPreviewId = 5 const pastedSubgraph: ExportedSubgraph = { id: subgraphId, version: 1, revision: 0, state: { - lastNodeId: 5, + lastNodeId: interiorPreviewId, lastLinkId: 0, lastGroupId: 0, lastRerouteId: 0 @@ -408,7 +368,7 @@ describe('_deserializeItems proxyWidgets migration', () => { widgets: [], nodes: [ { - id: 5, + id: interiorPreviewId, type: 'PreviewImage', pos: [0, 0], size: [140, 80], @@ -447,7 +407,21 @@ describe('_deserializeItems proxyWidgets migration', () => { canvas._deserializeItems(parsed, {}) - expect(autoExpose).toHaveBeenCalledTimes(1) - expect(autoExpose.mock.calls[0][0]).toBeInstanceOf(SubgraphNode) + const pastedHost = rootGraph.nodes.find( + (n): n is SubgraphNode => n instanceof SubgraphNode + ) + expect(pastedHost).toBeDefined() + + const exposures = usePreviewExposureStore().getExposures( + rootGraph.id, + String(pastedHost!.id) + ) + const interiorIdAfterRemap = pastedHost!.subgraph.nodes[0].id + expect(exposures).toEqual([ + expect.objectContaining({ + sourceNodeId: String(interiorIdAfterRemap), + sourcePreviewName: '$$canvas-image-preview' + }) + ]) }) })