mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 12:11:09 +00:00
242 lines
6.6 KiB
TypeScript
242 lines
6.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { handleSlashCommand } from './nonInteractiveCliCommands.js';
|
|
import type { Config } from '@qwen-code/qwen-code-core';
|
|
import type { LoadedSettings } from './config/settings.js';
|
|
import { CommandKind } from './ui/commands/types.js';
|
|
|
|
// Mock the CommandService
|
|
const mockGetCommands = vi.hoisted(() => vi.fn());
|
|
const mockCommandServiceCreate = vi.hoisted(() => vi.fn());
|
|
vi.mock('./services/CommandService.js', () => ({
|
|
CommandService: {
|
|
create: mockCommandServiceCreate,
|
|
},
|
|
}));
|
|
|
|
describe('handleSlashCommand', () => {
|
|
let mockConfig: Config;
|
|
let mockSettings: LoadedSettings;
|
|
let abortController: AbortController;
|
|
|
|
beforeEach(() => {
|
|
mockCommandServiceCreate.mockResolvedValue({
|
|
getCommands: mockGetCommands,
|
|
});
|
|
|
|
mockConfig = {
|
|
getExperimentalZedIntegration: vi.fn().mockReturnValue(false),
|
|
isInteractive: vi.fn().mockReturnValue(false),
|
|
getSessionId: vi.fn().mockReturnValue('test-session'),
|
|
getFolderTrustFeature: vi.fn().mockReturnValue(false),
|
|
getFolderTrust: vi.fn().mockReturnValue(false),
|
|
getProjectRoot: vi.fn().mockReturnValue('/test/project'),
|
|
storage: {},
|
|
} as unknown as Config;
|
|
|
|
mockSettings = {
|
|
system: { path: '', settings: {} },
|
|
systemDefaults: { path: '', settings: {} },
|
|
user: { path: '', settings: {} },
|
|
workspace: { path: '', settings: {} },
|
|
} as LoadedSettings;
|
|
|
|
abortController = new AbortController();
|
|
});
|
|
|
|
it('should return no_command for non-slash input', async () => {
|
|
const result = await handleSlashCommand(
|
|
'regular text',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
);
|
|
|
|
expect(result.type).toBe('no_command');
|
|
});
|
|
|
|
it('should return no_command for unknown slash commands', async () => {
|
|
mockGetCommands.mockReturnValue([]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/unknowncommand',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
);
|
|
|
|
expect(result.type).toBe('no_command');
|
|
});
|
|
|
|
it('should return unsupported for known built-in commands not in allowed list', async () => {
|
|
const mockHelpCommand = {
|
|
name: 'help',
|
|
description: 'Show help',
|
|
kind: CommandKind.BUILT_IN,
|
|
action: vi.fn(),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockHelpCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/help',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
[], // Empty allowed list
|
|
);
|
|
|
|
expect(result.type).toBe('unsupported');
|
|
if (result.type === 'unsupported') {
|
|
expect(result.reason).toContain('/help');
|
|
expect(result.reason).toContain('not supported');
|
|
}
|
|
});
|
|
|
|
it('should return unsupported for /help when using default allowed list', async () => {
|
|
const mockHelpCommand = {
|
|
name: 'help',
|
|
description: 'Show help',
|
|
kind: CommandKind.BUILT_IN,
|
|
action: vi.fn(),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockHelpCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/help',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
// Default allowed list: ['init', 'summary', 'compress']
|
|
);
|
|
|
|
expect(result.type).toBe('unsupported');
|
|
if (result.type === 'unsupported') {
|
|
expect(result.reason).toBe(
|
|
'The command "/help" is not supported in non-interactive mode.',
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should execute allowed built-in commands', async () => {
|
|
const mockInitCommand = {
|
|
name: 'init',
|
|
description: 'Initialize project',
|
|
kind: CommandKind.BUILT_IN,
|
|
action: vi.fn().mockResolvedValue({
|
|
type: 'message',
|
|
messageType: 'info',
|
|
content: 'Project initialized',
|
|
}),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockInitCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/init',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
['init'], // init is in the allowed list
|
|
);
|
|
|
|
expect(result.type).toBe('message');
|
|
if (result.type === 'message') {
|
|
expect(result.content).toBe('Project initialized');
|
|
}
|
|
});
|
|
|
|
it('should execute file commands regardless of allowed list', async () => {
|
|
const mockFileCommand = {
|
|
name: 'custom',
|
|
description: 'Custom file command',
|
|
kind: CommandKind.FILE,
|
|
action: vi.fn().mockResolvedValue({
|
|
type: 'submit_prompt',
|
|
content: [{ text: 'Custom prompt' }],
|
|
}),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockFileCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/custom',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
[], // Empty allowed list, but FILE commands should still work
|
|
);
|
|
|
|
expect(result.type).toBe('submit_prompt');
|
|
if (result.type === 'submit_prompt') {
|
|
expect(result.content).toEqual([{ text: 'Custom prompt' }]);
|
|
}
|
|
});
|
|
|
|
it('should return unsupported for other built-in commands like /quit', async () => {
|
|
const mockQuitCommand = {
|
|
name: 'quit',
|
|
description: 'Quit application',
|
|
kind: CommandKind.BUILT_IN,
|
|
action: vi.fn(),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockQuitCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/quit',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
);
|
|
|
|
expect(result.type).toBe('unsupported');
|
|
if (result.type === 'unsupported') {
|
|
expect(result.reason).toContain('/quit');
|
|
expect(result.reason).toContain('not supported');
|
|
}
|
|
});
|
|
|
|
it('should handle command with no action', async () => {
|
|
const mockCommand = {
|
|
name: 'noaction',
|
|
description: 'Command without action',
|
|
kind: CommandKind.FILE,
|
|
// No action property
|
|
};
|
|
mockGetCommands.mockReturnValue([mockCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/noaction',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
);
|
|
|
|
expect(result.type).toBe('no_command');
|
|
});
|
|
|
|
it('should return message when command returns void', async () => {
|
|
const mockCommand = {
|
|
name: 'voidcmd',
|
|
description: 'Command that returns void',
|
|
kind: CommandKind.FILE,
|
|
action: vi.fn().mockResolvedValue(undefined),
|
|
};
|
|
mockGetCommands.mockReturnValue([mockCommand]);
|
|
|
|
const result = await handleSlashCommand(
|
|
'/voidcmd',
|
|
abortController,
|
|
mockConfig,
|
|
mockSettings,
|
|
);
|
|
|
|
expect(result.type).toBe('message');
|
|
if (result.type === 'message') {
|
|
expect(result.content).toBe('Command executed successfully.');
|
|
expect(result.messageType).toBe('info');
|
|
}
|
|
});
|
|
});
|