import React from 'react'; import { SystemRoles } from 'librechat-data-provider'; import { render, screen } from '@testing-library/react'; import type { UseMutationResult } from '@tanstack/react-query'; import '@testing-library/jest-dom/extend-expect'; import type { Agent, AgentCreateParams, TUser } from 'librechat-data-provider'; import AgentFooter from '../AgentFooter'; import { Panel } from '~/common'; const mockUseWatch = jest.fn(); const mockUseAuthContext = jest.fn(); const mockUseHasAccess = jest.fn(); const mockUseResourcePermissions = jest.fn(); jest.mock('react-hook-form', () => ({ useFormContext: () => ({ control: {}, }), useWatch: (params) => mockUseWatch(params), })); // Default mock implementations mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', projectIds: ['project-1'], isCollaborative: false, }; } if (name === 'id') { return 'agent-123'; } return undefined; }); const mockUser = { id: 'user-123', username: 'testuser', email: 'test@example.com', name: 'Test User', avatar: '', role: 'USER', provider: 'local', emailVerified: true, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z', } as TUser; // Default auth context mockUseAuthContext.mockReturnValue({ user: mockUser, token: 'mock-token', isAuthenticated: true, error: undefined, login: jest.fn(), logout: jest.fn(), setError: jest.fn(), roles: {}, }); // Default access and permissions mockUseHasAccess.mockReturnValue(true); mockUseResourcePermissions.mockReturnValue({ hasPermission: () => true, isLoading: false, permissionBits: 0, }); jest.mock('~/hooks', () => ({ useLocalize: () => (key) => { const translations = { com_ui_save: 'Save', com_ui_create: 'Create', }; return translations[key] || key; }, useAuthContext: () => mockUseAuthContext(), useHasAccess: () => mockUseHasAccess(), useResourcePermissions: () => mockUseResourcePermissions(), })); const createBaseMutation = ( isLoading = false, ): UseMutationResult => { if (isLoading) { return { mutate: jest.fn(), mutateAsync: jest.fn().mockResolvedValue({} as T), isLoading: true, isError: false, isSuccess: false, isIdle: false as const, status: 'loading' as const, error: null, data: undefined, failureCount: 0, failureReason: null, reset: jest.fn(), context: undefined, variables: undefined, isPaused: false, }; } else { return { mutate: jest.fn(), mutateAsync: jest.fn().mockResolvedValue({} as T), isLoading: false, isError: false, isSuccess: false, isIdle: true as const, status: 'idle' as const, error: null, data: undefined, failureCount: 0, failureReason: null, reset: jest.fn(), context: undefined, variables: undefined, isPaused: false, }; } }; jest.mock('~/data-provider', () => ({ useUpdateAgentMutation: () => createBaseMutation(), })); jest.mock('../Advanced/AdvancedButton', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('../Version/VersionButton', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('../AdminSettings', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('../DeleteButton', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('../Sharing/GrantAccessDialog', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('../DuplicateAgent', () => ({ __esModule: true, default: jest.fn(() =>
), })); jest.mock('~/components', () => ({ Spinner: () =>
, })); describe('AgentFooter', () => { const mockUsers = { regular: mockUser, admin: { ...mockUser, id: 'admin-123', username: 'admin', email: 'admin@example.com', name: 'Admin User', role: SystemRoles.ADMIN, } as TUser, different: { ...mockUser, id: 'different-user', username: 'different', email: 'different@example.com', name: 'Different User', } as TUser, }; const createAuthContext = (user: TUser) => ({ user, token: 'mock-token', isAuthenticated: true, error: undefined, login: jest.fn(), logout: jest.fn(), setError: jest.fn(), roles: {}, }); const mockSetActivePanel = jest.fn(); const mockSetCurrentAgentId = jest.fn(); const mockCreateMutation = createBaseMutation(); const mockUpdateMutation = createBaseMutation(); const defaultProps = { activePanel: Panel.builder, createMutation: mockCreateMutation, updateMutation: mockUpdateMutation, setActivePanel: mockSetActivePanel, setCurrentAgentId: mockSetCurrentAgentId, }; beforeEach(() => { jest.clearAllMocks(); // Reset to default mock implementations mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', projectIds: ['project-1'], isCollaborative: false, }; } if (name === 'id') { return 'agent-123'; } return undefined; }); // Reset auth context to default user mockUseAuthContext.mockReturnValue({ user: mockUser, token: 'mock-token', isAuthenticated: true, error: undefined, login: jest.fn(), logout: jest.fn(), setError: jest.fn(), roles: {}, }); // Reset access and permissions to defaults mockUseHasAccess.mockReturnValue(true); mockUseResourcePermissions.mockReturnValue({ hasPermission: () => true, isLoading: false, permissionBits: 0, }); }); describe('Main Functionality', () => { test('renders with standard components based on default state', () => { render(); expect(screen.getByText('Save')).toBeInTheDocument(); expect(screen.getByTestId('advanced-button')).toBeInTheDocument(); expect(screen.getByTestId('version-button')).toBeInTheDocument(); expect(screen.getByTestId('delete-button')).toBeInTheDocument(); expect(screen.queryByTestId('admin-settings')).not.toBeInTheDocument(); expect(screen.getByTestId('grant-access-dialog')).toBeInTheDocument(); expect(screen.getByTestId('duplicate-agent')).toBeInTheDocument(); expect(screen.queryByTestId('spinner')).not.toBeInTheDocument(); }); test('handles loading states for createMutation', () => { const { unmount } = render( , ); expect(screen.getByTestId('spinner')).toBeInTheDocument(); expect(screen.queryByText('Save')).not.toBeInTheDocument(); expect(screen.getByRole('button')).toBeDisabled(); expect(screen.getByRole('button')).toHaveAttribute('aria-busy', 'true'); unmount(); }); test('handles loading states for updateMutation', () => { render(); expect(screen.getByTestId('spinner')).toBeInTheDocument(); expect(screen.queryByText('Save')).not.toBeInTheDocument(); }); }); describe('Conditional Rendering', () => { test('adjusts UI based on activePanel state', () => { render(); expect(screen.queryByTestId('advanced-button')).not.toBeInTheDocument(); expect(screen.queryByTestId('version-button')).not.toBeInTheDocument(); }); test('adjusts UI based on agent ID existence', () => { mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return null; // No agent means no delete/share/duplicate buttons } if (name === 'id') { return undefined; // No ID means create mode } return undefined; }); // When there's no agent, permissions should also return false mockUseResourcePermissions.mockReturnValue({ hasPermission: () => false, isLoading: false, permissionBits: 0, }); render(); expect(screen.getByText('Create')).toBeInTheDocument(); expect(screen.queryByTestId('version-button')).not.toBeInTheDocument(); expect(screen.queryByTestId('delete-button')).not.toBeInTheDocument(); expect(screen.queryByTestId('grant-access-dialog')).not.toBeInTheDocument(); expect(screen.queryByTestId('duplicate-agent')).not.toBeInTheDocument(); }); test('adjusts UI based on user role', () => { mockUseAuthContext.mockReturnValue(createAuthContext(mockUsers.admin)); const { unmount } = render(); expect(screen.getByTestId('admin-settings')).toBeInTheDocument(); expect(screen.getByTestId('grant-access-dialog')).toBeInTheDocument(); // Clean up the first render unmount(); jest.clearAllMocks(); mockUseAuthContext.mockReturnValue(createAuthContext(mockUsers.different)); mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return { name: 'Test Agent', author: 'different-author', _id: 'agent-123' }; } if (name === 'id') { return 'agent-123'; } return undefined; }); render(); expect(screen.queryByTestId('grant-access-dialog')).toBeInTheDocument(); // Still shows because hasAccess is true expect(screen.queryByTestId('duplicate-agent')).not.toBeInTheDocument(); // Should not show for different author }); test('adjusts UI based on permissions', () => { mockUseHasAccess.mockReturnValue(false); // Also need to ensure the agent is not owned by the user and user is not admin mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return { _id: 'agent-db-123', name: 'Test Agent', author: 'different-user', // Different author projectIds: ['project-1'], isCollaborative: false, }; } if (name === 'id') { return 'agent-123'; } return undefined; }); // Mock permissions to not allow sharing mockUseResourcePermissions.mockReturnValue({ hasPermission: () => false, // No permissions isLoading: false, permissionBits: 0, }); render(); expect(screen.queryByTestId('grant-access-dialog')).not.toBeInTheDocument(); }); test('hides action buttons when permissions are loading', () => { // Ensure we have an agent that would normally show buttons mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return { _id: 'agent-db-123', name: 'Test Agent', author: 'user-123', // Same as current user projectIds: ['project-1'], isCollaborative: false, }; } if (name === 'id') { return 'agent-123'; } return undefined; }); mockUseResourcePermissions.mockReturnValue({ hasPermission: () => true, isLoading: true, // This should hide the buttons permissionBits: 0, }); render(); expect(screen.queryByTestId('delete-button')).not.toBeInTheDocument(); expect(screen.queryByTestId('grant-access-dialog')).not.toBeInTheDocument(); // Duplicate button should still show as it doesn't depend on permissions loading expect(screen.getByTestId('duplicate-agent')).toBeInTheDocument(); }); }); describe('Edge Cases', () => { test('handles null agent data', () => { mockUseWatch.mockImplementation(({ name }) => { if (name === 'agent') { return null; } if (name === 'id') { return 'agent-123'; } return undefined; }); render(); expect(screen.getByText('Save')).toBeInTheDocument(); }); }); });