Files
oh-my-pi/packages/coding-agent/test/modes/utils/render-initial-messages.test.ts
T
can1357 c938fe4d27 feat(coding-agent): added focused subagent session mode for main-agent hub navigation
- Added a SessionFocusController to switch transcript and input context between main and subagent sessions.
- Added agent-hub Enter activation and double-left return behavior for focused local agents.
- Added view-session-based event and render logic to avoid stale focus-session state.
- Added status-line focused agent display with ghost icon and focused-mode border dimming.
2026-06-12 15:37:56 +02:00

110 lines
4.0 KiB
TypeScript

/**
* Contract: renderInitialMessages renders the DISPLAY TRANSCRIPT, not the LLM
* context. The transcript comes from `session.buildTranscriptSessionContext()`
* (full history, compactions inline); `sessionManager.buildSessionContext()`
* — the LLM-context builder — must not be consulted for display. Feeding the
* compacted LLM context to the chat is exactly the old "session starts over
* after compaction" bug.
*
* Also guards the cold-launch terminal cleanup: `omp` / `omp -c` leave the
* previous run's transcript in native scrollback because the TUI's initial
* paint preserves it, so the cold-launch render must request a
* scrollback-clearing repaint (`clearTerminalHistory`).
*/
import { beforeAll, describe, expect, it, type Mock, vi } from "bun:test";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types";
import { UiHelpers } from "@oh-my-pi/pi-coding-agent/modes/utils/ui-helpers";
import type { SessionContext } from "@oh-my-pi/pi-coding-agent/session/session-manager";
beforeAll(() => {
initTheme();
});
function makeEmptyContext(): SessionContext {
return {
messages: [],
thinkingLevel: "off",
serviceTier: undefined,
models: {},
injectedTtsrRules: [],
selectedMCPToolNames: [],
hasPersistedMCPToolSelection: false,
mode: "none",
};
}
/** Build a minimal InteractiveModeContext mock, returning spies for assertions. */
function makeCtx(): {
ctx: InteractiveModeContext;
transcriptSpy: Mock<() => SessionContext>;
llmContextSpy: Mock<() => SessionContext>;
renderSessionContextSpy: Mock<(...args: unknown[]) => void>;
} {
const transcriptSpy = vi.fn(() => makeEmptyContext());
const llmContextSpy = vi.fn(() => makeEmptyContext());
const renderSessionContextSpy = vi.fn();
const ctx = {
chatContainer: { clear: vi.fn(), addChild: vi.fn() },
pendingMessagesContainer: { clear: vi.fn() },
pendingBashComponents: [],
pendingPythonComponents: [],
session: { buildTranscriptSessionContext: transcriptSpy },
viewSession: {
buildTranscriptSessionContext: transcriptSpy,
sessionManager: {
buildSessionContext: llmContextSpy,
getEntries: vi.fn(() => []),
getCwd: vi.fn(() => "/tmp"),
},
},
sessionManager: {
buildSessionContext: llmContextSpy,
getEntries: vi.fn(() => []),
getCwd: vi.fn(() => "/tmp"),
},
renderSessionContext: renderSessionContextSpy,
showStatus: vi.fn(),
ui: { requestRender: vi.fn() },
resetTranscript: () => ctx.chatContainer.clear(),
} as unknown as InteractiveModeContext;
return { ctx, transcriptSpy, llmContextSpy, renderSessionContextSpy };
}
describe("UiHelpers.renderInitialMessages — transcript source", () => {
it("renders the display transcript, never the LLM context", () => {
const { ctx, transcriptSpy, llmContextSpy, renderSessionContextSpy } = makeCtx();
const transcript = makeEmptyContext();
transcriptSpy.mockReturnValue(transcript);
new UiHelpers(ctx).renderInitialMessages();
expect(transcriptSpy).toHaveBeenCalledTimes(1);
expect(llmContextSpy).not.toHaveBeenCalled();
expect(renderSessionContextSpy).toHaveBeenCalledWith(transcript, {
updateFooter: true,
populateHistory: true,
});
});
});
describe("UiHelpers.renderInitialMessages — clearTerminalHistory", () => {
it("requests a scrollback-clearing repaint when clearTerminalHistory is set", () => {
const { ctx } = makeCtx();
new UiHelpers(ctx).renderInitialMessages({ clearTerminalHistory: true });
expect(ctx.ui.requestRender).toHaveBeenCalledWith(true, { clearScrollback: true });
});
it("never clears scrollback when clearTerminalHistory is unset", () => {
const { ctx } = makeCtx();
new UiHelpers(ctx).renderInitialMessages();
const clearedCall = (ctx.ui.requestRender as Mock<(...a: unknown[]) => void>).mock.calls.find(
([force, opts]) => force === true && (opts as { clearScrollback?: boolean } | undefined)?.clearScrollback,
);
expect(clearedCall).toBeUndefined();
});
});