fix(tui): scope events by project (#26936)
This commit is contained in:
@@ -4,9 +4,10 @@ import { onMount } from "solid-js"
|
||||
import { ArgsProvider } from "../../../../src/cli/cmd/tui/context/args"
|
||||
import { ExitProvider } from "../../../../src/cli/cmd/tui/context/exit"
|
||||
import { KVProvider, useKV } from "../../../../src/cli/cmd/tui/context/kv"
|
||||
import { ProjectProvider } from "../../../../src/cli/cmd/tui/context/project"
|
||||
import { ProjectProvider, useProject } from "../../../../src/cli/cmd/tui/context/project"
|
||||
import { SDKProvider, type EventSource } from "../../../../src/cli/cmd/tui/context/sdk"
|
||||
import { SyncProvider, useSync } from "../../../../src/cli/cmd/tui/context/sync"
|
||||
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
|
||||
|
||||
export const worktree = "/tmp/opencode"
|
||||
export const directory = `${worktree}/packages/opencode`
|
||||
@@ -30,6 +31,25 @@ export function eventSource(): EventSource {
|
||||
return { subscribe: async () => () => {} }
|
||||
}
|
||||
|
||||
export function createEventSource() {
|
||||
let fn: ((event: GlobalEvent) => void) | undefined
|
||||
|
||||
return {
|
||||
source: {
|
||||
subscribe: async (handler: (event: GlobalEvent) => void) => {
|
||||
fn = handler
|
||||
return () => {
|
||||
if (fn === handler) fn = undefined
|
||||
}
|
||||
},
|
||||
} satisfies EventSource,
|
||||
emit(event: GlobalEvent) {
|
||||
if (!fn) throw new Error("event source not ready")
|
||||
fn(event)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type FetchHandler = (url: URL) => Response | Promise<Response> | undefined
|
||||
|
||||
export function createFetch(override?: FetchHandler) {
|
||||
@@ -77,11 +97,13 @@ export function createFetch(override?: FetchHandler) {
|
||||
return { fetch, session }
|
||||
}
|
||||
|
||||
type Ctx = { kv: ReturnType<typeof useKV>; sync: ReturnType<typeof useSync> }
|
||||
type Ctx = { kv: ReturnType<typeof useKV>; project: ReturnType<typeof useProject>; sync: ReturnType<typeof useSync> }
|
||||
|
||||
export async function mount(override?: FetchHandler) {
|
||||
const calls = createFetch(override)
|
||||
const events = createEventSource()
|
||||
let sync!: ReturnType<typeof useSync>
|
||||
let project!: ReturnType<typeof useProject>
|
||||
let kv!: ReturnType<typeof useKV>
|
||||
let done!: () => void
|
||||
const ready = new Promise<void>((resolve) => {
|
||||
@@ -89,9 +111,10 @@ export async function mount(override?: FetchHandler) {
|
||||
})
|
||||
|
||||
function Probe() {
|
||||
const ctx: Ctx = { kv: useKV(), sync: useSync() }
|
||||
const ctx: Ctx = { kv: useKV(), project: useProject(), sync: useSync() }
|
||||
onMount(() => {
|
||||
sync = ctx.sync
|
||||
project = ctx.project
|
||||
kv = ctx.kv
|
||||
done()
|
||||
})
|
||||
@@ -102,7 +125,7 @@ export async function mount(override?: FetchHandler) {
|
||||
<ArgsProvider>
|
||||
<ExitProvider>
|
||||
<KVProvider>
|
||||
<SDKProvider url="http://test" directory={directory} fetch={calls.fetch} events={eventSource()}>
|
||||
<SDKProvider url="http://test" directory={directory} fetch={calls.fetch} events={events.source}>
|
||||
<ProjectProvider>
|
||||
<SyncProvider>
|
||||
<Probe />
|
||||
@@ -116,5 +139,5 @@ export async function mount(override?: FetchHandler) {
|
||||
|
||||
await ready
|
||||
await wait(() => sync.status === "complete")
|
||||
return { app, kv, sync, session: calls.session }
|
||||
return { app, emit: events.emit, kv, project, sync, session: calls.session }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,21 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { tmpdir } from "../../../fixture/fixture"
|
||||
import { mount } from "./sync-fixture"
|
||||
import { mount, wait } from "./sync-fixture"
|
||||
import type { GlobalEvent } from "@opencode-ai/sdk/v2"
|
||||
|
||||
function branchEvent(branch: string, workspace?: string): GlobalEvent {
|
||||
return {
|
||||
directory: "/tmp/other",
|
||||
project: "proj_test",
|
||||
workspace,
|
||||
payload: {
|
||||
id: `evt_vcs_${branch}`,
|
||||
type: "vcs.branch.updated",
|
||||
properties: { branch },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("tui sync", () => {
|
||||
test("refresh scopes sessions by default and lists project sessions when disabled", async () => {
|
||||
@@ -27,4 +41,30 @@ describe("tui sync", () => {
|
||||
Global.Path.state = previous
|
||||
}
|
||||
})
|
||||
|
||||
test("vcs branch updates only apply for the active workspace", async () => {
|
||||
const previous = Global.Path.state
|
||||
await using tmp = await tmpdir()
|
||||
Global.Path.state = tmp.path
|
||||
await Bun.write(`${tmp.path}/kv.json`, "{}")
|
||||
const { app, emit, project, sync } = await mount()
|
||||
|
||||
try {
|
||||
expect(sync.data.vcs?.branch).toBe("main")
|
||||
|
||||
project.workspace.set("ws_a")
|
||||
emit(branchEvent("other", "ws_b"))
|
||||
await Bun.sleep(30)
|
||||
|
||||
expect(sync.data.vcs?.branch).toBe("main")
|
||||
|
||||
emit(branchEvent("feature", "ws_a"))
|
||||
await wait(() => sync.data.vcs?.branch === "feature")
|
||||
|
||||
expect(sync.data.vcs?.branch).toBe("feature")
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
Global.Path.state = previous
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user