| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- import { describe, expect, test } from "bun:test"
- import { runPromptQueue } from "@/cli/cmd/run/runtime.queue"
- import type { FooterApi, FooterEvent, RunPrompt, StreamCommit } from "@/cli/cmd/run/types"
- function footer() {
- const prompts = new Set<(input: RunPrompt) => void>()
- const closes = new Set<() => void>()
- const events: FooterEvent[] = []
- const commits: StreamCommit[] = []
- let closed = false
- const api: FooterApi = {
- get isClosed() {
- return closed
- },
- onPrompt(fn) {
- prompts.add(fn)
- return () => {
- prompts.delete(fn)
- }
- },
- onClose(fn) {
- if (closed) {
- fn()
- return () => {}
- }
- closes.add(fn)
- return () => {
- closes.delete(fn)
- }
- },
- event(next) {
- events.push(next)
- },
- append(next) {
- commits.push(next)
- },
- idle() {
- return Promise.resolve()
- },
- close() {
- if (closed) {
- return
- }
- closed = true
- for (const fn of [...closes]) {
- fn()
- }
- },
- destroy() {
- api.close()
- prompts.clear()
- closes.clear()
- },
- }
- return {
- api,
- events,
- commits,
- submit(text: string) {
- const next = { text, parts: [] as RunPrompt["parts"] }
- for (const fn of [...prompts]) {
- fn(next)
- }
- },
- }
- }
- describe("run runtime queue", () => {
- test("ignores empty prompts", async () => {
- const ui = footer()
- let calls = 0
- const task = runPromptQueue({
- footer: ui.api,
- run: async () => {
- calls += 1
- },
- })
- ui.submit(" ")
- ui.api.close()
- await task
- expect(calls).toBe(0)
- })
- test("treats /exit as a close command", async () => {
- const ui = footer()
- let calls = 0
- const task = runPromptQueue({
- footer: ui.api,
- run: async () => {
- calls += 1
- },
- })
- ui.submit("/exit")
- await task
- expect(calls).toBe(0)
- })
- test("preserves whitespace for initial input", async () => {
- const ui = footer()
- const seen: string[] = []
- await runPromptQueue({
- footer: ui.api,
- initialInput: " hello ",
- run: async (input) => {
- seen.push(input.text)
- ui.api.close()
- },
- })
- expect(seen).toEqual([" hello "])
- expect(ui.commits).toEqual([
- {
- kind: "user",
- text: " hello ",
- phase: "start",
- source: "system",
- },
- ])
- })
- test("runs queued prompts in order", async () => {
- const ui = footer()
- const seen: string[] = []
- let wake: (() => void) | undefined
- const gate = new Promise<void>((resolve) => {
- wake = resolve
- })
- const task = runPromptQueue({
- footer: ui.api,
- run: async (input) => {
- seen.push(input.text)
- if (seen.length === 1) {
- await gate
- return
- }
- ui.api.close()
- },
- })
- ui.submit("one")
- ui.submit("two")
- await Promise.resolve()
- expect(seen).toEqual(["one"])
- wake?.()
- await task
- expect(seen).toEqual(["one", "two"])
- })
- test("drains a prompt queued during an in-flight turn", async () => {
- const ui = footer()
- const seen: string[] = []
- let wake: (() => void) | undefined
- const gate = new Promise<void>((resolve) => {
- wake = resolve
- })
- const task = runPromptQueue({
- footer: ui.api,
- run: async (input) => {
- seen.push(input.text)
- if (seen.length === 1) {
- await gate
- return
- }
- ui.api.close()
- },
- })
- ui.submit("one")
- await Promise.resolve()
- expect(seen).toEqual(["one"])
- wake?.()
- await Promise.resolve()
- ui.submit("two")
- await task
- expect(seen).toEqual(["one", "two"])
- })
- test("close aborts the active run and drops pending queued work", async () => {
- const ui = footer()
- const seen: string[] = []
- let hit = false
- const task = runPromptQueue({
- footer: ui.api,
- run: async (input, signal) => {
- seen.push(input.text)
- await new Promise<void>((resolve) => {
- if (signal.aborted) {
- hit = true
- resolve()
- return
- }
- signal.addEventListener(
- "abort",
- () => {
- hit = true
- resolve()
- },
- { once: true },
- )
- })
- },
- })
- ui.submit("one")
- await Promise.resolve()
- ui.submit("two")
- ui.api.close()
- await task
- expect(hit).toBe(true)
- expect(seen).toEqual(["one"])
- })
- test("propagates run errors", async () => {
- const ui = footer()
- const task = runPromptQueue({
- footer: ui.api,
- run: async () => {
- throw new Error("boom")
- },
- })
- ui.submit("one")
- await expect(task).rejects.toThrow("boom")
- })
- })
|