[feat] Show version-specific missing core nodes in workflow warnings (#4227)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-06-20 15:33:47 -07:00
committed by GitHub
parent 2ef760c599
commit b99214bf5e
12 changed files with 540 additions and 13 deletions

View File

@@ -0,0 +1,205 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import { mount } from '@vue/test-utils'
import PrimeVue from 'primevue/config'
import Message from 'primevue/message'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'
import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
// Mock the stores
vi.mock('@/stores/systemStatsStore', () => ({
useSystemStatsStore: vi.fn()
}))
const createMockNode = (type: string, version?: string): LGraphNode =>
// @ts-expect-error - Creating a partial mock of LGraphNode for testing purposes.
// We only need specific properties for our tests, not the full LGraphNode interface.
({
type,
properties: { cnr_id: 'comfy-core', ver: version },
id: 1,
title: type,
pos: [0, 0],
size: [100, 100],
flags: {},
graph: null,
mode: 0,
inputs: [],
outputs: []
})
describe('MissingCoreNodesMessage', () => {
const mockSystemStatsStore = {
systemStats: null as { system?: { comfyui_version?: string } } | null,
fetchSystemStats: vi.fn()
}
beforeEach(() => {
vi.clearAllMocks()
// Reset the mock store state
mockSystemStatsStore.systemStats = null
mockSystemStatsStore.fetchSystemStats = vi.fn()
// @ts-expect-error - Mocking the return value of useSystemStatsStore for testing.
// The actual store has more properties, but we only need these for our tests.
useSystemStatsStore.mockReturnValue(mockSystemStatsStore)
})
const mountComponent = (props = {}) => {
return mount(MissingCoreNodesMessage, {
global: {
plugins: [PrimeVue],
components: { Message },
mocks: {
$t: (key: string, params?: { version?: string }) => {
const translations: Record<string, string> = {
'loadWorkflowWarning.outdatedVersion': `Some nodes require a newer version of ComfyUI (current: ${params?.version}). Please update to use all nodes.`,
'loadWorkflowWarning.outdatedVersionGeneric':
'Some nodes require a newer version of ComfyUI. Please update to use all nodes.',
'loadWorkflowWarning.coreNodesFromVersion': `Requires ComfyUI ${params?.version}:`
}
return translations[key] || key
}
}
},
props: {
missingCoreNodes: {},
...props
}
})
}
it('does not render when there are no missing core nodes', () => {
const wrapper = mountComponent()
expect(wrapper.findComponent(Message).exists()).toBe(false)
})
it('renders message when there are missing core nodes', async () => {
const missingCoreNodes = {
'1.2.0': [createMockNode('TestNode', '1.2.0')]
}
const wrapper = mountComponent({ missingCoreNodes })
await nextTick()
expect(wrapper.findComponent(Message).exists()).toBe(true)
})
it('fetches and displays current ComfyUI version', async () => {
// Start with no systemStats to trigger fetch
mockSystemStatsStore.fetchSystemStats.mockImplementation(() => {
// Simulate the fetch setting the systemStats
mockSystemStatsStore.systemStats = {
system: { comfyui_version: '1.0.0' }
}
return Promise.resolve()
})
const missingCoreNodes = {
'1.2.0': [createMockNode('TestNode', '1.2.0')]
}
const wrapper = mountComponent({ missingCoreNodes })
// Wait for all async operations
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 0))
await nextTick()
expect(mockSystemStatsStore.fetchSystemStats).toHaveBeenCalled()
expect(wrapper.text()).toContain(
'Some nodes require a newer version of ComfyUI (current: 1.0.0)'
)
})
it('displays generic message when version is unavailable', async () => {
// Mock fetchSystemStats to resolve without setting systemStats
mockSystemStatsStore.fetchSystemStats.mockResolvedValue(undefined)
const missingCoreNodes = {
'1.2.0': [createMockNode('TestNode', '1.2.0')]
}
const wrapper = mountComponent({ missingCoreNodes })
// Wait for the async operations to complete
await nextTick()
await new Promise((resolve) => setTimeout(resolve, 0))
await nextTick()
expect(wrapper.text()).toContain(
'Some nodes require a newer version of ComfyUI. Please update to use all nodes.'
)
})
it('groups nodes by version and displays them', async () => {
const missingCoreNodes = {
'1.2.0': [
createMockNode('NodeA', '1.2.0'),
createMockNode('NodeB', '1.2.0')
],
'1.3.0': [createMockNode('NodeC', '1.3.0')]
}
const wrapper = mountComponent({ missingCoreNodes })
await nextTick()
const text = wrapper.text()
expect(text).toContain('Requires ComfyUI 1.3.0:')
expect(text).toContain('NodeC')
expect(text).toContain('Requires ComfyUI 1.2.0:')
expect(text).toContain('NodeA, NodeB')
})
it('sorts versions in descending order', async () => {
const missingCoreNodes = {
'1.1.0': [createMockNode('Node1', '1.1.0')],
'1.3.0': [createMockNode('Node3', '1.3.0')],
'1.2.0': [createMockNode('Node2', '1.2.0')]
}
const wrapper = mountComponent({ missingCoreNodes })
await nextTick()
const text = wrapper.text()
const version13Index = text.indexOf('1.3.0')
const version12Index = text.indexOf('1.2.0')
const version11Index = text.indexOf('1.1.0')
expect(version13Index).toBeLessThan(version12Index)
expect(version12Index).toBeLessThan(version11Index)
})
it('removes duplicate node names within the same version', async () => {
const missingCoreNodes = {
'1.2.0': [
createMockNode('DuplicateNode', '1.2.0'),
createMockNode('DuplicateNode', '1.2.0'),
createMockNode('UniqueNode', '1.2.0')
]
}
const wrapper = mountComponent({ missingCoreNodes })
await nextTick()
const text = wrapper.text()
// Should only appear once in the sorted list
expect(text).toContain('DuplicateNode, UniqueNode')
// Count occurrences of 'DuplicateNode' - should be only 1
const matches = text.match(/DuplicateNode/g) || []
expect(matches.length).toBe(1)
})
it('handles nodes with missing version info', async () => {
const missingCoreNodes = {
'': [createMockNode('NoVersionNode')]
}
const wrapper = mountComponent({ missingCoreNodes })
await nextTick()
expect(wrapper.text()).toContain('Requires ComfyUI unknown:')
expect(wrapper.text()).toContain('NoVersionNode')
})
})