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) }) }) })