fix: resolve three flaky tests (#7058)

This commit is contained in:
Arya Gummadi
2025-08-25 17:27:36 -07:00
committed by GitHub
parent 8075300e34
commit 2c6794feed
3 changed files with 69 additions and 44 deletions

View File

@@ -6,6 +6,7 @@
import type { Mock } from 'vitest'; import type { Mock } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { waitFor } from '@testing-library/react';
import { renderWithProviders } from '../test-utils/render.js'; import { renderWithProviders } from '../test-utils/render.js';
import { AppWrapper as App } from './App.js'; import { AppWrapper as App } from './App.js';
import type { import type {
@@ -399,10 +400,11 @@ describe('App UI', () => {
); );
currentUnmount = unmount; currentUnmount = unmount;
await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for any potential async operations to complete
await waitFor(() => {
expect(spawn).not.toHaveBeenCalled(); expect(spawn).not.toHaveBeenCalled();
}); });
});
it('should show a success message when update succeeds', async () => { it('should show a success message when update succeeds', async () => {
mockedIsGitRepository.mockResolvedValue(false); mockedIsGitRepository.mockResolvedValue(false);
@@ -427,12 +429,13 @@ describe('App UI', () => {
updateEventEmitter.emit('update-success', info); updateEventEmitter.emit('update-success', info);
await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for the success message to appear
await waitFor(() => {
expect(lastFrame()).toContain( expect(lastFrame()).toContain(
'Update successful! The new version will be used on your next run.', 'Update successful! The new version will be used on your next run.',
); );
}); });
});
it('should show an error message when update fails', async () => { it('should show an error message when update fails', async () => {
mockedIsGitRepository.mockResolvedValue(false); mockedIsGitRepository.mockResolvedValue(false);
@@ -457,12 +460,13 @@ describe('App UI', () => {
updateEventEmitter.emit('update-failed', info); updateEventEmitter.emit('update-failed', info);
await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for the error message to appear
await waitFor(() => {
expect(lastFrame()).toContain( expect(lastFrame()).toContain(
'Automatic update failed. Please try updating manually', 'Automatic update failed. Please try updating manually',
); );
}); });
});
it('should show an error message when spawn fails', async () => { it('should show an error message when spawn fails', async () => {
mockedIsGitRepository.mockResolvedValue(false); mockedIsGitRepository.mockResolvedValue(false);
@@ -489,12 +493,13 @@ describe('App UI', () => {
// which is what should be emitted when a spawn error occurs elsewhere. // which is what should be emitted when a spawn error occurs elsewhere.
updateEventEmitter.emit('update-failed', info); updateEventEmitter.emit('update-failed', info);
await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for the error message to appear
await waitFor(() => {
expect(lastFrame()).toContain( expect(lastFrame()).toContain(
'Automatic update failed. Please try updating manually', 'Automatic update failed. Please try updating manually',
); );
}); });
});
it('should not auto-update if GEMINI_CLI_DISABLE_AUTOUPDATER is true', async () => { it('should not auto-update if GEMINI_CLI_DISABLE_AUTOUPDATER is true', async () => {
mockedIsGitRepository.mockResolvedValue(false); mockedIsGitRepository.mockResolvedValue(false);
@@ -519,11 +524,12 @@ describe('App UI', () => {
); );
currentUnmount = unmount; currentUnmount = unmount;
await new Promise((resolve) => setTimeout(resolve, 10)); // Wait for any potential async operations to complete
await waitFor(() => {
expect(spawn).not.toHaveBeenCalled(); expect(spawn).not.toHaveBeenCalled();
}); });
}); });
});
it('should display active file when available', async () => { it('should display active file when available', async () => {
vi.mocked(ideContext.getIdeContext).mockReturnValue({ vi.mocked(ideContext.getIdeContext).mockReturnValue({

View File

@@ -5,7 +5,7 @@
*/ */
import { renderWithProviders } from '../../test-utils/render.js'; import { renderWithProviders } from '../../test-utils/render.js';
import { waitFor } from '@testing-library/react'; import { waitFor, act } from '@testing-library/react';
import type { InputPromptProps } from './InputPrompt.js'; import type { InputPromptProps } from './InputPrompt.js';
import { InputPrompt } from './InputPrompt.js'; import { InputPrompt } from './InputPrompt.js';
import type { TextBuffer } from './shared/text-buffer.js'; import type { TextBuffer } from './shared/text-buffer.js';
@@ -1412,12 +1412,20 @@ describe('InputPrompt', () => {
const { stdin, stdout, unmount } = renderWithProviders( const { stdin, stdout, unmount } = renderWithProviders(
<InputPrompt {...props} />, <InputPrompt {...props} />,
); );
// Enter reverse search mode with Ctrl+R
act(() => {
stdin.write('\x12'); stdin.write('\x12');
});
await wait(); await wait();
// Verify reverse search is active // Verify reverse search is active
expect(stdout.lastFrame()).toContain('(r:)'); expect(stdout.lastFrame()).toContain('(r:)');
// Press Tab to complete the highlighted entry
act(() => {
stdin.write('\t'); stdin.write('\t');
});
await waitFor( await waitFor(
() => { () => {

View File

@@ -201,12 +201,14 @@ describe('useAtCompletion', () => {
}); });
await realFileSearch.initialize(); await realFileSearch.initialize();
// Mock that returns results immediately but we'll control timing with fake timers
const mockFileSearch: FileSearch = { const mockFileSearch: FileSearch = {
initialize: vi.fn().mockResolvedValue(undefined), initialize: vi.fn().mockResolvedValue(undefined),
search: vi.fn().mockImplementation(async (...args) => { search: vi
await new Promise((resolve) => setTimeout(resolve, 300)); .fn()
return realFileSearch.search(...args); .mockImplementation(async (...args) =>
}), realFileSearch.search(...args),
),
}; };
vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch); vi.spyOn(FileSearchFactory, 'create').mockReturnValue(mockFileSearch);
@@ -216,33 +218,42 @@ describe('useAtCompletion', () => {
{ initialProps: { pattern: 'a' } }, { initialProps: { pattern: 'a' } },
); );
// Wait for the initial (slow) search to complete // Wait for the initial search to complete (using real timers)
await waitFor(() => { await waitFor(() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([ expect(result.current.suggestions.map((s) => s.value)).toEqual([
'a.txt', 'a.txt',
]); ]);
}); });
// Now, rerender to trigger the second search // Now switch to fake timers for precise control of the loading behavior
rerender({ pattern: 'b' }); vi.useFakeTimers();
// Wait for the loading indicator to appear // Trigger the second search
await waitFor(() => { act(() => {
expect(result.current.isLoadingSuggestions).toBe(true); 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([]); expect(result.current.suggestions).toEqual([]);
// Wait for the final (slow) search to complete // Switch back to real timers for the final waitFor
await waitFor( vi.useRealTimers();
() => {
// Wait for the search results to be processed
await waitFor(() => {
expect(result.current.suggestions.map((s) => s.value)).toEqual([ expect(result.current.suggestions.map((s) => s.value)).toEqual([
'b.txt', 'b.txt',
]); ]);
}, });
{ timeout: 1000 },
); // Increase timeout for the slow search
expect(result.current.isLoadingSuggestions).toBe(false); expect(result.current.isLoadingSuggestions).toBe(false);
}); });