feat(ui): add source indicators to slash commands (#18839)

This commit is contained in:
Emily Hedlund
2026-02-20 10:54:59 -05:00
committed by GitHub
parent c3b52b8206
commit d08b1efc72
5 changed files with 293 additions and 116 deletions

View File

@@ -32,6 +32,9 @@ vi.mock('./prompt-processors/atFileProcessor.js', () => ({
process: mockAtFileProcess,
})),
}));
vi.mock('../utils/osUtils.js', () => ({
getUsername: vi.fn().mockReturnValue('mock-user'),
}));
vi.mock('./prompt-processors/shellProcessor.js', () => ({
ShellProcessor: vi.fn().mockImplementation(() => ({
process: mockShellProcess,
@@ -582,7 +585,7 @@ describe('FileCommandLoader', () => {
const extCommand = commands.find((cmd) => cmd.name === 'ext');
expect(extCommand?.extensionName).toBe('test-ext');
expect(extCommand?.description).toMatch(/^\[test-ext\]/);
expect(extCommand?.description).toBe('Custom command from ext.toml');
});
it('extension commands have extensionName metadata for conflict resolution', async () => {
@@ -670,7 +673,7 @@ describe('FileCommandLoader', () => {
expect(commands[2].name).toBe('deploy');
expect(commands[2].extensionName).toBe('test-ext');
expect(commands[2].description).toMatch(/^\[test-ext\]/);
expect(commands[2].description).toBe('Custom command from deploy.toml');
const result2 = await commands[2].action?.(
createMockCommandContext({
invocation: {
@@ -747,7 +750,7 @@ describe('FileCommandLoader', () => {
expect(commands).toHaveLength(1);
expect(commands[0].name).toBe('active');
expect(commands[0].extensionName).toBe('active-ext');
expect(commands[0].description).toMatch(/^\[active-ext\]/);
expect(commands[0].description).toBe('Custom command from active.toml');
});
it('handles missing extension commands directory gracefully', async () => {
@@ -830,7 +833,7 @@ describe('FileCommandLoader', () => {
const nestedCmd = commands.find((cmd) => cmd.name === 'b:c');
expect(nestedCmd?.extensionName).toBe('a');
expect(nestedCmd?.description).toMatch(/^\[a\]/);
expect(nestedCmd?.description).toBe('Custom command from c.toml');
expect(nestedCmd).toBeDefined();
const result = await nestedCmd!.action?.(
createMockCommandContext({
@@ -1402,4 +1405,109 @@ describe('FileCommandLoader', () => {
expect(commands[0].description).toBe('d'.repeat(97) + '...');
});
});
describe('command namespace', () => {
it('is "user" for user commands', async () => {
const userCommandsDir = Storage.getUserCommandsDir();
mock({
[userCommandsDir]: {
'test.toml': 'prompt = "User prompt"',
},
});
const loader = new FileCommandLoader(null);
const commands = await loader.loadCommands(signal);
expect(commands[0].name).toBe('test');
expect(commands[0].namespace).toBe('user');
expect(commands[0].description).toBe('Custom command from test.toml');
});
it.each([
{
name: 'standard path',
projectRoot: '/path/to/my-awesome-project',
},
{
name: 'Windows-style path',
projectRoot: 'C:\\Users\\test\\projects\\win-project',
},
])(
'is "workspace" for project commands ($name)',
async ({ projectRoot }) => {
const projectCommandsDir = path.join(
projectRoot,
GEMINI_DIR,
'commands',
);
mock({
[projectCommandsDir]: {
'project.toml': 'prompt = "Project prompt"',
},
});
const mockConfig = {
getProjectRoot: vi.fn(() => projectRoot),
getExtensions: vi.fn(() => []),
getFolderTrust: vi.fn(() => false),
isTrustedFolder: vi.fn(() => false),
storage: new Storage(projectRoot),
} as unknown as Config;
const loader = new FileCommandLoader(mockConfig);
const commands = await loader.loadCommands(signal);
const projectCmd = commands.find((c) => c.name === 'project');
expect(projectCmd).toBeDefined();
expect(projectCmd?.namespace).toBe('workspace');
expect(projectCmd?.description).toBe(
`Custom command from project.toml`,
);
},
);
it('is the extension name for extension commands', async () => {
const extensionDir = path.join(
process.cwd(),
GEMINI_DIR,
'extensions',
'my-ext',
);
mock({
[extensionDir]: {
'gemini-extension.json': JSON.stringify({
name: 'my-ext',
version: '1.0.0',
}),
commands: {
'ext.toml': 'prompt = "Extension prompt"',
},
},
});
const mockConfig = {
getProjectRoot: vi.fn(() => process.cwd()),
getExtensions: vi.fn(() => [
{
name: 'my-ext',
version: '1.0.0',
isActive: true,
path: extensionDir,
},
]),
getFolderTrust: vi.fn(() => false),
isTrustedFolder: vi.fn(() => false),
} as unknown as Config;
const loader = new FileCommandLoader(mockConfig);
const commands = await loader.loadCommands(signal);
const extCmd = commands.find((c) => c.name === 'ext');
expect(extCmd).toBeDefined();
expect(extCmd?.namespace).toBe('my-ext');
expect(extCmd?.description).toBe('Custom command from ext.toml');
});
});
});