Files
ComfyUI_frontend/tests-ui/tests/renderer/extensions/vueNodes/components/NodeHeader.subgraph.test.ts
AustinMroz f2a0e5102e 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)
2025-12-11 23:37:34 -07:00

209 lines
5.4 KiB
TypeScript

/**
* Tests for NodeHeader subgraph functionality
*/
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: mockApp
}))
vi.mock('@/utils/graphTraversalUtil', () => ({
getNodeByLocatorId: vi.fn(),
getLocatorIdFromNodeData: vi.fn((nodeData) =>
nodeData.subgraphId
? `${nodeData.subgraphId}:${String(nodeData.id)}`
: String(nodeData.id)
)
}))
vi.mock('@/composables/useErrorHandling', () => ({
useErrorHandling: () => ({
toastErrorHandler: vi.fn()
})
}))
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: vi.fn((key) => key)
}),
createI18n: vi.fn(() => ({
global: {
t: vi.fn((key) => key)
}
}))
}))
vi.mock('@/i18n', () => ({
st: vi.fn((key) => key),
t: vi.fn((key) => key),
i18n: {
global: {
t: vi.fn((key) => key)
}
}
}))
describe('NodeHeader - Subgraph Functionality', () => {
// Helper to setup common mocks
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
if (hasGraph) mockApp.rootGraph = {}
else mockApp.rootGraph = undefined
vi.mocked(getNodeByLocatorId).mockReturnValue({
isSubgraphNode: (): this is SubgraphNode => isSubgraph
} as LGraphNode)
}
beforeEach(() => {
vi.clearAllMocks()
})
const createMockNodeData = (
id: string,
subgraphId?: string
): VueNodeData => ({
id,
title: 'Test Node',
type: 'TestNode',
mode: 0,
selected: false,
executing: false,
subgraphId,
widgets: [],
inputs: [],
outputs: [],
hasErrors: false,
flags: {}
})
const createWrapper = (props = {}) => {
return mount(NodeHeader, {
props,
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
mocks: {
$t: vi.fn((key: string) => key),
$primevue: { config: {} }
}
}
})
}
it('should show subgraph button for subgraph nodes', async () => {
await setupMocks(true) // isSubgraph = true
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})
await wrapper.vm.$nextTick()
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
expect(subgraphButton.exists()).toBe(true)
})
it('should not show subgraph button for regular nodes', async () => {
await setupMocks(false) // isSubgraph = false
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})
await wrapper.vm.$nextTick()
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
expect(subgraphButton.exists()).toBe(false)
})
it('should emit enter-subgraph event when button is clicked', async () => {
await setupMocks(true) // isSubgraph = true
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})
await wrapper.vm.$nextTick()
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
await subgraphButton.trigger('click')
expect(wrapper.emitted('enter-subgraph')).toBeTruthy()
expect(wrapper.emitted('enter-subgraph')).toHaveLength(1)
})
it('should handle subgraph context correctly', async () => {
await setupMocks(true) // isSubgraph = true
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1', 'subgraph-id'),
readonly: false
})
await wrapper.vm.$nextTick()
// Should call getNodeByLocatorId with correct locator ID
expect(vi.mocked(getNodeByLocatorId)).toHaveBeenCalledWith(
expect.anything(),
'subgraph-id:test-node-1'
)
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
expect(subgraphButton.exists()).toBe(true)
})
it('should handle missing graph gracefully', async () => {
await setupMocks(true, false) // isSubgraph = true, hasGraph = false
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})
await wrapper.vm.$nextTick()
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
expect(subgraphButton.exists()).toBe(false)
})
it('should prevent event propagation on double click', async () => {
await setupMocks(true) // isSubgraph = true
const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})
await wrapper.vm.$nextTick()
const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
// Mock event object
const mockEvent = {
stopPropagation: vi.fn()
}
// Trigger dblclick event
await subgraphButton.trigger('dblclick', mockEvent)
// Should prevent propagation (handled by @dblclick.stop directive)
// This is tested by ensuring the component doesn't error and renders correctly
expect(subgraphButton.exists()).toBe(true)
})
})