Cleanup app.graph usage (#7399)

Prior to the release of subgraphs, there was a single graph accessed
through `app.graph`. Now that there's multiple graphs, there's a lot of
code that needs to be reviewed and potentially updated depending on if
it cares about nearby nodes, all nodes, or something else requiring
specific attention.

This was done by simply changing the type of `app.graph` to unknown so
the typechecker will complain about every place it's currently used.
References were then updated to `app.rootGraph` if the previous usage
was correct, or actually rewritten.

By not getting rid of `app.graph`, this change already ensures that
there's no loss of functionality for custom nodes, but the prior typing
of `app.graph` can always be restored if future dissuasion of
`app.graph` usage creates issues.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7399-Cleanup-app-graph-usage-2c76d73d365081178743dfdcf07f44d0)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2025-12-11 22:37:34 -08:00
committed by GitHub
parent 88bdc605a7
commit f2a0e5102e
39 changed files with 192 additions and 209 deletions

View File

@@ -32,7 +32,7 @@ vi.mock('@/scripts/app', () => {
}
}),
canvas: mockCanvas,
graph: {
rootGraph: {
clear: mockGraphClear
}
}
@@ -161,7 +161,7 @@ describe('useCoreCommands', () => {
await clearCommand.function()
expect(app.clean).toHaveBeenCalled()
expect(app.graph.clear).toHaveBeenCalled()
expect(app.rootGraph.clear).toHaveBeenCalled()
expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared')
})
@@ -178,7 +178,7 @@ describe('useCoreCommands', () => {
await clearCommand.function()
expect(app.clean).toHaveBeenCalled()
expect(app.graph.clear).not.toHaveBeenCalled()
expect(app.rootGraph.clear).not.toHaveBeenCalled()
// Should only remove user nodes, not input/output nodes
expect(mockSubgraph.remove).toHaveBeenCalledTimes(2)
@@ -212,7 +212,7 @@ describe('useCoreCommands', () => {
// Should not clear anything when user cancels
expect(app.clean).not.toHaveBeenCalled()
expect(app.graph.clear).not.toHaveBeenCalled()
expect(app.rootGraph.clear).not.toHaveBeenCalled()
expect(api.dispatchCustomEvent).not.toHaveBeenCalled()
})
})

View File

@@ -1,8 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
@@ -40,12 +39,10 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
}))
}))
const mockApp: { rootGraph?: Partial<LGraph> } = vi.hoisted(() => ({}))
vi.mock('@/scripts/app', () => ({
app: {
graph: {
nodes: []
}
}
app: mockApp
}))
vi.mock('@/utils/graphTraversalUtil', () => ({
@@ -107,9 +104,8 @@ describe('useMissingNodes', () => {
nodeDefsByName: {}
})
// Reset app.graph.nodes
// @ts-expect-error - app.graph.nodes is readonly, but we need to modify it for testing.
app.graph.nodes = []
// Reset app.rootGraph.nodes
mockApp.rootGraph = { nodes: [] }
// Default mock for collectAllNodes - returns empty array
mockCollectAllNodes.mockReturnValue([])
@@ -306,13 +302,7 @@ describe('useMissingNodes', () => {
})
describe('missing core nodes detection', () => {
const createMockNode = (
type: string,
packId?: string,
version?: string
): LGraphNode =>
// @ts-expect-error - Creating a partial mock of LGraphNode for testing.
// We only need specific properties for our tests, not the full LGraphNode interface.
const createMockNode = (type: string, packId?: string, version?: string) =>
({
type,
properties: { cnr_id: packId, ver: version },
@@ -325,7 +315,7 @@ describe('useMissingNodes', () => {
mode: 0,
inputs: [],
outputs: []
})
}) as unknown as LGraphNode
it('identifies missing core nodes not in nodeDefStore', () => {
const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0')
@@ -467,8 +457,7 @@ describe('useMissingNodes', () => {
it('calls collectAllNodes with the app graph and filter function', () => {
const mockGraph = { nodes: [], subgraphs: new Map() }
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockGraph
mockApp.rootGraph = mockGraph
const { missingCoreNodes } = useMissingNodes()
// Access the computed to trigger the function
@@ -490,8 +479,7 @@ describe('useMissingNodes', () => {
it('filter function correctly identifies missing core nodes', () => {
const mockGraph = { nodes: [], subgraphs: new Map() }
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockGraph
mockApp.rootGraph = mockGraph
mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {
@@ -579,14 +567,13 @@ describe('useMissingNodes', () => {
subgraph: mockSubgraph,
type: 'SubgraphContainer',
properties: { cnr_id: 'custom-pack' }
}
} as unknown as LGraphNode
const mockMainGraph = {
nodes: [mainMissingNode, mockSubgraphNode]
}
} as Partial<LGraph> as LGraph
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockMainGraph
mockApp.rootGraph = mockMainGraph
mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {

View File

@@ -5,15 +5,19 @@ import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type {
LGraph,
LGraphNode,
SubgraphNode
} from '@/lib/litegraph/src/litegraph'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
const mockApp: { rootGraph?: Partial<LGraph> } = vi.hoisted(() => ({}))
// Mock dependencies
vi.mock('@/scripts/app', () => ({
app: {
graph: null as any
}
app: mockApp
}))
vi.mock('@/utils/graphTraversalUtil', () => ({
@@ -55,17 +59,12 @@ vi.mock('@/i18n', () => ({
describe('NodeHeader - Subgraph Functionality', () => {
// Helper to setup common mocks
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
const { app } = await import('@/scripts/app')
if (hasGraph) {
;(app as any).graph = { rootGraph: {} }
} else {
;(app as any).graph = null
}
if (hasGraph) mockApp.rootGraph = {}
else mockApp.rootGraph = undefined
vi.mocked(getNodeByLocatorId).mockReturnValue({
isSubgraphNode: () => isSubgraph
} as any)
isSubgraphNode: (): this is SubgraphNode => isSubgraph
} as LGraphNode)
}
beforeEach(() => {

View File

@@ -38,9 +38,9 @@ vi.mock('@/composables/node/useNodeProgressText', () => ({
// Mock the app import with proper implementation
vi.mock('@/scripts/app', () => ({
app: {
graph: {
rootGraph: {
getNodeById: vi.fn(),
_nodes: [] // Add _nodes array for workflowStore iteration
nodes: [] // Add nodes array for workflowStore iteration
},
revokePreviews: vi.fn(),
nodePreviewImages: {}
@@ -66,7 +66,7 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
// Mock subgraph structure
const mockSubgraph = {
id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
_nodes: []
nodes: []
}
const mockNode = {
@@ -75,8 +75,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
subgraph: mockSubgraph
} as any
// Mock app.graph.getNodeById to return the mock node
vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode)
// Mock app.rootGraph.getNodeById to return the mock node
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode)
const result = store.executionIdToNodeLocatorId('123:456')
@@ -98,8 +98,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
})
it('should return undefined when conversion fails', () => {
// Mock app.graph.getNodeById to return null (node not found)
vi.mocked(app.graph.getNodeById).mockReturnValue(null)
// Mock app.rootGraph.getNodeById to return null (node not found)
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(null)
expect(store.executionIdToNodeLocatorId('999:456')).toBe(undefined)
})
@@ -171,7 +171,8 @@ describe('useExecutionStore - Node Error Lookups', () => {
const subgraphUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
const mockSubgraph = {
id: subgraphUuid,
_nodes: []
getNodeById: vi.fn(),
nodes: []
}
const mockNode = {
@@ -180,7 +181,7 @@ describe('useExecutionStore - Node Error Lookups', () => {
subgraph: mockSubgraph
} as any
vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode)
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode)
store.lastNodeErrors = {
'123:456': {

View File

@@ -622,7 +622,7 @@ describe('useWorkflowStore', () => {
mockSubgraph.rootGraph = mockRootGraph as any
vi.mocked(comfyApp).graph = mockRootGraph as any
vi.mocked(comfyApp).rootGraph = mockRootGraph as any
vi.mocked(comfyApp.canvas).subgraph = mockSubgraph as any
store.activeSubgraph = mockSubgraph as any
})