fix: guard hidden hydration against transport replacement (#2686)
This commit is contained in:
parent
7a9b0465dc
commit
b2df6eff74
|
|
@ -388,6 +388,77 @@ describe('useTerminalPaneGlobalEffects', () => {
|
|||
expect(terminal.write).toHaveBeenCalledWith('new-output', expect.any(Function))
|
||||
})
|
||||
|
||||
it('does not replay stale hydration after the pane transport is replaced', async () => {
|
||||
const terminal = {
|
||||
name: 'terminal-a',
|
||||
options: { scrollback: 1000 },
|
||||
write: vi.fn((_data: string, callback?: () => void) => callback?.())
|
||||
}
|
||||
const pane = { id: 1, terminal }
|
||||
const oldTransport = { getPtyId: vi.fn(() => 'pty-1') }
|
||||
const newTransport = { getPtyId: vi.fn(() => 'pty-2') }
|
||||
const paneTransports = new Map<number, typeof oldTransport | typeof newTransport>([
|
||||
[1, oldTransport]
|
||||
])
|
||||
const manager = {
|
||||
getPanes: vi.fn(() => [pane]),
|
||||
resumeRendering: vi.fn(),
|
||||
suspendRendering: vi.fn(),
|
||||
fitAllPanes: vi.fn(),
|
||||
getActivePane: vi.fn(() => null),
|
||||
setActivePane: vi.fn()
|
||||
}
|
||||
let resolveSnapshot!: (snapshot: { data: string; cols: number; rows: number } | null) => void
|
||||
const snapshotPromise = new Promise<{ data: string; cols: number; rows: number } | null>(
|
||||
(resolve) => {
|
||||
resolveSnapshot = resolve
|
||||
}
|
||||
)
|
||||
window.api.pty.serializeHeadlessBuffer = vi.fn((id: string) => {
|
||||
if (id === 'pty-1') {
|
||||
return snapshotPromise
|
||||
}
|
||||
return Promise.resolve(null)
|
||||
}) as typeof window.api.pty.serializeHeadlessBuffer
|
||||
const baseArgs = {
|
||||
tabId: 'tab-1',
|
||||
worktreeId: 'wt-1',
|
||||
isActive: true,
|
||||
isVisible: true,
|
||||
paneCount: 1,
|
||||
managerRef: { current: manager as never },
|
||||
containerRef: { current: null },
|
||||
paneTransportsRef: { current: paneTransports as never },
|
||||
replayingPanesRef: { current: new Map<number, number>() },
|
||||
isActiveRef: { current: false },
|
||||
isVisibleRef: { current: false },
|
||||
toggleExpandPane: vi.fn()
|
||||
}
|
||||
queueHiddenTerminalOutput(terminal, 'pty-1', 'old-output')
|
||||
|
||||
beginHookRender()
|
||||
useTerminalPaneGlobalEffects(baseArgs)
|
||||
|
||||
paneTransports.set(1, newTransport)
|
||||
queueHiddenTerminalOutput(terminal, 'pty-2', 'new-output')
|
||||
resolveSnapshot({ data: 'stale-headless-output', cols: 120, rows: 40 })
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
|
||||
expect(terminal.write).not.toHaveBeenCalledWith('stale-headless-output', expect.any(Function))
|
||||
terminal.write.mockClear()
|
||||
|
||||
beginHookRender()
|
||||
useTerminalPaneGlobalEffects(baseArgs)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
|
||||
expect(window.api.pty.serializeHeadlessBuffer).toHaveBeenCalledWith('pty-2', {
|
||||
scrollbackRows: 1000
|
||||
})
|
||||
expect(terminal.write).toHaveBeenCalledWith('new-output', expect.any(Function))
|
||||
})
|
||||
|
||||
it('restores from the pre-hide scroll state when hidden layout changes the viewport', () => {
|
||||
const terminalA = { name: 'terminal-a' }
|
||||
const manager = {
|
||||
|
|
|
|||
|
|
@ -88,8 +88,16 @@ export function useTerminalPaneGlobalEffects({
|
|||
if (!hydration) {
|
||||
return
|
||||
}
|
||||
const transport = paneTransportsRef.current.get(pane.id)
|
||||
if (transport?.getPtyId() !== hydration.ptyId) {
|
||||
const getCurrentPtyId = (): string | null => {
|
||||
const currentPane = managerRef.current
|
||||
?.getPanes()
|
||||
.find((candidate) => candidate.id === pane.id)
|
||||
if (currentPane?.terminal !== pane.terminal) {
|
||||
return null
|
||||
}
|
||||
return paneTransportsRef.current.get(pane.id)?.getPtyId() ?? null
|
||||
}
|
||||
if (getCurrentPtyId() !== hydration.ptyId) {
|
||||
clearHiddenTerminalOutputForPty(pane.terminal, hydration.ptyId)
|
||||
return
|
||||
}
|
||||
|
|
@ -104,7 +112,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
cancelHiddenTerminalHydration(pane.terminal, hydration)
|
||||
return
|
||||
}
|
||||
if (transport.getPtyId() !== hydration.ptyId) {
|
||||
if (getCurrentPtyId() !== hydration.ptyId) {
|
||||
clearHiddenTerminalOutputForPty(pane.terminal, hydration.ptyId)
|
||||
return
|
||||
}
|
||||
|
|
@ -138,7 +146,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
cancelHiddenTerminalHydration(pane.terminal, hydration)
|
||||
return
|
||||
}
|
||||
if (transport.getPtyId() !== hydration.ptyId) {
|
||||
if (getCurrentPtyId() !== hydration.ptyId) {
|
||||
clearHiddenTerminalOutputForPty(pane.terminal, hydration.ptyId)
|
||||
return
|
||||
}
|
||||
|
|
@ -156,7 +164,7 @@ export function useTerminalPaneGlobalEffects({
|
|||
markHiddenTerminalFallbackReplayed(pane.terminal, hydration)
|
||||
})
|
||||
},
|
||||
[isVisibleRef, paneTransportsRef, replayingPanesRef]
|
||||
[isVisibleRef, managerRef, paneTransportsRef, replayingPanesRef]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue