Combine pending PR #18982 for deflake CI validation

This commit is contained in:
Neil
2026-09-05 19:03:33 -07:00
3 changed files with 96 additions and 0 deletions
@@ -90,6 +90,7 @@ describe('fitAndFocusPanes', () => {
vi.stubGlobal('HTMLElement', FakeHTMLElement)
vi.stubGlobal('document', {
activeElement,
querySelectorAll: vi.fn(() => []),
querySelector: vi.fn((selector: string) =>
selector === '[data-tab-rename-input="true"]' && renameInputMounted
? (new FakeHTMLElement({ tagName: 'INPUT' }) as unknown as Element)
@@ -1,10 +1,18 @@
import type { PaneManager } from '@/lib/pane-manager/pane-manager'
import { hasVisibleOverlay } from '@/lib/visible-overlay'
export function fitPanes(manager: PaneManager): void {
manager.fitAllPanes()
}
export function focusActivePane(manager: PaneManager): void {
// Deferred layout work must not dismiss a menu that opened after it was queued.
if (
typeof document !== 'undefined' &&
hasVisibleOverlay({ ignoreSelector: '[data-worktree-sidebar]' })
) {
return
}
// Why: tab rename focuses the input on the next frame. A queued terminal
// layout focus can land in between mount and focus, blurring rename closed.
if (typeof document !== 'undefined' && document.querySelector('[data-tab-rename-input="true"]')) {
@@ -0,0 +1,87 @@
// @vitest-environment happy-dom
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { PaneManager } from '@/lib/pane-manager/pane-manager'
import { fitAndFocusPanes } from './pane-helpers'
function createLayoutFixture() {
const textarea = document.createElement('textarea')
textarea.className = 'xterm-helper-textarea'
document.body.append(textarea)
textarea.focus()
const terminal = { focus: vi.fn(() => textarea.focus()) }
const manager = {
fitAllPanes: vi.fn(),
getActivePane: () => ({ terminal }),
getPanes: () => [{ terminal }]
} as unknown as PaneManager
return { manager, terminal, textarea }
}
function mountOverlay(role: string) {
const overlay = document.createElement('div')
overlay.setAttribute('role', role)
overlay.tabIndex = -1
vi.spyOn(overlay, 'getClientRects').mockReturnValue([
new DOMRect(0, 0, 100, 100)
] as unknown as DOMRectList)
document.body.append(overlay)
return overlay
}
afterEach(() => {
document.body.replaceChildren()
vi.restoreAllMocks()
})
describe('terminal layout preserves overlay focus', () => {
it.each(['menu', 'dialog', 'alertdialog', 'listbox'])(
'does not blur an open %s during a queued fit',
(role) => {
const { manager, terminal } = createLayoutFixture()
const overlay = mountOverlay(role)
overlay.focus()
const blurred = vi.fn()
overlay.addEventListener('blur', blurred)
fitAndFocusPanes(manager)
expect(manager.fitAllPanes).toHaveBeenCalledOnce()
expect(terminal.focus).not.toHaveBeenCalled()
expect(document.activeElement).toBe(overlay)
expect(blurred).not.toHaveBeenCalled()
}
)
it('leaves a mounted menu time to acquire focus', () => {
const { manager, terminal, textarea } = createLayoutFixture()
textarea.blur()
mountOverlay('menu')
fitAndFocusPanes(manager)
expect(terminal.focus).not.toHaveBeenCalled()
expect(document.activeElement).toBe(document.body)
})
it('allows focus after the menu closes', () => {
const { manager, terminal, textarea } = createLayoutFixture()
const overlay = mountOverlay('menu')
overlay.focus()
overlay.remove()
fitAndFocusPanes(manager)
expect(terminal.focus).toHaveBeenCalledOnce()
expect(document.activeElement).toBe(textarea)
})
it('does not treat the workspace sidebar as a focus-owning overlay', () => {
const { manager, terminal } = createLayoutFixture()
const sidebar = mountOverlay('listbox')
sidebar.setAttribute('data-worktree-sidebar', '')
fitAndFocusPanes(manager)
expect(terminal.focus).toHaveBeenCalledOnce()
})
})