import { renderHook } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { BlockEnum } from '@/app/components/workflow/types' import { useRagPipelineSearch } from '../use-rag-pipeline-search' const mockNodes: Array<{ id: string, data: Record }> = [] vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({ default: () => mockNodes, })) const mockHandleNodeSelect = vi.fn() vi.mock('@/app/components/workflow/hooks/use-nodes-interactions', () => ({ useNodesInteractions: () => ({ handleNodeSelect: mockHandleNodeSelect, }), })) vi.mock('@/app/components/workflow/hooks/use-tool-icon', () => ({ useGetToolIcon: () => () => null, })) vi.mock('@/app/components/workflow/block-icon', () => ({ default: () => null, })) type MockSearchResult = { title: string type: string description?: string metadata?: { nodeId: string } } const mockRagPipelineNodesAction = vi.hoisted(() => { return { searchFn: undefined as undefined | ((query: string) => MockSearchResult[]) } }) vi.mock('@/app/components/goto-anything/actions/rag-pipeline-nodes', () => ({ ragPipelineNodesAction: mockRagPipelineNodesAction, })) const mockCleanupListener = vi.fn() vi.mock('@/app/components/workflow/utils/node-navigation', () => ({ setupNodeSelectionListener: () => mockCleanupListener, })) describe('useRagPipelineSearch', () => { beforeEach(() => { vi.clearAllMocks() mockNodes.length = 0 mockRagPipelineNodesAction.searchFn = undefined }) afterEach(() => { vi.clearAllMocks() }) describe('hook lifecycle', () => { it('should return null', () => { const { result } = renderHook(() => useRagPipelineSearch()) expect(result.current).toBeNull() }) it('should register search function when nodes exist', () => { mockNodes.push({ id: 'node-1', data: { type: BlockEnum.LLM, title: 'LLM Node', desc: '' }, }) renderHook(() => useRagPipelineSearch()) expect(mockRagPipelineNodesAction.searchFn).toBeDefined() }) it('should not register search function when no nodes', () => { renderHook(() => useRagPipelineSearch()) expect(mockRagPipelineNodesAction.searchFn).toBeUndefined() }) it('should cleanup search function on unmount', () => { mockNodes.push({ id: 'node-1', data: { type: BlockEnum.Start, title: 'Start', desc: '' }, }) const { unmount } = renderHook(() => useRagPipelineSearch()) expect(mockRagPipelineNodesAction.searchFn).toBeDefined() unmount() expect(mockRagPipelineNodesAction.searchFn).toBeUndefined() }) it('should setup node selection listener', () => { const { unmount } = renderHook(() => useRagPipelineSearch()) unmount() expect(mockCleanupListener).toHaveBeenCalled() }) }) describe('search functionality', () => { beforeEach(() => { mockNodes.push( { id: 'node-1', data: { type: BlockEnum.LLM, title: 'GPT Model', desc: 'Language model' }, }, { id: 'node-2', data: { type: BlockEnum.KnowledgeRetrieval, title: 'Knowledge Base', desc: 'Search knowledge', dataset_ids: ['ds1', 'ds2'] }, }, { id: 'node-3', data: { type: BlockEnum.Tool, title: 'Web Search', desc: '', tool_description: 'Search the web', tool_label: 'WebSearch' }, }, { id: 'node-4', data: { type: BlockEnum.Start, title: 'Start Node', desc: 'Pipeline entry' }, }, ) }) it('should find nodes by title', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('GPT') expect(results.length).toBeGreaterThan(0) expect(results[0].title).toBe('GPT Model') }) it('should find nodes by type', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn(BlockEnum.LLM) expect(results.some(r => r.title === 'GPT Model')).toBe(true) }) it('should find nodes by description', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('knowledge') expect(results.some(r => r.title === 'Knowledge Base')).toBe(true) }) it('should return all nodes when search term is empty', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('') expect(results.length).toBe(4) }) it('should sort by alphabetical order when no search term', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('') const titles = results.map(r => r.title) const sortedTitles = [...titles].sort((a, b) => a.localeCompare(b)) expect(titles).toEqual(sortedTitles) }) it('should sort by relevance score when search term provided', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('Search') expect(results[0].title).toBe('Web Search') }) it('should return empty array when no nodes match', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('nonexistent-xyz-12345') expect(results).toEqual([]) }) it('should enhance Tool node description from tool_description', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('web') const toolResult = results.find(r => r.title === 'Web Search') expect(toolResult).toBeDefined() expect(toolResult?.description).toContain('Search the web') }) it('should include metadata with nodeId', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('Start') const startResult = results.find(r => r.title === 'Start Node') expect(startResult?.metadata?.nodeId).toBe('node-4') }) it('should set result type as workflow-node', () => { renderHook(() => useRagPipelineSearch()) const searchFn = mockRagPipelineNodesAction.searchFn! const results = searchFn('Start') expect(results[0].type).toBe('workflow-node') }) }) })