diff --git a/src/renderer/src/lib/activate-tab-and-focus-pane.test.ts b/src/renderer/src/lib/activate-tab-and-focus-pane.test.ts new file mode 100644 index 00000000000..b1573641fa9 --- /dev/null +++ b/src/renderer/src/lib/activate-tab-and-focus-pane.test.ts @@ -0,0 +1,38 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { activateTabAndFocusPane } from './activate-tab-and-focus-pane' + +const setActiveTab = vi.hoisted(() => vi.fn()) + +vi.mock('@/store', () => ({ + useAppStore: { + getState: () => ({ + setActiveTab + }) + } +})) + +describe('activateTabAndFocusPane', () => { + afterEach(() => { + vi.unstubAllGlobals() + vi.clearAllMocks() + }) + + it('cancels a pending pane focus frame when a newer activation starts', () => { + const cancelAnimationFrame = vi.fn() + vi.stubGlobal( + 'requestAnimationFrame', + vi.fn(() => 12) + ) + vi.stubGlobal('cancelAnimationFrame', cancelAnimationFrame) + vi.stubGlobal('window', { + dispatchEvent: vi.fn() + }) + + activateTabAndFocusPane('tab-1', 'leaf-1') + activateTabAndFocusPane('tab-2', 'leaf-2') + + expect(setActiveTab).toHaveBeenNthCalledWith(1, 'tab-1') + expect(setActiveTab).toHaveBeenNthCalledWith(2, 'tab-2') + expect(cancelAnimationFrame).toHaveBeenCalledWith(12) + }) +}) diff --git a/src/renderer/src/lib/activate-tab-and-focus-pane.ts b/src/renderer/src/lib/activate-tab-and-focus-pane.ts index 8f7860bcbab..eca2922eae3 100644 --- a/src/renderer/src/lib/activate-tab-and-focus-pane.ts +++ b/src/renderer/src/lib/activate-tab-and-focus-pane.ts @@ -1,6 +1,15 @@ import { useAppStore } from '@/store' import { FOCUS_TERMINAL_PANE_EVENT, type FocusTerminalPaneDetail } from '@/constants/terminal' +let pendingFocusPaneFrameId: number | null = null + +function cancelPendingFocusPaneFrame(): void { + if (pendingFocusPaneFrameId !== null) { + cancelAnimationFrame(pendingFocusPaneFrameId) + pendingFocusPaneFrameId = null + } +} + export function activateTabAndFocusPane( tabId: string, leafId: string | null, @@ -11,12 +20,14 @@ export function activateTabAndFocusPane( } ): void { useAppStore.getState().setActiveTab(tabId) + cancelPendingFocusPaneFrame() if (leafId === null) { return } // Why: defer one frame so the new TerminalPane has mounted its // FOCUS_TERMINAL_PANE_EVENT listener before we dispatch. - requestAnimationFrame(() => { + pendingFocusPaneFrameId = requestAnimationFrame(() => { + pendingFocusPaneFrameId = null const detail: FocusTerminalPaneDetail = { tabId, leafId,