sync gemini-cli 0.1.17

Co-Authored-By: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Yiheng Xu 2025-08-05 16:44:06 +08:00
commit cd375fefe5
235 changed files with 16997 additions and 3736 deletions

View file

@ -15,24 +15,16 @@ import {
} from 'vitest';
import { ideCommand } from './ideCommand.js';
import { type CommandContext } from './types.js';
import { type Config } from '@qwen-code/qwen-code-core';
import * as child_process from 'child_process';
import { glob } from 'glob';
import { IDEConnectionStatus } from '@qwen-code/qwen-code-core/index.js';
import { type Config, DetectedIde } from '@qwen-code/qwen-code-core';
import * as core from '@qwen-code/qwen-code-core';
vi.mock('child_process');
vi.mock('glob');
function regexEscape(value: string) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
vi.mock('@qwen-code/qwen-code-core');
describe('ideCommand', () => {
let mockContext: CommandContext;
let mockConfig: Config;
let execSyncSpy: MockInstance;
let globSyncSpy: MockInstance;
let platformSpy: MockInstance;
beforeEach(() => {
@ -40,15 +32,21 @@ describe('ideCommand', () => {
ui: {
addItem: vi.fn(),
},
services: {
settings: {
setValue: vi.fn(),
},
},
} as unknown as CommandContext;
mockConfig = {
getIdeModeFeature: vi.fn(),
getIdeMode: vi.fn(),
getIdeClient: vi.fn(),
setIdeMode: vi.fn(),
setIdeClientDisconnected: vi.fn(),
} as unknown as Config;
execSyncSpy = vi.spyOn(child_process, 'execSync');
globSyncSpy = vi.spyOn(glob, 'sync');
platformSpy = vi.spyOn(process, 'platform', 'get');
});
@ -56,51 +54,61 @@ describe('ideCommand', () => {
vi.restoreAllMocks();
});
it('should return null if ideMode is not enabled', () => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(false);
it('should return null if ideModeFeature is not enabled', () => {
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(false);
const command = ideCommand(mockConfig);
expect(command).toBeNull();
});
it('should return the ide command if ideMode is enabled', () => {
it('should return the ide command if ideModeFeature is enabled', () => {
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode,
getDetectedIdeDisplayName: () => 'VS Code',
} as ReturnType<Config['getIdeClient']>);
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');
expect(command?.subCommands).toHaveLength(3);
expect(command?.subCommands?.[0].name).toBe('disable');
expect(command?.subCommands?.[1].name).toBe('status');
expect(command?.subCommands?.[2].name).toBe('install');
});
describe('status subcommand', () => {
const mockGetConnectionStatus = vi.fn();
beforeEach(() => {
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getConnectionStatus: mockGetConnectionStatus,
} as ReturnType<Config['getIdeClient']>);
getCurrentIde: () => DetectedIde.VSCode,
getDetectedIdeDisplayName: () => 'VS Code',
} as unknown as ReturnType<Config['getIdeClient']>);
});
it('should show connected status', () => {
mockGetConnectionStatus.mockReturnValue({
status: IDEConnectionStatus.Connected,
status: core.IDEConnectionStatus.Connected,
});
const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, '');
const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({
type: 'message',
messageType: 'info',
content: '🟢 Connected',
content: '🟢 Connected to VS Code',
});
});
it('should show connecting status', () => {
mockGetConnectionStatus.mockReturnValue({
status: IDEConnectionStatus.Connecting,
status: core.IDEConnectionStatus.Connecting,
});
const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, '');
const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({
type: 'message',
@ -110,10 +118,11 @@ describe('ideCommand', () => {
});
it('should show disconnected status', () => {
mockGetConnectionStatus.mockReturnValue({
status: IDEConnectionStatus.Disconnected,
status: core.IDEConnectionStatus.Disconnected,
});
const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, '');
const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({
type: 'message',
@ -125,11 +134,12 @@ describe('ideCommand', () => {
it('should show disconnected status with details', () => {
const details = 'Something went wrong';
mockGetConnectionStatus.mockReturnValue({
status: IDEConnectionStatus.Disconnected,
status: core.IDEConnectionStatus.Disconnected,
details,
});
const command = ideCommand(mockConfig);
const result = command!.subCommands![0].action!(mockContext, '');
const result = command!.subCommands!.find((c) => c.name === 'status')!
.action!(mockContext, '');
expect(mockGetConnectionStatus).toHaveBeenCalled();
expect(result).toEqual({
type: 'message',
@ -140,128 +150,77 @@ describe('ideCommand', () => {
});
describe('install subcommand', () => {
const mockInstall = vi.fn();
beforeEach(() => {
vi.mocked(mockConfig.getIdeModeFeature).mockReturnValue(true);
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
vi.mocked(mockConfig.getIdeClient).mockReturnValue({
getCurrentIde: () => DetectedIde.VSCode,
getConnectionStatus: vi.fn(),
getDetectedIdeDisplayName: () => 'VS Code',
} as unknown as ReturnType<Config['getIdeClient']>);
vi.mocked(core.getIdeInstaller).mockReturnValue({
install: mockInstall,
isInstalled: vi.fn(),
});
platformSpy.mockReturnValue('linux');
});
it('should show an error if VSCode is not installed', async () => {
execSyncSpy.mockImplementation(() => {
throw new Error('Command not found');
it('should install the extension', async () => {
mockInstall.mockResolvedValue({
success: true,
message: 'Successfully installed.',
});
const command = ideCommand(mockConfig);
await command!.subCommands![1].action!(mockContext, '');
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
text: expect.stringMatching(/VS Code command-line tool .* not found/),
}),
expect.any(Number),
await command!.subCommands!.find((c) => c.name === 'install')!.action!(
mockContext,
'',
);
});
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(
expect.stringMatching(
new RegExp(
`code(.cmd)? --install-extension ${regexEscape(vsixPath)} --force`,
),
),
{ stdio: 'pipe' },
);
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
expect(mockInstall).toHaveBeenCalled();
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
text: `Installing VS Code companion extension...`,
text: `Installing IDE companion...`,
}),
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(
expect.stringMatching(
new RegExp(
`code(.cmd)? --install-extension ${regexEscape(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.',
text: 'Successfully installed.',
}),
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]);
mockInstall.mockResolvedValue({
success: false,
message: 'Installation failed.',
});
const command = ideCommand(mockConfig);
await command!.subCommands![1].action!(mockContext, '');
await command!.subCommands!.find((c) => c.name === 'install')!.action!(
mockContext,
'',
);
expect(core.getIdeInstaller).toHaveBeenCalledWith('vscode');
expect(mockInstall).toHaveBeenCalled();
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'info',
text: `Installing IDE companion...`,
}),
expect.any(Number),
);
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
expect.objectContaining({
type: 'error',
text: `Failed to install VS Code companion extension.`,
text: 'Installation failed.',
}),
expect.any(Number),
);