test: add comprehensive tests for boulder lineage and completion
Add tests for descendant session detection, agent mismatch handling,
background task retry logic, and start-work functionality.
🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
@@ -3,11 +3,18 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
import type { RunContext } from "./types"
|
||||
import {
|
||||
_resetForTesting,
|
||||
registerAgentName,
|
||||
setSessionAgent,
|
||||
subagentSessions,
|
||||
} from "../../features/claude-code-session-state"
|
||||
import { writeState as writeRalphLoopState } from "../../hooks/ralph-loop/storage"
|
||||
|
||||
const testDirs: string[] = []
|
||||
|
||||
afterEach(() => {
|
||||
_resetForTesting()
|
||||
while (testDirs.length > 0) {
|
||||
const dir = testDirs.pop()
|
||||
if (dir) {
|
||||
@@ -29,6 +36,12 @@ function createMockContext(directory: string): RunContext {
|
||||
todo: mock(() => Promise.resolve({ data: [] })),
|
||||
children: mock(() => Promise.resolve({ data: [] })),
|
||||
status: mock(() => Promise.resolve({ data: {} })),
|
||||
get: mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: undefined,
|
||||
},
|
||||
})),
|
||||
},
|
||||
} as unknown as RunContext["client"],
|
||||
sessionID: "test-session",
|
||||
@@ -90,6 +103,94 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when current session is a descendant of an active boulder session with unchecked plan items", async () => {
|
||||
// given
|
||||
spyOn(console, "log").mockImplementation(() => {})
|
||||
registerAgentName("atlas")
|
||||
const directory = createTempDir()
|
||||
const planPath = join(directory, ".sisyphus", "plans", "active-descendant-plan.md")
|
||||
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
|
||||
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
|
||||
writeBoulderStateFile(directory, planPath, ["root-session"])
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "child-session"
|
||||
subagentSessions.add("child-session")
|
||||
setSessionAgent("child-session", "atlas")
|
||||
ctx.client.session.get = mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "child-session" ? "root-session" : undefined,
|
||||
},
|
||||
})) as unknown as RunContext["client"]["session"]["get"]
|
||||
|
||||
const { checkCompletionConditions } = await import("./completion")
|
||||
|
||||
// when
|
||||
const result = await checkCompletionConditions(ctx)
|
||||
|
||||
// then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("returns true when current session is only in lineage but is not a registered subagent", async () => {
|
||||
// given
|
||||
spyOn(console, "log").mockImplementation(() => {})
|
||||
registerAgentName("atlas")
|
||||
const directory = createTempDir()
|
||||
const planPath = join(directory, ".sisyphus", "plans", "lineage-non-subagent-plan.md")
|
||||
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
|
||||
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
|
||||
writeBoulderStateFile(directory, planPath, ["root-session"])
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "lineage-only-session"
|
||||
ctx.client.session.get = mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "lineage-only-session" ? "root-session" : undefined,
|
||||
},
|
||||
})) as unknown as RunContext["client"]["session"]["get"]
|
||||
|
||||
const { checkCompletionConditions } = await import("./completion")
|
||||
|
||||
// when
|
||||
const result = await checkCompletionConditions(ctx)
|
||||
|
||||
// then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("returns true when descendant subagent has agent mismatch and atlas would not continue it", async () => {
|
||||
// given
|
||||
spyOn(console, "log").mockImplementation(() => {})
|
||||
registerAgentName("atlas")
|
||||
const directory = createTempDir()
|
||||
const planPath = join(directory, ".sisyphus", "plans", "lineage-agent-mismatch-plan.md")
|
||||
mkdirSync(join(directory, ".sisyphus", "plans"), { recursive: true })
|
||||
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
|
||||
writeBoulderStateFile(directory, planPath, ["root-session"])
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "mismatch-subagent-session"
|
||||
subagentSessions.add("mismatch-subagent-session")
|
||||
setSessionAgent("mismatch-subagent-session", "sisyphus-junior")
|
||||
ctx.client.session.get = mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "mismatch-subagent-session" ? "root-session" : undefined,
|
||||
},
|
||||
})) as unknown as RunContext["client"]["session"]["get"]
|
||||
|
||||
const { checkCompletionConditions } = await import("./completion")
|
||||
|
||||
// when
|
||||
const result = await checkCompletionConditions(ctx)
|
||||
|
||||
// then
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
|
||||
it("returns false when active ralph-loop continuation exists for this session", async () => {
|
||||
// given
|
||||
spyOn(console, "log").mockImplementation(() => {})
|
||||
|
||||
@@ -23,6 +23,20 @@ describe("atlas background task retry", () => {
|
||||
await Promise.resolve()
|
||||
}
|
||||
|
||||
function createDeferred<T>(): {
|
||||
promise: Promise<T>
|
||||
resolve: (value: T | PromiseLike<T>) => void
|
||||
reject: (reason?: unknown) => void
|
||||
} {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void
|
||||
let reject!: (reason?: unknown) => void
|
||||
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
|
||||
resolve = resolvePromise
|
||||
reject = rejectPromise
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
|
||||
async function firePendingTimers(): Promise<void> {
|
||||
const entries = [...capturedTimers.entries()]
|
||||
for (const [id, entry] of entries) {
|
||||
@@ -222,4 +236,243 @@ describe("atlas background task retry", () => {
|
||||
expect(promptMock).toHaveBeenCalledTimes(1)
|
||||
expect(capturedTimers.size).toBe(0)
|
||||
})
|
||||
|
||||
test("#given retry gate sees no running task but injector still does #when retry fires #then atlas schedules another retry and does not advance cooldown", async () => {
|
||||
// given
|
||||
const planPath = join(testDir, "test-plan.md")
|
||||
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
writeBoulderState(testDir, {
|
||||
active_plan: planPath,
|
||||
started_at: "2026-01-02T10:00:00Z",
|
||||
session_ids: [sessionID],
|
||||
plan_name: "test-plan",
|
||||
agent: "atlas",
|
||||
})
|
||||
|
||||
const promptAsyncMock = mock(async () => ({}))
|
||||
let backgroundCheckCount = 0
|
||||
|
||||
const hook = createAtlasHook({
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginInput, {
|
||||
directory: testDir,
|
||||
backgroundManager: {
|
||||
getTasksByParentSession: () => {
|
||||
backgroundCheckCount += 1
|
||||
if (backgroundCheckCount === 1) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (backgroundCheckCount === 2) {
|
||||
return [{ status: "running" }]
|
||||
}
|
||||
|
||||
return []
|
||||
},
|
||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||
},
|
||||
})
|
||||
|
||||
// when
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(0)
|
||||
|
||||
await firePendingTimers()
|
||||
|
||||
// then
|
||||
expect(backgroundCheckCount).toBe(4)
|
||||
expect(capturedTimers.size).toBe(0)
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("#given a retry timer is pending #when a normal idle event resumes work first #then the stale retry timer does not inject again", async () => {
|
||||
// given
|
||||
const planPath = join(testDir, "test-plan.md")
|
||||
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
writeBoulderState(testDir, {
|
||||
active_plan: planPath,
|
||||
started_at: "2026-01-02T10:00:00Z",
|
||||
session_ids: [sessionID],
|
||||
plan_name: "test-plan",
|
||||
agent: "atlas",
|
||||
})
|
||||
|
||||
let backgroundRunning = true
|
||||
const promptAsyncMock = mock(async () => ({}))
|
||||
const hook = createAtlasHook({
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginInput, {
|
||||
directory: testDir,
|
||||
backgroundManager: {
|
||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||
},
|
||||
})
|
||||
|
||||
// when
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
|
||||
backgroundRunning = false
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
expect(capturedTimers.size).toBe(0)
|
||||
|
||||
await firePendingTimers()
|
||||
|
||||
// then
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("#given continuation injection is already in flight #when another idle event arrives #then atlas does not inject twice", async () => {
|
||||
// given
|
||||
const planPath = join(testDir, "test-plan.md")
|
||||
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
writeBoulderState(testDir, {
|
||||
active_plan: planPath,
|
||||
started_at: "2026-01-02T10:00:00Z",
|
||||
session_ids: [sessionID],
|
||||
plan_name: "test-plan",
|
||||
agent: "atlas",
|
||||
})
|
||||
|
||||
const deferredPrompt = createDeferred<{}>()
|
||||
const promptAsyncMock = mock(() => deferredPrompt.promise)
|
||||
const hook = createAtlasHook({
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginInput)
|
||||
|
||||
// when
|
||||
const firstIdle = hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
await flushMicrotasks()
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
deferredPrompt.resolve({})
|
||||
await firstIdle
|
||||
|
||||
// then
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("#given a retry timer fires during an in-flight continuation that later fails #when the in-flight guard re-arms retry #then atlas can recover on the next retry", async () => {
|
||||
// given
|
||||
const planPath = join(testDir, "test-plan.md")
|
||||
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
writeBoulderState(testDir, {
|
||||
active_plan: planPath,
|
||||
started_at: "2026-01-02T10:00:00Z",
|
||||
session_ids: [sessionID],
|
||||
plan_name: "test-plan",
|
||||
agent: "atlas",
|
||||
})
|
||||
|
||||
const deferredPrompt = createDeferred<unknown>()
|
||||
const promptAsyncMock = mock(() => deferredPrompt.promise)
|
||||
promptAsyncMock.mockImplementationOnce(() => deferredPrompt.promise)
|
||||
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
||||
|
||||
const hook = createAtlasHook({
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginInput, {
|
||||
directory: testDir,
|
||||
backgroundManager: {
|
||||
getTasksByParentSession: () => [],
|
||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||
},
|
||||
})
|
||||
|
||||
// when
|
||||
const firstIdle = hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
await flushMicrotasks()
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
|
||||
await firePendingTimers()
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
|
||||
deferredPrompt.reject(new Error("slow failure"))
|
||||
await firstIdle
|
||||
await firePendingTimers()
|
||||
|
||||
// then
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test("#given a retry-driven continuation fails once #when retry handling re-arms the chain #then atlas recovers on the next retry", async () => {
|
||||
// given
|
||||
const planPath = join(testDir, "test-plan.md")
|
||||
writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
writeBoulderState(testDir, {
|
||||
active_plan: planPath,
|
||||
started_at: "2026-01-02T10:00:00Z",
|
||||
session_ids: [sessionID],
|
||||
plan_name: "test-plan",
|
||||
agent: "atlas",
|
||||
})
|
||||
|
||||
let backgroundRunning = true
|
||||
const promptAsyncMock = mock(async () => ({}))
|
||||
promptAsyncMock.mockImplementationOnce(async () => {
|
||||
throw new Error("retry failed once")
|
||||
})
|
||||
promptAsyncMock.mockImplementationOnce(async () => ({}))
|
||||
|
||||
const hook = createAtlasHook({
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as PluginInput, {
|
||||
directory: testDir,
|
||||
backgroundManager: {
|
||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"] & {
|
||||
getTasksByParentSession: (sessionID: string) => Array<{ status: string }>
|
||||
},
|
||||
})
|
||||
|
||||
// when
|
||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID } } })
|
||||
backgroundRunning = false
|
||||
|
||||
await firePendingTimers()
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
|
||||
await firePendingTimers()
|
||||
|
||||
// then
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(2)
|
||||
expect(capturedTimers.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { createStartWorkHook } from "./index"
|
||||
import { createAtlasHook } from "../atlas"
|
||||
import { getAgentListDisplayName } from "../../shared/agent-display-names"
|
||||
import {
|
||||
writeBoulderState,
|
||||
@@ -538,6 +541,139 @@ You are starting a Sisyphus work session.
|
||||
expect(output.message.agent).toBe("Sisyphus (Ultraworker)")
|
||||
expect(readBoulderState(testDir)?.agent).toBe("sisyphus")
|
||||
})
|
||||
|
||||
test("#given start-work hands the session to Atlas #when Atlas later receives session.idle #then the same session continues the selected plan", async () => {
|
||||
// given
|
||||
const plansDir = join(testDir, ".sisyphus", "plans")
|
||||
mkdirSync(plansDir, { recursive: true })
|
||||
writeFileSync(join(plansDir, "atlas-plan.md"), "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
|
||||
const promptAsyncMock = spyOn({
|
||||
promptAsync: async (_request: unknown) => undefined,
|
||||
}, "promptAsync")
|
||||
const ctx = {
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
prompt: async (_request: unknown) => undefined,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as Parameters<typeof createAtlasHook>[0]
|
||||
const startWorkHook = createStartWorkHook(ctx)
|
||||
const atlasHook = createAtlasHook(ctx)
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: createStartWorkPrompt({ userRequest: "atlas-plan" }) }],
|
||||
}
|
||||
|
||||
// when
|
||||
await startWorkHook["chat.message"]({ sessionID: "session-123" }, output)
|
||||
await atlasHook.handler({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
|
||||
|
||||
// then
|
||||
expect(output.message.agent).toBe(getAgentListDisplayName("atlas"))
|
||||
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
||||
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
promptAsyncMock.mockRestore()
|
||||
})
|
||||
|
||||
test("#given start-work hands the session to Atlas but background work is still running #when that work finishes #then Atlas resumes via retry for the same session", async () => {
|
||||
// given
|
||||
const plansDir = join(testDir, ".sisyphus", "plans")
|
||||
mkdirSync(plansDir, { recursive: true })
|
||||
writeFileSync(join(plansDir, "atlas-plan.md"), "# Plan\n- [ ] Task 1\n- [ ] Task 2")
|
||||
|
||||
const capturedTimers = new Map<number, { callback: Function; cleared: boolean }>()
|
||||
let nextTimerId = 4000
|
||||
let backgroundRunning = true
|
||||
const originalSetTimeout = globalThis.setTimeout
|
||||
const originalClearTimeout = globalThis.clearTimeout
|
||||
const originalDateNow = Date.now
|
||||
let fakeNow = 10000
|
||||
const promptAsyncMock = spyOn({
|
||||
promptAsync: async (_request: unknown) => undefined,
|
||||
}, "promptAsync")
|
||||
|
||||
globalThis.setTimeout = ((callback: Function, delay?: number, ...args: unknown[]) => {
|
||||
const normalized = typeof delay === "number" ? delay : 0
|
||||
if (normalized >= 5000) {
|
||||
const id = nextTimerId++
|
||||
capturedTimers.set(id, { callback: () => callback(...args), cleared: false })
|
||||
return id as unknown as ReturnType<typeof setTimeout>
|
||||
}
|
||||
|
||||
return originalSetTimeout(callback as Parameters<typeof originalSetTimeout>[0], delay)
|
||||
}) as unknown as typeof setTimeout
|
||||
|
||||
globalThis.clearTimeout = ((id?: number | ReturnType<typeof setTimeout>) => {
|
||||
if (typeof id === "number" && capturedTimers.has(id)) {
|
||||
capturedTimers.get(id)!.cleared = true
|
||||
capturedTimers.delete(id)
|
||||
return
|
||||
}
|
||||
|
||||
originalClearTimeout(id as Parameters<typeof originalClearTimeout>[0])
|
||||
}) as unknown as typeof clearTimeout
|
||||
|
||||
Date.now = () => fakeNow
|
||||
|
||||
const ctx = {
|
||||
directory: testDir,
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: promptAsyncMock,
|
||||
prompt: async (_request: unknown) => undefined,
|
||||
messages: async () => ({ data: [] }),
|
||||
},
|
||||
},
|
||||
} as unknown as Parameters<typeof createAtlasHook>[0]
|
||||
const startWorkHook = createStartWorkHook(ctx)
|
||||
const atlasHook = createAtlasHook(ctx, {
|
||||
directory: testDir,
|
||||
backgroundManager: {
|
||||
getTasksByParentSession: () => backgroundRunning ? [{ status: "running" }] : [],
|
||||
} as unknown as NonNullable<Parameters<typeof createAtlasHook>[1]>["backgroundManager"],
|
||||
})
|
||||
const output = {
|
||||
message: {} as Record<string, unknown>,
|
||||
parts: [{ type: "text", text: createStartWorkPrompt({ userRequest: "atlas-plan" }) }],
|
||||
}
|
||||
|
||||
async function firePendingTimers(): Promise<void> {
|
||||
for (const [id, entry] of capturedTimers) {
|
||||
if (!entry.cleared) {
|
||||
capturedTimers.delete(id)
|
||||
fakeNow += 6000
|
||||
await entry.callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// when
|
||||
await startWorkHook["chat.message"]({ sessionID: "session-123" }, output)
|
||||
await atlasHook.handler({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(0)
|
||||
expect(capturedTimers.size).toBe(1)
|
||||
|
||||
backgroundRunning = false
|
||||
await firePendingTimers()
|
||||
|
||||
// then
|
||||
expect(output.message.agent).toBe(getAgentListDisplayName("atlas"))
|
||||
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
||||
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout
|
||||
globalThis.clearTimeout = originalClearTimeout
|
||||
Date.now = originalDateNow
|
||||
promptAsyncMock.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("worktree support", () => {
|
||||
|
||||
Reference in New Issue
Block a user