| import React from 'react'; |
| import { render, screen, waitFor } from '@testing-library/react'; |
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; |
| import { jest } from '@jest/globals'; |
| import VirtualizedAgentGrid from '../VirtualizedAgentGrid'; |
| import type t from 'librechat-data-provider'; |
|
|
| |
| jest.mock('react-virtualized', () => ({ |
| AutoSizer: ({ |
| children, |
| disableHeight, |
| }: { |
| children: (props: { width: number; height?: number }) => React.ReactNode; |
| disableHeight?: boolean; |
| }) => { |
| if (disableHeight) { |
| return children({ width: 800 }); |
| } |
| return children({ width: 800, height: 600 }); |
| }, |
| List: ({ |
| rowRenderer, |
| rowCount, |
| width, |
| style, |
| 'aria-rowcount': ariaRowCount, |
| 'data-testid': dataTestId, |
| 'data-total-rows': dataTotalRows, |
| }: { |
| rowRenderer: any; |
| rowCount: number; |
| autoHeight?: boolean; |
| height?: number; |
| width?: number; |
| rowHeight?: number; |
| overscanRowCount?: number; |
| scrollTop?: number; |
| isScrolling?: boolean; |
| onScroll?: any; |
| style?: any; |
| 'aria-rowcount'?: number; |
| 'data-testid'?: string; |
| 'data-total-rows'?: number; |
| }) => ( |
| <div |
| data-testid={dataTestId || 'virtual-list'} |
| aria-rowcount={ariaRowCount} |
| data-total-rows={dataTotalRows} |
| style={style} |
| > |
| {Array.from({ length: Math.min(rowCount, 5) }, (_, index) => |
| rowRenderer({ |
| index, |
| key: `row-${index}`, |
| style: {}, |
| parent: { props: { width: width || 800 } }, |
| }), |
| )} |
| </div> |
| ), |
| WindowScroller: ({ |
| children, |
| }: { |
| children: (props: any) => React.ReactNode; |
| scrollElement?: HTMLElement | null; |
| }) => { |
| return children({ |
| height: 600, |
| isScrolling: false, |
| registerChild: (_ref: any) => {}, |
| onChildScroll: () => {}, |
| scrollTop: 0, |
| }); |
| }, |
| })); |
|
|
| |
| const mockInfiniteQuery = { |
| data: { |
| pages: [ |
| { |
| data: [ |
| { |
| id: '1', |
| name: 'Test Agent 1', |
| description: 'A test agent for virtual scrolling', |
| category: 'productivity', |
| }, |
| { |
| id: '2', |
| name: 'Test Agent 2', |
| description: 'Another test agent', |
| category: 'development', |
| }, |
| ], |
| }, |
| ], |
| }, |
| isLoading: false, |
| error: null, |
| isFetching: false, |
| fetchNextPage: jest.fn(), |
| hasNextPage: true, |
| refetch: jest.fn(), |
| isFetchingNextPage: false, |
| }; |
|
|
| jest.mock('~/data-provider/Agents', () => ({ |
| useMarketplaceAgentsInfiniteQuery: jest.fn(() => mockInfiniteQuery), |
| })); |
|
|
| |
| jest.mock('~/hooks', () => ({ |
| useAgentCategories: () => ({ |
| categories: [ |
| { value: 'productivity', label: 'Productivity' }, |
| { value: 'development', label: 'Development' }, |
| ], |
| }), |
| useLocalize: () => (key: string, params?: any) => { |
| if (key === 'com_agents_grid_announcement') { |
| return `Found ${params?.count || 0} agents in ${params?.category || 'category'}`; |
| } |
| return key; |
| }, |
| })); |
|
|
| jest.mock('../SmartLoader', () => ({ |
| useHasData: () => true, |
| })); |
|
|
| jest.mock('../AgentCard', () => { |
| return function MockAgentCard({ agent, onClick }: { agent: t.Agent; onClick: () => void }) { |
| return ( |
| <div data-testid={`agent-card-${agent.id}`} onClick={onClick}> |
| <h3>{agent.name}</h3> |
| <p>{agent.description}</p> |
| </div> |
| ); |
| }; |
| }); |
|
|
| describe('VirtualizedAgentGrid', () => { |
| let queryClient: QueryClient; |
|
|
| beforeEach(() => { |
| queryClient = new QueryClient({ |
| defaultOptions: { |
| queries: { retry: false }, |
| mutations: { retry: false }, |
| }, |
| }); |
| }); |
|
|
| const renderComponent = (props = {}) => { |
| const defaultProps = { |
| category: 'all', |
| searchQuery: '', |
| onSelectAgent: jest.fn(), |
| }; |
|
|
| return render( |
| <QueryClientProvider client={queryClient}> |
| <VirtualizedAgentGrid {...defaultProps} {...props} /> |
| </QueryClientProvider>, |
| ); |
| }; |
|
|
| it('renders virtual list container', async () => { |
| renderComponent(); |
|
|
| await waitFor(() => { |
| expect(screen.getByTestId('virtual-list')).toBeInTheDocument(); |
| }); |
| }); |
|
|
| it('displays agent cards in virtual rows', async () => { |
| renderComponent(); |
|
|
| await waitFor(() => { |
| expect(screen.getByTestId('agent-card-1')).toBeInTheDocument(); |
| expect(screen.getByTestId('agent-card-2')).toBeInTheDocument(); |
| }); |
|
|
| expect(screen.getByText('Test Agent 1')).toBeInTheDocument(); |
| expect(screen.getByText('Test Agent 2')).toBeInTheDocument(); |
| }); |
|
|
| it('calls onSelectAgent when agent card is clicked', async () => { |
| const onSelectAgent = jest.fn(); |
| renderComponent({ onSelectAgent }); |
|
|
| await waitFor(() => { |
| expect(screen.getByTestId('agent-card-1')).toBeInTheDocument(); |
| }); |
|
|
| screen.getByTestId('agent-card-1').click(); |
|
|
| expect(onSelectAgent).toHaveBeenCalledWith({ |
| id: '1', |
| name: 'Test Agent 1', |
| description: 'A test agent for virtual scrolling', |
| category: 'productivity', |
| }); |
| }); |
|
|
| it('shows loading spinner when loading', async () => { |
| const mockQuery = jest.fn(() => ({ |
| ...mockInfiniteQuery, |
| isLoading: true, |
| data: undefined, |
| })); |
|
|
| const useMarketplaceAgentsInfiniteQuery = |
| jest.requireMock('~/data-provider/Agents').useMarketplaceAgentsInfiniteQuery; |
| useMarketplaceAgentsInfiniteQuery.mockImplementation(mockQuery); |
|
|
| renderComponent(); |
|
|
| |
| const spinner = document.querySelector('.spinner'); |
| expect(spinner).toBeInTheDocument(); |
| expect(spinner).toHaveClass('h-8 w-8 text-primary'); |
| }); |
|
|
| it('has proper accessibility attributes', async () => { |
| |
| const useMarketplaceAgentsInfiniteQuery = |
| jest.requireMock('~/data-provider/Agents').useMarketplaceAgentsInfiniteQuery; |
| useMarketplaceAgentsInfiniteQuery.mockImplementation(() => mockInfiniteQuery); |
|
|
| renderComponent({ category: 'productivity' }); |
|
|
| await waitFor(() => { |
| expect(screen.getByTestId('virtual-list')).toBeInTheDocument(); |
| }); |
|
|
| const gridContainer = screen.getByRole('grid'); |
| expect(gridContainer).toHaveAttribute('aria-label'); |
| expect(gridContainer.getAttribute('aria-label')).toContain('2'); |
| expect(gridContainer.getAttribute('aria-label')).toContain('Productivity'); |
|
|
| const tabpanel = screen.getByRole('tabpanel'); |
| expect(tabpanel).toHaveAttribute('id', 'category-panel-productivity'); |
| expect(tabpanel).toHaveAttribute('aria-labelledby', 'category-tab-productivity'); |
| }); |
| }); |
|
|