| | |
| | |
| | |
| | import * as React from 'react'; |
| | import { describe, it, expect, beforeEach, jest } from '@jest/globals'; |
| | import { render, waitFor, fireEvent } from '@testing-library/react'; |
| | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; |
| | import type { Agent } from 'librechat-data-provider'; |
| |
|
| | |
| | let mockShowToast: jest.Mock; |
| |
|
| | |
| | jest.mock('~/common/types', () => ({ |
| | NotificationSeverity: { |
| | SUCCESS: 'success', |
| | ERROR: 'error', |
| | INFO: 'info', |
| | WARNING: 'warning', |
| | }, |
| | })); |
| |
|
| | |
| | jest.mock('~/store/toast', () => ({ |
| | default: () => ({ |
| | showToast: jest.fn(), |
| | }), |
| | })); |
| |
|
| | jest.mock('~/store', () => {}); |
| |
|
| | |
| | jest.mock('librechat-data-provider', () => { |
| | const actualModule = jest.requireActual('librechat-data-provider') as any; |
| | return { |
| | ...actualModule, |
| | dataService: { |
| | updateAgent: jest.fn(), |
| | }, |
| | Tools: actualModule.Tools || { |
| | execute_code: 'execute_code', |
| | file_search: 'file_search', |
| | web_search: 'web_search', |
| | }, |
| | Constants: actualModule.Constants || { |
| | EPHEMERAL_AGENT_ID: 'ephemeral', |
| | }, |
| | SystemRoles: actualModule.SystemRoles || { |
| | ADMIN: 'ADMIN', |
| | }, |
| | EModelEndpoint: actualModule.EModelEndpoint || { |
| | agents: 'agents', |
| | chatGPTBrowser: 'chatGPTBrowser', |
| | gptPlugins: 'gptPlugins', |
| | }, |
| | ResourceType: actualModule.ResourceType || { |
| | AGENT: 'agent', |
| | }, |
| | PermissionBits: actualModule.PermissionBits || { |
| | EDIT: 2, |
| | }, |
| | isAssistantsEndpoint: jest.fn(() => false), |
| | }; |
| | }); |
| |
|
| | jest.mock('@librechat/client', () => ({ |
| | Button: ({ children, onClick, ...props }: any) => ( |
| | <button onClick={onClick} {...props}> |
| | {children} |
| | </button> |
| | ), |
| | useToastContext: () => ({ |
| | get showToast() { |
| | return mockShowToast || jest.fn(); |
| | }, |
| | }), |
| | })); |
| |
|
| | |
| | jest.mock('librechat-data-provider/react-query', () => ({ |
| | useGetModelsQuery: () => ({ data: {} }), |
| | useGetEffectivePermissionsQuery: () => ({ |
| | data: { permissionBits: 0xffffffff }, |
| | isLoading: false, |
| | }), |
| | hasPermissions: (_bits: number, _required: number) => true, |
| | })); |
| |
|
| | jest.mock('~/utils', () => ({ |
| | createProviderOption: jest.fn((provider: string) => ({ value: provider, label: provider })), |
| | getDefaultAgentFormValues: jest.fn(() => ({ |
| | id: '', |
| | name: '', |
| | description: '', |
| | model: '', |
| | provider: '', |
| | })), |
| | })); |
| |
|
| | jest.mock('~/hooks', () => ({ |
| | useSelectAgent: () => ({ onSelect: jest.fn() }), |
| | useLocalize: () => (key: string) => key, |
| | useAuthContext: () => ({ user: { id: 'user-123', role: 'USER' } }), |
| | })); |
| |
|
| | jest.mock('~/hooks/useResourcePermissions', () => ({ |
| | useResourcePermissions: () => ({ |
| | hasPermission: jest.fn(() => true), |
| | isLoading: false, |
| | }), |
| | })); |
| |
|
| | jest.mock('~/Providers/AgentPanelContext', () => ({ |
| | useAgentPanelContext: () => ({ |
| | activePanel: 'builder', |
| | agentsConfig: { allowedProviders: [] }, |
| | setActivePanel: jest.fn(), |
| | endpointsConfig: {}, |
| | setCurrentAgentId: jest.fn(), |
| | agent_id: 'agent-123', |
| | }), |
| | })); |
| |
|
| | jest.mock('~/common', () => ({ |
| | isEphemeralAgent: (agentId: string | null | undefined): boolean => { |
| | return agentId == null || agentId === '' || agentId === 'ephemeral'; |
| | }, |
| | Panel: { |
| | model: 'model', |
| | builder: 'builder', |
| | advanced: 'advanced', |
| | }, |
| | })); |
| |
|
| | |
| | jest.mock('./AgentPanelSkeleton', () => ({ |
| | __esModule: true, |
| | default: () => <div>{`Loading...`}</div>, |
| | })); |
| |
|
| | jest.mock('./Advanced/AdvancedPanel', () => ({ |
| | __esModule: true, |
| | default: () => <div>{`Advanced Panel`}</div>, |
| | })); |
| |
|
| | jest.mock('./AgentConfig', () => ({ |
| | __esModule: true, |
| | default: () => <div>{`Agent Config`}</div>, |
| | })); |
| |
|
| | jest.mock('./AgentSelect', () => ({ |
| | __esModule: true, |
| | default: () => <div>{`Agent Select`}</div>, |
| | })); |
| |
|
| | jest.mock('./ModelPanel', () => ({ |
| | __esModule: true, |
| | default: () => <div>{`Model Panel`}</div>, |
| | })); |
| |
|
| | |
| | jest.mock('./AgentFooter', () => ({ |
| | __esModule: true, |
| | default: () => ( |
| | <button type="submit" data-testid="save-agent-button"> |
| | {`Save Agent`} |
| | </button> |
| | ), |
| | })); |
| |
|
| | |
| | let mockFormSubmitHandler: (() => void) | null = null; |
| |
|
| | jest.mock('react-hook-form', () => { |
| | const actual = jest.requireActual('react-hook-form') as any; |
| | return { |
| | ...actual, |
| | useForm: () => { |
| | const methods = actual.useForm({ |
| | defaultValues: { |
| | id: 'agent-123', |
| | name: 'Test Agent', |
| | description: 'Test description', |
| | model: 'gpt-4', |
| | provider: 'openai', |
| | tools: [], |
| | execute_code: false, |
| | file_search: false, |
| | web_search: false, |
| | }, |
| | }); |
| |
|
| | return { |
| | ...methods, |
| | handleSubmit: (onSubmit: any) => (e?: any) => { |
| | e?.preventDefault?.(); |
| | mockFormSubmitHandler = () => onSubmit(methods.getValues()); |
| | return mockFormSubmitHandler; |
| | }, |
| | }; |
| | }, |
| | FormProvider: ({ children }: any) => children, |
| | useWatch: () => 'agent-123', |
| | }; |
| | }); |
| |
|
| | |
| | import { dataService } from 'librechat-data-provider'; |
| | import { useGetAgentByIdQuery } from '~/data-provider'; |
| | import AgentPanel from './AgentPanel'; |
| |
|
| | |
| | jest.mock('~/data-provider', () => { |
| | const actual = jest.requireActual('~/data-provider') as any; |
| | return { |
| | ...actual, |
| | useGetAgentByIdQuery: jest.fn(), |
| | useGetExpandedAgentByIdQuery: jest.fn(() => ({ |
| | data: null, |
| | isInitialLoading: false, |
| | })), |
| | useUpdateAgentMutation: actual.useUpdateAgentMutation, |
| | }; |
| | }); |
| |
|
| | |
| | const createWrapper = () => { |
| | const queryClient = new QueryClient({ |
| | defaultOptions: { |
| | queries: { retry: false }, |
| | mutations: { retry: false }, |
| | }, |
| | }); |
| |
|
| | return ({ children }: { children: React.ReactNode }) => ( |
| | <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> |
| | ); |
| | }; |
| |
|
| | |
| | const setupMocks = () => { |
| | const mockUseGetAgentByIdQuery = useGetAgentByIdQuery as jest.MockedFunction< |
| | typeof useGetAgentByIdQuery |
| | >; |
| | const mockUpdateAgent = dataService.updateAgent as jest.MockedFunction< |
| | typeof dataService.updateAgent |
| | >; |
| |
|
| | return { mockUseGetAgentByIdQuery, mockUpdateAgent }; |
| | }; |
| |
|
| | const mockAgentQuery = ( |
| | mockUseGetAgentByIdQuery: jest.MockedFunction<typeof useGetAgentByIdQuery>, |
| | agent: Partial<Agent>, |
| | ) => { |
| | mockUseGetAgentByIdQuery.mockReturnValue({ |
| | data: { |
| | id: 'agent-123', |
| | author: 'user-123', |
| | isCollaborative: false, |
| | ...agent, |
| | } as Agent, |
| | isInitialLoading: false, |
| | } as any); |
| | }; |
| |
|
| | const createMockAgent = (overrides: Partial<Agent> = {}): Agent => |
| | ({ |
| | id: 'agent-123', |
| | provider: 'openai', |
| | model: 'gpt-4', |
| | ...overrides, |
| | }) as Agent; |
| |
|
| | const renderAndSubmitForm = async () => { |
| | const Wrapper = createWrapper(); |
| | const { container, rerender } = render(<AgentPanel />, { wrapper: Wrapper }); |
| |
|
| | const form = container.querySelector('form'); |
| | expect(form).toBeTruthy(); |
| |
|
| | fireEvent.submit(form!); |
| |
|
| | if (mockFormSubmitHandler) { |
| | mockFormSubmitHandler(); |
| | } |
| |
|
| | return { container, rerender, form }; |
| | }; |
| |
|
| | describe('AgentPanel - Update Agent Toast Messages', () => { |
| | beforeEach(() => { |
| | jest.clearAllMocks(); |
| | mockShowToast = jest.fn(); |
| | mockFormSubmitHandler = null; |
| | }); |
| |
|
| | describe('AgentPanel', () => { |
| | it('should show "no changes" toast when version does not change', async () => { |
| | const { mockUseGetAgentByIdQuery, mockUpdateAgent } = setupMocks(); |
| |
|
| | |
| | mockAgentQuery(mockUseGetAgentByIdQuery, { |
| | name: 'Test Agent', |
| | version: 2, |
| | }); |
| |
|
| | |
| | mockUpdateAgent.mockResolvedValue(createMockAgent({ name: 'Test Agent', version: 2 })); |
| |
|
| | await renderAndSubmitForm(); |
| |
|
| | |
| | await waitFor(() => { |
| | expect(mockShowToast).toHaveBeenCalledWith({ |
| | message: 'com_ui_no_changes', |
| | status: 'info', |
| | }); |
| | }); |
| | }); |
| |
|
| | it('should show "update success" toast when version changes', async () => { |
| | const { mockUseGetAgentByIdQuery, mockUpdateAgent } = setupMocks(); |
| |
|
| | |
| | mockAgentQuery(mockUseGetAgentByIdQuery, { |
| | name: 'Test Agent', |
| | version: 2, |
| | }); |
| |
|
| | |
| | mockUpdateAgent.mockResolvedValue(createMockAgent({ name: 'Test Agent', version: 3 })); |
| |
|
| | await renderAndSubmitForm(); |
| |
|
| | await waitFor(() => { |
| | expect(mockShowToast).toHaveBeenCalledWith({ |
| | message: 'com_assistants_update_success Test Agent', |
| | }); |
| | }); |
| | }); |
| |
|
| | it('should show "update success" with default name when agent has no name', async () => { |
| | const { mockUseGetAgentByIdQuery, mockUpdateAgent } = setupMocks(); |
| |
|
| | |
| | mockAgentQuery(mockUseGetAgentByIdQuery, { |
| | version: 1, |
| | }); |
| |
|
| | |
| | mockUpdateAgent.mockResolvedValue(createMockAgent({ version: 2 })); |
| |
|
| | await renderAndSubmitForm(); |
| |
|
| | await waitFor(() => { |
| | expect(mockShowToast).toHaveBeenCalledWith({ |
| | message: 'com_assistants_update_success com_ui_agent', |
| | }); |
| | }); |
| | }); |
| |
|
| | it('should show "update success" when agent query has no version (undefined)', async () => { |
| | const { mockUseGetAgentByIdQuery, mockUpdateAgent } = setupMocks(); |
| |
|
| | |
| | mockAgentQuery(mockUseGetAgentByIdQuery, { |
| | name: 'Test Agent', |
| | |
| | }); |
| |
|
| | mockUpdateAgent.mockResolvedValue(createMockAgent({ name: 'Test Agent', version: 1 })); |
| |
|
| | await renderAndSubmitForm(); |
| |
|
| | await waitFor(() => { |
| | expect(mockShowToast).toHaveBeenCalledWith({ |
| | message: 'com_assistants_update_success Test Agent', |
| | }); |
| | }); |
| | }); |
| |
|
| | it('should show error toast on update failure', async () => { |
| | const { mockUseGetAgentByIdQuery, mockUpdateAgent } = setupMocks(); |
| |
|
| | |
| | mockAgentQuery(mockUseGetAgentByIdQuery, { |
| | name: 'Test Agent', |
| | version: 1, |
| | }); |
| |
|
| | |
| | mockUpdateAgent.mockRejectedValue(new Error('Update failed')); |
| |
|
| | await renderAndSubmitForm(); |
| |
|
| | await waitFor(() => { |
| | expect(mockShowToast).toHaveBeenCalledWith({ |
| | message: 'com_agents_update_error com_ui_error: Update failed', |
| | status: 'error', |
| | }); |
| | }); |
| | }); |
| | }); |
| | }); |
| |
|