test(pty): pin terminal query-reply order; align one renderer call site (#16862)

* fix(pty): preserve renderer query reply ordering

* docs(pty): explain renderer query ordering
This commit is contained in:
Brennan Benson
2026-08-27 14:50:58 -07:00
committed by GitHub
parent d3475957f3
commit 1485aa4de2
4 changed files with 58 additions and 8 deletions
@@ -356,10 +356,12 @@ describe('connectPanePty', () => {
await flushAsyncTicks(8)
const replies = transport.sendInputImmediate.mock.calls.map((call) => String(call[0]))
expect(replies.some((reply) => reply.startsWith('\x1b]11;rgb:'))).toBe(true)
// oxlint-disable-next-line no-control-regex -- the ESC byte IS the payload: this matches the CPR reply
expect(replies.some((reply) => /^\u001b\[\d+;\d+R$/.test(reply))).toBe(true)
expect(replies).toContain(DEFAULT_DA1_RESPONSE)
const cprReply = replies.find((reply) => /^\u001b\[\d+;\d+R$/.test(reply))
const oscReply = replies.find((reply) => reply.startsWith('\x1b]11;rgb:'))
expect(oscReply).toBeDefined()
expect(cprReply).toBeDefined()
expect(replies).toEqual([oscReply, cprReply, DEFAULT_DA1_RESPONSE])
const written = writtenFloodData(pane)
expect(written).not.toContain('aaaa')
expect(written).not.toContain('bbbb')
@@ -123,11 +123,7 @@ export function bindLiveDataCallback(session: ConnectPanePtySession): void {
session.schedulePendingStartupCommandDelivery()
return
}
if (pendingForegroundQuery?.statelessQueryData) {
session.writePtyOutputToXterm(pendingForegroundQuery.statelessQueryData, true, {
hiddenStartupRendererQuery: true
})
}
// Keep source order aligned with sibling producers; xterm's async write buffer made the old inversion latent.
if (pendingForegroundQuery?.oscColorQueryData) {
sendTerminalOscColorQueryReplies(
pendingForegroundQuery.oscColorQueryData,
@@ -136,6 +132,11 @@ export function bindLiveDataCallback(session: ConnectPanePtySession): void {
session.sendDesktopQueryReplyImmediate
)
}
if (pendingForegroundQuery?.statelessQueryData) {
session.writePtyOutputToXterm(pendingForegroundQuery.statelessQueryData, true, {
hiddenStartupRendererQuery: true
})
}
const restoreAppliesToCurrentPty =
session.hiddenOutputRestorePtyId !== null &&
session.transport.getPtyId() === session.hiddenOutputRestorePtyId
@@ -88,6 +88,34 @@ describe('installTerminalCapabilityReplyHandlers', () => {
}
})
it.each([
['OSC 11 then CPR', '\x1b]11;?\x1b\\\x1b[6n', ['osc', 'cpr']],
['CPR then OSC 11', '\x1b[6n\x1b]11;?\x1b\\', ['cpr', 'osc']]
] as const)('preserves combined query order (%s)', async (_name, input, expectedKinds) => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true })
term.options.theme = { background: '#ffffff' }
const replies: string[] = []
const disposable = installTerminalCapabilityReplyHandlers({
terminal: term as never,
parser: term.parser,
sendInput: (data) => {
replies.push(data)
},
isReplaying: () => false
})
const onData = term.onData((data) => replies.push(data))
try {
await writeTerminal(term, input)
const kinds = replies.map((reply) => (reply.startsWith('\x1b]11;') ? 'osc' : 'cpr'))
expect(kinds).toEqual(expectedKinds)
} finally {
onData.dispose()
disposable.dispose()
term.dispose()
}
})
it('answers OSC color queries for active rgba and modern rgb theme colors', async () => {
const term = new Terminal({ cols: 80, rows: 24, allowProposedApi: true })
term.options.theme = {
+19
View File
@@ -176,6 +176,25 @@ describe('query reply ordering (termenv OSC-then-CPR)', () => {
vi.useRealTimers()
})
it('preserves reverse query order when CPR arrives before the color query', async () => {
vi.useFakeTimers()
const pty: string[] = []
const ingress = new PtyStartupIngress({
ownerBackend: 'posix-pty',
write: (data) => pty.push(data),
onEmission: () => {}
})
const write = hostWrites(ingress, pty)
write(CPR_REPLY)
write(OSC_11_REPLY)
await vi.advanceTimersByTimeAsync(200)
expect(pty).toEqual([CPR_REPLY, OSC_11_REPLY])
ingress.drainAndClose()
vi.useRealTimers()
})
it('keeps a CPR immediate when no color reply is deferred', () => {
vi.useFakeTimers()
const pty: string[] = []