diff --git a/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx b/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx index 0f022253500..1b204fdd54d 100644 --- a/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx +++ b/web/app/components/base/chat/embedded-chatbot/__tests__/hooks.spec.tsx @@ -608,6 +608,45 @@ describe('useEmbeddedChatbot', () => { expect(result.current.clearChatList).toBe(true) }) + // Regression for #38573: handleNewConversation must include handleChangeConversation + // in its useCallback deps so the reset button always invokes the freshest + // handleConversationIdInfoChange. Without the dep, the reset click captures a + // stale closure over the original handleChangeConversation identity. + it('handleNewConversation tracks handleChangeConversation identity', async () => { + const { result, rerender } = await renderWithClient(() => + useEmbeddedChatbot(AppSourceType.webApp), + ) + + const firstHandleNewConversation = result.current.handleNewConversation + const firstHandleChangeConversation = result.current.handleChangeConversation + expect(typeof firstHandleNewConversation).toBe('function') + expect(typeof firstHandleChangeConversation).toBe('function') + + // No-prop rerender: useCallback must short-circuit and preserve identity + // because no dependency reference changed. + await act(async () => { + rerender() + }) + + expect(result.current.handleNewConversation).toBe(firstHandleNewConversation) + expect(result.current.handleChangeConversation).toBe(firstHandleChangeConversation) + + // Flip the resolved userId via the store mock so handleChangeConversation's + // identity is invalidated. With the fix, handleNewConversation regenerates + // alongside it; without the fix, handleNewConversation would still reference + // the stale closure. + mockStoreState.embeddedUserId = 'embedded-user-2' + + await act(async () => { + rerender() + }) + + await waitFor(() => { + expect(result.current.handleChangeConversation).not.toBe(firstHandleChangeConversation) + }) + expect(result.current.handleNewConversation).not.toBe(firstHandleNewConversation) + }) + it('handleChangeConversation updates current conversation and refetches chat list', async () => { mockStoreState.embeddedConversationId = null const { result } = await renderWithClient(() => useEmbeddedChatbot(AppSourceType.webApp)) diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 0c7950f562c..2f96f4dc566 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -393,6 +393,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri }, [ isTryApp, setShowNewConversationItemInList, + handleChangeConversation, handleNewConversationInputsChange, setClearChatList, ])