| | |
| | jest.mock('recoil', () => { |
| | const originalModule = jest.requireActual('recoil'); |
| | return { |
| | ...originalModule, |
| | atom: jest.fn().mockImplementation((config) => ({ |
| | key: config.key, |
| | default: config.default, |
| | })), |
| | useRecoilValue: jest.fn(), |
| | }; |
| | }); |
| |
|
| | |
| | jest.mock('~/store', () => ({ |
| | modularChat: { key: 'modularChat', default: false }, |
| | availableTools: { key: 'availableTools', default: [] }, |
| | })); |
| |
|
| | import { renderHook, act } from '@testing-library/react'; |
| | import { useSearchParams } from 'react-router-dom'; |
| | import { useQueryClient } from '@tanstack/react-query'; |
| | import { useRecoilValue } from 'recoil'; |
| | import useQueryParams from './useQueryParams'; |
| | import { useChatContext, useChatFormContext } from '~/Providers'; |
| | import useSubmitMessage from '~/hooks/Messages/useSubmitMessage'; |
| | import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo'; |
| | import store from '~/store'; |
| |
|
| | |
| | jest.mock('react-router-dom', () => ({ |
| | useSearchParams: jest.fn(), |
| | })); |
| |
|
| | jest.mock('@tanstack/react-query', () => ({ |
| | useQueryClient: jest.fn(), |
| | useQuery: jest.fn(), |
| | })); |
| |
|
| | jest.mock('~/Providers', () => ({ |
| | useChatContext: jest.fn(), |
| | useChatFormContext: jest.fn(), |
| | })); |
| |
|
| | jest.mock('~/hooks/Messages/useSubmitMessage', () => ({ |
| | __esModule: true, |
| | default: jest.fn(), |
| | })); |
| |
|
| | jest.mock('~/hooks/Conversations/useDefaultConvo', () => ({ |
| | __esModule: true, |
| | default: jest.fn(), |
| | })); |
| |
|
| | jest.mock('~/hooks/AuthContext', () => ({ |
| | useAuthContext: jest.fn(), |
| | })); |
| |
|
| | jest.mock('~/hooks/Agents/useAgentsMap', () => ({ |
| | __esModule: true, |
| | default: jest.fn(() => ({})), |
| | })); |
| | jest.mock('~/hooks/Agents/useAgentDefaultPermissionLevel', () => ({ |
| | __esModule: true, |
| | default: jest.fn(() => ({})), |
| | })); |
| |
|
| | jest.mock('~/utils', () => ({ |
| | getConvoSwitchLogic: jest.fn(() => ({ |
| | template: {}, |
| | shouldSwitch: false, |
| | isNewModular: false, |
| | newEndpointType: null, |
| | isCurrentModular: false, |
| | isExistingConversation: false, |
| | })), |
| | getModelSpecIconURL: jest.fn(() => 'icon-url'), |
| | removeUnavailableTools: jest.fn((preset) => preset), |
| | logger: { log: jest.fn() }, |
| | getInitialTheme: jest.fn(() => 'light'), |
| | applyFontSize: jest.fn(), |
| | })); |
| |
|
| | |
| | jest.mock('librechat-data-provider', () => ({ |
| | ...jest.requireActual('librechat-data-provider'), |
| | tQueryParamsSchema: { |
| | shape: { |
| | model: { parse: jest.fn((value) => value) }, |
| | endpoint: { parse: jest.fn((value) => value) }, |
| | temperature: { parse: jest.fn((value) => value) }, |
| | |
| | }, |
| | }, |
| | isAgentsEndpoint: jest.fn(() => false), |
| | isAssistantsEndpoint: jest.fn(() => false), |
| | QueryKeys: { startupConfig: 'startupConfig', endpoints: 'endpoints' }, |
| | EModelEndpoint: { custom: 'custom', assistants: 'assistants', agents: 'agents' }, |
| | })); |
| |
|
| | |
| | jest.mock('~/data-provider', () => ({ |
| | useGetAgentByIdQuery: jest.fn(() => ({ |
| | data: null, |
| | isLoading: false, |
| | error: null, |
| | })), |
| | useListAgentsQuery: jest.fn(() => ({ |
| | data: null, |
| | isLoading: false, |
| | error: null, |
| | })), |
| | })); |
| |
|
| | |
| | global.window = Object.create(window); |
| | global.window.history = { |
| | replaceState: jest.fn(), |
| | pushState: jest.fn(), |
| | go: jest.fn(), |
| | back: jest.fn(), |
| | forward: jest.fn(), |
| | length: 1, |
| | scrollRestoration: 'auto', |
| | state: null, |
| | }; |
| |
|
| | describe('useQueryParams', () => { |
| | |
| | beforeEach(() => { |
| | jest.useFakeTimers(); |
| |
|
| | |
| | jest.spyOn(window.history, 'replaceState').mockClear(); |
| |
|
| | |
| | const dataProvider = jest.requireMock('~/data-provider'); |
| | (dataProvider.useGetAgentByIdQuery as jest.Mock).mockReturnValue({ |
| | data: null, |
| | isLoading: false, |
| | error: null, |
| | }); |
| |
|
| | |
| | const mockSearchParams = new URLSearchParams(); |
| | (useSearchParams as jest.Mock).mockReturnValue([mockSearchParams, jest.fn()]); |
| |
|
| | const mockQueryClient = { |
| | getQueryData: jest.fn().mockImplementation((key) => { |
| | if (key === 'startupConfig') { |
| | return { modelSpecs: { list: [] } }; |
| | } |
| | if (key === 'endpoints') { |
| | return {}; |
| | } |
| | return null; |
| | }), |
| | }; |
| | (useQueryClient as jest.Mock).mockReturnValue(mockQueryClient); |
| |
|
| | (useRecoilValue as jest.Mock).mockImplementation((atom) => { |
| | if (atom === store.modularChat) return false; |
| | if (atom === store.availableTools) return []; |
| | return null; |
| | }); |
| |
|
| | const mockConversation = { model: null, endpoint: null }; |
| | const mockNewConversation = jest.fn(); |
| | (useChatContext as jest.Mock).mockReturnValue({ |
| | conversation: mockConversation, |
| | newConversation: mockNewConversation, |
| | }); |
| |
|
| | const mockMethods = { |
| | setValue: jest.fn(), |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: jest.fn((callback) => () => callback({ text: 'test message' })), |
| | }; |
| | (useChatFormContext as jest.Mock).mockReturnValue(mockMethods); |
| |
|
| | const mockSubmitMessage = jest.fn(); |
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | const mockGetDefaultConversation = jest.fn().mockReturnValue({}); |
| | (useDefaultConvo as jest.Mock).mockReturnValue(mockGetDefaultConversation); |
| |
|
| | |
| | const { useAuthContext } = jest.requireMock('~/hooks/AuthContext'); |
| | (useAuthContext as jest.Mock).mockReturnValue({ |
| | user: { id: 'test-user-id' }, |
| | isAuthenticated: true, |
| | }); |
| | }); |
| |
|
| | afterEach(() => { |
| | jest.clearAllMocks(); |
| | jest.useRealTimers(); |
| | }); |
| |
|
| | |
| | const setUrlParams = (params: Record<string, string>) => { |
| | const searchParams = new URLSearchParams(); |
| | Object.entries(params).forEach(([key, value]) => { |
| | searchParams.set(key, value); |
| | }); |
| | (useSearchParams as jest.Mock).mockReturnValue([searchParams, jest.fn()]); |
| | }; |
| |
|
| | |
| | it('should process query parameters on initial render', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: jest.fn((callback) => () => callback({ text: 'test message' })), |
| | }); |
| |
|
| | |
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: jest.fn().mockReturnValue({ modelSpecs: { list: [] } }), |
| | }); |
| |
|
| | setUrlParams({ q: 'hello world' }); |
| |
|
| | |
| | renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockSetValue).toHaveBeenCalledWith( |
| | 'text', |
| | 'hello world', |
| | expect.objectContaining({ shouldValidate: true }), |
| | ); |
| | expect(window.history.replaceState).toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should auto-submit message when submit=true and no settings to apply', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockHandleSubmit = jest.fn((callback) => () => callback({ text: 'test message' })); |
| | const mockSubmitMessage = jest.fn(); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: mockHandleSubmit, |
| | }); |
| |
|
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | |
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: jest.fn().mockReturnValue({ modelSpecs: { list: [] } }), |
| | }); |
| |
|
| | setUrlParams({ q: 'hello world', submit: 'true' }); |
| |
|
| | |
| | renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockSetValue).toHaveBeenCalledWith( |
| | 'text', |
| | 'hello world', |
| | expect.objectContaining({ shouldValidate: true }), |
| | ); |
| | expect(mockHandleSubmit).toHaveBeenCalled(); |
| | expect(mockSubmitMessage).toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should defer submission when settings need to be applied first', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockHandleSubmit = jest.fn((callback) => () => callback({ text: 'test message' })); |
| | const mockSubmitMessage = jest.fn(); |
| | const mockNewConversation = jest.fn(); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | |
| | const mockGetQueryData = jest.fn().mockImplementation((key) => { |
| | if (Array.isArray(key) && key[0] === 'startupConfig') { |
| | return { modelSpecs: { list: [] } }; |
| | } |
| | if (key === 'startupConfig') { |
| | return { modelSpecs: { list: [] } }; |
| | } |
| | return null; |
| | }); |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: mockHandleSubmit, |
| | }); |
| |
|
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | (useChatContext as jest.Mock).mockReturnValue({ |
| | conversation: { model: null, endpoint: null }, |
| | newConversation: mockNewConversation, |
| | }); |
| |
|
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: mockGetQueryData, |
| | }); |
| |
|
| | setUrlParams({ q: 'hello world', submit: 'true', model: 'gpt-4' }); |
| |
|
| | |
| | const { rerender } = renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockGetQueryData).toHaveBeenCalledWith(expect.anything()); |
| | expect(mockNewConversation).toHaveBeenCalled(); |
| | expect(mockSubmitMessage).not.toHaveBeenCalled(); |
| |
|
| | |
| | (useChatContext as jest.Mock).mockReturnValue({ |
| | conversation: { model: 'gpt-4', endpoint: null }, |
| | newConversation: mockNewConversation, |
| | }); |
| |
|
| | |
| | rerender(); |
| |
|
| | |
| | expect(mockSetValue).toHaveBeenCalledWith( |
| | 'text', |
| | 'hello world', |
| | expect.objectContaining({ shouldValidate: true }), |
| | ); |
| | expect(mockHandleSubmit).toHaveBeenCalled(); |
| | expect(mockSubmitMessage).toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should submit after timeout if settings never get applied', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockHandleSubmit = jest.fn((callback) => () => callback({ text: 'test message' })); |
| | const mockSubmitMessage = jest.fn(); |
| | const mockNewConversation = jest.fn(); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: mockHandleSubmit, |
| | }); |
| |
|
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | (useChatContext as jest.Mock).mockReturnValue({ |
| | conversation: { model: null, endpoint: null }, |
| | newConversation: mockNewConversation, |
| | }); |
| |
|
| | |
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: jest.fn().mockImplementation((key) => { |
| | if (Array.isArray(key) && key[0] === 'startupConfig') { |
| | return { modelSpecs: { list: [] } }; |
| | } |
| | if (key === 'startupConfig') { |
| | return { modelSpecs: { list: [] } }; |
| | } |
| | return null; |
| | }), |
| | }); |
| |
|
| | setUrlParams({ q: 'hello world', submit: 'true', model: 'non-existent-model' }); |
| |
|
| | |
| | renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockSubmitMessage).not.toHaveBeenCalled(); |
| |
|
| | |
| | act(() => { |
| | |
| | jest.advanceTimersByTime(3000); |
| | }); |
| |
|
| | |
| | expect(mockSubmitMessage).toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should mark as submitted when no submit parameter is present', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockHandleSubmit = jest.fn((callback) => () => callback({ text: 'test message' })); |
| | const mockSubmitMessage = jest.fn(); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: mockHandleSubmit, |
| | }); |
| |
|
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | |
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: jest.fn().mockReturnValue({ modelSpecs: { list: [] } }), |
| | }); |
| |
|
| | setUrlParams({ model: 'gpt-4' }); |
| |
|
| | |
| | renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockSubmitMessage).not.toHaveBeenCalled(); |
| |
|
| | |
| | act(() => { |
| | jest.advanceTimersByTime(4000); |
| | }); |
| |
|
| | |
| | expect(mockSubmitMessage).not.toHaveBeenCalled(); |
| | }); |
| |
|
| | it('should handle empty query parameters', () => { |
| | |
| | const mockSetValue = jest.fn(); |
| | const mockHandleSubmit = jest.fn(); |
| | const mockSubmitMessage = jest.fn(); |
| |
|
| | |
| | window.history.replaceState = jest.fn(); |
| |
|
| | (useChatFormContext as jest.Mock).mockReturnValue({ |
| | setValue: mockSetValue, |
| | getValues: jest.fn().mockReturnValue(''), |
| | handleSubmit: mockHandleSubmit, |
| | }); |
| |
|
| | (useSubmitMessage as jest.Mock).mockReturnValue({ |
| | submitMessage: mockSubmitMessage, |
| | }); |
| |
|
| | |
| | (useQueryClient as jest.Mock).mockReturnValue({ |
| | getQueryData: jest.fn().mockReturnValue({ modelSpecs: { list: [] } }), |
| | }); |
| |
|
| | setUrlParams({}); |
| | const mockTextAreaRef = { |
| | current: { |
| | focus: jest.fn(), |
| | setSelectionRange: jest.fn(), |
| | } as unknown as HTMLTextAreaElement, |
| | }; |
| |
|
| | |
| | renderHook(() => useQueryParams({ textAreaRef: mockTextAreaRef })); |
| |
|
| | act(() => { |
| | jest.advanceTimersByTime(100); |
| | }); |
| |
|
| | |
| | expect(mockSetValue).not.toHaveBeenCalled(); |
| | expect(mockHandleSubmit).not.toHaveBeenCalled(); |
| | expect(mockSubmitMessage).not.toHaveBeenCalled(); |
| | expect(window.history.replaceState).toHaveBeenCalled(); |
| | }); |
| | }); |
| |
|