fix(web): re-add handleChangeConversation to handleNewConversation deps

Supersedes #39865 (closed as superseded; the original branch
drifted against main while the useConversationSelection refactor
landed and the diff was no longer mergeable). Rebuilt on
`upstream/main` (dfac3e524e).

The bug is still present on main:
`useEmbeddedChatbot` calls `handleChangeConversation('')` inside
`handleNewConversation` but only declares it through the closure
instead of in the useCallback dep array. The reset button therefore
captures a stale `handleChangeConversation` identity after any
state change that invalidates its reference (e.g. `userId`).

Adds the missing dep and a regression test that asserts both
handlers regenerate together after an identity-invalidation rerender,
while preserving identity across a no-prop rerender.
This commit is contained in:
Taranum01 2026-08-15 05:48:59 +05:30
parent dfac3e524e
commit 3620587641
2 changed files with 40 additions and 0 deletions

View File

@ -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))

View File

@ -393,6 +393,7 @@ export const useEmbeddedChatbot = (appSourceType: AppSourceType, tryAppId?: stri
}, [
isTryApp,
setShowNewConversationItemInList,
handleChangeConversation,
handleNewConversationInputsChange,
setClearChatList,
])