diff options
Diffstat (limited to 'packages/cli/src/ui/hooks/slashCommandProcessor.test.ts')
| -rw-r--r-- | packages/cli/src/ui/hooks/slashCommandProcessor.test.ts | 461 |
1 files changed, 241 insertions, 220 deletions
diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts index d10ae22b..137098df 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.test.ts @@ -56,11 +56,8 @@ vi.mock('../../utils/version.js', () => ({ import { act, renderHook } from '@testing-library/react'; import { vi, describe, it, expect, beforeEach, afterEach, Mock } from 'vitest'; import open from 'open'; -import { - useSlashCommandProcessor, - type SlashCommandActionReturn, -} from './slashCommandProcessor.js'; -import { MessageType } from '../types.js'; +import { useSlashCommandProcessor } from './slashCommandProcessor.js'; +import { MessageType, SlashCommandProcessorResult } from '../types.js'; import { Config, MCPDiscoveryState, @@ -73,11 +70,15 @@ import { useSessionStats } from '../contexts/SessionContext.js'; import { LoadedSettings } from '../../config/settings.js'; import * as ShowMemoryCommandModule from './useShowMemoryCommand.js'; import { GIT_COMMIT_INFO } from '../../generated/git-commit.js'; +import { CommandService } from '../../services/CommandService.js'; +import { SlashCommand } from '../commands/types.js'; vi.mock('../contexts/SessionContext.js', () => ({ useSessionStats: vi.fn(), })); +vi.mock('../../services/CommandService.js'); + vi.mock('./useShowMemoryCommand.js', () => ({ SHOW_MEMORY_COMMAND_NAME: '/memory show', createShowMemoryAction: vi.fn(() => vi.fn()), @@ -87,6 +88,16 @@ vi.mock('open', () => ({ default: vi.fn(), })); +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const actual = + await importOriginal<typeof import('@google/gemini-cli-core')>(); + return { + ...actual, + getMCPServerStatus: vi.fn(), + getMCPDiscoveryState: vi.fn(), + }; +}); + describe('useSlashCommandProcessor', () => { let mockAddItem: ReturnType<typeof vi.fn>; let mockClearItems: ReturnType<typeof vi.fn>; @@ -97,7 +108,6 @@ describe('useSlashCommandProcessor', () => { let mockOpenThemeDialog: ReturnType<typeof vi.fn>; let mockOpenAuthDialog: ReturnType<typeof vi.fn>; let mockOpenEditorDialog: ReturnType<typeof vi.fn>; - let mockPerformMemoryRefresh: ReturnType<typeof vi.fn>; let mockSetQuittingMessages: ReturnType<typeof vi.fn>; let mockTryCompressChat: ReturnType<typeof vi.fn>; let mockGeminiClient: GeminiClient; @@ -106,6 +116,20 @@ describe('useSlashCommandProcessor', () => { const mockUseSessionStats = useSessionStats as Mock; beforeEach(() => { + // Reset all mocks to clear any previous state or calls. + vi.clearAllMocks(); + + // Default mock setup for CommandService for all the OLD tests. + // This makes them pass again by simulating the original behavior where + // the service is constructed but doesn't do much yet. + vi.mocked(CommandService).mockImplementation( + () => + ({ + loadCommands: vi.fn().mockResolvedValue(undefined), + getCommands: vi.fn().mockReturnValue([]), // Return an empty array by default + }) as unknown as CommandService, + ); + mockAddItem = vi.fn(); mockClearItems = vi.fn(); mockLoadHistory = vi.fn(); @@ -115,7 +139,6 @@ describe('useSlashCommandProcessor', () => { mockOpenThemeDialog = vi.fn(); mockOpenAuthDialog = vi.fn(); mockOpenEditorDialog = vi.fn(); - mockPerformMemoryRefresh = vi.fn().mockResolvedValue(undefined); mockSetQuittingMessages = vi.fn(); mockTryCompressChat = vi.fn(); mockGeminiClient = { @@ -129,6 +152,7 @@ describe('useSlashCommandProcessor', () => { getProjectRoot: vi.fn(() => '/test/dir'), getCheckpointingEnabled: vi.fn(() => true), getBugCommand: vi.fn(() => undefined), + getSessionId: vi.fn(() => 'test-session-id'), } as unknown as Config; mockCorgiMode = vi.fn(); mockUseSessionStats.mockReturnValue({ @@ -149,7 +173,6 @@ describe('useSlashCommandProcessor', () => { (open as Mock).mockClear(); mockProcessExit.mockClear(); (ShowMemoryCommandModule.createShowMemoryAction as Mock).mockClear(); - mockPerformMemoryRefresh.mockClear(); process.env = { ...globalThis.process.env }; }); @@ -158,7 +181,7 @@ describe('useSlashCommandProcessor', () => { merged: { contextFileName: 'GEMINI.md', }, - } as LoadedSettings; + } as unknown as LoadedSettings; return renderHook(() => useSlashCommandProcessor( mockConfig, @@ -173,10 +196,10 @@ describe('useSlashCommandProcessor', () => { mockOpenThemeDialog, mockOpenAuthDialog, mockOpenEditorDialog, - mockPerformMemoryRefresh, mockCorgiMode, showToolDescriptions, mockSetQuittingMessages, + vi.fn(), // mockOpenPrivacyNotice ), ); }; @@ -184,115 +207,6 @@ describe('useSlashCommandProcessor', () => { const getProcessor = (showToolDescriptions: boolean = false) => getProcessorHook(showToolDescriptions).result.current; - describe('/memory add', () => { - it('should return tool scheduling info on valid input', async () => { - const { handleSlashCommand } = getProcessor(); - const fact = 'Remember this fact'; - let commandResult: SlashCommandActionReturn | boolean = false; - await act(async () => { - commandResult = await handleSlashCommand(`/memory add ${fact}`); - }); - - expect(mockAddItem).toHaveBeenNthCalledWith( - 1, // User message - expect.objectContaining({ - type: MessageType.USER, - text: `/memory add ${fact}`, - }), - expect.any(Number), - ); - expect(mockAddItem).toHaveBeenNthCalledWith( - 2, // Info message about attempting to save - expect.objectContaining({ - type: MessageType.INFO, - text: `Attempting to save to memory: "${fact}"`, - }), - expect.any(Number), - ); - - expect(commandResult).toEqual({ - shouldScheduleTool: true, - toolName: 'save_memory', - toolArgs: { fact }, - }); - - // performMemoryRefresh is no longer called directly here - expect(mockPerformMemoryRefresh).not.toHaveBeenCalled(); - }); - - it('should show usage error and return true if no text is provided', async () => { - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; - await act(async () => { - commandResult = await handleSlashCommand('/memory add '); - }); - - expect(mockAddItem).toHaveBeenNthCalledWith( - 2, // After user message - expect.objectContaining({ - type: MessageType.ERROR, - text: 'Usage: /memory add <text to remember>', - }), - expect.any(Number), - ); - expect(commandResult).toBe(true); // Command was handled (by showing an error) - }); - }); - - describe('/memory show', () => { - it('should call the showMemoryAction and return true', async () => { - const mockReturnedShowAction = vi.fn(); - vi.mocked(ShowMemoryCommandModule.createShowMemoryAction).mockReturnValue( - mockReturnedShowAction, - ); - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; - await act(async () => { - commandResult = await handleSlashCommand('/memory show'); - }); - expect( - ShowMemoryCommandModule.createShowMemoryAction, - ).toHaveBeenCalledWith( - mockConfig, - expect.any(Object), - expect.any(Function), - ); - expect(mockReturnedShowAction).toHaveBeenCalled(); - expect(commandResult).toBe(true); - }); - }); - - describe('/memory refresh', () => { - it('should call performMemoryRefresh and return true', async () => { - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; - await act(async () => { - commandResult = await handleSlashCommand('/memory refresh'); - }); - expect(mockPerformMemoryRefresh).toHaveBeenCalled(); - expect(commandResult).toBe(true); - }); - }); - - describe('Unknown /memory subcommand', () => { - it('should show an error for unknown /memory subcommand and return true', async () => { - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; - await act(async () => { - commandResult = await handleSlashCommand('/memory foobar'); - }); - expect(mockAddItem).toHaveBeenNthCalledWith( - 2, - expect.objectContaining({ - type: MessageType.ERROR, - text: 'Unknown /memory command: foobar. Available: show, refresh, add', - }), - expect.any(Number), - ); - expect(commandResult).toBe(true); - }); - }); - describe('/stats command', () => { it('should show detailed session statistics', async () => { // Arrange @@ -376,7 +290,7 @@ describe('useSlashCommandProcessor', () => { selectedAuthType: 'test-auth-type', contextFileName: 'GEMINI.md', }, - } as LoadedSettings; + } as unknown as LoadedSettings; const { result } = renderHook(() => useSlashCommandProcessor( @@ -392,10 +306,10 @@ describe('useSlashCommandProcessor', () => { mockOpenThemeDialog, mockOpenAuthDialog, mockOpenEditorDialog, - mockPerformMemoryRefresh, mockCorgiMode, false, mockSetQuittingMessages, + vi.fn(), // mockOpenPrivacyNotice ), ); @@ -447,45 +361,187 @@ describe('useSlashCommandProcessor', () => { }); describe('Other commands', () => { - it('/help should open help and return true', async () => { + it('/editor should open editor dialog and return handled', async () => { const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { - commandResult = await handleSlashCommand('/help'); + commandResult = await handleSlashCommand('/editor'); }); - expect(mockSetShowHelp).toHaveBeenCalledWith(true); - expect(commandResult).toBe(true); + expect(mockOpenEditorDialog).toHaveBeenCalled(); + expect(commandResult).toEqual({ type: 'handled' }); }); + }); - it('/clear should clear items, reset chat, and refresh static', async () => { - const mockResetChat = vi.fn(); - mockConfig = { - ...mockConfig, - getGeminiClient: () => ({ - resetChat: mockResetChat, - }), - } as unknown as Config; + describe('New command registry', () => { + let ActualCommandService: typeof CommandService; - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + beforeAll(async () => { + const actual = (await vi.importActual( + '../../services/CommandService.js', + )) as { CommandService: typeof CommandService }; + ActualCommandService = actual.CommandService; + }); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should execute a command from the new registry', async () => { + const mockAction = vi.fn(); + const newCommand: SlashCommand = { name: 'test', action: mockAction }; + const mockLoader = async () => [newCommand]; + + // We create the instance outside the mock implementation. + const commandServiceInstance = new ActualCommandService(mockLoader); + + // This mock ensures the hook uses our pre-configured instance. + vi.mocked(CommandService).mockImplementation( + () => commandServiceInstance, + ); + + const { result } = getProcessorHook(); + + await vi.waitFor(() => { + // We check that the `slashCommands` array, which is the public API + // of our hook, eventually contains the command we injected. + expect( + result.current.slashCommands.some((c) => c.name === 'test'), + ).toBe(true); + }); + + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { - commandResult = await handleSlashCommand('/clear'); + commandResult = await result.current.handleSlashCommand('/test'); }); - expect(mockClearItems).toHaveBeenCalled(); - expect(mockResetChat).toHaveBeenCalled(); - expect(mockRefreshStatic).toHaveBeenCalled(); - expect(commandResult).toBe(true); + expect(mockAction).toHaveBeenCalledTimes(1); + expect(commandResult).toEqual({ type: 'handled' }); }); - it('/editor should open editor dialog and return true', async () => { - const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + it('should return "schedule_tool" when a new command returns a tool action', async () => { + const mockAction = vi.fn().mockResolvedValue({ + type: 'tool', + toolName: 'my_tool', + toolArgs: { arg1: 'value1' }, + }); + const newCommand: SlashCommand = { name: 'test', action: mockAction }; + const mockLoader = async () => [newCommand]; + const commandServiceInstance = new ActualCommandService(mockLoader); + vi.mocked(CommandService).mockImplementation( + () => commandServiceInstance, + ); + + const { result } = getProcessorHook(); + await vi.waitFor(() => { + expect( + result.current.slashCommands.some((c) => c.name === 'test'), + ).toBe(true); + }); + + const commandResult = await result.current.handleSlashCommand('/test'); + + expect(mockAction).toHaveBeenCalledTimes(1); + expect(commandResult).toEqual({ + type: 'schedule_tool', + toolName: 'my_tool', + toolArgs: { arg1: 'value1' }, + }); + }); + + it('should return "handled" when a new command returns a message action', async () => { + const mockAction = vi.fn().mockResolvedValue({ + type: 'message', + messageType: 'info', + content: 'This is a message', + }); + const newCommand: SlashCommand = { name: 'test', action: mockAction }; + const mockLoader = async () => [newCommand]; + const commandServiceInstance = new ActualCommandService(mockLoader); + vi.mocked(CommandService).mockImplementation( + () => commandServiceInstance, + ); + + const { result } = getProcessorHook(); + await vi.waitFor(() => { + expect( + result.current.slashCommands.some((c) => c.name === 'test'), + ).toBe(true); + }); + + const commandResult = await result.current.handleSlashCommand('/test'); + + expect(mockAction).toHaveBeenCalledTimes(1); + expect(mockAddItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'info', + text: 'This is a message', + }), + expect.any(Number), + ); + expect(commandResult).toEqual({ type: 'handled' }); + }); + + it('should return "handled" when a new command returns a dialog action', async () => { + const mockAction = vi.fn().mockResolvedValue({ + type: 'dialog', + dialog: 'help', + }); + const newCommand: SlashCommand = { name: 'test', action: mockAction }; + const mockLoader = async () => [newCommand]; + const commandServiceInstance = new ActualCommandService(mockLoader); + vi.mocked(CommandService).mockImplementation( + () => commandServiceInstance, + ); + + const { result } = getProcessorHook(); + await vi.waitFor(() => { + expect( + result.current.slashCommands.some((c) => c.name === 'test'), + ).toBe(true); + }); + + const commandResult = await result.current.handleSlashCommand('/test'); + + expect(mockAction).toHaveBeenCalledTimes(1); + expect(mockSetShowHelp).toHaveBeenCalledWith(true); + expect(commandResult).toEqual({ type: 'handled' }); + }); + + it('should show help for a parent command with no action', async () => { + const parentCommand: SlashCommand = { + name: 'parent', + subCommands: [ + { name: 'child', description: 'A child.', action: vi.fn() }, + ], + }; + + const mockLoader = async () => [parentCommand]; + const commandServiceInstance = new ActualCommandService(mockLoader); + vi.mocked(CommandService).mockImplementation( + () => commandServiceInstance, + ); + + const { result } = getProcessorHook(); + + await vi.waitFor(() => { + expect( + result.current.slashCommands.some((c) => c.name === 'parent'), + ).toBe(true); + }); + await act(async () => { - commandResult = await handleSlashCommand('/editor'); + await result.current.handleSlashCommand('/parent'); }); - expect(mockOpenEditorDialog).toHaveBeenCalled(); - expect(commandResult).toBe(true); + + expect(mockAddItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'info', + text: expect.stringContaining( + "Command '/parent' requires a subcommand.", + ), + }), + expect.any(Number), + ); }); }); @@ -498,6 +554,7 @@ describe('useSlashCommandProcessor', () => { }); afterEach(() => { + vi.useRealTimers(); process.env = originalEnv; }); @@ -547,14 +604,14 @@ describe('useSlashCommandProcessor', () => { process.env.SEATBELT_PROFILE, 'test-version', ); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand(`/bug ${bugDescription}`); }); expect(mockAddItem).toHaveBeenCalledTimes(2); expect(open).toHaveBeenCalledWith(expectedUrl); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should use the custom bug command URL from config if available', async () => { @@ -585,14 +642,14 @@ describe('useSlashCommandProcessor', () => { .replace('{title}', encodeURIComponent(bugDescription)) .replace('{info}', encodeURIComponent(info)); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand(`/bug ${bugDescription}`); }); expect(mockAddItem).toHaveBeenCalledTimes(2); expect(open).toHaveBeenCalledWith(expectedUrl); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); }); @@ -640,9 +697,9 @@ describe('useSlashCommandProcessor', () => { }); describe('Unknown command', () => { - it('should show an error and return true for a general unknown command', async () => { + it('should show an error and return handled for a general unknown command', async () => { const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/unknowncommand'); }); @@ -654,7 +711,7 @@ describe('useSlashCommandProcessor', () => { }), expect.any(Number), ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); }); @@ -665,7 +722,7 @@ describe('useSlashCommandProcessor', () => { getToolRegistry: vi.fn().mockResolvedValue(undefined), } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/tools'); }); @@ -678,7 +735,7 @@ describe('useSlashCommandProcessor', () => { }), expect.any(Number), ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should show an error if getAllTools returns undefined', async () => { @@ -689,7 +746,7 @@ describe('useSlashCommandProcessor', () => { }), } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/tools'); }); @@ -702,7 +759,7 @@ describe('useSlashCommandProcessor', () => { }), expect.any(Number), ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display only Gemini CLI tools (filtering out MCP tools)', async () => { @@ -722,7 +779,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/tools'); }); @@ -731,7 +788,7 @@ describe('useSlashCommandProcessor', () => { const message = mockAddItem.mock.calls[1][0].text; expect(message).toContain('Tool1'); expect(message).toContain('Tool2'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display a message when no Gemini CLI tools are available', async () => { @@ -749,14 +806,14 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/tools'); }); const message = mockAddItem.mock.calls[1][0].text; expect(message).toContain('No tools available'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display tool descriptions when /tools desc is used', async () => { @@ -781,7 +838,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/tools desc'); }); @@ -791,40 +848,18 @@ describe('useSlashCommandProcessor', () => { expect(message).toContain('Description for Tool1'); expect(message).toContain('Tool2'); expect(message).toContain('Description for Tool2'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); }); describe('/mcp command', () => { - beforeEach(() => { - // Mock the core module with getMCPServerStatus and getMCPDiscoveryState - vi.mock('@google/gemini-cli-core', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - MCPServerStatus: { - CONNECTED: 'connected', - CONNECTING: 'connecting', - DISCONNECTED: 'disconnected', - }, - MCPDiscoveryState: { - NOT_STARTED: 'not_started', - IN_PROGRESS: 'in_progress', - COMPLETED: 'completed', - }, - getMCPServerStatus: vi.fn(), - getMCPDiscoveryState: vi.fn(), - }; - }); - }); - it('should show an error if tool registry is not available', async () => { mockConfig = { ...mockConfig, getToolRegistry: vi.fn().mockResolvedValue(undefined), } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -837,7 +872,7 @@ describe('useSlashCommandProcessor', () => { }), expect.any(Number), ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display a message with a URL when no MCP servers are configured in a sandbox', async () => { @@ -851,7 +886,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -864,7 +899,7 @@ describe('useSlashCommandProcessor', () => { }), expect.any(Number), ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); delete process.env.SANDBOX; }); @@ -878,7 +913,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -892,7 +927,7 @@ describe('useSlashCommandProcessor', () => { expect.any(Number), ); expect(open).toHaveBeenCalledWith('https://goo.gle/gemini-cli-docs-mcp'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display configured MCP servers with status indicators and their tools', async () => { @@ -941,7 +976,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -976,7 +1011,7 @@ describe('useSlashCommandProcessor', () => { ); expect(message).toContain('\u001b[36mserver3_tool1\u001b[0m'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should display tool descriptions when showToolDescriptions is true', async () => { @@ -1014,7 +1049,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(true); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -1046,7 +1081,7 @@ describe('useSlashCommandProcessor', () => { '\u001b[32mThis is tool 2 description\u001b[0m', ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should indicate when a server has no tools', async () => { @@ -1071,7 +1106,7 @@ describe('useSlashCommandProcessor', () => { // Mock tools from each server - server2 has no tools const mockServer1Tools = [{ name: 'server1_tool1' }]; - const mockServer2Tools = []; + const mockServer2Tools: Array<{ name: string }> = []; const mockGetToolsByServer = vi.fn().mockImplementation((serverName) => { if (serverName === 'server1') return mockServer1Tools; @@ -1088,7 +1123,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -1113,7 +1148,7 @@ describe('useSlashCommandProcessor', () => { ); expect(message).toContain('No tools available'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); it('should show startup indicator when servers are connecting', async () => { @@ -1154,7 +1189,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp'); }); @@ -1177,7 +1212,7 @@ describe('useSlashCommandProcessor', () => { '🔄 \u001b[1mserver2\u001b[0m - Starting... (first startup may take longer) (tools will appear when ready)', ); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); }); @@ -1229,7 +1264,7 @@ describe('useSlashCommandProcessor', () => { } as unknown as Config; const { handleSlashCommand } = getProcessor(true); - let commandResult: SlashCommandActionReturn | boolean = false; + let commandResult: SlashCommandProcessorResult | false = false; await act(async () => { commandResult = await handleSlashCommand('/mcp schema'); }); @@ -1257,30 +1292,16 @@ describe('useSlashCommandProcessor', () => { expect(message).toContain('param2'); expect(message).toContain('number'); - expect(commandResult).toBe(true); + expect(commandResult).toEqual({ type: 'handled' }); }); }); describe('/compress command', () => { it('should call tryCompressChat(true)', async () => { const hook = getProcessorHook(); - mockTryCompressChat.mockImplementationOnce(async (force?: boolean) => { - expect(force).toBe(true); - await act(async () => { - hook.rerender(); - }); - expect(hook.result.current.pendingHistoryItems).toContainEqual({ - type: MessageType.COMPRESSION, - compression: { - isPending: true, - originalTokenCount: null, - newTokenCount: null, - }, - }); - return { - originalTokenCount: 100, - newTokenCount: 50, - }; + mockTryCompressChat.mockResolvedValue({ + originalTokenCount: 100, + newTokenCount: 50, }); await act(async () => { |
