Merge tag 'v0.3.0' into chore/sync-gemini-cli-v0.3.0

This commit is contained in:
mingholy.lmh
2025-09-10 21:01:40 +08:00
583 changed files with 30160 additions and 10770 deletions

View File

@@ -9,18 +9,12 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { useAtCompletion } from './useAtCompletion.js';
import {
Config,
FileSearch,
FileSearchFactory,
} from '@qwen-code/qwen-code-core';
import {
createTmpDir,
cleanupTmpDir,
FileSystemStructure,
} from '@qwen-code/qwen-code-test-utils';
import type { Config, FileSearch } from '@qwen-code/qwen-code-core';
import { FileSearchFactory } from '@qwen-code/qwen-code-core';
import type { FileSystemStructure } from '@qwen-code/qwen-code-test-utils';
import { createTmpDir, cleanupTmpDir } from '@qwen-code/qwen-code-test-utils';
import { useState } from 'react';
import { Suggestion } from '../components/SuggestionsDisplay.js';
import type { Suggestion } from '../components/SuggestionsDisplay.js';
// Test harness to capture the state from the hook's callbacks.
function useTestHarnessForAtCompletion(
@@ -55,6 +49,7 @@ describe('useAtCompletion', () => {
respectGeminiIgnore: true,
})),
getEnableRecursiveFileSearch: () => true,
getFileFilteringDisableFuzzySearch: () => false,
} as unknown as Config;
vi.clearAllMocks();
});
@@ -202,15 +197,18 @@ describe('useAtCompletion', () => {
cache: false,
cacheTtl: 0,
enableRecursiveFileSearch: true,
disableFuzzySearch: false,
});
await realFileSearch.initialize();
// Mock that returns results immediately but we'll control timing with fake timers
const mockFileSearch: FileSearch = {
initialize: vi.fn().mockResolvedValue(undefined),
search: vi.fn().mockImplementation(async (...args) => {
await new Promise((resolve) => setTimeout(resolve, 300));
return realFileSearch.search(...args);
}),
search: vi
.fn()
.mockImplementation(async (...args) =>
realFileSearch.search(...args),
),
};
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
@@ -220,33 +218,42 @@ describe('useAtCompletion', () => {
{ initialProps: { pattern: 'a' } },
);
// Wait for the initial (slow) search to complete
// Wait for the initial search to complete (using real timers)
await waitFor(() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([
'a.txt',
]);
});
// Now, rerender to trigger the second search
rerender({ pattern: 'b' });
// Now switch to fake timers for precise control of the loading behavior
vi.useFakeTimers();
// Wait for the loading indicator to appear
await waitFor(() => {
expect(result.current.isLoadingSuggestions).toBe(true);
// Trigger the second search
act(() => {
rerender({ pattern: 'b' });
});
// Suggestions should be cleared while loading
// Initially, loading should be false (before 200ms timer)
expect(result.current.isLoadingSuggestions).toBe(false);
// Advance time by exactly 200ms to trigger the loading state
act(() => {
vi.advanceTimersByTime(200);
});
// Now loading should be true and suggestions should be cleared
expect(result.current.isLoadingSuggestions).toBe(true);
expect(result.current.suggestions).toEqual([]);
// Wait for the final (slow) search to complete
await waitFor(
() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([
'b.txt',
]);
},
{ timeout: 1000 },
); // Increase timeout for the slow search
// Switch back to real timers for the final waitFor
vi.useRealTimers();
// Wait for the search results to be processed
await waitFor(() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([
'b.txt',
]);
});
expect(result.current.isLoadingSuggestions).toBe(false);
});
@@ -472,6 +479,7 @@ describe('useAtCompletion', () => {
respectGitIgnore: true,
respectGeminiIgnore: true,
})),
getFileFilteringDisableFuzzySearch: () => false,
} as unknown as Config;
const { result } = renderHook(() =>