mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
109 lines
3.7 KiB
TypeScript
109 lines
3.7 KiB
TypeScript
import { describe, expect, it } from "bun:test"
|
|
import { ProjectContext, type ProjectContextDeps } from "../../src/agent-manager/project/context"
|
|
import type { ProjectSnapshot } from "../../src/agent-manager/project/contexts"
|
|
import { hydrateExpanded } from "../../src/agent-manager/project/hydrate"
|
|
import type { WorktreeStateManager } from "../../src/agent-manager/WorktreeStateManager"
|
|
|
|
const deps: ProjectContextDeps = {
|
|
log: () => {},
|
|
state: () =>
|
|
({ getWorktrees: () => [], getSessions: () => [], flush: async () => {} }) as unknown as WorktreeStateManager,
|
|
}
|
|
|
|
function snapshot(id: string, over: Partial<ProjectSnapshot> = {}): ProjectSnapshot {
|
|
return {
|
|
id,
|
|
root: `/repo/${id}`,
|
|
label: id,
|
|
pinned: false,
|
|
active: false,
|
|
expanded: true,
|
|
initialized: false,
|
|
missing: false,
|
|
...over,
|
|
}
|
|
}
|
|
|
|
/** Contexts keyed by id, created cold. */
|
|
function contexts(ids: string[]) {
|
|
const map = new Map(ids.map((id) => [id, new ProjectContext(id, `/repo/${id}`, false, deps)]))
|
|
return {
|
|
map,
|
|
hooks: () => {
|
|
const pushed: string[] = []
|
|
const inited: string[] = []
|
|
return {
|
|
pushed,
|
|
inited,
|
|
expand: (id: string) => map.get(id),
|
|
push: (ctx: ProjectContext) => pushed.push(ctx.id),
|
|
init: (ctx: ProjectContext) => inited.push(ctx.id),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
describe("hydrateExpanded", () => {
|
|
it("loads a cold expanded background project", () => {
|
|
const all = contexts(["a"])
|
|
const hooks = all.hooks()
|
|
hydrateExpanded([snapshot("a")], hooks)
|
|
expect(hooks.inited).toEqual(["a"])
|
|
expect(hooks.pushed).toEqual([])
|
|
})
|
|
|
|
/**
|
|
* Regression: a background project loads asynchronously, so its state push can
|
|
* land before the webview mounts. The catalog push that follows the mount must
|
|
* re-push it, otherwise the accordion stays on loading placeholders until the
|
|
* user clicks a row inside the project.
|
|
*/
|
|
it("re-pushes state for an already loaded background project", async () => {
|
|
const all = contexts(["a"])
|
|
await all.map.get("a")!.ensureReady(async () => ({ ok: true, refsFixed: 0 }))
|
|
const hooks = all.hooks()
|
|
hydrateExpanded([snapshot("a")], hooks)
|
|
expect(hooks.pushed).toEqual(["a"])
|
|
expect(hooks.inited).toEqual([])
|
|
})
|
|
|
|
it("skips the active project, which the provider pushes itself", async () => {
|
|
const all = contexts(["a"])
|
|
await all.map.get("a")!.ensureReady(async () => ({ ok: true, refsFixed: 0 }))
|
|
const hooks = all.hooks()
|
|
hydrateExpanded([snapshot("a", { active: true })], hooks)
|
|
expect(hooks.pushed).toEqual([])
|
|
expect(hooks.inited).toEqual([])
|
|
})
|
|
|
|
it("skips collapsed and missing projects", () => {
|
|
const all = contexts(["collapsed", "missing"])
|
|
const hooks = all.hooks()
|
|
hydrateExpanded([snapshot("collapsed", { expanded: false }), snapshot("missing", { missing: true })], hooks)
|
|
expect(hooks.pushed).toEqual([])
|
|
expect(hooks.inited).toEqual([])
|
|
})
|
|
|
|
it("skips a project the coordinator refuses to expand", () => {
|
|
const hooks = {
|
|
pushed: [] as string[],
|
|
inited: [] as string[],
|
|
expand: () => undefined,
|
|
push: () => {},
|
|
init: () => {},
|
|
}
|
|
hydrateExpanded([snapshot("gone")], hooks)
|
|
expect(hooks.pushed).toEqual([])
|
|
expect(hooks.inited).toEqual([])
|
|
})
|
|
|
|
it("hydrates every expanded background project, not just the first", async () => {
|
|
const all = contexts(["a", "b", "c"])
|
|
await all.map.get("b")!.ensureReady(async () => ({ ok: true, refsFixed: 0 }))
|
|
const hooks = all.hooks()
|
|
hydrateExpanded([snapshot("a"), snapshot("b"), snapshot("c")], hooks)
|
|
expect(hooks.inited).toEqual(["a", "c"])
|
|
expect(hooks.pushed).toEqual(["b"])
|
|
})
|
|
})
|