Compare commits

...

2 Commits

Author SHA1 Message Date
Glary-Bot
0e1281c36d chore: drop redundant comment on bringNodeToFront call 2026-05-12 05:08:22 +00:00
Glary-Bot
666a82e186 fix: bring newly created nodes to front in createNode helper
Nodes created via paste/drop of media assets (LoadImage, LoadAudio,
LoadVideo) defaulted to zIndex 0 in the Vue node layout store, causing
them to render underneath existing nodes in Nodes 2.0.

Have createNode() call useNodeZIndex().bringNodeToFront() after the
node is added, which sets its zIndex to max(zIndex) + 1 across the
graph.

- Fixes FE-555
2026-05-06 02:47:03 +00:00
2 changed files with 134 additions and 4 deletions

View File

@@ -1,9 +1,20 @@
import { describe, expect, it } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { LGraph, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { createTestSubgraph } from '@/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers'
import { resolveNode } from './litegraphUtil'
import { createNode, resolveNode } from './litegraphUtil'
const mockBringNodeToFront = vi.fn()
vi.mock('@/renderer/extensions/vueNodes/composables/useNodeZIndex', () => ({
useNodeZIndex: () => ({ bringNodeToFront: mockBringNodeToFront })
}))
vi.mock('@/platform/updates/common/toastStore', () => ({
useToastStore: () => ({ addAlert: vi.fn() })
}))
describe('resolveNode', () => {
it('returns undefined when graph is null', () => {
@@ -68,3 +79,118 @@ describe('resolveNode', () => {
expect(resolveNode(targetNode.id, rootGraph)).toBe(targetNode)
})
})
describe('createNode', () => {
function makeCanvas(graph: LGraph): LGraphCanvas {
return {
graph,
graph_mouse: [100, 200] as [number, number]
} as Partial<LGraphCanvas> as LGraphCanvas
}
beforeEach(() => {
mockBringNodeToFront.mockClear()
})
it('returns null when name is empty', async () => {
const result = await createNode(makeCanvas(new LGraph()), '')
expect(result).toBeNull()
expect(mockBringNodeToFront).not.toHaveBeenCalled()
})
it('places the new node at the canvas graph_mouse position', async () => {
const newNode = new LGraphNode('LoadImage')
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(newNode)
const graph = new LGraph()
const result = await createNode(makeCanvas(graph), 'LoadImage')
expect(result).toBe(newNode)
expect(Array.from(newNode.pos)).toEqual([100, 200])
createNodeSpy.mockRestore()
})
it('brings the newly created node to the front via useNodeZIndex', async () => {
const newNode = new LGraphNode('LoadImage')
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(newNode)
const graph = new LGraph()
const result = await createNode(makeCanvas(graph), 'LoadImage')
expect(result).toBe(newNode)
expect(mockBringNodeToFront).toHaveBeenCalledTimes(1)
expect(mockBringNodeToFront).toHaveBeenCalledWith(newNode.id)
createNodeSpy.mockRestore()
})
it('brings the new node to the front AFTER it has been added to the graph', async () => {
const newNode = new LGraphNode('LoadImage')
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(newNode)
const graph = new LGraph()
const callOrder: string[] = []
const originalAdd = graph.add.bind(graph)
graph.add = vi.fn((...args: Parameters<typeof originalAdd>) => {
callOrder.push('graph.add')
return originalAdd(...args)
}) as typeof graph.add
mockBringNodeToFront.mockImplementation(() => {
callOrder.push('bringNodeToFront')
})
await createNode(makeCanvas(graph), 'LoadImage')
expect(callOrder).toEqual(['graph.add', 'bringNodeToFront'])
createNodeSpy.mockRestore()
})
it('does not call bringNodeToFront when LiteGraph fails to create the node', async () => {
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(null)
const graph = new LGraph()
const result = await createNode(makeCanvas(graph), 'NonExistentNode')
expect(result).toBeNull()
expect(mockBringNodeToFront).not.toHaveBeenCalled()
createNodeSpy.mockRestore()
})
it('does not call bringNodeToFront when the canvas has no graph', async () => {
const newNode = new LGraphNode('LoadImage')
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(newNode)
const canvas = {
graph: null,
graph_mouse: [0, 0] as [number, number]
} as Partial<LGraphCanvas> as LGraphCanvas
const result = await createNode(canvas, 'LoadImage')
expect(result).toBeNull()
expect(mockBringNodeToFront).not.toHaveBeenCalled()
createNodeSpy.mockRestore()
})
it('does not call bringNodeToFront when graph.add fails to attach the node', async () => {
const newNode = new LGraphNode('LoadImage')
const createNodeSpy = vi
.spyOn(LiteGraph, 'createNode')
.mockReturnValue(newNode)
const graph = new LGraph()
graph.add = vi.fn(() => null) as unknown as typeof graph.add
await createNode(makeCanvas(graph), 'LoadImage')
expect(mockBringNodeToFront).not.toHaveBeenCalled()
createNodeSpy.mockRestore()
})
})

View File

@@ -24,6 +24,7 @@ import type {
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useNodeZIndex } from '@/renderer/extensions/vueNodes/composables/useNodeZIndex'
import { app } from '@/scripts/app'
import { t } from '@/i18n'
@@ -57,7 +58,10 @@ export async function createNode(
newNode.pos = [posX, posY]
const addedNode = graph.add(newNode) ?? null
if (addedNode) graph.change()
if (addedNode) {
useNodeZIndex().bringNodeToFront(addedNode.id)
graph.change()
}
return addedNode
} else {
useToastStore().addAlert(t('assetBrowser.failedToCreateNode'))