diff --git a/client/src/components/Agents/AgentGrid.tsx b/client/src/components/Agents/AgentGrid.tsx index cb11012f15..58517c60da 100644 --- a/client/src/components/Agents/AgentGrid.tsx +++ b/client/src/components/Agents/AgentGrid.tsx @@ -225,7 +225,7 @@ const AgentGrid: React.FC = ({ ); - if (isLoading || (isFetching && !isFetchingNextPage)) { + if ((isLoading || (isFetching && !isFetchingNextPage)) && !hasData) { return loadingSpinner; } return mainContent; diff --git a/client/src/components/Agents/SmartLoader.tsx b/client/src/components/Agents/SmartLoader.tsx index 58e741b936..b857dd1978 100644 --- a/client/src/components/Agents/SmartLoader.tsx +++ b/client/src/components/Agents/SmartLoader.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { AgentListResponse } from 'librechat-data-provider'; +import type { AgentListResponse } from 'librechat-data-provider'; interface SmartLoaderProps { /** Whether the content is currently loading */ @@ -73,6 +73,12 @@ export const useHasData = (data: AgentListResponse | undefined): boolean => { // Type guard for object data if (typeof data === 'object' && data !== null) { + // Check for agent list data (AgentListResponse shape, e.g. marketplace pages) + const agents = data.data; + if (Array.isArray(agents)) { + return agents.length > 0; + } + // Check for agent list data if ('agents' in data) { const agents = (data as any).agents; diff --git a/client/src/components/Agents/VirtualizedAgentGrid.tsx b/client/src/components/Agents/VirtualizedAgentGrid.tsx index d5f026cb13..165c6e9d0e 100644 --- a/client/src/components/Agents/VirtualizedAgentGrid.tsx +++ b/client/src/components/Agents/VirtualizedAgentGrid.tsx @@ -225,7 +225,7 @@ const VirtualizedAgentGrid: React.FC = ({ } // Handle loading state - if (isLoading || (isFetching && !isFetchingNextPage)) { + if ((isLoading || (isFetching && !isFetchingNextPage)) && !hasData) { return loadingSpinner; } diff --git a/client/src/components/Agents/tests/AgentGrid.integration.spec.tsx b/client/src/components/Agents/tests/AgentGrid.integration.spec.tsx index a4d6282aa7..03043535a8 100644 --- a/client/src/components/Agents/tests/AgentGrid.integration.spec.tsx +++ b/client/src/components/Agents/tests/AgentGrid.integration.spec.tsx @@ -18,11 +18,6 @@ jest.mock('~/hooks/Agents', () => ({ })), })); -// Mock SmartLoader -jest.mock('../SmartLoader', () => ({ - useHasData: jest.fn(() => true), -})); - // Mock useLocalize hook jest.mock('~/hooks/useLocalize', () => () => (key: string, options?: any) => { const mockTranslations: Record = { @@ -362,6 +357,23 @@ describe('AgentGrid Integration with useGetMarketplaceAgentsQuery', () => { expect(spinner).toBeInTheDocument(); }); + it('should retain cached agents while refetching', () => { + mockUseMarketplaceAgentsInfiniteQuery.mockReturnValue({ + ...defaultMockQueryResult, + isFetching: true, + }); + + const Wrapper = createWrapper(); + render( + + + , + ); + + expect(screen.getByTestId('agent-card-1')).toBeInTheDocument(); + expect(screen.getByTestId('agent-card-2')).toBeInTheDocument(); + }); + it('should show empty state when no agents are available', () => { mockUseMarketplaceAgentsInfiniteQuery.mockReturnValue({ ...defaultMockQueryResult, diff --git a/client/src/components/Agents/tests/SmartLoader.spec.tsx b/client/src/components/Agents/tests/SmartLoader.spec.tsx index 766d5a2707..3d2609c94b 100644 --- a/client/src/components/Agents/tests/SmartLoader.spec.tsx +++ b/client/src/components/Agents/tests/SmartLoader.spec.tsx @@ -313,6 +313,35 @@ describe('useHasData', () => { expect(screen.getByTestId('result')).toHaveTextContent('no-data'); }); + it('detects empty data array (AgentListResponse) as no data', () => { + render( + , + ); + expect(screen.getByTestId('result')).toHaveTextContent('no-data'); + }); + + it('detects non-empty data array (AgentListResponse) as has data', () => { + render( + , + ); + expect(screen.getByTestId('result')).toHaveTextContent('has-data'); + }); + + it('detects invalid data property as no data', () => { + render(); + expect(screen.getByTestId('result')).toHaveTextContent('no-data'); + }); + it('detects empty agents array as no data', () => { render(); expect(screen.getByTestId('result')).toHaveTextContent('no-data'); diff --git a/client/src/components/Agents/tests/VirtualizedAgentGrid.test.tsx b/client/src/components/Agents/tests/VirtualizedAgentGrid.test.tsx index b756fb9add..04425af54d 100644 --- a/client/src/components/Agents/tests/VirtualizedAgentGrid.test.tsx +++ b/client/src/components/Agents/tests/VirtualizedAgentGrid.test.tsx @@ -160,10 +160,6 @@ jest.mock('~/hooks', () => ({ }, })); -jest.mock('../SmartLoader', () => ({ - useHasData: () => true, -})); - jest.mock('../AgentCard', () => { return function MockAgentCard({ agent, @@ -266,6 +262,21 @@ describe('VirtualizedAgentGrid', () => { expect(spinner).toHaveClass('h-8 w-8 text-text-primary'); }); + it('retains cached agents while refetching', () => { + const useMarketplaceAgentsInfiniteQuery = ( + jest.requireMock('~/data-provider/Agents') as MarketplaceAgentsMock + ).useMarketplaceAgentsInfiniteQuery; + useMarketplaceAgentsInfiniteQuery.mockImplementation(() => + createMockInfiniteQuery({ isFetching: true }), + ); + + renderComponent(); + + expect(screen.getByTestId('virtual-list')).toBeInTheDocument(); + expect(screen.getByTestId('agent-card-1')).toBeInTheDocument(); + expect(screen.getByTestId('agent-card-2')).toBeInTheDocument(); + }); + it('has proper accessibility attributes', () => { renderComponent({ category: 'productivity' });