mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
157 lines
5.1 KiB
TypeScript
157 lines
5.1 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import { Browser } from "@opencode/plugin-browser/rpc"
|
|
import type { BrowserPaneEvent, BrowserPaneTarget } from "@/runtime/platform/browser-pane"
|
|
import { createBrowserConnection, type BrowserConnectionState } from "./connection"
|
|
|
|
const tabID = Browser.TabID.make(`tab_${crypto.randomUUID()}`)
|
|
const browser: Browser.State = {
|
|
tabs: [
|
|
{
|
|
id: tabID,
|
|
url: "http://localhost:4173/",
|
|
title: "Preview",
|
|
loading: false,
|
|
canGoBack: true,
|
|
canGoForward: false,
|
|
generation: 3,
|
|
},
|
|
],
|
|
focusedTabID: tabID,
|
|
}
|
|
|
|
function fixture() {
|
|
const states: BrowserConnectionState[] = []
|
|
const calls: {
|
|
target: BrowserPaneTarget
|
|
emit: (event: BrowserPaneEvent) => void
|
|
closed: boolean
|
|
commands: Browser.Action[]
|
|
}[] = []
|
|
const endpoint = { url: "http://localhost:4096" }
|
|
const previews: string[] = []
|
|
const inspections: Extract<BrowserPaneEvent, { type: "inspect" }>[] = []
|
|
const highlights: (Browser.Ref | undefined)[] = []
|
|
const connection = createBrowserConnection({
|
|
target: () => ({ serverKey: "browser-test", sessionID: "ses_browser", endpoint: { ...endpoint } }),
|
|
change: (state) => states.push(state),
|
|
focus: () => {},
|
|
preview: (path) => previews.push(path),
|
|
inspect: (event) => inspections.push(event),
|
|
pane: {
|
|
register(target, emit) {
|
|
const call = { target, emit, closed: false, commands: [] as Browser.Action[] }
|
|
calls.push(call)
|
|
return {
|
|
setLayout() {},
|
|
async command(command) {
|
|
call.commands.push(command)
|
|
},
|
|
async capture() {
|
|
return null
|
|
},
|
|
inspect() {},
|
|
highlight(_tabID, ref) {
|
|
highlights.push(ref)
|
|
},
|
|
close() {
|
|
call.closed = true
|
|
},
|
|
}
|
|
},
|
|
},
|
|
})
|
|
connection.wake()
|
|
calls[0].emit({ type: "state", state: browser })
|
|
return { connection, calls, states, endpoint, previews, inspections, highlights }
|
|
}
|
|
|
|
test("picked elements reach the session and highlights reach the page without touching connection state", () => {
|
|
const app = fixture()
|
|
try {
|
|
const before = app.states.length
|
|
const element = {
|
|
ref: Browser.Ref.make("e4"),
|
|
selector: "#save",
|
|
label: "button#save",
|
|
rect: { x: 1, y: 2, width: 3, height: 4 },
|
|
}
|
|
app.calls[0].emit({ type: "inspect", tabID, active: false, element })
|
|
expect(app.inspections).toEqual([{ type: "inspect", tabID, active: false, element }])
|
|
app.connection.highlight(tabID, element.ref)
|
|
app.connection.highlight(tabID)
|
|
expect(app.highlights).toEqual([element.ref, undefined])
|
|
expect(app.states).toHaveLength(before)
|
|
} finally {
|
|
app.connection.dispose()
|
|
}
|
|
})
|
|
|
|
test("preview requests reach the session without touching connection state", () => {
|
|
const app = fixture()
|
|
try {
|
|
const before = app.states.length
|
|
app.calls[0].emit({ type: "preview", path: "docs/report.pdf" })
|
|
expect(app.previews).toEqual(["docs/report.pdf"])
|
|
expect(app.states).toHaveLength(before)
|
|
} finally {
|
|
app.connection.dispose()
|
|
}
|
|
})
|
|
|
|
test("suspension retains tabs and reconnects once on demand using the current endpoint", async () => {
|
|
const app = fixture()
|
|
try {
|
|
app.calls[0].emit({ type: "state", state: browser, error: "browser.pane.suspended" })
|
|
expect(app.calls[0].closed).toBe(true)
|
|
expect(app.states.at(-1)).toMatchObject({ registration: undefined, browser, suspended: true })
|
|
// A suspended attachment must not schedule the one-second transport retry.
|
|
await Bun.sleep(1_100)
|
|
expect(app.calls).toHaveLength(1)
|
|
app.endpoint.url = "http://localhost:4999"
|
|
app.connection.wake()
|
|
app.connection.wake()
|
|
expect(app.calls).toHaveLength(2)
|
|
expect(app.calls[1].target).toEqual({
|
|
serverKey: "browser-test",
|
|
sessionID: "ses_browser",
|
|
endpoint: app.endpoint,
|
|
restore: browser,
|
|
})
|
|
expect(app.states.at(-1)?.suspended).toBe(false)
|
|
app.calls[0].emit({ type: "state", state: null, error: "browser.pane.registration.closed" })
|
|
expect(app.states.at(-1)?.registration).toBeDefined()
|
|
} finally {
|
|
app.connection.dispose()
|
|
}
|
|
})
|
|
|
|
test("a command wakes its attachment once and is not replayed", async () => {
|
|
const app = fixture()
|
|
try {
|
|
app.calls[0].emit({ type: "state", state: browser, error: "browser.pane.suspended" })
|
|
await app.connection.command({ type: "reload", tabID })
|
|
expect(app.calls).toHaveLength(2)
|
|
expect(app.calls[1].commands).toEqual([{ type: "reload", tabID }])
|
|
app.connection.dispose()
|
|
app.connection.wake()
|
|
expect(app.calls[1].closed).toBe(true)
|
|
expect(app.calls).toHaveLength(2)
|
|
} finally {
|
|
app.connection.dispose()
|
|
}
|
|
})
|
|
|
|
for (const error of ["browser.pane.replaced", "browser.pane.unsupported"]) {
|
|
test(`${error} blocks automatic ownership recovery`, () => {
|
|
const app = fixture()
|
|
try {
|
|
app.calls[0].emit({ type: "state", state: null, error })
|
|
app.connection.wake()
|
|
expect(app.calls).toHaveLength(1)
|
|
expect(app.states.at(-1)?.error).toBe(error)
|
|
} finally {
|
|
app.connection.dispose()
|
|
}
|
|
})
|
|
}
|