mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-08 17:59:09 +00:00
Fix terminal e2e flakiness with a real terminal driver (#17144)
This commit is contained in:
@@ -10,6 +10,7 @@ import { useSDK } from "@/context/sdk"
|
||||
import { useServer } from "@/context/server"
|
||||
import { monoFontFamily, useSettings } from "@/context/settings"
|
||||
import type { LocalPTY } from "@/context/terminal"
|
||||
import { terminalAttr, terminalProbe } from "@/testing/terminal"
|
||||
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
|
||||
import { terminalWriter } from "@/utils/terminal-writer"
|
||||
|
||||
@@ -160,6 +161,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
let container!: HTMLDivElement
|
||||
const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"])
|
||||
const id = local.pty.id
|
||||
const probe = terminalProbe(id)
|
||||
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
|
||||
const restoreSize =
|
||||
restore &&
|
||||
@@ -326,6 +328,9 @@ export const Terminal = (props: TerminalProps) => {
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
probe.init()
|
||||
cleanups.push(() => probe.drop())
|
||||
|
||||
const run = async () => {
|
||||
const loaded = await loadGhostty()
|
||||
if (disposed) return
|
||||
@@ -353,7 +358,13 @@ export const Terminal = (props: TerminalProps) => {
|
||||
}
|
||||
ghostty = g
|
||||
term = t
|
||||
output = terminalWriter((data, done) => t.write(data, done))
|
||||
output = terminalWriter((data, done) =>
|
||||
t.write(data, () => {
|
||||
probe.render(data)
|
||||
probe.settle()
|
||||
done?.()
|
||||
}),
|
||||
)
|
||||
|
||||
t.attachCustomKeyEventHandler((event) => {
|
||||
const key = event.key.toLowerCase()
|
||||
@@ -441,10 +452,6 @@ export const Terminal = (props: TerminalProps) => {
|
||||
startResize()
|
||||
}
|
||||
|
||||
// t.onScroll((ydisp) => {
|
||||
// console.log("Scroll position:", ydisp)
|
||||
// })
|
||||
|
||||
const once = { value: false }
|
||||
let closing = false
|
||||
|
||||
@@ -460,6 +467,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
ws = socket
|
||||
|
||||
const handleOpen = () => {
|
||||
probe.connect()
|
||||
local.onConnect?.()
|
||||
scheduleSize(t.cols, t.rows)
|
||||
}
|
||||
@@ -560,6 +568,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
<div
|
||||
ref={container}
|
||||
data-component="terminal"
|
||||
{...{ [terminalAttr]: id }}
|
||||
data-prevent-autofocus
|
||||
tabIndex={-1}
|
||||
style={{ "background-color": terminalColors().background }}
|
||||
|
||||
64
packages/app/src/testing/terminal.ts
Normal file
64
packages/app/src/testing/terminal.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
export const terminalAttr = "data-pty-id"
|
||||
|
||||
export type TerminalProbeState = {
|
||||
connected: boolean
|
||||
rendered: string
|
||||
settled: number
|
||||
}
|
||||
|
||||
export type E2EWindow = Window & {
|
||||
__opencode_e2e?: {
|
||||
terminal?: {
|
||||
enabled?: boolean
|
||||
terminals?: Record<string, TerminalProbeState>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const seed = (): TerminalProbeState => ({
|
||||
connected: false,
|
||||
rendered: "",
|
||||
settled: 0,
|
||||
})
|
||||
|
||||
const root = () => {
|
||||
if (typeof window === "undefined") return
|
||||
const state = (window as E2EWindow).__opencode_e2e?.terminal
|
||||
if (!state?.enabled) return
|
||||
state.terminals ??= {}
|
||||
return state.terminals
|
||||
}
|
||||
|
||||
export const terminalProbe = (id: string) => {
|
||||
const set = (next: Partial<TerminalProbeState>) => {
|
||||
const terms = root()
|
||||
if (!terms) return
|
||||
terms[id] = { ...(terms[id] ?? seed()), ...next }
|
||||
}
|
||||
|
||||
return {
|
||||
init() {
|
||||
set(seed())
|
||||
},
|
||||
connect() {
|
||||
set({ connected: true })
|
||||
},
|
||||
render(data: string) {
|
||||
const terms = root()
|
||||
if (!terms) return
|
||||
const prev = terms[id] ?? seed()
|
||||
terms[id] = { ...prev, rendered: prev.rendered + data }
|
||||
},
|
||||
settle() {
|
||||
const terms = root()
|
||||
if (!terms) return
|
||||
const prev = terms[id] ?? seed()
|
||||
terms[id] = { ...prev, settled: prev.settled + 1 }
|
||||
},
|
||||
drop() {
|
||||
const terms = root()
|
||||
if (!terms) return
|
||||
delete terms[id]
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user