mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 17:27:54 +00:00
Sync upstream Gemini-CLI v0.8.2 (#838)
This commit is contained in:
301
packages/cli/src/ui/hooks/useInputHistoryStore.test.ts
Normal file
301
packages/cli/src/ui/hooks/useInputHistoryStore.test.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useInputHistoryStore } from './useInputHistoryStore.js';
|
||||
|
||||
describe('useInputHistoryStore', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize with empty input history', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
});
|
||||
|
||||
it('should add input to history', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('test message 1');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual(['test message 1']);
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('test message 2');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([
|
||||
'test message 1',
|
||||
'test message 2',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not add empty or whitespace-only inputs', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
|
||||
act(() => {
|
||||
result.current.addInput(' ');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
});
|
||||
|
||||
it('should deduplicate consecutive identical messages', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('test message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('test message'); // Same as previous
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual(['test message']);
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('different message');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('test message'); // Same as first, but not consecutive
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([
|
||||
'test message',
|
||||
'different message',
|
||||
'test message',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should initialize from logger successfully', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockResolvedValue(['newest', 'middle', 'oldest']),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Should reverse the order to oldest first
|
||||
expect(result.current.inputHistory).toEqual(['oldest', 'middle', 'newest']);
|
||||
expect(mockLogger.getPreviousUserMessages).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should handle logger initialization failure gracefully', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Logger error')),
|
||||
};
|
||||
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Failed to initialize input history from logger:',
|
||||
expect.any(Error),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should initialize only once', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockResolvedValue(['message1', 'message2']),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
// Call initializeFromLogger twice
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Should be called only once
|
||||
expect(mockLogger.getPreviousUserMessages).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.inputHistory).toEqual(['message2', 'message1']);
|
||||
});
|
||||
|
||||
it('should handle null logger gracefully', async () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(null);
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual([]);
|
||||
});
|
||||
|
||||
it('should trim input before adding to history', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
act(() => {
|
||||
result.current.addInput(' test message ');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual(['test message']);
|
||||
});
|
||||
|
||||
describe('deduplication logic from previous implementation', () => {
|
||||
it('should deduplicate consecutive messages from past sessions during initialization', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockResolvedValue([
|
||||
'message1',
|
||||
'message1',
|
||||
'message2',
|
||||
'message2',
|
||||
'message3',
|
||||
]), // newest first with duplicates
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Should deduplicate consecutive messages and reverse to oldest first
|
||||
expect(result.current.inputHistory).toEqual([
|
||||
'message3',
|
||||
'message2',
|
||||
'message1',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should deduplicate across session boundaries', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi.fn().mockResolvedValue(['old2', 'old1']), // newest first
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
// Initialize with past session
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Add current session inputs
|
||||
act(() => {
|
||||
result.current.addInput('old2'); // Same as last past session message
|
||||
});
|
||||
|
||||
// Should deduplicate across session boundary
|
||||
expect(result.current.inputHistory).toEqual(['old1', 'old2']);
|
||||
|
||||
act(() => {
|
||||
result.current.addInput('new1');
|
||||
});
|
||||
|
||||
expect(result.current.inputHistory).toEqual(['old1', 'old2', 'new1']);
|
||||
});
|
||||
|
||||
it('should preserve non-consecutive duplicates', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockResolvedValue(['message2', 'message1', 'message2']), // newest first with non-consecutive duplicate
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Non-consecutive duplicates should be preserved
|
||||
expect(result.current.inputHistory).toEqual([
|
||||
'message2',
|
||||
'message1',
|
||||
'message2',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle complex deduplication with current session', () => {
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
// Add multiple messages with duplicates
|
||||
act(() => {
|
||||
result.current.addInput('hello');
|
||||
});
|
||||
act(() => {
|
||||
result.current.addInput('hello'); // consecutive duplicate
|
||||
});
|
||||
act(() => {
|
||||
result.current.addInput('world');
|
||||
});
|
||||
act(() => {
|
||||
result.current.addInput('world'); // consecutive duplicate
|
||||
});
|
||||
act(() => {
|
||||
result.current.addInput('hello'); // non-consecutive duplicate
|
||||
});
|
||||
|
||||
// Should have deduplicated consecutive ones
|
||||
expect(result.current.inputHistory).toEqual(['hello', 'world', 'hello']);
|
||||
});
|
||||
|
||||
it('should maintain oldest-first order in final output', async () => {
|
||||
const mockLogger = {
|
||||
getPreviousUserMessages: vi
|
||||
.fn()
|
||||
.mockResolvedValue(['newest', 'middle', 'oldest']), // newest first
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useInputHistoryStore());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.initializeFromLogger(mockLogger);
|
||||
});
|
||||
|
||||
// Add current session messages
|
||||
act(() => {
|
||||
result.current.addInput('current1');
|
||||
});
|
||||
act(() => {
|
||||
result.current.addInput('current2');
|
||||
});
|
||||
|
||||
// Should maintain oldest-first order
|
||||
expect(result.current.inputHistory).toEqual([
|
||||
'oldest',
|
||||
'middle',
|
||||
'newest',
|
||||
'current1',
|
||||
'current2',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user