mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-12 15:10:59 -07:00
178 lines
5.9 KiB
TypeScript
178 lines
5.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
import { SlashCommandResolver } from './SlashCommandResolver.js';
|
|
import { CommandKind, type SlashCommand } from '../ui/commands/types.js';
|
|
|
|
const createMockCommand = (name: string, kind: CommandKind): SlashCommand => ({
|
|
name,
|
|
description: `Description for ${name}`,
|
|
kind,
|
|
action: vi.fn(),
|
|
});
|
|
|
|
describe('SlashCommandResolver', () => {
|
|
describe('resolve', () => {
|
|
it('should return all commands when there are no conflicts', () => {
|
|
const cmdA = createMockCommand('a', CommandKind.BUILT_IN);
|
|
const cmdB = createMockCommand('b', CommandKind.USER_FILE);
|
|
|
|
const { finalCommands, conflicts } = SlashCommandResolver.resolve([
|
|
cmdA,
|
|
cmdB,
|
|
]);
|
|
|
|
expect(finalCommands).toHaveLength(2);
|
|
expect(conflicts).toHaveLength(0);
|
|
});
|
|
|
|
it('should rename extension commands when they conflict with built-in', () => {
|
|
const builtin = createMockCommand('deploy', CommandKind.BUILT_IN);
|
|
const extension = {
|
|
...createMockCommand('deploy', CommandKind.EXTENSION_FILE),
|
|
extensionName: 'firebase',
|
|
};
|
|
|
|
const { finalCommands, conflicts } = SlashCommandResolver.resolve([
|
|
builtin,
|
|
extension,
|
|
]);
|
|
|
|
expect(finalCommands.map((c) => c.name)).toContain('deploy');
|
|
expect(finalCommands.map((c) => c.name)).toContain('firebase.deploy');
|
|
expect(conflicts).toHaveLength(1);
|
|
});
|
|
|
|
it('should prefix both user and workspace commands when they conflict', () => {
|
|
const userCmd = createMockCommand('sync', CommandKind.USER_FILE);
|
|
const workspaceCmd = createMockCommand(
|
|
'sync',
|
|
CommandKind.WORKSPACE_FILE,
|
|
);
|
|
|
|
const { finalCommands, conflicts } = SlashCommandResolver.resolve([
|
|
userCmd,
|
|
workspaceCmd,
|
|
]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).not.toContain('sync');
|
|
expect(names).toContain('user.sync');
|
|
expect(names).toContain('workspace.sync');
|
|
expect(conflicts).toHaveLength(1);
|
|
expect(conflicts[0].losers).toHaveLength(2); // Both are considered losers
|
|
});
|
|
|
|
it('should prefix file commands but keep built-in names during conflicts', () => {
|
|
const builtin = createMockCommand('help', CommandKind.BUILT_IN);
|
|
const user = createMockCommand('help', CommandKind.USER_FILE);
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([builtin, user]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).toContain('help');
|
|
expect(names).toContain('user.help');
|
|
});
|
|
|
|
it('should prefix both commands when MCP and user file conflict', () => {
|
|
const mcp = {
|
|
...createMockCommand('test', CommandKind.MCP_PROMPT),
|
|
mcpServerName: 'test-server',
|
|
};
|
|
const user = createMockCommand('test', CommandKind.USER_FILE);
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([mcp, user]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).not.toContain('test');
|
|
expect(names).toContain('test-server.test');
|
|
expect(names).toContain('user.test');
|
|
});
|
|
|
|
it('should prefix MCP commands with server name when they conflict with built-in', () => {
|
|
const builtin = createMockCommand('help', CommandKind.BUILT_IN);
|
|
const mcp = {
|
|
...createMockCommand('help', CommandKind.MCP_PROMPT),
|
|
mcpServerName: 'test-server',
|
|
};
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([builtin, mcp]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).toContain('help');
|
|
expect(names).toContain('test-server.help');
|
|
});
|
|
|
|
it('should prefix both MCP commands when they conflict with each other', () => {
|
|
const mcp1 = {
|
|
...createMockCommand('test', CommandKind.MCP_PROMPT),
|
|
mcpServerName: 'server1',
|
|
};
|
|
const mcp2 = {
|
|
...createMockCommand('test', CommandKind.MCP_PROMPT),
|
|
mcpServerName: 'server2',
|
|
};
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([mcp1, mcp2]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).not.toContain('test');
|
|
expect(names).toContain('server1.test');
|
|
expect(names).toContain('server2.test');
|
|
});
|
|
|
|
it('should favor the last built-in command silently during conflicts', () => {
|
|
const builtin1 = {
|
|
...createMockCommand('help', CommandKind.BUILT_IN),
|
|
description: 'first',
|
|
};
|
|
const builtin2 = {
|
|
...createMockCommand('help', CommandKind.BUILT_IN),
|
|
description: 'second',
|
|
};
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([
|
|
builtin1,
|
|
builtin2,
|
|
]);
|
|
|
|
expect(finalCommands).toHaveLength(1);
|
|
expect(finalCommands[0].description).toBe('second');
|
|
});
|
|
|
|
it('should fallback to numeric suffixes when both prefix and kind-based prefix are missing', () => {
|
|
const cmd1 = createMockCommand('test', CommandKind.BUILT_IN);
|
|
const cmd2 = {
|
|
...createMockCommand('test', 'unknown' as CommandKind),
|
|
};
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([cmd1, cmd2]);
|
|
|
|
const names = finalCommands.map((c) => c.name);
|
|
expect(names).toContain('test');
|
|
expect(names).toContain('test1');
|
|
});
|
|
|
|
it('should apply numeric suffixes when renames also conflict', () => {
|
|
const user1 = createMockCommand('deploy', CommandKind.USER_FILE);
|
|
const user2 = createMockCommand('gcp.deploy', CommandKind.USER_FILE);
|
|
const extension = {
|
|
...createMockCommand('deploy', CommandKind.EXTENSION_FILE),
|
|
extensionName: 'gcp',
|
|
};
|
|
|
|
const { finalCommands } = SlashCommandResolver.resolve([
|
|
user1,
|
|
user2,
|
|
extension,
|
|
]);
|
|
|
|
expect(finalCommands.find((c) => c.name === 'gcp.deploy1')).toBeDefined();
|
|
});
|
|
});
|
|
});
|