diff options
| author | Shreya Keshive <[email protected]> | 2025-07-16 18:36:14 -0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-16 22:36:14 +0000 |
| commit | ab9eb9377fdfe7823be8ea0c7c394c5368b28951 (patch) | |
| tree | ad7f0ebf45902513d129d4eb09897a5345cad195 /packages/cli/src/ui/commands/ideCommand.test.ts | |
| parent | 69a8ae6a89918f4255304975f8653ff808264bc8 (diff) | |
Add /ide status & /ide install commands to manage IDE integration (#4265)
Diffstat (limited to 'packages/cli/src/ui/commands/ideCommand.test.ts')
| -rw-r--r-- | packages/cli/src/ui/commands/ideCommand.test.ts | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/packages/cli/src/ui/commands/ideCommand.test.ts b/packages/cli/src/ui/commands/ideCommand.test.ts new file mode 100644 index 00000000..9e5f798d --- /dev/null +++ b/packages/cli/src/ui/commands/ideCommand.test.ts @@ -0,0 +1,256 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { ideCommand } from './ideCommand.js'; +import { type CommandContext } from './types.js'; +import { type Config } from '@google/gemini-cli-core'; +import * as child_process from 'child_process'; +import { glob } from 'glob'; + +import { + getMCPDiscoveryState, + getMCPServerStatus, + IDE_SERVER_NAME, + MCPDiscoveryState, + MCPServerStatus, +} from '@google/gemini-cli-core'; + +vi.mock('child_process'); +vi.mock('glob'); +vi.mock('@google/gemini-cli-core', async (importOriginal) => { + const original = + await importOriginal<typeof import('@google/gemini-cli-core')>(); + return { + ...original, + getMCPServerStatus: vi.fn(), + getMCPDiscoveryState: vi.fn(), + }; +}); + +describe('ideCommand', () => { + let mockContext: CommandContext; + let mockConfig: Config; + let execSyncSpy: vi.SpyInstance; + let globSyncSpy: vi.SpyInstance; + let platformSpy: vi.SpyInstance; + let getMCPServerStatusSpy: vi.SpyInstance; + let getMCPDiscoveryStateSpy: vi.SpyInstance; + + beforeEach(() => { + mockContext = { + ui: { + addItem: vi.fn(), + }, + } as unknown as CommandContext; + + mockConfig = { + getIdeMode: vi.fn(), + } as unknown as Config; + + execSyncSpy = vi.spyOn(child_process, 'execSync'); + globSyncSpy = vi.spyOn(glob, 'sync'); + platformSpy = vi.spyOn(process, 'platform', 'get'); + getMCPServerStatusSpy = vi.mocked(getMCPServerStatus); + getMCPDiscoveryStateSpy = vi.mocked(getMCPDiscoveryState); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return null if ideMode is not enabled', () => { + (mockConfig.getIdeMode as vi.Mock).mockReturnValue(false); + const command = ideCommand(mockConfig); + expect(command).toBeNull(); + }); + + it('should return the ide command if ideMode is enabled', () => { + (mockConfig.getIdeMode as vi.Mock).mockReturnValue(true); + const command = ideCommand(mockConfig); + expect(command).not.toBeNull(); + expect(command?.name).toBe('ide'); + expect(command?.subCommands).toHaveLength(2); + expect(command?.subCommands?.[0].name).toBe('status'); + expect(command?.subCommands?.[1].name).toBe('install'); + }); + + describe('status subcommand', () => { + beforeEach(() => { + (mockConfig.getIdeMode as vi.Mock).mockReturnValue(true); + }); + + it('should show connected status', () => { + getMCPServerStatusSpy.mockReturnValue(MCPServerStatus.CONNECTED); + const command = ideCommand(mockConfig); + const result = command?.subCommands?.[0].action(mockContext, ''); + expect(getMCPServerStatusSpy).toHaveBeenCalledWith(IDE_SERVER_NAME); + expect(result).toEqual({ + type: 'message', + messageType: 'info', + content: '🟢 Connected', + }); + }); + + it('should show connecting status', () => { + getMCPServerStatusSpy.mockReturnValue(MCPServerStatus.CONNECTING); + const command = ideCommand(mockConfig); + const result = command?.subCommands?.[0].action(mockContext, ''); + expect(result).toEqual({ + type: 'message', + messageType: 'info', + content: '🔄 Initializing...', + }); + }); + + it('should show discovery in progress status', () => { + getMCPServerStatusSpy.mockReturnValue(MCPServerStatus.DISCONNECTED); + getMCPDiscoveryStateSpy.mockReturnValue(MCPDiscoveryState.IN_PROGRESS); + const command = ideCommand(mockConfig); + const result = command?.subCommands?.[0].action(mockContext, ''); + expect(result).toEqual({ + type: 'message', + messageType: 'info', + content: '🔄 Initializing...', + }); + }); + + it('should show disconnected status', () => { + getMCPServerStatusSpy.mockReturnValue(MCPServerStatus.DISCONNECTED); + getMCPDiscoveryStateSpy.mockReturnValue(MCPDiscoveryState.NOT_FOUND); + const command = ideCommand(mockConfig); + const result = command?.subCommands?.[0].action(mockContext, ''); + expect(result).toEqual({ + type: 'message', + messageType: 'error', + content: '🔴 Disconnected', + }); + }); + }); + + describe('install subcommand', () => { + beforeEach(() => { + (mockConfig.getIdeMode as vi.Mock).mockReturnValue(true); + platformSpy.mockReturnValue('linux'); + }); + + it('should show an error if VSCode is not installed', async () => { + execSyncSpy.mockImplementation(() => { + throw new Error('Command not found'); + }); + + const command = ideCommand(mockConfig); + await command?.subCommands?.[1].action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'error', + text: expect.stringContaining( + 'VS Code command-line tool "code" not found', + ), + }), + expect.any(Number), + ); + }); + + it('should show an error if the VSIX file is not found', async () => { + execSyncSpy.mockReturnValue(''); // VSCode is installed + globSyncSpy.mockReturnValue([]); // No .vsix file found + + const command = ideCommand(mockConfig); + await command?.subCommands?.[1].action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'error', + text: 'Could not find the required VS Code companion extension. Please file a bug via /bug.', + }), + expect.any(Number), + ); + }); + + it('should install the extension if found in the bundle directory', async () => { + const vsixPath = '/path/to/bundle/gemini.vsix'; + execSyncSpy.mockReturnValue(''); // VSCode is installed + globSyncSpy.mockReturnValue([vsixPath]); // Found .vsix file + + const command = ideCommand(mockConfig); + await command?.subCommands?.[1].action(mockContext, ''); + + expect(globSyncSpy).toHaveBeenCalledWith( + expect.stringContaining('.vsix'), + ); + expect(execSyncSpy).toHaveBeenCalledWith( + `code --install-extension ${vsixPath} --force`, + { stdio: 'pipe' }, + ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'info', + text: `Installing VS Code companion extension...`, + }), + expect.any(Number), + ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'info', + text: 'VS Code companion extension installed successfully. Restart gemini-cli in a fresh terminal window.', + }), + expect.any(Number), + ); + }); + + it('should install the extension if found in the dev directory', async () => { + const vsixPath = '/path/to/dev/gemini.vsix'; + execSyncSpy.mockReturnValue(''); // VSCode is installed + // First glob call for bundle returns nothing, second for dev returns path. + globSyncSpy.mockReturnValueOnce([]).mockReturnValueOnce([vsixPath]); + + const command = ideCommand(mockConfig); + await command?.subCommands?.[1].action(mockContext, ''); + + expect(globSyncSpy).toHaveBeenCalledTimes(2); + expect(execSyncSpy).toHaveBeenCalledWith( + `code --install-extension ${vsixPath} --force`, + { stdio: 'pipe' }, + ); + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'info', + text: 'VS Code companion extension installed successfully. Restart gemini-cli in a fresh terminal window.', + }), + expect.any(Number), + ); + }); + + it('should show an error if installation fails', async () => { + const vsixPath = '/path/to/bundle/gemini.vsix'; + const errorMessage = 'Installation failed'; + execSyncSpy + .mockReturnValueOnce('') // VSCode is installed check + .mockImplementation(() => { + // Installation command + const error: Error & { stderr?: Buffer } = new Error( + 'Command failed', + ); + error.stderr = Buffer.from(errorMessage); + throw error; + }); + globSyncSpy.mockReturnValue([vsixPath]); + + const command = ideCommand(mockConfig); + await command?.subCommands?.[1].action(mockContext, ''); + + expect(mockContext.ui.addItem).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'error', + text: `Failed to install VS Code companion extension.`, + }), + expect.any(Number), + ); + }); + }); +}); |
