From bb8c0f12d328a7cfd2db5ab600efea62dda73b40 Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 26 Jul 2026 18:52:11 +0000 Subject: [PATCH] test(live): covered selected voice session boundary Replaced schema self-comparisons with a controller contract test that selects vale and captures the options used to construct the live session. --- .../controllers/live-command-controller.ts | 12 +++-- .../live-command-controller.test.ts | 54 +++++++++++++++++++ .../test/settings-manager.test.ts | 11 ---- 3 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 packages/coding-agent/test/modes/controllers/live-command-controller.test.ts diff --git a/packages/coding-agent/src/modes/controllers/live-command-controller.ts b/packages/coding-agent/src/modes/controllers/live-command-controller.ts index 9ab84c811..a32620a97 100644 --- a/packages/coding-agent/src/modes/controllers/live-command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/live-command-controller.ts @@ -1,6 +1,6 @@ import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import { logger } from "@oh-my-pi/pi-utils"; -import { LiveSessionController, type LiveTranscript } from "../../live/controller"; +import { LiveSessionController, type LiveSessionControllerOptions, type LiveTranscript } from "../../live/controller"; import { LIVE_MODEL } from "../../live/protocol"; import { LiveVisualizer } from "../../live/visualizer"; import { vocalizer } from "../../tts/vocalizer"; @@ -11,6 +11,7 @@ import type { InteractiveModeContext } from "../types"; import { createAssistantMessageComponent } from "../utils/interactive-context-helpers"; const ANIMATION_INTERVAL_MS = 80; +type LiveSessionFactory = (options: LiveSessionControllerOptions) => LiveSessionController; const LIVE_MESSAGE_USAGE: AssistantMessage["usage"] = { input: 0, @@ -27,6 +28,7 @@ function errorFrom(cause: unknown): Error { /** Owns the editor-replacing visualizer and realtime session lifecycle for `/live`. */ export class LiveCommandController { readonly #ctx: InteractiveModeContext; + readonly #createSession: LiveSessionFactory | undefined; #session: LiveSessionController | undefined; #settling: Promise | undefined; @@ -40,8 +42,9 @@ export class LiveCommandController { #assistantTranscriptTurn = 0; #assistantTranscriptStartedAt = 0; - constructor(ctx: InteractiveModeContext) { + constructor(ctx: InteractiveModeContext, createSession?: LiveSessionFactory) { this.#ctx = ctx; + this.#createSession = createSession; } /** Whether a live session is connected, connecting, or closing. */ @@ -100,7 +103,7 @@ export class LiveCommandController { this.#mountVisualizer(visualizer); let session: LiveSessionController; - session = new LiveSessionController({ + const options: LiveSessionControllerOptions = { session: this.#ctx.session, extractAssistantText: message => this.#ctx.extractAssistantText(message), voice: this.#ctx.settings.get("live.voice"), @@ -129,7 +132,8 @@ export class LiveCommandController { }, onTerminal: error => this.#finish(session, error), }, - }); + }; + session = this.#createSession ? this.#createSession(options) : new LiveSessionController(options); this.#session = session; try { diff --git a/packages/coding-agent/test/modes/controllers/live-command-controller.test.ts b/packages/coding-agent/test/modes/controllers/live-command-controller.test.ts new file mode 100644 index 000000000..0ac00e838 --- /dev/null +++ b/packages/coding-agent/test/modes/controllers/live-command-controller.test.ts @@ -0,0 +1,54 @@ +import { afterEach, describe, expect, it, vi } from "bun:test"; +import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings"; +import { LiveSessionController } from "@oh-my-pi/pi-coding-agent/live/controller"; +import { LiveCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/live-command-controller"; +import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; + +function createContext(): InteractiveModeContext { + const editor = { + getUseTerminalCursor: vi.fn(() => true), + setUseTerminalCursor: vi.fn(), + }; + return { + settings: Settings.isolated({ "live.voice": "vale" }), + session: {}, + extractAssistantText: vi.fn(() => ""), + editor, + editorContainer: { clear: vi.fn(), addChild: vi.fn() }, + ui: { + getShowHardwareCursor: vi.fn(() => true), + setShowHardwareCursor: vi.fn(), + setFocus: vi.fn(), + requestRender: vi.fn(), + requestComponentRender: vi.fn(), + }, + showError: vi.fn(), + chatContainer: { children: [] }, + present: vi.fn(), + } as unknown as InteractiveModeContext; +} + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe("LiveCommandController", () => { + it("forwards the selected voice across the live-session boundary", async () => { + const ctx = createContext(); + let receivedVoice: string | undefined; + const controller = new LiveCommandController(ctx, options => { + receivedVoice = options.voice; + const session = new LiveSessionController(options); + vi.spyOn(session, "start").mockResolvedValue(); + vi.spyOn(session, "stop").mockResolvedValue(); + return session; + }); + + try { + await controller.handleCommand(); + expect(receivedVoice).toBe("vale"); + } finally { + await controller.stop(); + } + }); +}); diff --git a/packages/coding-agent/test/settings-manager.test.ts b/packages/coding-agent/test/settings-manager.test.ts index 8b1b65254..080f8d2e2 100644 --- a/packages/coding-agent/test/settings-manager.test.ts +++ b/packages/coding-agent/test/settings-manager.test.ts @@ -9,14 +9,12 @@ import type { Context } from "@oh-my-pi/pi-ai/types"; import { getDefault, getEnumValues, - getUi, onAppendOnlyModeChanged, onStatusLineSessionAccentChanged, resetSettingsForTest, type SettingPath, Settings, } from "@oh-my-pi/pi-coding-agent/config/settings"; -import { DEFAULT_LIVE_VOICE, LIVE_VOICE_OPTIONS, LIVE_VOICE_VALUES } from "@oh-my-pi/pi-coding-agent/live/voices"; import { AgentStorage } from "@oh-my-pi/pi-coding-agent/session/agent-storage"; import { AUTO_IMAGE_PROVIDER_ORDER } from "@oh-my-pi/pi-coding-agent/tools/image-providers"; import { SEARCH_PROVIDER_ORDER } from "@oh-my-pi/pi-coding-agent/web/search/types"; @@ -145,15 +143,6 @@ describe("Settings", () => { expect(getDefault("providers.maxInFlightRequests")).toEqual({}); }); - it("exposes the supported live voices with sol as the default", () => { - expect(getDefault("live.voice")).toBe(DEFAULT_LIVE_VOICE); - expect(getEnumValues("live.voice")).toEqual(LIVE_VOICE_VALUES); - expect(getUi("live.voice")?.options).toEqual(LIVE_VOICE_OPTIONS); - - const settings = Settings.isolated({ "live.voice": "vale" }); - expect(settings.get("live.voice")).toBe("vale"); - }); - it("exposes all tool calling mode options", () => { const values = getEnumValues("tools.format"); expect(values).toEqual([