mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-03 22:32:42 +00:00
🧪 ci: Stabilize Virtualized Agent Grid Tests (#13214)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
This commit is contained in:
parent
9cb650d1d8
commit
8310e9a840
2 changed files with 219 additions and 152 deletions
|
|
@ -2,13 +2,52 @@ import React from 'react';
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { jest } from '@jest/globals';
|
||||
import VirtualizedAgentGrid from '../VirtualizedAgentGrid';
|
||||
import type * as t from 'librechat-data-provider';
|
||||
import VirtualizedAgentGrid from '../VirtualizedAgentGrid';
|
||||
|
||||
type RowRendererProps = {
|
||||
index: number;
|
||||
key: string;
|
||||
style: React.CSSProperties;
|
||||
parent: { props: { width: number } };
|
||||
};
|
||||
|
||||
type VirtualListMockProps = {
|
||||
rowRenderer: (props: RowRendererProps) => React.ReactNode;
|
||||
rowCount: number;
|
||||
width?: number;
|
||||
style?: React.CSSProperties;
|
||||
'aria-rowcount'?: number;
|
||||
'data-testid'?: string;
|
||||
'data-total-rows'?: number;
|
||||
};
|
||||
|
||||
type WindowScrollerChildProps = {
|
||||
height: number;
|
||||
isScrolling: boolean;
|
||||
registerChild: (ref: HTMLElement | null) => void;
|
||||
onChildScroll: () => void;
|
||||
scrollTop: number;
|
||||
};
|
||||
|
||||
type LocalizeParams = {
|
||||
count?: number;
|
||||
category?: string;
|
||||
};
|
||||
|
||||
type MockAgentCardProps = {
|
||||
agent: {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
};
|
||||
};
|
||||
|
||||
// Mock react-virtualized for performance testing
|
||||
const mockRowRenderer = jest.fn();
|
||||
|
||||
jest.mock('react-virtualized', () => {
|
||||
const ReactActual = jest.requireActual<typeof import('react')>('react');
|
||||
const mockRowRendererRef = { current: jest.fn() };
|
||||
|
||||
return {
|
||||
|
|
@ -24,62 +63,60 @@ jest.mock('react-virtualized', () => {
|
|||
}
|
||||
return children({ width: 1200, height: 800 });
|
||||
},
|
||||
List: ({
|
||||
rowRenderer,
|
||||
rowCount,
|
||||
autoHeight,
|
||||
height,
|
||||
width,
|
||||
rowHeight,
|
||||
overscanRowCount,
|
||||
scrollTop,
|
||||
isScrolling,
|
||||
onScroll,
|
||||
style,
|
||||
'aria-rowcount': ariaRowCount,
|
||||
'data-testid': dataTestId,
|
||||
'data-total-rows': dataTotalRows,
|
||||
}: {
|
||||
rowRenderer: any;
|
||||
rowCount: number;
|
||||
[key: string]: any;
|
||||
}) => {
|
||||
// Store the row renderer for testing
|
||||
if (typeof rowRenderer === 'function') {
|
||||
mockRowRendererRef.current = rowRenderer;
|
||||
mockRowRenderer.mockImplementation(rowRenderer);
|
||||
}
|
||||
// Only render visible rows to simulate virtualization
|
||||
const visibleRows = Math.min(10, rowCount); // Simulate 10 visible rows
|
||||
return (
|
||||
<div
|
||||
data-testid={dataTestId || 'virtual-list'}
|
||||
data-total-rows={dataTotalRows || rowCount}
|
||||
aria-rowcount={ariaRowCount}
|
||||
style={style}
|
||||
>
|
||||
{Array.from({ length: visibleRows }, (_, index) =>
|
||||
rowRenderer({
|
||||
index,
|
||||
key: `row-${index}`,
|
||||
style: { height: 184 },
|
||||
parent: { props: { width: width || 1200 } },
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
List: ReactActual.forwardRef(
|
||||
(
|
||||
{
|
||||
rowRenderer,
|
||||
rowCount,
|
||||
width,
|
||||
style,
|
||||
'aria-rowcount': ariaRowCount,
|
||||
'data-testid': dataTestId,
|
||||
'data-total-rows': dataTotalRows,
|
||||
}: VirtualListMockProps,
|
||||
ref: React.ForwardedRef<{ forceUpdateGrid: () => void }>,
|
||||
) => {
|
||||
ReactActual.useImperativeHandle(ref, () => ({
|
||||
forceUpdateGrid: () => {},
|
||||
}));
|
||||
|
||||
// Store the row renderer for testing
|
||||
if (typeof rowRenderer === 'function') {
|
||||
mockRowRendererRef.current = rowRenderer;
|
||||
mockRowRenderer.mockImplementation(rowRenderer);
|
||||
}
|
||||
// Only render visible rows to simulate virtualization
|
||||
const visibleRows = Math.min(10, rowCount); // Simulate 10 visible rows
|
||||
return (
|
||||
<div
|
||||
data-testid={dataTestId || 'virtual-list'}
|
||||
data-total-rows={dataTotalRows || rowCount}
|
||||
aria-rowcount={ariaRowCount}
|
||||
style={style}
|
||||
>
|
||||
{Array.from({ length: visibleRows }, (_, index) =>
|
||||
rowRenderer({
|
||||
index,
|
||||
key: `row-${index}`,
|
||||
style: { height: 184 },
|
||||
parent: { props: { width: width || 1200 } },
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
),
|
||||
WindowScroller: ({
|
||||
children,
|
||||
scrollElement,
|
||||
scrollElement: _scrollElement,
|
||||
}: {
|
||||
children: (props: any) => React.ReactNode;
|
||||
children: (props: WindowScrollerChildProps) => React.ReactNode;
|
||||
scrollElement?: HTMLElement | null;
|
||||
}) => {
|
||||
return children({
|
||||
height: 800,
|
||||
isScrolling: false,
|
||||
registerChild: (ref: any) => {},
|
||||
registerChild: (_ref: HTMLElement | null) => {},
|
||||
onChildScroll: () => {},
|
||||
scrollTop: 0,
|
||||
});
|
||||
|
|
@ -126,7 +163,7 @@ jest.mock('~/hooks', () => ({
|
|||
{ value: 'development', label: 'Development' },
|
||||
],
|
||||
}),
|
||||
useLocalize: () => (key: string, params?: any) => {
|
||||
useLocalize: () => (key: string, params?: LocalizeParams) => {
|
||||
if (key === 'com_agents_grid_announcement') {
|
||||
return `Found ${params?.count || 0} agents in ${params?.category || 'category'}`;
|
||||
}
|
||||
|
|
@ -139,7 +176,7 @@ jest.mock('../SmartLoader', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../AgentCard', () => {
|
||||
return function MockAgentCard({ agent }: { agent: any }) {
|
||||
return function MockAgentCard({ agent }: MockAgentCardProps) {
|
||||
return (
|
||||
<div data-testid={`agent-card-${agent.id}`} style={{ height: '160px' }}>
|
||||
<h3>{agent.name}</h3>
|
||||
|
|
|
|||
|
|
@ -1,82 +1,116 @@
|
|||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen } 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';
|
||||
import VirtualizedAgentGrid from '../VirtualizedAgentGrid';
|
||||
|
||||
type RowRendererProps = {
|
||||
index: number;
|
||||
key: string;
|
||||
style: React.CSSProperties;
|
||||
parent: { props: { width: number } };
|
||||
};
|
||||
|
||||
type VirtualListMockProps = {
|
||||
rowRenderer: (props: RowRendererProps) => React.ReactNode;
|
||||
rowCount: number;
|
||||
width?: number;
|
||||
style?: React.CSSProperties;
|
||||
'aria-rowcount'?: number;
|
||||
'data-testid'?: string;
|
||||
'data-total-rows'?: number;
|
||||
};
|
||||
|
||||
type WindowScrollerChildProps = {
|
||||
height: number;
|
||||
isScrolling: boolean;
|
||||
registerChild: (ref: HTMLElement | null) => void;
|
||||
onChildScroll: () => void;
|
||||
scrollTop: number;
|
||||
};
|
||||
|
||||
type MarketplaceAgentsMock = {
|
||||
useMarketplaceAgentsInfiniteQuery: jest.Mock;
|
||||
};
|
||||
|
||||
type LocalizeParams = {
|
||||
count?: number;
|
||||
category?: string;
|
||||
};
|
||||
|
||||
// Mock react-virtualized
|
||||
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,
|
||||
});
|
||||
},
|
||||
}));
|
||||
jest.mock('react-virtualized', () => {
|
||||
const ReactActual = jest.requireActual<typeof import('react')>('react');
|
||||
|
||||
return {
|
||||
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: ReactActual.forwardRef(
|
||||
(
|
||||
{
|
||||
rowRenderer,
|
||||
rowCount,
|
||||
width,
|
||||
style,
|
||||
'aria-rowcount': ariaRowCount,
|
||||
'data-testid': dataTestId,
|
||||
'data-total-rows': dataTotalRows,
|
||||
}: VirtualListMockProps,
|
||||
ref: React.ForwardedRef<{ forceUpdateGrid: () => void }>,
|
||||
) => {
|
||||
ReactActual.useImperativeHandle(ref, () => ({
|
||||
forceUpdateGrid: () => {},
|
||||
}));
|
||||
|
||||
return (
|
||||
<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: WindowScrollerChildProps) => React.ReactNode;
|
||||
scrollElement?: HTMLElement | null;
|
||||
}) => {
|
||||
return children({
|
||||
height: 600,
|
||||
isScrolling: false,
|
||||
registerChild: (_ref: HTMLElement | null) => {},
|
||||
onChildScroll: () => {},
|
||||
scrollTop: 0,
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the data provider
|
||||
const mockInfiniteQuery = {
|
||||
const createMockInfiniteQuery = (overrides = {}) => ({
|
||||
data: {
|
||||
pages: [
|
||||
{
|
||||
|
|
@ -104,10 +138,11 @@ const mockInfiniteQuery = {
|
|||
hasNextPage: true,
|
||||
refetch: jest.fn(),
|
||||
isFetchingNextPage: false,
|
||||
};
|
||||
...overrides,
|
||||
});
|
||||
|
||||
jest.mock('~/data-provider/Agents', () => ({
|
||||
useMarketplaceAgentsInfiniteQuery: jest.fn(() => mockInfiniteQuery),
|
||||
useMarketplaceAgentsInfiniteQuery: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock other hooks
|
||||
|
|
@ -118,7 +153,7 @@ jest.mock('~/hooks', () => ({
|
|||
{ value: 'development', label: 'Development' },
|
||||
],
|
||||
}),
|
||||
useLocalize: () => (key: string, params?: any) => {
|
||||
useLocalize: () => (key: string, params?: LocalizeParams) => {
|
||||
if (key === 'com_agents_grid_announcement') {
|
||||
return `Found ${params?.count || 0} agents in ${params?.category || 'category'}`;
|
||||
}
|
||||
|
|
@ -151,9 +186,16 @@ describe('VirtualizedAgentGrid', () => {
|
|||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
const useMarketplaceAgentsInfiniteQuery = (
|
||||
jest.requireMock('~/data-provider/Agents') as MarketplaceAgentsMock
|
||||
).useMarketplaceAgentsInfiniteQuery;
|
||||
useMarketplaceAgentsInfiniteQuery.mockImplementation(() => createMockInfiniteQuery());
|
||||
});
|
||||
|
||||
const renderComponent = (props = {}) => {
|
||||
const renderComponent = (
|
||||
props: Partial<React.ComponentProps<typeof VirtualizedAgentGrid>> = {},
|
||||
) => {
|
||||
const defaultProps = {
|
||||
category: 'all',
|
||||
searchQuery: '',
|
||||
|
|
@ -167,33 +209,27 @@ describe('VirtualizedAgentGrid', () => {
|
|||
);
|
||||
};
|
||||
|
||||
it('renders virtual list container', async () => {
|
||||
it('renders virtual list container', () => {
|
||||
renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays agent cards in virtual rows', async () => {
|
||||
it('displays agent cards in virtual rows', () => {
|
||||
renderComponent();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('agent-card-1')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('agent-card-2')).toBeInTheDocument();
|
||||
});
|
||||
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 () => {
|
||||
it('calls onSelectAgent when agent card is clicked', () => {
|
||||
const onSelectAgent = jest.fn();
|
||||
renderComponent({ onSelectAgent });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('agent-card-1')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('agent-card-1')).toBeInTheDocument();
|
||||
|
||||
screen.getByTestId('agent-card-1').click();
|
||||
|
||||
|
|
@ -205,15 +241,16 @@ describe('VirtualizedAgentGrid', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows loading spinner when loading', async () => {
|
||||
it('shows loading spinner when loading', () => {
|
||||
const mockQuery = jest.fn(() => ({
|
||||
...mockInfiniteQuery,
|
||||
...createMockInfiniteQuery(),
|
||||
isLoading: true,
|
||||
data: undefined,
|
||||
}));
|
||||
|
||||
const useMarketplaceAgentsInfiniteQuery =
|
||||
jest.requireMock('~/data-provider/Agents').useMarketplaceAgentsInfiniteQuery;
|
||||
const useMarketplaceAgentsInfiniteQuery = (
|
||||
jest.requireMock('~/data-provider/Agents') as MarketplaceAgentsMock
|
||||
).useMarketplaceAgentsInfiniteQuery;
|
||||
useMarketplaceAgentsInfiniteQuery.mockImplementation(mockQuery);
|
||||
|
||||
renderComponent();
|
||||
|
|
@ -224,17 +261,10 @@ describe('VirtualizedAgentGrid', () => {
|
|||
expect(spinner).toHaveClass('h-8 w-8 text-primary');
|
||||
});
|
||||
|
||||
it('has proper accessibility attributes', async () => {
|
||||
// Reset the mock to ensure we have data
|
||||
const useMarketplaceAgentsInfiniteQuery =
|
||||
jest.requireMock('~/data-provider/Agents').useMarketplaceAgentsInfiniteQuery;
|
||||
useMarketplaceAgentsInfiniteQuery.mockImplementation(() => mockInfiniteQuery);
|
||||
|
||||
it('has proper accessibility attributes', () => {
|
||||
renderComponent({ category: 'productivity' });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument();
|
||||
|
||||
const gridContainer = screen.getByRole('grid');
|
||||
expect(gridContainer).toHaveAttribute('aria-label');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue