From 34f9845278a47599340581f05f6e193029ae082a Mon Sep 17 00:00:00 2001 From: gaebal-gajae Date: Sun, 6 Sep 2026 01:45:37 +0000 Subject: [PATCH] test(coding-agent): restore presentation reset fixtures Assistant text presentation reset became a required InteractiveModeContext contract, but seven controller and transcript fixtures still cast incomplete objects through unknown. Hosted shards then failed before reaching their lifecycle assertions. Provide the required callback in every fixture implicated by run 34001273527 and assert reset ownership on transcript rebuild, committed resume, and successful session replacement paths. Production calls stay strict; no optional fallback masks incomplete contexts. Lore-id: issue-5326-presentation-fixtures Constraint: preserve production resetAssistantTextPresentation as a required callback Constraint: exclude resume-session-reentrancy.test.ts already owned by PR #5323 Rejected: optional chaining in production | would hide invalid runtime contexts Confidence: high Scope-risk: narrow Reversibility: clean Tested: seven affected suites 20 repeated passes; coding-agent package check; coding-agent package build Not-tested: full coding-agent shard matrix --- .../coding-agent/test/interactive-mode-status.test.ts | 2 ++ .../test/modes/controllers/bash-command.test.ts | 6 +++++- .../issue-2261-new-session-fail-closed.test.ts | 3 +++ .../controllers/selector-controller-resume-model.test.ts | 3 +++ .../selector-controller-session-delete.test.ts | 5 +++++ .../modes/utils/render-initial-messages-dedupe.test.ts | 8 ++++++-- packages/coding-agent/test/resume-progress-lease.test.ts | 3 +++ 7 files changed, 27 insertions(+), 3 deletions(-) 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),