fix(mobile): keep terminal caret visible without focus (#10101)
This commit is contained in:
parent
4274dbc48a
commit
2cf41ab864
|
|
@ -726,7 +726,9 @@ ${TERMINAL_WEBGL_RECOVERY_JS}
|
|||
disableStdin: false,
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'bar',
|
||||
cursorInactiveStyle: 'none',
|
||||
// Why: native TextInput owns mobile keyboard focus, so xterm stays inactive.
|
||||
// Match its active bar while still honoring application cursor-hide sequences.
|
||||
cursorInactiveStyle: 'bar',
|
||||
convertEol: false,
|
||||
allowProposedApi: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// @vitest-environment happy-dom
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { XTERM_HTML } from './terminal-webview-html'
|
||||
|
||||
function iifeSource(): string {
|
||||
|
|
@ -15,6 +15,15 @@ function bodyMarkup(): string {
|
|||
}
|
||||
|
||||
type TerminalStub = ReturnType<typeof makeTerminal>
|
||||
type TerminalOptions = {
|
||||
cursorInactiveStyle?: string
|
||||
cursorStyle?: string
|
||||
}
|
||||
type RegisteredWindowListener = {
|
||||
listener: EventListenerOrEventListenerObject
|
||||
options?: boolean | AddEventListenerOptions
|
||||
type: string
|
||||
}
|
||||
|
||||
function makeTerminal(writeCallbacks: Array<() => void>) {
|
||||
const terminal = {
|
||||
|
|
@ -79,13 +88,26 @@ function dispatchInit(cols: number, initialData: string): void {
|
|||
|
||||
describe('terminal WebView init surface replacement', () => {
|
||||
let animationFrames: Array<() => void>
|
||||
let registeredWindowListeners: RegisteredWindowListener[]
|
||||
let terminalOptions: TerminalOptions[]
|
||||
let terminals: TerminalStub[]
|
||||
let writeCallbacks: Array<() => void>
|
||||
|
||||
beforeEach(() => {
|
||||
animationFrames = []
|
||||
registeredWindowListeners = []
|
||||
terminalOptions = []
|
||||
terminals = []
|
||||
writeCallbacks = []
|
||||
const addWindowEventListener = window.addEventListener.bind(window)
|
||||
vi.spyOn(window, 'addEventListener').mockImplementation(((
|
||||
type: string,
|
||||
listener: EventListenerOrEventListenerObject,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
) => {
|
||||
registeredWindowListeners.push({ type, listener, options })
|
||||
addWindowEventListener(type, listener, options)
|
||||
}) as typeof window.addEventListener)
|
||||
vi.stubGlobal('requestAnimationFrame', (callback: () => void) => {
|
||||
animationFrames.push(callback)
|
||||
return animationFrames.length
|
||||
|
|
@ -93,20 +115,42 @@ describe('terminal WebView init surface replacement', () => {
|
|||
Object.defineProperty(window, 'innerWidth', { value: 381, configurable: true })
|
||||
Object.defineProperty(window, 'innerHeight', { value: 612, configurable: true })
|
||||
const webWindow = window as unknown as {
|
||||
Terminal: new () => TerminalStub
|
||||
Terminal: new (options: TerminalOptions) => TerminalStub
|
||||
ReactNativeWebView: { postMessage: (data: string) => void }
|
||||
}
|
||||
webWindow.Terminal = function () {
|
||||
webWindow.Terminal = function (options: TerminalOptions) {
|
||||
terminalOptions.push(options)
|
||||
const terminal = makeTerminal(writeCallbacks)
|
||||
terminals.push(terminal)
|
||||
return terminal
|
||||
} as unknown as new () => TerminalStub
|
||||
} as unknown as new (options: TerminalOptions) => TerminalStub
|
||||
webWindow.ReactNativeWebView = { postMessage: vi.fn() }
|
||||
document.body.innerHTML = bodyMarkup()
|
||||
// eslint-disable-next-line no-new-func
|
||||
new Function(iifeSource())()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
for (const { type, listener, options } of registeredWindowListeners) {
|
||||
window.removeEventListener(type, listener as EventListener, options)
|
||||
}
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("keeps xterm's inactive cursor visible across replacement surfaces", () => {
|
||||
dispatchInit(120, 'desktop')
|
||||
dispatchInit(51, 'phone-resize')
|
||||
dispatchInit(51, 'phone-scrollback')
|
||||
|
||||
expect(terminalOptions).toHaveLength(3)
|
||||
for (const options of terminalOptions) {
|
||||
expect(options).toMatchObject({
|
||||
cursorStyle: 'bar',
|
||||
cursorInactiveStyle: 'bar'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('commits only the newest surface when phone-fit init calls overlap', () => {
|
||||
// Why: restored terminals can receive desktop scrollback, a phone resize,
|
||||
// and phone scrollback before any xterm replay callback has completed.
|
||||
|
|
|
|||
Loading…
Reference in New Issue