import React from 'react'; import { render, screen, fireEvent, waitFor, within, } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { AgentDiscoveryPage } from '../../../src/renderer/pages/AgentDiscoveryPage'; import { useAgentStore } from '../../../src/renderer/stores/agentStore'; jest.mock('../../../src/renderer/stores/agentStore'); jest.mock('../../../src/renderer/hooks/useHRLImageUrl'); jest.mock('sonner', () => ({ toast: { success: jest.fn(), error: jest.fn(), info: jest.fn(), }, })); jest.mock('../../../src/renderer/components/ui/input', () => ({ Input: ({ value, onChange, placeholder, ...props }: any) => ( onChange?.(e.target.value)} placeholder={placeholder} {...props} /> ), })); jest.mock('../../../src/renderer/components/ui/Button', () => ({ Button: ({ children, onClick, disabled, variant, ...props }: any) => ( ), })); jest.mock('../../../src/renderer/components/ui/badge', () => ({ Badge: ({ children, ...props }: any) => ( {children} ), })); jest.mock('../../../src/renderer/components/ui/dropdown-menu', () => ({ DropdownMenu: ({ children }: any) => (
{children}
), DropdownMenuContent: ({ children }: any) => (
{children}
), DropdownMenuItem: ({ children, onClick }: any) => (
{children}
), DropdownMenuTrigger: ({ children }: any) => (
{children}
), })); jest.mock('../../../src/renderer/components/ui/dialog', () => ({ Dialog: ({ children, open }: any) => open ?
{children}
: null, DialogContent: ({ children }: any) => (
{children}
), DialogHeader: ({ children }: any) => (
{children}
), DialogTitle: ({ children }: any) => (
{children}
), })); jest.mock('../../../src/renderer/components/ui/Card', () => ({ Card: ({ children, ...props }: any) => (
{children}
), CardContent: ({ children }: any) => (
{children}
), CardDescription: ({ children }: any) => (
{children}
), CardFooter: ({ children }: any) => (
{children}
), CardHeader: ({ children }: any) => (
{children}
), CardTitle: ({ children }: any) => (
{children}
), })); jest.mock('../../../src/renderer/components/ui/Typography', () => ({ __esModule: true, default: ({ children, variant, onClick, className }: any) => (
{children}
), })); jest.mock('../../../src/renderer/components/AgentProfileModal', () => ({ AgentProfileModal: ({ isOpen, onClose, accountId }: any) => isOpen ? (
Profile for {accountId}
) : null, })); jest.mock( '../../../src/renderer/components/shared/ConnectionConfirmDialog', () => ({ __esModule: true, default: ({ isOpen, onConfirm, onCancel, agentName }: any) => isOpen ? (
Connect to {agentName}?
) : null, }) ); jest.mock('../../../src/renderer/lib/styles', () => ({ gradients: { primary: 'bg-gradient-to-r from-blue-500 to-purple-600', }, })); jest.mock('react-icons/fi', () => ({ FiSearch: () =>
, FiFilter: () =>
, FiGrid: () =>
, FiList: () =>
, FiRefreshCw: () =>
, FiChevronLeft: () =>
, FiChevronRight: () =>
, FiStar: () =>
, FiMessageCircle: () =>
, FiUser: () =>
, FiCpu: () =>
, FiLink: () =>
, FiXCircle: () =>
, FiImage: () =>
, FiMinus: () =>
, FiPlus: () =>
, FiChevronDown: () =>
, FiCopy: () =>
, FiCamera: () =>
, FiMic: () =>
, FiFilm: () =>
, FiCode: () =>
, FiGlobe: () =>
, FiBarChart2: () =>
, FiDatabase: () =>
, FiShare2: () =>
, FiTrendingUp: () =>
, FiActivity: () =>
, FiShield: () =>
, FiCheckCircle: () =>
, FiAlertTriangle: () =>
, FiBriefcase: () =>
, FiLock: () =>
, FiUsers: () =>
, FiZap: () =>
, FiDollarSign: () =>
, })); jest.mock('react-icons/fa', () => ({ FaTwitter: () =>
, FaGithub: () =>
, FaGlobe: () =>
, FaDiscord: () =>
, })); describe('AgentDiscoveryPage', () => { const mockAgents = [ { id: 'agent-1', accountId: '0.0.12345', network: 'testnet', rating: 4.5, ratingCount: 10, createdAt: '2024-01-01T00:00:00Z', metadata: { name: 'Test Agent 1', display_name: 'Test Agent One', description: 'A helpful AI agent for testing', bio: 'I am a test agent', profileImage: 'ipfs://QmTest1', socials: { twitter: 'testagent1', github: 'testagent1', }, aiAgent: { capabilities: [0, 4, 7], // Text Generation, Code Generation, Knowledge Retrieval }, }, }, { id: 'agent-2', accountId: '0.0.67890', network: 'mainnet', rating: 3.8, ratingCount: 5, createdAt: '2024-01-02T00:00:00Z', metadata: { name: 'Test Agent 2', display_name: 'Test Agent Two', description: 'Another test agent', aiAgent: { capabilities: [1, 5], // Image Generation, Language Translation }, }, }, ]; const mockAgentStore = { status: 'idle', isConnected: false, messages: [], hcs10Messages: {}, currentSessionId: null, currentSession: null, sessions: [], isTyping: false, operationalMode: 'autonomous' as const, chatContext: { mode: 'personal' as const }, hcs10LoadingMessages: {}, isInitialized: true, lastActiveSessionId: null, _operationLocks: {}, _isCreatingSession: false, connect: jest.fn(), disconnect: jest.fn(), sendMessage: jest.fn(), setStatus: jest.fn(), setConnected: jest.fn(), setConnectionError: jest.fn(), clearConnectionError: jest.fn(), setIsTyping: jest.fn(), setOperationalMode: jest.fn(), setChatContext: jest.fn(), addMessage: jest.fn(), clearMessages: jest.fn(), getMessages: jest.fn(), loadConversationMessages: jest.fn(), refreshConversationMessages: jest.fn(), setHCS10Messages: jest.fn(), setSessionId: jest.fn(), startNewSession: jest.fn(), setCurrentSession: jest.fn(), initializeSessions: jest.fn(), restoreLastSession: jest.fn(), _lockOperation: jest.fn(), _unlockOperation: jest.fn(), createSession: jest.fn(), loadSession: jest.fn(), saveSession: jest.fn(), deleteSession: jest.fn(), loadAllSessions: jest.fn(), saveMessage: jest.fn(), loadSessionMessages: jest.fn(), approveTransaction: jest.fn(), rejectTransaction: jest.fn(), findFormMessage: jest.fn(), updateFormState: jest.fn(), processFormSubmission: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); (useAgentStore as jest.MockedFunction).mockReturnValue(mockAgentStore); Object.defineProperty(window, 'electron', { value: { invoke: jest.fn().mockResolvedValue({ success: true, data: mockAgents, total: 2, currentPage: 1, totalPages: 1, }), }, writable: true, }); Object.defineProperty(navigator, 'clipboard', { value: { writeText: jest.fn().mockResolvedValue(undefined), }, writable: true, }); }); describe('Initial Render', () => { test('should render the page with correct title and layout', async () => { render(); expect(screen.getByText('Agent Discovery')).toBeInTheDocument(); expect( screen.getByText('Discover AI agents on Hedera') ).toBeInTheDocument(); expect(screen.getByTestId('search-input')).toBeInTheDocument(); }); test('should show loading state initially', () => { render(); expect(screen.getByText('Loading agents...')).toBeInTheDocument(); }); test('should load agents on mount', async () => { render(); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledWith('agent:discover', { page: 1, limit: 12, sortBy: 'created-desc', filters: { search: '', tags: [], hasProfileImage: null, includeRegistryBroker: false, registries: [], }, }); }); }); }); describe('Search Functionality', () => { test('should update search query and trigger search', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByTestId('search-input')).toBeInTheDocument(); }); const searchInput = screen.getByTestId('search-input'); await user.type(searchInput, 'test agent'); await waitFor( () => { expect(window.electron!.invoke).toHaveBeenCalledWith( 'agent:discover', expect.objectContaining({ filters: expect.objectContaining({ search: 'test agent', }), }) ); }, { timeout: 1000 } ); }); test('should clear search when clear button is clicked', async () => { const user = userEvent.setup(); render(); const searchInput = screen.getByTestId('search-input'); await user.type(searchInput, 'test search'); const clearButton = screen.getByRole('button', { name: /clear/i }); await user.click(clearButton); expect(searchInput).toHaveValue(''); }); }); describe('Sorting', () => { test('should change sort order', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledTimes(1); }); const sortTrigger = screen.getByTestId('dropdown-trigger'); await user.click(sortTrigger); const ratingOption = screen.getByTestId('dropdown-item'); await user.click(ratingOption); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledWith( 'agent:discover', expect.objectContaining({ sortBy: 'rating-desc', }) ); }); }); }); describe('Agent Cards', () => { test('should render agent cards when agents are loaded', async () => { render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); expect(screen.getByText('Test Agent Two')).toBeInTheDocument(); }); expect(screen.getByText('0.0.12345')).toBeInTheDocument(); expect(screen.getByText('0.0.67890')).toBeInTheDocument(); }); test('should display agent ratings', async () => { render(); await waitFor(() => { expect(screen.getByText('4.5')).toBeInTheDocument(); expect(screen.getByText('(10)')).toBeInTheDocument(); }); }); test('should show agent capabilities as badges', async () => { render(); await waitFor(() => { expect(screen.getAllByTestId('badge')).toHaveLength(5); // 3 + 2 capabilities }); }); }); describe('Agent Profile Modal', () => { test('should open profile modal when agent name is clicked', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); }); const agentName = screen.getByText('Test Agent One'); await user.click(agentName); expect(screen.getByTestId('agent-profile-modal')).toBeInTheDocument(); expect(screen.getByText('Profile for 0.0.12345')).toBeInTheDocument(); }); test('should close profile modal', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); }); const agentName = screen.getByText('Test Agent One'); await user.click(agentName); const closeButton = screen.getByTestId('close-modal'); await user.click(closeButton); expect( screen.queryByTestId('agent-profile-modal') ).not.toBeInTheDocument(); }); }); describe('Connection Dialog', () => { test('should open connection dialog when connect button is clicked', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); }); const connectButtons = screen.getAllByRole('button', { name: /connect/i, }); await user.click(connectButtons[0]); expect( screen.getByTestId('connection-confirm-dialog') ).toBeInTheDocument(); expect( screen.getByText('Connect to Test Agent One?') ).toBeInTheDocument(); }); test('should connect to agent when confirmed', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); }); const connectButtons = screen.getAllByRole('button', { name: /connect/i, }); await user.click(connectButtons[0]); const confirmButton = screen.getByTestId('confirm-connect'); await user.click(confirmButton); expect(mockAgentStore.connect).toHaveBeenCalled(); }); test('should cancel connection when cancelled', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('Test Agent One')).toBeInTheDocument(); }); const connectButtons = screen.getAllByRole('button', { name: /connect/i, }); await user.click(connectButtons[0]); const cancelButton = screen.getByTestId('cancel-connect'); await user.click(cancelButton); expect( screen.queryByTestId('connection-confirm-dialog') ).not.toBeInTheDocument(); expect(mockAgentStore.connect).not.toHaveBeenCalled(); }); }); describe('Copy Account ID', () => { test('should copy account ID to clipboard', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByText('0.0.12345')).toBeInTheDocument(); }); const copyButtons = screen.getAllByTestId('fi-copy'); await user.click(copyButtons[0]); expect(navigator.clipboard.writeText).toHaveBeenCalledWith('0.0.12345'); }); }); describe('Pagination', () => { test('should show pagination controls when there are multiple pages', async () => { (window.electron!.invoke as jest.Mock).mockResolvedValue({ success: true, data: mockAgents, total: 25, currentPage: 1, totalPages: 3, }); render(); await waitFor(() => { expect(screen.getByTestId('fi-chevron-left')).toBeInTheDocument(); expect(screen.getByTestId('fi-chevron-right')).toBeInTheDocument(); }); }); test('should navigate to next page', async () => { const user = userEvent.setup(); (window.electron!.invoke as jest.Mock).mockResolvedValue({ success: true, data: mockAgents, total: 25, currentPage: 1, totalPages: 3, }); render(); await waitFor(() => { expect(screen.getByTestId('fi-chevron-right')).toBeInTheDocument(); }); const nextButton = screen .getByTestId('fi-chevron-right') .closest('button'); await user.click(nextButton!); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledWith( 'agent:discover', expect.objectContaining({ page: 2, }) ); }); }); }); describe('View Mode Toggle', () => { test('should toggle between grid and list view', async () => { const user = userEvent.setup(); render(); const gridButton = screen.getByTestId('fi-grid').closest('button'); const listButton = screen.getByTestId('fi-list').closest('button'); expect(gridButton).toBeInTheDocument(); expect(listButton).toBeInTheDocument(); await user.click(listButton!); }); }); describe('Filtering', () => { test('should apply capability filters', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getAllByTestId('badge')).toHaveLength(5); }); const badges = screen.getAllByTestId('badge'); await user.click(badges[0]); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledWith( 'agent:discover', expect.objectContaining({ filters: expect.objectContaining({ tags: expect.arrayContaining([expect.any(Number)]), }), }) ); }); }); test('should clear all filters', async () => { const user = userEvent.setup(); render(); const searchInput = screen.getByTestId('search-input'); await user.type(searchInput, 'test'); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledWith( 'agent:discover', expect.objectContaining({ filters: expect.objectContaining({ search: 'test', }), }) ); }); const clearButton = screen.getByRole('button', { name: /clear/i }); await user.click(clearButton); expect(searchInput).toHaveValue(''); }); }); describe('Error Handling', () => { test('should show error message when agent discovery fails', async () => { (window.electron!.invoke as jest.Mock).mockResolvedValue({ success: false, error: 'Failed to discover agents', }); render(); await waitFor(() => { expect( screen.getByText('Failed to discover agents') ).toBeInTheDocument(); }); }); test('should show empty state when no agents found', async () => { (window.electron!.invoke as jest.Mock).mockResolvedValue({ success: true, data: [], total: 0, currentPage: 1, totalPages: 0, }); render(); await waitFor(() => { expect(screen.getByText('No agents found')).toBeInTheDocument(); }); }); }); describe('Social Links', () => { test('should render social media links', async () => { render(); await waitFor(() => { expect(screen.getByTestId('fa-twitter')).toBeInTheDocument(); expect(screen.getByTestId('fa-github')).toBeInTheDocument(); }); }); test('should prevent event propagation on social link clicks', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByTestId('fa-twitter')).toBeInTheDocument(); }); const twitterLink = screen.getByTestId('fa-twitter'); await user.click(twitterLink); }); }); describe('Refresh Functionality', () => { test('should refresh agent list when refresh button is clicked', async () => { const user = userEvent.setup(); render(); await waitFor(() => { expect(screen.getByTestId('fi-refresh')).toBeInTheDocument(); }); const refreshButton = screen.getByTestId('fi-refresh').closest('button'); await user.click(refreshButton!); await waitFor(() => { expect(window.electron!.invoke).toHaveBeenCalledTimes(2); }); }); }); describe('Loading States', () => { test('should show loading spinner during search', async () => { const user = userEvent.setup(); render(); const searchInput = screen.getByTestId('search-input'); await user.type(searchInput, 'test'); expect(screen.getByTestId('fi-refresh')).toBeInTheDocument(); }); test('should disable search input during loading', async () => { (window.electron!.invoke as jest.Mock).mockImplementation( () => new Promise((resolve) => setTimeout( () => resolve({ success: true, data: mockAgents, total: 2, currentPage: 1, totalPages: 1, }), 1000 ) ) ); render(); const searchInput = screen.getByTestId('search-input'); expect(searchInput).toBeDisabled(); }); }); describe('Accessibility', () => { test('should have proper ARIA labels', async () => { render(); await waitFor(() => { expect(screen.getByTestId('search-input')).toHaveAttribute( 'placeholder' ); }); }); test('should support keyboard navigation', async () => { render(); await waitFor(() => { expect(screen.getByTestId('search-input')).toBeInTheDocument(); }); const searchInput = screen.getByTestId('search-input'); searchInput.focus(); expect(document.activeElement).toBe(searchInput); }); }); });