mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-21 23:40:44 -07:00
Fix so rewind starts at the bottom and loadHistory refreshes static content. (#17335)
This commit is contained in:
@@ -156,11 +156,28 @@ describe('useSlashCommandProcessor', () => {
|
||||
});
|
||||
|
||||
const setupProcessorHook = async (
|
||||
builtinCommands: SlashCommand[] = [],
|
||||
fileCommands: SlashCommand[] = [],
|
||||
mcpCommands: SlashCommand[] = [],
|
||||
setIsProcessing = vi.fn(),
|
||||
options: {
|
||||
builtinCommands?: SlashCommand[];
|
||||
fileCommands?: SlashCommand[];
|
||||
mcpCommands?: SlashCommand[];
|
||||
setIsProcessing?: (isProcessing: boolean) => void;
|
||||
refreshStatic?: () => void;
|
||||
openAgentConfigDialog?: (
|
||||
name: string,
|
||||
displayName: string,
|
||||
definition: unknown,
|
||||
) => void;
|
||||
} = {},
|
||||
) => {
|
||||
const {
|
||||
builtinCommands = [],
|
||||
fileCommands = [],
|
||||
mcpCommands = [],
|
||||
setIsProcessing = vi.fn(),
|
||||
refreshStatic = vi.fn(),
|
||||
openAgentConfigDialog = vi.fn(),
|
||||
} = options;
|
||||
|
||||
mockBuiltinLoadCommands.mockResolvedValue(Object.freeze(builtinCommands));
|
||||
mockFileLoadCommands.mockResolvedValue(Object.freeze(fileCommands));
|
||||
mockMcpLoadCommands.mockResolvedValue(Object.freeze(mcpCommands));
|
||||
@@ -177,7 +194,7 @@ describe('useSlashCommandProcessor', () => {
|
||||
mockAddItem,
|
||||
mockClearItems,
|
||||
mockLoadHistory,
|
||||
vi.fn(), // refreshStatic
|
||||
refreshStatic,
|
||||
vi.fn(), // toggleVimEnabled
|
||||
setIsProcessing,
|
||||
{
|
||||
@@ -188,7 +205,7 @@ describe('useSlashCommandProcessor', () => {
|
||||
openSettingsDialog: vi.fn(),
|
||||
openSessionBrowser: vi.fn(),
|
||||
openModelDialog: mockOpenModelDialog,
|
||||
openAgentConfigDialog: vi.fn(),
|
||||
openAgentConfigDialog,
|
||||
openPermissionsDialog: vi.fn(),
|
||||
quit: mockSetQuittingMessages,
|
||||
setDebugMessage: vi.fn(),
|
||||
@@ -235,7 +252,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
context.ui.clear();
|
||||
},
|
||||
});
|
||||
const result = await setupProcessorHook([clearCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [clearCommand],
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand('/clear');
|
||||
@@ -252,7 +271,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
context.ui.clear();
|
||||
},
|
||||
});
|
||||
const result = await setupProcessorHook([clearCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [clearCommand],
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand('/clear');
|
||||
@@ -272,7 +293,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
|
||||
it('should call loadCommands and populate state after mounting', async () => {
|
||||
const testCommand = createTestCommand({ name: 'test' });
|
||||
const result = await setupProcessorHook([testCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [testCommand],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.slashCommands).toHaveLength(1);
|
||||
@@ -286,7 +309,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
|
||||
it('should provide an immutable array of commands to consumers', async () => {
|
||||
const testCommand = createTestCommand({ name: 'test' });
|
||||
const result = await setupProcessorHook([testCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [testCommand],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.slashCommands).toHaveLength(1);
|
||||
@@ -314,7 +339,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
CommandKind.FILE,
|
||||
);
|
||||
|
||||
const result = await setupProcessorHook([builtinCommand], [fileCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [builtinCommand],
|
||||
fileCommands: [fileCommand],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// The service should only return one command with the name 'override'
|
||||
@@ -364,7 +392,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = await setupProcessorHook([parentCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [parentCommand],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -398,7 +428,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
const result = await setupProcessorHook([parentCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [parentCommand],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -422,7 +454,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
|
||||
it('sets isProcessing to false if the the input is not a command', async () => {
|
||||
const setMockIsProcessing = vi.fn();
|
||||
const result = await setupProcessorHook([], [], [], setMockIsProcessing);
|
||||
const result = await setupProcessorHook({
|
||||
setIsProcessing: setMockIsProcessing,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand('imnotacommand');
|
||||
@@ -438,12 +472,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
action: vi.fn().mockRejectedValue(new Error('oh no!')),
|
||||
});
|
||||
|
||||
const result = await setupProcessorHook(
|
||||
[failCommand],
|
||||
[],
|
||||
[],
|
||||
setMockIsProcessing,
|
||||
);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [failCommand],
|
||||
setIsProcessing: setMockIsProcessing,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.slashCommands).toBeDefined());
|
||||
|
||||
@@ -462,12 +494,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
action: () => new Promise((resolve) => setTimeout(resolve, 50)),
|
||||
});
|
||||
|
||||
const result = await setupProcessorHook(
|
||||
[command],
|
||||
[],
|
||||
[],
|
||||
mockSetIsProcessing,
|
||||
);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
setIsProcessing: mockSetIsProcessing,
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
const executionPromise = act(async () => {
|
||||
@@ -509,7 +539,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
.fn()
|
||||
.mockResolvedValue({ type: 'dialog', dialog: dialogType }),
|
||||
});
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(result.current.slashCommands).toHaveLength(1),
|
||||
);
|
||||
@@ -539,48 +571,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
// Re-setup the hook with the mock action that we can inspect
|
||||
mockBuiltinLoadCommands.mockResolvedValue(Object.freeze([command]));
|
||||
mockFileLoadCommands.mockResolvedValue(Object.freeze([]));
|
||||
mockMcpLoadCommands.mockResolvedValue(Object.freeze([]));
|
||||
|
||||
let result!: { current: ReturnType<typeof useSlashCommandProcessor> };
|
||||
await act(async () => {
|
||||
const hook = renderHook(() =>
|
||||
useSlashCommandProcessor(
|
||||
mockConfig,
|
||||
mockSettings,
|
||||
mockAddItem,
|
||||
mockClearItems,
|
||||
mockLoadHistory,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
{
|
||||
openAuthDialog: vi.fn(),
|
||||
openThemeDialog: vi.fn(),
|
||||
openEditorDialog: vi.fn(),
|
||||
openPrivacyNotice: vi.fn(),
|
||||
openSettingsDialog: vi.fn(),
|
||||
openSessionBrowser: vi.fn(),
|
||||
openModelDialog: vi.fn(),
|
||||
openAgentConfigDialog: mockOpenAgentConfigDialog,
|
||||
openPermissionsDialog: vi.fn(),
|
||||
quit: vi.fn(),
|
||||
setDebugMessage: vi.fn(),
|
||||
toggleCorgiMode: vi.fn(),
|
||||
toggleDebugProfiler: vi.fn(),
|
||||
dispatchExtensionStateUpdate: vi.fn(),
|
||||
addConfirmUpdateExtensionRequest: vi.fn(),
|
||||
setText: vi.fn(),
|
||||
},
|
||||
new Map(),
|
||||
true,
|
||||
vi.fn(),
|
||||
vi.fn(),
|
||||
),
|
||||
);
|
||||
result = hook.result;
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
openAgentConfigDialog: mockOpenAgentConfigDialog,
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
@@ -614,20 +607,42 @@ describe('useSlashCommandProcessor', () => {
|
||||
clientHistory: [{ role: 'user', parts: [{ text: 'old prompt' }] }],
|
||||
}),
|
||||
});
|
||||
const result = await setupProcessorHook([command]);
|
||||
|
||||
const mockRefreshStatic = vi.fn();
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
refreshStatic: mockRefreshStatic,
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
await result.current.handleSlashCommand('/load');
|
||||
});
|
||||
|
||||
// ui.clear() is called which calls refreshStatic()
|
||||
expect(mockClearItems).toHaveBeenCalledTimes(1);
|
||||
expect(mockRefreshStatic).toHaveBeenCalledTimes(1);
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{ type: 'user', text: 'old prompt' },
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
|
||||
it('should call refreshStatic exactly once when ui.loadHistory is called', async () => {
|
||||
const mockRefreshStatic = vi.fn();
|
||||
const result = await setupProcessorHook({
|
||||
refreshStatic: mockRefreshStatic,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
result.current.commandContext.ui.loadHistory([]);
|
||||
});
|
||||
|
||||
expect(mockLoadHistory).toHaveBeenCalled();
|
||||
expect(mockRefreshStatic).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle a "quit" action', async () => {
|
||||
const quitAction = vi
|
||||
.fn()
|
||||
@@ -636,7 +651,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
name: 'exit',
|
||||
action: quitAction,
|
||||
});
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
@@ -659,7 +676,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
CommandKind.FILE,
|
||||
);
|
||||
|
||||
const result = await setupProcessorHook([], [fileCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
fileCommands: [fileCommand],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
let actionResult;
|
||||
@@ -691,7 +710,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
CommandKind.MCP_PROMPT,
|
||||
);
|
||||
|
||||
const result = await setupProcessorHook([], [], [mcpCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
mcpCommands: [mcpCommand],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
let actionResult;
|
||||
@@ -714,7 +735,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
describe('Command Parsing and Matching', () => {
|
||||
it('should be case-sensitive', async () => {
|
||||
const command = createTestCommand({ name: 'test' });
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -740,7 +763,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
description: 'a command with an alias',
|
||||
action,
|
||||
});
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -756,7 +781,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
it('should handle extra whitespace around the command', async () => {
|
||||
const action = vi.fn();
|
||||
const command = createTestCommand({ name: 'test', action });
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -769,7 +796,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
it('should handle `?` as a command prefix', async () => {
|
||||
const action = vi.fn();
|
||||
const command = createTestCommand({ name: 'help', action });
|
||||
const result = await setupProcessorHook([command]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [command],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(1));
|
||||
|
||||
await act(async () => {
|
||||
@@ -798,7 +827,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
CommandKind.FILE,
|
||||
);
|
||||
|
||||
const result = await setupProcessorHook([], [fileCommand], [mcpCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
fileCommands: [fileCommand],
|
||||
mcpCommands: [mcpCommand],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// The service should only return one command with the name 'override'
|
||||
@@ -834,7 +866,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
|
||||
// The order of commands in the final loaded array is not guaranteed,
|
||||
// so the test must work regardless of which comes first.
|
||||
const result = await setupProcessorHook([quitCommand], [exitCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [quitCommand],
|
||||
fileCommands: [exitCommand],
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.slashCommands).toHaveLength(2);
|
||||
@@ -861,7 +896,10 @@ describe('useSlashCommandProcessor', () => {
|
||||
CommandKind.FILE,
|
||||
);
|
||||
|
||||
const result = await setupProcessorHook([quitCommand], [exitCommand]);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: [quitCommand],
|
||||
fileCommands: [exitCommand],
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toHaveLength(2));
|
||||
|
||||
await act(async () => {
|
||||
@@ -957,7 +995,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
desc: 'command path when alias is used',
|
||||
},
|
||||
])('should log $desc', async ({ command, expectedLog }) => {
|
||||
const result = await setupProcessorHook(loggingTestCommands);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: loggingTestCommands,
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toBeDefined());
|
||||
|
||||
await act(async () => {
|
||||
@@ -976,7 +1016,9 @@ describe('useSlashCommandProcessor', () => {
|
||||
{ command: '/bogusbogusbogus', desc: 'bogus command' },
|
||||
{ command: '/unknown', desc: 'unknown command' },
|
||||
])('should not log for $desc', async ({ command }) => {
|
||||
const result = await setupProcessorHook(loggingTestCommands);
|
||||
const result = await setupProcessorHook({
|
||||
builtinCommands: loggingTestCommands,
|
||||
});
|
||||
await waitFor(() => expect(result.current.slashCommands).toBeDefined());
|
||||
|
||||
await act(async () => {
|
||||
|
||||
Reference in New Issue
Block a user