mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
import { describe, it, expect } from "bun:test"
|
|
import { WorktreeDiffController } from "../../src/agent-manager/worktree-diff-controller"
|
|
import type { DiffSourceCatalog } from "../../src/diff/sources/catalog"
|
|
import type { DiffSource } from "../../src/diff/sources/types"
|
|
import type { PanelContext } from "../../src/diff/types"
|
|
import type { GitOps } from "../../src/agent-manager/GitOps"
|
|
import type { WorktreeStateManager } from "../../src/agent-manager/WorktreeStateManager"
|
|
|
|
// Records every PanelContext handed to catalog.build so tests can assert which
|
|
// base branch the active source was (re)built with. The controller, scope
|
|
// resolution, and SourceController lifecycle under test are all real.
|
|
function make(onFetch?: (n: number) => Promise<void>) {
|
|
const builds: { id: string; ctx: PanelContext }[] = []
|
|
let fetches = 0
|
|
const catalog = {
|
|
build: (id: string, ctx: PanelContext): DiffSource => {
|
|
builds.push({ id, ctx })
|
|
return {
|
|
descriptor: { id, type: "workspace", group: "Git", capabilities: { revert: true, comments: true } },
|
|
async fetch() {
|
|
await onFetch?.(++fetches)
|
|
return { diffs: [] }
|
|
},
|
|
}
|
|
},
|
|
} as unknown as DiffSourceCatalog
|
|
|
|
const state = {
|
|
getSession: (id: string) => (id === "s1" ? { id: "s1", worktreeId: "w1", createdAt: "" } : undefined),
|
|
getWorktree: (id: string) =>
|
|
id === "w1" ? { id: "w1", path: "/wt", parentBranch: "main", remote: "origin" } : undefined,
|
|
} as unknown as WorktreeStateManager
|
|
|
|
const controller = new WorktreeDiffController({
|
|
getState: () => state,
|
|
getRoot: () => "/repo",
|
|
getStateReady: () => undefined,
|
|
catalog,
|
|
git: {} as GitOps,
|
|
localDiffFile: async () => null,
|
|
post: () => {},
|
|
log: () => {},
|
|
})
|
|
return { controller, builds }
|
|
}
|
|
|
|
const tick = () => new Promise((resolve) => setTimeout(resolve, 0))
|
|
|
|
async function waitFor(cond: () => boolean): Promise<void> {
|
|
for (let i = 0; i < 50; i++) {
|
|
if (cond()) return
|
|
await tick()
|
|
}
|
|
throw new Error("waitFor timed out")
|
|
}
|
|
|
|
describe("WorktreeDiffController.setBase", () => {
|
|
it("rebuilds the active source against the overridden base branch", async () => {
|
|
const { controller, builds } = make()
|
|
controller.start("s1#branch")
|
|
await waitFor(() => builds.length === 1)
|
|
expect(builds[0]!.ctx.dir).toBe("/wt")
|
|
expect(builds[0]!.ctx.baseBranch).toBe("origin/main")
|
|
|
|
await controller.setBase("s1#branch", "feature-x")
|
|
expect(builds.length).toBe(2)
|
|
expect(builds[1]!.ctx.dir).toBe("/wt")
|
|
expect(builds[1]!.ctx.baseBranch).toBe("feature-x")
|
|
|
|
// Clearing the override falls back to the recorded parent ref.
|
|
await controller.setBase("s1#branch", undefined)
|
|
expect(builds.length).toBe(3)
|
|
expect(builds[2]!.ctx.baseBranch).toBe("origin/main")
|
|
|
|
controller.stop()
|
|
})
|
|
|
|
it("stores the override without rebuilding when the context isn't active", async () => {
|
|
const { controller, builds } = make()
|
|
|
|
await controller.setBase("s1#branch", "feature-x")
|
|
expect(builds.length).toBe(0)
|
|
|
|
// The next activation of that context resolves the stored override.
|
|
controller.start("s1#branch")
|
|
await waitFor(() => builds.length === 1)
|
|
expect(builds[0]!.ctx.baseBranch).toBe("feature-x")
|
|
|
|
controller.stop()
|
|
})
|
|
|
|
it("keeps watching when the base changes during the initial fetch", async () => {
|
|
// Hold the first activation's fetch in flight, simulating a slow worktree
|
|
// diff. isPolling is still false in this window, but the watch intent must
|
|
// survive the base change rather than downgrading the panel to one-shot.
|
|
let release: () => void = () => {}
|
|
const gate = new Promise<void>((resolve) => (release = resolve))
|
|
const { controller, builds } = make(async (n) => {
|
|
if (n === 1) await gate
|
|
})
|
|
|
|
controller.start("s1#branch")
|
|
await waitFor(() => builds.length === 1)
|
|
|
|
const change = controller.setBase("s1#branch", "feature-x")
|
|
release()
|
|
await change
|
|
expect(builds.length).toBe(2)
|
|
expect(builds[1]!.ctx.baseBranch).toBe("feature-x")
|
|
|
|
// Polling survives: start() early-returns for an id that is already
|
|
// watched. A downgraded one-shot panel would re-activate and rebuild here.
|
|
controller.start("s1#branch")
|
|
await tick()
|
|
expect(builds.length).toBe(2)
|
|
|
|
controller.stop()
|
|
})
|
|
})
|