summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/hooks/useInputHistory.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/cli/src/ui/hooks/useInputHistory.test.ts')
-rw-r--r--packages/cli/src/ui/hooks/useInputHistory.test.ts261
1 files changed, 261 insertions, 0 deletions
diff --git a/packages/cli/src/ui/hooks/useInputHistory.test.ts b/packages/cli/src/ui/hooks/useInputHistory.test.ts
new file mode 100644
index 00000000..8d10c376
--- /dev/null
+++ b/packages/cli/src/ui/hooks/useInputHistory.test.ts
@@ -0,0 +1,261 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { act, renderHook } from '@testing-library/react';
+import { useInputHistory } from './useInputHistory.js';
+
+describe('useInputHistory', () => {
+ const mockOnSubmit = vi.fn();
+ const mockOnChange = vi.fn();
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ const userMessages = ['message 1', 'message 2', 'message 3'];
+
+ it('should initialize with historyIndex -1 and empty originalQueryBeforeNav', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages: [],
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: '',
+ onChange: mockOnChange,
+ }),
+ );
+
+ // Internal state is not directly testable, but we can infer from behavior.
+ // Attempting to navigate down should do nothing if historyIndex is -1.
+ act(() => {
+ result.current.navigateDown();
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ describe('handleSubmit', () => {
+ it('should call onSubmit with trimmed value and reset history', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: ' test query ',
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.handleSubmit(' submit value ');
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith('submit value');
+ // Check if history is reset (e.g., by trying to navigate down)
+ act(() => {
+ result.current.navigateDown();
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ it('should not call onSubmit if value is empty after trimming', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: '',
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.handleSubmit(' ');
+ });
+
+ expect(mockOnSubmit).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('navigateUp', () => {
+ it('should not navigate if isActive is false', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: false,
+ currentQuery: 'current',
+ onChange: mockOnChange,
+ }),
+ );
+ act(() => {
+ const navigated = result.current.navigateUp();
+ expect(navigated).toBe(false);
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ it('should not navigate if userMessages is empty', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages: [],
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: 'current',
+ onChange: mockOnChange,
+ }),
+ );
+ act(() => {
+ const navigated = result.current.navigateUp();
+ expect(navigated).toBe(false);
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ it('should call onChange with the last message when navigating up from initial state', () => {
+ const currentQuery = 'current query';
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery,
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.navigateUp();
+ });
+
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[2]); // Last message
+ });
+
+ it('should store currentQuery as originalQueryBeforeNav on first navigateUp', () => {
+ const currentQuery = 'original user input';
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery,
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.navigateUp(); // historyIndex becomes 0
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[2]);
+
+ // Navigate down to restore original query
+ act(() => {
+ result.current.navigateDown(); // historyIndex becomes -1
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(currentQuery);
+ });
+
+ it('should navigate through history messages on subsequent navigateUp calls', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: '',
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.navigateUp(); // Navigates to 'message 3'
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[2]);
+
+ act(() => {
+ result.current.navigateUp(); // Navigates to 'message 2'
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[1]);
+
+ act(() => {
+ result.current.navigateUp(); // Navigates to 'message 1'
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[0]);
+ });
+ });
+
+ describe('navigateDown', () => {
+ it('should not navigate if isActive is false', () => {
+ const initialProps = {
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true, // Start active to allow setup navigation
+ currentQuery: 'current',
+ onChange: mockOnChange,
+ };
+ const { result, rerender } = renderHook(
+ (props) => useInputHistory(props),
+ {
+ initialProps,
+ },
+ );
+
+ // First navigate up to have something in history
+ act(() => {
+ result.current.navigateUp();
+ });
+ mockOnChange.mockClear(); // Clear calls from setup
+
+ // Set isActive to false for the actual test
+ rerender({ ...initialProps, isActive: false });
+
+ act(() => {
+ const navigated = result.current.navigateDown();
+ expect(navigated).toBe(false);
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ it('should not navigate if historyIndex is -1 (not in history navigation)', () => {
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: 'current',
+ onChange: mockOnChange,
+ }),
+ );
+ act(() => {
+ const navigated = result.current.navigateDown();
+ expect(navigated).toBe(false);
+ });
+ expect(mockOnChange).not.toHaveBeenCalled();
+ });
+
+ it('should restore originalQueryBeforeNav when navigating down to initial state', () => {
+ const originalQuery = 'my original input';
+ const { result } = renderHook(() =>
+ useInputHistory({
+ userMessages,
+ onSubmit: mockOnSubmit,
+ isActive: true,
+ currentQuery: originalQuery,
+ onChange: mockOnChange,
+ }),
+ );
+
+ act(() => {
+ result.current.navigateUp(); // Navigates to 'message 3', stores 'originalQuery'
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(userMessages[2]);
+ mockOnChange.mockClear();
+
+ act(() => {
+ result.current.navigateDown(); // Navigates back to original query
+ });
+ expect(mockOnChange).toHaveBeenCalledWith(originalQuery);
+ });
+ });
+});