import { render, waitFor } from '@testing-library/vue'
import { ref } from 'vue'
import { describe, expect, it, vi } from 'vitest'
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
import EssentialNodesPanel from './EssentialNodesPanel.vue'
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: () => ({
get: vi.fn().mockReturnValue('left')
})
}))
vi.mock('@/composables/node/useNodeDragToCanvas', () => ({
useNodeDragToCanvas: () => ({
startDrag: vi.fn(),
handleNativeDrop: vi.fn(),
cancelDrag: vi.fn()
})
}))
vi.mock('@/components/node/NodePreviewCard.vue', () => ({
default: { template: '
' }
}))
describe('EssentialNodesPanel', () => {
function createMockNode(
name: string
): RenderedTreeExplorerNode {
return {
key: `node-${name}`,
label: name,
icon: 'icon-[comfy--node]',
type: 'node',
totalLeaves: 1,
data: {
name,
display_name: name
} as ComfyNodeDefImpl
}
}
function createMockFolder(
name: string,
children: RenderedTreeExplorerNode[]
): RenderedTreeExplorerNode {
return {
key: `folder-${name}`,
label: name,
icon: 'icon-[lucide--folder]',
type: 'folder',
totalLeaves: children.length,
children
}
}
function createMockRoot(): RenderedTreeExplorerNode {
return {
key: 'root',
label: 'Root',
icon: '',
type: 'folder',
totalLeaves: 6,
children: [
createMockFolder('images', [
createMockNode('LoadImage'),
createMockNode('SaveImage')
]),
createMockFolder('video', [
createMockNode('LoadVideo'),
createMockNode('SaveVideo')
]),
createMockFolder('audio', [
createMockNode('LoadAudio'),
createMockNode('SaveAudio')
])
]
}
}
function renderComponent(
root = createMockRoot(),
expandedKeys: string[] = [],
flatNodes: RenderedTreeExplorerNode[] = []
) {
const WrapperComponent = {
template: ``,
components: { EssentialNodesPanel },
setup() {
const keys = ref(expandedKeys)
return { root, flatNodes, keys }
}
}
return render(WrapperComponent, {
global: {
stubs: {
Teleport: true,
TabsContent: {
template: '
'
},
CollapsibleRoot: {
template:
'
',
props: ['open'],
emits: ['update:open']
},
CollapsibleTrigger: {
template:
''
},
CollapsibleContent: {
template: '
'
},
EssentialNodeCard: {
template: ''
}
}
}
})
}
describe('folder rendering', () => {
it('should render all top-level folders', () => {
const { container } = renderComponent()
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
expect(container.querySelectorAll('.collapsible-trigger')).toHaveLength(3)
})
it('should display folder labels', () => {
const { container } = renderComponent()
expect(container.textContent).toContain('images')
expect(container.textContent).toContain('video')
expect(container.textContent).toContain('audio')
})
})
describe('default expansion', () => {
it('should expand all folders by default when expandedKeys is empty', async () => {
const { container } = renderComponent(createMockRoot(), [])
await waitFor(() => {
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const roots = container.querySelectorAll('.collapsible-root')
expect(roots).toHaveLength(3)
expect(roots[0].getAttribute('data-state')).toBe('open')
expect(roots[1].getAttribute('data-state')).toBe('open')
expect(roots[2].getAttribute('data-state')).toBe('open')
})
})
it('should respect provided expandedKeys', async () => {
const { container } = renderComponent(createMockRoot(), ['folder-audio'])
await waitFor(() => {
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const roots = container.querySelectorAll('.collapsible-root')
expect(roots).toHaveLength(3)
expect(roots[0].getAttribute('data-state')).toBe('closed')
expect(roots[1].getAttribute('data-state')).toBe('closed')
expect(roots[2].getAttribute('data-state')).toBe('open')
})
})
it('should expand all provided keys', async () => {
const { container } = renderComponent(createMockRoot(), [
'folder-images',
'folder-video',
'folder-audio'
])
await waitFor(() => {
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const roots = container.querySelectorAll('.collapsible-root')
expect(roots).toHaveLength(3)
expect(roots[0].getAttribute('data-state')).toBe('open')
expect(roots[1].getAttribute('data-state')).toBe('open')
expect(roots[2].getAttribute('data-state')).toBe('open')
})
})
})
describe('with single folder', () => {
it('should expand only one folder when there is only one', async () => {
const root: RenderedTreeExplorerNode = {
key: 'root',
label: 'Root',
icon: '',
type: 'folder',
totalLeaves: 2,
children: [
createMockFolder('images', [
createMockNode('LoadImage'),
createMockNode('SaveImage')
])
]
}
const { container } = renderComponent(root, [])
await waitFor(() => {
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const roots = container.querySelectorAll('.collapsible-root')
expect(roots).toHaveLength(1)
expect(roots[0].getAttribute('data-state')).toBe('open')
})
})
})
describe('node cards', () => {
it('should render node cards for each node in expanded folders', () => {
const { container } = renderComponent(createMockRoot(), ['folder-images'])
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const cards = container.querySelectorAll(
'[data-testid="essential-node-card"]'
)
expect(cards.length).toBeGreaterThanOrEqual(2)
})
})
describe('flat nodes mode', () => {
it('should render flat grid without collapsible folders when flatNodes is provided', () => {
const flatNodes = [
createMockNode('LoadAudio'),
createMockNode('LoadImage'),
createMockNode('SaveImage')
]
const { container } = renderComponent(createMockRoot(), [], flatNodes)
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
expect(container.querySelectorAll('.collapsible-root')).toHaveLength(0)
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const cards = container.querySelectorAll(
'[data-testid="essential-node-card"]'
)
expect(cards).toHaveLength(3)
})
})
})