diff --git a/client/src/hooks/Input/useAskAnswerMode.spec.ts b/client/src/hooks/Input/useAskAnswerMode.spec.ts index 7122122946..3b19074403 100644 --- a/client/src/hooks/Input/useAskAnswerMode.spec.ts +++ b/client/src/hooks/Input/useAskAnswerMode.spec.ts @@ -266,6 +266,39 @@ describe('useAskAnswerMode', () => { expect(mockReleasedComposerText).toEqual({}); }); + it('hands the released text back for an option answer too', () => { + /** Only the free-text path sets `consumedComposerText`, so gating the + * restore on it discarded the stash whenever the user answered by + * clicking an option (or skipping) instead of typing. */ + mockReleasedComposerText = { a1: 'an ordinary unsent message' }; + const askWithOptions = { + actionId: 'a1', + question: { + question: 'Pick one', + options: [ + { label: 'Blue', value: 'blue' }, + { label: 'Green', value: 'green' }, + ], + multiSelect: false, + }, + } as unknown as typeof liveAsk; + mockUseGetMessages.mockReturnValue({ data: askWithOptions }); + mockSubmitAskAnswer.mockImplementation( + (_actionId: string, _answer: string, options?: { onSuccess?: () => void }) => { + options?.onSuccess?.(); + }, + ); + const { result } = renderHook(() => useAskAnswerMode('conversation-1')); + + act(() => { + result.current.submitOption(0); + }); + + expect(mockSubmitAskAnswer).toHaveBeenCalledWith('a1', 'blue', expect.anything()); + expect(mockSetComposerText).toHaveBeenLastCalledWith('text', 'an ordinary unsent message'); + expect(mockReleasedComposerText).toEqual({}); + }); + it('does not let a delayed answer success clear the composer or selection after navigation', () => { let finishAnswer: (() => void) | undefined; mockUseGetMessages.mockReturnValue({ data: liveAsk }); diff --git a/client/src/hooks/Input/useAskAnswerMode.ts b/client/src/hooks/Input/useAskAnswerMode.ts index 182012c2b7..e9c0fce4ad 100644 --- a/client/src/hooks/Input/useAskAnswerMode.ts +++ b/client/src/hooks/Input/useAskAnswerMode.ts @@ -369,20 +369,23 @@ export default function useAskAnswerMode(conversationId?: string | null) { return next; }); /** A successful answer never goes through `collapse`, so the released - * text has to be handed back here too or the user's ordinary - * message dies with the question. Restored in place of the reset - * when one is stashed; the entry is dropped either way so the map - * stays bounded. */ + * text has to be handed back here too or the user's ordinary message + * dies with the question. + * + * Restored whenever one is stashed, independently of the reset + * condition below: only the free-text path sets + * `consumedComposerText`, so gating on it discarded the stash on an + * option click or Skip. The stash also outranks whatever the + * composer currently holds, which is the answer draft this + * submission just consumed. */ const released = releasedComposerTextRef.current[submittedActionId]; - if ( + if (released) { + currentScope.formContext?.setValue('text', released); + } else if ( (consumedComposerText || (wasActive && saveDrafts)) && currentScope.formContext?.getValues('text') === submittedComposerText ) { - if (released) { - currentScope.formContext.setValue('text', released); - } else { - currentScope.formContext.reset(); - } + currentScope.formContext.reset(); } setReleasedComposerText((current) => { if (current[submittedActionId] == null) {