/** * @license * Copyright 2025 Vybestack LLC * SPDX-License-Identifier: Apache-2.0 */ import { vi } from 'bun:test'; import type { CommandContext } from '../ui/commands/types.js'; import type { LoadedSettings } from '../config/settings.js'; import type { GitService, Config, Logger } from '@vybestack/llxprt-code-core'; import type { SessionStatsState } from '../ui/contexts/SessionContext.js'; // A utility type to make all properties of an object, and its nested objects, partial. type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T; /** * Creates a deep, fully-typed mock of the CommandContext for use in tests. * All functions are pre-mocked with `vi.fn()`. * * @param overrides - A deep partial object to override any default mock values. * @returns A complete, mocked CommandContext object. */ const buildDefaultMocks = (): CommandContext => ({ invocation: { raw: '', name: '', args: '', }, services: { config: { getEphemeralSetting: vi.fn(), setEphemeralSetting: vi.fn(), getAgentClient: vi.fn(), getSubagentManager: vi.fn(), } as unknown as Config, agent: null, settings: { merged: {} } as LoadedSettings, git: undefined as GitService | undefined, logger: { logMessage: vi.fn(), } as unknown as Logger, // Cast because Logger is a class. // Follow-up (#1569): Add profileManager and subagentManager when CommandContext interface is updated. // @plan:PLAN-20250117-SUBAGENTCONFIG.P07 }, ui: { addItem: vi.fn(), clear: vi.fn(), setDebugMessage: vi.fn(), pendingItem: null, setPendingItem: vi.fn(), loadHistory: vi.fn(), toggleCorgiMode: vi.fn(), toggleDebugProfiler: vi.fn(), toggleVimEnabled: vi.fn(), setLlxprtMdFileCount: vi.fn(), updateHistoryTokenCount: vi.fn(), reloadCommands: vi.fn(), extensionsUpdateState: new Map(), dispatchExtensionStateUpdate: vi.fn(), addConfirmUpdateExtensionRequest: vi.fn(), setExtensionsUpdateState: vi.fn(), } as unknown as CommandContext['ui'], session: { sessionShellAllowlist: new Set(), stats: { sessionStartTime: new Date(), lastPromptTokenCount: 0, metrics: { models: {}, tools: { totalCalls: 0, totalSuccess: 0, totalFail: 0, totalDurationMs: 0, totalDecisions: { accept: 0, reject: 0, modify: 0 }, byName: {}, }, }, } as SessionStatsState, }, }); export const createMockCommandContext = ( overrides: DeepPartial = {}, ): CommandContext => { const defaultMocks: CommandContext = buildDefaultMocks(); // Deep merge that preserves special objects (Dates, vitest mocks, etc.) const merge = (target: unknown, source: unknown): CommandContext => { const output = { ...(target as Record) }; for (const key in source as Record) { if (Object.prototype.hasOwnProperty.call(source, key)) { const sourceValue = (source as Record)[key]; const targetValue = (output as Record)[key]; if ( // We only want to recursively merge plain objects Object.prototype.toString.call(sourceValue) === '[object Object]' && Object.prototype.toString.call(targetValue) === '[object Object]' ) { (output as Record)[key] = merge( targetValue, sourceValue, ); } else { // If not, we do a direct assignment. This preserves Date objects, vitest mocks, and others. (output as Record)[key] = sourceValue; } } } return output as unknown as CommandContext; }; return merge(defaultMocks, overrides); };