perf: clean tab bar focus timers from root ref (#3627)
* perf: clean tab bar focus timers from root ref * test: cover tab bar focus cleanup on unmount Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
6e2f20abe2
commit
cbca6b2456
|
|
@ -1,3 +1,5 @@
|
|||
/* oxlint-disable max-lines -- Why: keeping these mocked TabBar wiring cases
|
||||
* together avoids duplicating the lightweight renderer harness. */
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const appStoreSnapshot: {
|
||||
|
|
@ -155,6 +157,7 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
|||
type ReactElementLike = {
|
||||
type: unknown
|
||||
props: Record<string, unknown>
|
||||
ref?: unknown
|
||||
}
|
||||
|
||||
function findChildrenByType(node: unknown, typeName: string): ReactElementLike[] {
|
||||
|
|
@ -241,6 +244,7 @@ describe('TabBar context menu wiring', () => {
|
|||
callback(0)
|
||||
return 1
|
||||
})
|
||||
vi.stubGlobal('cancelAnimationFrame', vi.fn())
|
||||
vi.stubGlobal('window', {
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
|
|
@ -324,4 +328,35 @@ describe('TabBar context menu wiring', () => {
|
|||
expect(focusTerminalTabSurface).toHaveBeenCalledWith('new-terminal')
|
||||
expect(focusTerminalTabSurface).not.toHaveBeenCalledWith('old-terminal')
|
||||
})
|
||||
|
||||
it('cancels delayed menu focus when the tab bar root unmounts', async () => {
|
||||
vi.useFakeTimers()
|
||||
Object.assign(window, { setTimeout, clearTimeout })
|
||||
const { focusTerminalTabSurface } = await import('@/lib/focus-terminal-tab-surface')
|
||||
const element = await renderTabBar({
|
||||
tabs: [TERMINAL_TAB],
|
||||
activeTabId: 'old-terminal',
|
||||
activeTabType: 'terminal',
|
||||
onNewTerminalTab: () => {
|
||||
window.setTimeout(() => {
|
||||
appStoreSnapshot.activeTabId = 'new-terminal'
|
||||
appStoreSnapshot.activeTabType = 'terminal'
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
|
||||
const newTerminalItem = findChildrenByType(element, 'DropdownMenuItem')[0]
|
||||
const menuContent = findChildrenByType(element, 'DropdownMenuContent')[0]
|
||||
;(newTerminalItem.props.onSelect as () => void)()
|
||||
;(menuContent.props.onCloseAutoFocus as (event: { preventDefault: () => void }) => void)({
|
||||
preventDefault: vi.fn()
|
||||
})
|
||||
|
||||
const root = findChildrenByType(element, 'div')[0]
|
||||
const rootRef = (root.props.ref ?? root.ref) as (node: HTMLDivElement | null) => void
|
||||
rootRef(null)
|
||||
|
||||
await vi.advanceTimersByTimeAsync(5000)
|
||||
expect(focusTerminalTabSurface).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -338,13 +338,24 @@ function TabBarInner({
|
|||
})
|
||||
}
|
||||
}
|
||||
useEffect(
|
||||
() => () => {
|
||||
|
||||
const clearPendingNewTabMenuFocusOnUnmountRef = useRef<
|
||||
((node: HTMLDivElement | null) => void) | null
|
||||
>(null)
|
||||
if (clearPendingNewTabMenuFocusOnUnmountRef.current === null) {
|
||||
clearPendingNewTabMenuFocusOnUnmountRef.current = (node: HTMLDivElement | null): void => {
|
||||
if (node !== null) {
|
||||
return
|
||||
}
|
||||
// Why: the delayed focus handoff is scoped to this tab bar instance.
|
||||
// A root ref cleanup cancels it at the DOM owner boundary without an
|
||||
// otherwise cleanup-only React Effect.
|
||||
clearPendingNewTabMenuFocusAnimation()
|
||||
clearPendingNewTabMenuFocusRetry()
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
}
|
||||
const clearPendingNewTabMenuFocusOnUnmount = clearPendingNewTabMenuFocusOnUnmountRef.current
|
||||
|
||||
useEffect(() => {
|
||||
if (!newTabMenuOpen) {
|
||||
return
|
||||
|
|
@ -517,6 +528,7 @@ function TabBarInner({
|
|||
|
||||
return (
|
||||
<div
|
||||
ref={clearPendingNewTabMenuFocusOnUnmount}
|
||||
className="flex items-stretch h-full overflow-hidden flex-1 min-w-0"
|
||||
// Why: only drops aimed at the top tab/session strip should open files in
|
||||
// Orca's editor. Terminal-pane drops need to keep inserting file paths
|
||||
|
|
|
|||
Loading…
Reference in New Issue