diff --git a/packages/coding-agent/test/interactive-mode-status.test.ts b/packages/coding-agent/test/interactive-mode-status.test.ts index 4503f4cf018..9f4bb20e94d 100644 --- a/packages/coding-agent/test/interactive-mode-status.test.ts +++ b/packages/coding-agent/test/interactive-mode-status.test.ts @@ -46,6 +46,7 @@ function createInitialRenderHarness(): { ctx: InteractiveModeContext; helpers: U }, toolOutputExpanded: false, hideThinkingBlock: false, + resetAssistantTextPresentation: vi.fn(), } as unknown as InteractiveModeContext; helpers = new UiHelpers(ctx); return { ctx, helpers }; @@ -108,6 +109,7 @@ describe("InteractiveMode.showStatus", () => { helpers.renderInitialMessages(undefined, { preserveExistingChat: true }); expect(renderContainer(ctx.chatContainer)).toContain("startup notification probe"); + expect(ctx.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); }); test("preserves optimistic user signatures when rebuilding transcript state", () => { diff --git a/packages/coding-agent/test/modes/controllers/bash-command.test.ts b/packages/coding-agent/test/modes/controllers/bash-command.test.ts index 8e7786f44c7..148d6482662 100644 --- a/packages/coding-agent/test/modes/controllers/bash-command.test.ts +++ b/packages/coding-agent/test/modes/controllers/bash-command.test.ts @@ -3,7 +3,7 @@ * streaming must render its header/output/exit status continuously and land in * the chat transcript on completion, exactly like the idle path. */ -import { beforeAll, describe, expect, it } from "bun:test"; +import { beforeAll, describe, expect, it, vi } from "bun:test"; import { BashExecutionComponent } from "@gajae-code/coding-agent/modes/components/bash-execution"; import type { EvalExecutionComponent } from "@gajae-code/coding-agent/modes/components/eval-execution"; import { CommandController } from "@gajae-code/coding-agent/modes/controllers/command-controller"; @@ -115,6 +115,7 @@ function createHarness(options: { isStreaming: boolean }): Harness { statusLine: { invalidate: () => {}, setSessionStartTime: () => {} }, updateEditorTopBorder: () => {}, updateEditorBorderColor: () => {}, + resetAssistantTextPresentation: vi.fn(), resetIrcSidebarSession: () => {}, resetObserverRegistry: () => {}, reloadTodos: async () => {}, @@ -258,6 +259,7 @@ describe("deferred shell command display", () => { harness.rebuiltTranscriptRows.push("earlier command"); new UiHelpers(harness.ctx).renderInitialMessages(); + expect(harness.ctx.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); expect(harness.ctx.pendingBashComponents).toEqual([liveComponent]); expect(harness.pendingMessagesContainer.children).toEqual([liveComponent]); expect(harness.pendingMessagesContainer.render(80).join("\n")).toContain("$ printf parked"); @@ -336,6 +338,7 @@ describe("deferred shell command parentage after the transcript is cleared", () expect(harness.pendingMessagesContainer.children).toContain(parked); expect(await controller.handleClearCommand()).toBe(true); + expect(harness.ctx.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); expect(harness.pendingMessagesContainer.hasLiveChild(parked)).toBe(false); const chatChildrenAfterClear = harness.chatContainer.children.length; @@ -356,6 +359,7 @@ describe("deferred shell command parentage after the transcript is cleared", () const parked = harness.ctx.pendingBashComponents[0]; await controller.handleContextClearCommand(); + expect(harness.ctx.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); expect(harness.pendingMessagesContainer.hasLiveChild(parked)).toBe(false); const chatChildrenAfterClear = harness.chatContainer.children.length; diff --git a/packages/coding-agent/test/modes/controllers/issue-2261-new-session-fail-closed.test.ts b/packages/coding-agent/test/modes/controllers/issue-2261-new-session-fail-closed.test.ts index 8b81054ef5e..9185fa23f00 100644 --- a/packages/coding-agent/test/modes/controllers/issue-2261-new-session-fail-closed.test.ts +++ b/packages/coding-agent/test/modes/controllers/issue-2261-new-session-fail-closed.test.ts @@ -18,6 +18,7 @@ function createContext(success: boolean): InteractiveModeContext { statusLine: { invalidate: vi.fn(), setSessionStartTime: vi.fn() }, updateEditorTopBorder: vi.fn(), updateEditorBorderColor: vi.fn(), + resetAssistantTextPresentation: vi.fn(), ui: { requestRender: vi.fn(), resetViewportAnchorIntent: vi.fn() }, chatContainer: { clear: vi.fn(), addChild: vi.fn() }, pendingMessagesContainer: { clear: vi.fn() }, @@ -45,6 +46,7 @@ describe("Issue #2261 CommandController fail-closed session replacement", () => expect(context.loadingAnimation?.stop).not.toHaveBeenCalled(); expect(context.statusContainer.clear).not.toHaveBeenCalled(); expect(context.resetIrcSidebarSession).not.toHaveBeenCalled(); + expect(context.resetAssistantTextPresentation).not.toHaveBeenCalled(); expect(context.chatContainer.clear).not.toHaveBeenCalled(); expect(context.pendingMessagesContainer.clear).not.toHaveBeenCalled(); expect(context.compactionQueuedMessages).toEqual([{ text: "old", mode: "followUp" }]); @@ -63,6 +65,7 @@ describe("Issue #2261 CommandController fail-closed session replacement", () => expect(stopLoading).toHaveBeenCalledTimes(1); expect(context.statusContainer.clear).toHaveBeenCalledTimes(1); expect(context.resetIrcSidebarSession).toHaveBeenCalledTimes(1); + expect(context.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); expect(context.chatContainer.clear).toHaveBeenCalledTimes(1); expect(context.pendingMessagesContainer.clear).toHaveBeenCalledTimes(1); expect(context.compactionQueuedMessages).toEqual([]); diff --git a/packages/coding-agent/test/modes/controllers/selector-controller-resume-model.test.ts b/packages/coding-agent/test/modes/controllers/selector-controller-resume-model.test.ts index 0ba6751b268..04e81b76c17 100644 --- a/packages/coding-agent/test/modes/controllers/selector-controller-resume-model.test.ts +++ b/packages/coding-agent/test/modes/controllers/selector-controller-resume-model.test.ts @@ -62,6 +62,7 @@ function createContext(options: { streamingComponent: undefined, streamingMessage: undefined, pendingTools: { clear: vi.fn() }, + resetAssistantTextPresentation: vi.fn(), loadingAnimation: undefined, stopLoadingAnimation: vi.fn(), syncActivityIndicator: vi.fn(), @@ -110,6 +111,7 @@ describe("SelectorController resume model choice", () => { restoreBackground: false, }); expect(ctx.syncActivityIndicator).toHaveBeenCalledTimes(1); + expect(ctx.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); }); it("prompts and switches when resumeModelBehavior is ask and models differ", async () => { @@ -166,6 +168,7 @@ describe("SelectorController resume model choice", () => { expect(ctx.retryLoader).toBe(typedRetryLoader); expect(ctx.retryCountdownTimer).toBe(retryTimer); expect(retryLoader.stop).not.toHaveBeenCalled(); + expect(ctx.resetAssistantTextPresentation).not.toHaveBeenCalled(); clearInterval(retryTimer); }); diff --git a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts b/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts index ab4ecec85be..17a76029759 100644 --- a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts +++ b/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts @@ -159,6 +159,9 @@ function createContext(currentSessionFile: string): { calls.push(`showStatus:${message}`); }), showError: vi.fn(), + resetAssistantTextPresentation: vi.fn(() => { + calls.push("resetAssistantTextPresentation"); + }), resetIrcSidebarSession: vi.fn(() => { calls.push("resetIrcSidebarSession"); }), @@ -425,6 +428,7 @@ describe("SelectorController session deletion", () => { "ui.requestRender", "session.newSession", "resetIrcSidebarSession", + "resetAssistantTextPresentation", "loadingAnimation.stop", "statusContainer.clear", "pendingMessagesContainer.clear", @@ -501,6 +505,7 @@ describe("SelectorController session deletion", () => { expect(calls).toEqual([ "session.newSession", "resetIrcSidebarSession", + "resetAssistantTextPresentation", "loadingAnimation.stop", "statusContainer.clear", "pendingMessagesContainer.clear", diff --git a/packages/coding-agent/test/modes/utils/render-initial-messages-dedupe.test.ts b/packages/coding-agent/test/modes/utils/render-initial-messages-dedupe.test.ts index a3ec49e12f4..8305b6d3cb8 100644 --- a/packages/coding-agent/test/modes/utils/render-initial-messages-dedupe.test.ts +++ b/packages/coding-agent/test/modes/utils/render-initial-messages-dedupe.test.ts @@ -48,9 +48,11 @@ function makeCtx(sessionManager?: Pick SessionContext>; renderSessionContextSpy: Mock<(...args: unknown[]) => void>; + resetAssistantTextPresentationSpy: Mock<() => void>; } { const buildSessionContextSpy = vi.fn(() => makeEmptyContext()); const renderSessionContextSpy = vi.fn(); + const resetAssistantTextPresentationSpy = vi.fn(); const sm = sessionManager ?? { buildSessionContext: buildSessionContextSpy, @@ -65,20 +67,22 @@ function makeCtx(sessionManager?: Pick { it("calls sessionManager.buildSessionContext when no prebuilt context is given", () => { - const { ctx, buildSessionContextSpy } = makeCtx(); + const { ctx, buildSessionContextSpy, resetAssistantTextPresentationSpy } = makeCtx(); new UiHelpers(ctx).renderInitialMessages(); expect(buildSessionContextSpy).toHaveBeenCalledTimes(1); + expect(resetAssistantTextPresentationSpy).toHaveBeenCalledTimes(1); }); it("does NOT call sessionManager.buildSessionContext when a prebuilt context is provided", () => { diff --git a/packages/coding-agent/test/resume-progress-lease.test.ts b/packages/coding-agent/test/resume-progress-lease.test.ts index f95ce335951..1f4d0c9de57 100644 --- a/packages/coding-agent/test/resume-progress-lease.test.ts +++ b/packages/coding-agent/test/resume-progress-lease.test.ts @@ -54,6 +54,7 @@ describe("resume progress lease", () => { getCwd: () => "/tmp", }, resetIrcSidebarSession: vi.fn(), + resetAssistantTextPresentation: vi.fn(), updateEditorBorderColor: vi.fn(), rebuildInitialMessages: vi.fn(), reloadTodos: vi.fn(async () => undefined), @@ -71,6 +72,7 @@ describe("resume progress lease", () => { await resume; expect(statusContainer.children).toHaveLength(0); expect(context.showStatus).toHaveBeenCalledWith("Resumed session"); + expect(context.resetAssistantTextPresentation).toHaveBeenCalledTimes(1); expect(session.switchSession).toHaveBeenCalledWith("/tmp/target.jsonl", expect.any(Object)); ui.stop(); @@ -115,6 +117,7 @@ describe("resume progress lease", () => { getCwd: () => "/tmp", }, resetIrcSidebarSession: vi.fn(), + resetAssistantTextPresentation: vi.fn(), updateEditorBorderColor: vi.fn(), rebuildInitialMessages: vi.fn(), reloadTodos: vi.fn(async () => undefined),