diff --git a/client/src/hooks/Input/useAutoSave.spec.ts b/client/src/hooks/Input/useAutoSave.spec.ts new file mode 100644 index 0000000000..5a5f6b423a --- /dev/null +++ b/client/src/hooks/Input/useAutoSave.spec.ts @@ -0,0 +1,110 @@ +jest.mock('recoil', () => ({ + ...jest.requireActual('recoil'), + useRecoilValue: jest.fn(), +})); + +jest.mock('~/store', () => ({ + saveDrafts: { key: 'saveDrafts', default: true }, +})); + +jest.mock('~/Providers', () => ({ + useChatFormContext: jest.fn(), +})); + +jest.mock('~/data-provider', () => ({ + useGetFiles: jest.fn(), +})); + +jest.mock('~/utils', () => ({ + ...jest.requireActual('~/utils'), + getDraft: jest.fn(), + setDraft: jest.fn(), + clearDraft: jest.fn(), + clearAllDrafts: jest.fn(), +})); + +import React from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { useRecoilValue } from 'recoil'; +import { useChatFormContext } from '~/Providers'; +import { useGetFiles } from '~/data-provider'; +import { getDraft, setDraft } from '~/utils'; +import store from '~/store'; +import { useAutoSave } from '~/hooks'; + +const mockSetValue = jest.fn(); +const mockGetDraft = getDraft as jest.Mock; +const mockSetDraft = setDraft as jest.Mock; + +const makeTextAreaRef = (value = '') => + ({ + current: { value, addEventListener: jest.fn(), removeEventListener: jest.fn() }, + }) as unknown as React.RefObject; + +beforeEach(() => { + (useRecoilValue as jest.Mock).mockImplementation((atom) => { + if (atom === store.saveDrafts) return true; + return undefined; + }); + (useChatFormContext as jest.Mock).mockReturnValue({ setValue: mockSetValue }); + (useGetFiles as jest.Mock).mockReturnValue({ data: [] }); + mockGetDraft.mockReturnValue(''); +}); + +describe('useAutoSave — conversation switching', () => { + it('clears the textarea when switching to a conversation with no draft', () => { + const { rerender } = renderHook( + ({ conversationId }: { conversationId: string }) => + useAutoSave({ + conversationId, + textAreaRef: makeTextAreaRef(), + files: new Map(), + setFiles: jest.fn(), + }), + { initialProps: { conversationId: 'convo-1' } }, + ); + + act(() => { + rerender({ conversationId: 'convo-2' }); + }); + + expect(mockSetValue).toHaveBeenLastCalledWith('text', ''); + }); + + it('restores the saved draft when switching to a conversation with one', () => { + mockGetDraft.mockImplementation((id: string) => (id === 'convo-2' ? 'Hello, world!' : '')); + + const { rerender } = renderHook( + ({ conversationId }: { conversationId: string }) => + useAutoSave({ + conversationId, + textAreaRef: makeTextAreaRef(), + files: new Map(), + setFiles: jest.fn(), + }), + { initialProps: { conversationId: 'convo-1' } }, + ); + + act(() => { + rerender({ conversationId: 'convo-2' }); + }); + + expect(mockSetValue).toHaveBeenLastCalledWith('text', 'Hello, world!'); + }); + + it('saves the current textarea content before switching away', () => { + const textAreaRef = makeTextAreaRef('draft in progress'); + + const { rerender } = renderHook( + ({ conversationId }: { conversationId: string }) => + useAutoSave({ conversationId, textAreaRef, files: new Map(), setFiles: jest.fn() }), + { initialProps: { conversationId: 'convo-1' } }, + ); + + act(() => { + rerender({ conversationId: 'convo-2' }); + }); + + expect(mockSetDraft).toHaveBeenCalledWith({ id: 'convo-1', value: 'draft in progress' }); + }); +}); diff --git a/client/src/hooks/Input/useAutoSave.ts b/client/src/hooks/Input/useAutoSave.ts index 95cd0d1933..b0c588db46 100644 --- a/client/src/hooks/Input/useAutoSave.ts +++ b/client/src/hooks/Input/useAutoSave.ts @@ -73,11 +73,7 @@ export const useAutoSave = ({ const restoreText = useCallback( (id: string) => { - const savedDraft = getDraft(id); - if (!savedDraft) { - return; - } - setValue('text', savedDraft); + setValue('text', getDraft(id) ?? ''); }, [setValue], );