summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src
diff options
context:
space:
mode:
authorLuke Parker <[email protected]>2026-03-17 12:55:58 +1000
committerGitHub <[email protected]>2026-03-17 12:55:58 +1000
commite416e59ea69f7600acbdb593ba68ac0fb1ee2633 (patch)
treef1594d949fc3ad0d37f871e3d1028fcc77674035 /packages/app/src
parentcb69501098c603ccd7d3e3dbe6655d401c1d815c (diff)
downloadopencode-e416e59ea69f7600acbdb593ba68ac0fb1ee2633.tar.gz
opencode-e416e59ea69f7600acbdb593ba68ac0fb1ee2633.zip
test(app): deflake slash terminal toggle flow (#17881)
Diffstat (limited to 'packages/app/src')
-rw-r--r--packages/app/src/components/prompt-input.tsx16
-rw-r--r--packages/app/src/pages/session/terminal-panel.tsx9
-rw-r--r--packages/app/src/testing/prompt.ts56
-rw-r--r--packages/app/src/testing/terminal.ts15
4 files changed, 95 insertions, 1 deletions
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index b2553e4c0..4fbc82a70 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -36,6 +36,7 @@ import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
+import { promptEnabled, promptProbe } from "@/testing/prompt"
import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom"
import { createPromptAttachments } from "./prompt-input/attachments"
import { ACCEPTED_FILE_TYPES } from "./prompt-input/files"
@@ -604,6 +605,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const handleSlashSelect = (cmd: SlashCommand | undefined) => {
if (!cmd) return
+ promptProbe.select(cmd.id)
closePopover()
if (cmd.type === "custom") {
@@ -692,6 +694,20 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
})
+ if (promptEnabled()) {
+ createEffect(() => {
+ promptProbe.set({
+ popover: store.popover,
+ slash: {
+ active: slashActive() ?? null,
+ ids: slashFlat().map((cmd) => cmd.id),
+ },
+ })
+ })
+
+ onCleanup(() => promptProbe.clear())
+ }
+
const selectPopoverActive = () => {
if (store.popover === "at") {
const items = atFlat()
diff --git a/packages/app/src/pages/session/terminal-panel.tsx b/packages/app/src/pages/session/terminal-panel.tsx
index e78ebecfc..d62d91c19 100644
--- a/packages/app/src/pages/session/terminal-panel.tsx
+++ b/packages/app/src/pages/session/terminal-panel.tsx
@@ -18,8 +18,10 @@ import { terminalTabLabel } from "@/pages/session/terminal-label"
import { createSizing, focusTerminalById } from "@/pages/session/helpers"
import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff"
import { useSessionLayout } from "@/pages/session/session-layout"
+import { terminalProbe } from "@/testing/terminal"
export function TerminalPanel() {
+ const delays = [120, 240]
const layout = useLayout()
const terminal = useTerminal()
const language = useLanguage()
@@ -79,16 +81,20 @@ export function TerminalPanel() {
)
const focus = (id: string) => {
+ const probe = terminalProbe(id)
+ probe.focus(delays.length + 1)
focusTerminalById(id)
const frame = requestAnimationFrame(() => {
+ probe.step()
if (!opened()) return
if (terminal.active() !== id) return
focusTerminalById(id)
})
- const timers = [120, 240].map((ms) =>
+ const timers = delays.map((ms) =>
window.setTimeout(() => {
+ probe.step()
if (!opened()) return
if (terminal.active() !== id) return
focusTerminalById(id)
@@ -96,6 +102,7 @@ export function TerminalPanel() {
)
return () => {
+ probe.focus(0)
cancelAnimationFrame(frame)
for (const timer of timers) clearTimeout(timer)
}
diff --git a/packages/app/src/testing/prompt.ts b/packages/app/src/testing/prompt.ts
new file mode 100644
index 000000000..e11462f30
--- /dev/null
+++ b/packages/app/src/testing/prompt.ts
@@ -0,0 +1,56 @@
+import type { E2EWindow } from "./terminal"
+
+export type PromptProbeState = {
+ popover: "at" | "slash" | null
+ slash: {
+ active: string | null
+ ids: string[]
+ }
+ selected: string | null
+ selects: number
+}
+
+export const promptEnabled = () => {
+ if (typeof window === "undefined") return false
+ return (window as E2EWindow).__opencode_e2e?.prompt?.enabled === true
+}
+
+const root = () => {
+ if (!promptEnabled()) return
+ return (window as E2EWindow).__opencode_e2e?.prompt
+}
+
+export const promptProbe = {
+ set(input: Omit<PromptProbeState, "selected" | "selects">) {
+ const state = root()
+ if (!state) return
+ state.current = {
+ popover: input.popover,
+ slash: {
+ active: input.slash.active,
+ ids: [...input.slash.ids],
+ },
+ selected: state.current?.selected ?? null,
+ selects: state.current?.selects ?? 0,
+ }
+ },
+ select(id: string) {
+ const state = root()
+ if (!state) return
+ const prev = state.current
+ state.current = {
+ popover: prev?.popover ?? null,
+ slash: {
+ active: prev?.slash.active ?? null,
+ ids: [...(prev?.slash.ids ?? [])],
+ },
+ selected: id,
+ selects: (prev?.selects ?? 0) + 1,
+ }
+ },
+ clear() {
+ const state = root()
+ if (!state) return
+ state.current = undefined
+ },
+}
diff --git a/packages/app/src/testing/terminal.ts b/packages/app/src/testing/terminal.ts
index af1c33309..2bca39b31 100644
--- a/packages/app/src/testing/terminal.ts
+++ b/packages/app/src/testing/terminal.ts
@@ -7,6 +7,7 @@ export type TerminalProbeState = {
connects: number
rendered: string
settled: number
+ focusing: number
}
type TerminalProbeControl = {
@@ -19,6 +20,10 @@ export type E2EWindow = Window & {
enabled?: boolean
current?: ModelProbeState
}
+ prompt?: {
+ enabled?: boolean
+ current?: import("./prompt").PromptProbeState
+ }
terminal?: {
enabled?: boolean
terminals?: Record<string, TerminalProbeState>
@@ -32,6 +37,7 @@ const seed = (): TerminalProbeState => ({
connects: 0,
rendered: "",
settled: 0,
+ focusing: 0,
})
const root = () => {
@@ -88,6 +94,15 @@ export const terminalProbe = (id: string) => {
const prev = state[id] ?? seed()
state[id] = { ...prev, settled: prev.settled + 1 }
},
+ focus(count: number) {
+ set({ focusing: Math.max(0, count) })
+ },
+ step() {
+ const state = terms()
+ if (!state) return
+ const prev = state[id] ?? seed()
+ state[id] = { ...prev, focusing: Math.max(0, prev.focusing - 1) }
+ },
control(next: Partial<TerminalProbeControl>) {
const state = controls()
if (!state) return