Improve altName completion behavior in slash commands (#4227)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Lee Won Jun 2025-07-16 08:45:10 +09:00 committed by GitHub
parent 615748657a
commit ec5e9d1025
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 63 additions and 12 deletions

View File

@ -54,6 +54,12 @@ describe('useCompletion git-aware filtering integration', () => {
description: 'Show help', description: 'Show help',
action: vi.fn(), action: vi.fn(),
}, },
{
name: 'stats',
altName: 'usage',
description: 'check session stats. Usage: /stats [model|tools]',
action: vi.fn(),
},
{ {
name: 'clear', name: 'clear',
description: 'Clear the screen', description: 'Clear the screen',
@ -511,10 +517,27 @@ describe('useCompletion git-aware filtering integration', () => {
expect(result.current.showSuggestions).toBe(true); expect(result.current.showSuggestions).toBe(true);
}); });
it('should suggest commands based on altName', async () => { it.each([['/?'], ['/usage']])(
'should not suggest commands when altName is fully typed',
async (altName) => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useCompletion( useCompletion(
'/?', altName,
'/test/cwd',
true,
mockSlashCommands,
mockCommandContext,
),
);
expect(result.current.suggestions).toHaveLength(0);
},
);
it('should suggest commands based on partial altName matches', async () => {
const { result } = renderHook(() =>
useCompletion(
'/usag', // part of the word "usage"
'/test/cwd', '/test/cwd',
true, true,
mockSlashCommands, mockSlashCommands,
@ -523,7 +546,11 @@ describe('useCompletion git-aware filtering integration', () => {
); );
expect(result.current.suggestions).toEqual([ expect(result.current.suggestions).toEqual([
{ label: 'help', value: 'help', description: 'Show help' }, {
label: 'stats',
value: 'stats',
description: 'check session stats. Usage: /stats [model|tools]',
},
]); ]);
}); });
@ -734,7 +761,7 @@ describe('useCompletion git-aware filtering integration', () => {
expect(result.current.suggestions.length).toBe(mockSlashCommands.length); expect(result.current.suggestions.length).toBe(mockSlashCommands.length);
expect(result.current.suggestions.map((s) => s.label)).toEqual( expect(result.current.suggestions.map((s) => s.label)).toEqual(
expect.arrayContaining(['help', 'clear', 'memory', 'chat']), expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']),
); );
}); });

View File

@ -64,6 +64,12 @@ describe('useCompletion', () => {
description: 'Show help', description: 'Show help',
action: vi.fn(), action: vi.fn(),
}, },
{
name: 'stats',
altName: 'usage',
description: 'check session stats. Usage: /stats [model|tools]',
action: vi.fn(),
},
{ {
name: 'clear', name: 'clear',
description: 'Clear the screen', description: 'Clear the screen',
@ -299,7 +305,7 @@ describe('useCompletion', () => {
), ),
); );
expect(result.current.suggestions.length).toBe(4); expect(result.current.suggestions.length).toBe(5);
expect(result.current.activeSuggestionIndex).toBe(0); expect(result.current.activeSuggestionIndex).toBe(0);
act(() => { act(() => {
@ -325,7 +331,7 @@ describe('useCompletion', () => {
act(() => { act(() => {
result.current.navigateUp(); result.current.navigateUp();
}); });
expect(result.current.activeSuggestionIndex).toBe(3); expect(result.current.activeSuggestionIndex).toBe(4);
}); });
it('should handle navigation with large suggestion lists and scrolling', () => { it('should handle navigation with large suggestion lists and scrolling', () => {
@ -372,9 +378,9 @@ describe('useCompletion', () => {
), ),
); );
expect(result.current.suggestions).toHaveLength(4); expect(result.current.suggestions).toHaveLength(5);
expect(result.current.suggestions.map((s) => s.label)).toEqual( expect(result.current.suggestions.map((s) => s.label)).toEqual(
expect.arrayContaining(['help', 'clear', 'memory', 'chat']), expect.arrayContaining(['help', 'clear', 'memory', 'chat', 'stats']),
); );
expect(result.current.showSuggestions).toBe(true); expect(result.current.showSuggestions).toBe(true);
expect(result.current.activeSuggestionIndex).toBe(0); expect(result.current.activeSuggestionIndex).toBe(0);
@ -397,10 +403,28 @@ describe('useCompletion', () => {
expect(result.current.suggestions[0].description).toBe('Show help'); expect(result.current.suggestions[0].description).toBe('Show help');
}); });
it('should suggest commands by altName', () => { it.each([['/?'], ['/usage']])(
'should not suggest commands when altName is fully typed',
(altName) => {
const { result } = renderHook(() => const { result } = renderHook(() =>
useCompletion( useCompletion(
'/?', altName,
testCwd,
true,
mockSlashCommands,
mockCommandContext,
mockConfig,
),
);
expect(result.current.suggestions).toHaveLength(0);
},
);
it('should suggest commands based on partial altName matches', () => {
const { result } = renderHook(() =>
useCompletion(
'/usag', // part of the word "usage"
testCwd, testCwd,
true, true,
mockSlashCommands, mockSlashCommands,
@ -410,7 +434,7 @@ describe('useCompletion', () => {
); );
expect(result.current.suggestions).toHaveLength(1); expect(result.current.suggestions).toHaveLength(1);
expect(result.current.suggestions[0].label).toBe('help'); expect(result.current.suggestions[0].label).toBe('stats');
}); });
it('should not show suggestions for exact leaf command match', () => { it('should not show suggestions for exact leaf command match', () => {

View File

@ -221,7 +221,7 @@ export function useCompletion(
// enter should submit immediately. // enter should submit immediately.
if (potentialSuggestions.length > 0 && !hasTrailingSpace) { if (potentialSuggestions.length > 0 && !hasTrailingSpace) {
const perfectMatch = potentialSuggestions.find( const perfectMatch = potentialSuggestions.find(
(s) => s.name === partial, (s) => s.name === partial || s.altName === partial,
); );
if (perfectMatch && !perfectMatch.subCommands) { if (perfectMatch && !perfectMatch.subCommands) {
potentialSuggestions = []; potentialSuggestions = [];