mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-01 17:02:29 -07:00
fix(ui): prevent over-eager slash subcommand completion (#20136)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -464,7 +464,7 @@ describe('useSlashCompletion', () => {
|
||||
() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/chat',
|
||||
'/chat ',
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
@@ -484,7 +484,7 @@ describe('useSlashCompletion', () => {
|
||||
() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/resume',
|
||||
'/resume ',
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
@@ -513,53 +513,6 @@ describe('useSlashCompletion', () => {
|
||||
unmountResume();
|
||||
});
|
||||
|
||||
it('should show the grouped /resume menu for unique /resum prefix input', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
name: 'resume',
|
||||
description: 'Resume command',
|
||||
action: vi.fn(),
|
||||
subCommands: [
|
||||
createTestCommand({
|
||||
name: 'list',
|
||||
description: 'List checkpoints',
|
||||
suggestionGroup: 'checkpoints',
|
||||
}),
|
||||
createTestCommand({
|
||||
name: 'save',
|
||||
description: 'Save checkpoint',
|
||||
suggestionGroup: 'checkpoints',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/resum',
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
);
|
||||
|
||||
await resolveMatch();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.suggestions[0]).toMatchObject({
|
||||
label: 'list',
|
||||
sectionTitle: 'auto',
|
||||
submitValue: '/resume',
|
||||
});
|
||||
expect(result.current.isPerfectMatch).toBe(false);
|
||||
expect(result.current.suggestions.slice(1).map((s) => s.label)).toEqual(
|
||||
expect.arrayContaining(['list', 'save']),
|
||||
);
|
||||
});
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should sort exact altName matches to the top', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
@@ -594,7 +547,7 @@ describe('useSlashCompletion', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should suggest subcommands when a parent command is fully typed without a trailing space', async () => {
|
||||
it('should suggest the command itself instead of subcommands when a parent command is fully typed without a trailing space', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
name: 'chat',
|
||||
@@ -618,18 +571,47 @@ describe('useSlashCompletion', () => {
|
||||
await resolveMatch();
|
||||
|
||||
await waitFor(() => {
|
||||
// Should show the auto-session entry plus subcommands of 'chat'
|
||||
expect(result.current.suggestions).toHaveLength(3);
|
||||
expect(result.current.suggestions[0]).toMatchObject({
|
||||
label: 'list',
|
||||
sectionTitle: 'auto',
|
||||
submitValue: '/chat',
|
||||
});
|
||||
expect(result.current.suggestions.map((s) => s.label)).toEqual(
|
||||
expect.arrayContaining(['list', 'save']),
|
||||
);
|
||||
// completionStart should be at the end of '/chat' to append subcommands
|
||||
expect(result.current.completionStart).toBe(5);
|
||||
// Should show 'chat' as the suggestion, NOT its subcommands
|
||||
expect(result.current.suggestions).toHaveLength(1);
|
||||
expect(result.current.suggestions[0].label).toBe('chat');
|
||||
// completionStart should be at 1 (to replace 'chat')
|
||||
expect(result.current.completionStart).toBe(1);
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should NOT suggest subcommands when a parent command is fully typed without a trailing space (fix for over-eager completion)', async () => {
|
||||
const slashCommands = [
|
||||
createTestCommand({
|
||||
name: 'stats',
|
||||
description: 'Check session stats',
|
||||
action: vi.fn(), // Has action
|
||||
subCommands: [
|
||||
createTestCommand({
|
||||
name: 'session',
|
||||
description: 'Show session-specific usage statistics',
|
||||
}),
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
const { result, unmount } = await renderHook(() =>
|
||||
useTestHarnessForSlashCompletion(
|
||||
true,
|
||||
'/stats',
|
||||
slashCommands,
|
||||
mockCommandContext,
|
||||
),
|
||||
);
|
||||
|
||||
await resolveMatch();
|
||||
|
||||
await waitFor(() => {
|
||||
// Should show 'stats' as the suggestion, NOT 'session'
|
||||
expect(result.current.suggestions).toHaveLength(1);
|
||||
expect(result.current.suggestions[0].label).toBe('stats');
|
||||
// isPerfectMatch should be true because it has an action
|
||||
expect(result.current.isPerfectMatch).toBe(true);
|
||||
});
|
||||
unmount();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user