mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 12:54:07 -07:00
fix(cli): fix race condition and unskip tests in useGitBranchName (#11759)
This commit is contained in:
@@ -9,7 +9,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import { act } from 'react';
|
import { act } from 'react';
|
||||||
import { renderHook, waitFor } from '@testing-library/react';
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
import { useGitBranchName } from './useGitBranchName.js';
|
import { useGitBranchName } from './useGitBranchName.js';
|
||||||
import { fs, vol } from 'memfs'; // For mocking fs
|
import { fs, vol } from 'memfs';
|
||||||
|
import * as fsPromises from 'node:fs/promises';
|
||||||
|
import path from 'node:path'; // For mocking fs
|
||||||
import { spawnAsync as mockSpawnAsync } from '@google/gemini-cli-core';
|
import { spawnAsync as mockSpawnAsync } from '@google/gemini-cli-core';
|
||||||
|
|
||||||
// Mock @google/gemini-cli-core
|
// Mock @google/gemini-cli-core
|
||||||
@@ -34,11 +36,11 @@ vi.mock('node:fs', async () => {
|
|||||||
|
|
||||||
vi.mock('node:fs/promises', async () => {
|
vi.mock('node:fs/promises', async () => {
|
||||||
const memfs = await vi.importActual<typeof import('memfs')>('memfs');
|
const memfs = await vi.importActual<typeof import('memfs')>('memfs');
|
||||||
return memfs.fs.promises;
|
return { ...memfs.fs.promises, default: memfs.fs.promises };
|
||||||
});
|
});
|
||||||
|
|
||||||
const CWD = '/test/project';
|
const CWD = '/test/project';
|
||||||
const GIT_LOGS_HEAD_PATH = `${CWD}/.git/logs/HEAD`;
|
const GIT_LOGS_HEAD_PATH = path.join(CWD, '.git', 'logs', 'HEAD');
|
||||||
|
|
||||||
describe('useGitBranchName', () => {
|
describe('useGitBranchName', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -46,12 +48,10 @@ describe('useGitBranchName', () => {
|
|||||||
vol.fromJSON({
|
vol.fromJSON({
|
||||||
[GIT_LOGS_HEAD_PATH]: 'ref: refs/heads/main',
|
[GIT_LOGS_HEAD_PATH]: 'ref: refs/heads/main',
|
||||||
});
|
});
|
||||||
vi.useFakeTimers(); // Use fake timers for async operations
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
vi.clearAllTimers();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return branch name', async () => {
|
it('should return branch name', async () => {
|
||||||
@@ -63,7 +63,6 @@ describe('useGitBranchName', () => {
|
|||||||
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers(); // Advance timers to trigger useEffect and exec callback
|
|
||||||
rerender(); // Rerender to get the updated state
|
rerender(); // Rerender to get the updated state
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +78,6 @@ describe('useGitBranchName', () => {
|
|||||||
expect(result.current).toBeUndefined();
|
expect(result.current).toBeUndefined();
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
expect(result.current).toBeUndefined();
|
expect(result.current).toBeUndefined();
|
||||||
@@ -99,7 +97,6 @@ describe('useGitBranchName', () => {
|
|||||||
|
|
||||||
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
expect(result.current).toBe('a1b2c3d');
|
expect(result.current).toBe('a1b2c3d');
|
||||||
@@ -119,20 +116,21 @@ describe('useGitBranchName', () => {
|
|||||||
|
|
||||||
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
expect(result.current).toBeUndefined();
|
expect(result.current).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update branch name when .git/HEAD changes', async ({ skip }) => {
|
it('should update branch name when .git/HEAD changes', async () => {
|
||||||
skip(); // TODO: fix
|
vi.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
|
||||||
|
const watchSpy = vi.spyOn(fs, 'watch');
|
||||||
|
|
||||||
(mockSpawnAsync as MockedFunction<typeof mockSpawnAsync>)
|
(mockSpawnAsync as MockedFunction<typeof mockSpawnAsync>)
|
||||||
.mockResolvedValueOnce({ stdout: 'main\n' } as {
|
.mockResolvedValueOnce({ stdout: 'main\n' } as {
|
||||||
stdout: string;
|
stdout: string;
|
||||||
stderr: string;
|
stderr: string;
|
||||||
})
|
})
|
||||||
.mockResolvedValueOnce({ stdout: 'develop\n' } as {
|
.mockResolvedValue({ stdout: 'develop\n' } as {
|
||||||
stdout: string;
|
stdout: string;
|
||||||
stderr: string;
|
stderr: string;
|
||||||
});
|
});
|
||||||
@@ -140,16 +138,18 @@ describe('useGitBranchName', () => {
|
|||||||
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
expect(result.current).toBe('main');
|
expect(result.current).toBe('main');
|
||||||
|
|
||||||
|
// Wait for watcher to be set up
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(watchSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
// Simulate file change event
|
// Simulate file change event
|
||||||
// Ensure the watcher is set up before triggering the change
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop'); // Trigger watcher
|
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop'); // Trigger watcher
|
||||||
vi.runAllTimers(); // Process timers for watcher and exec
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -171,7 +171,6 @@ describe('useGitBranchName', () => {
|
|||||||
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { result, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -192,7 +191,6 @@ describe('useGitBranchName', () => {
|
|||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop');
|
fs.writeFileSync(GIT_LOGS_HEAD_PATH, 'ref: refs/heads/develop');
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -200,8 +198,8 @@ describe('useGitBranchName', () => {
|
|||||||
expect(result.current).toBe('main');
|
expect(result.current).toBe('main');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cleanup watcher on unmount', async ({ skip }) => {
|
it('should cleanup watcher on unmount', async () => {
|
||||||
skip(); // TODO: fix
|
vi.spyOn(fsPromises, 'access').mockResolvedValue(undefined);
|
||||||
const closeMock = vi.fn();
|
const closeMock = vi.fn();
|
||||||
const watchMock = vi.spyOn(fs, 'watch').mockReturnValue({
|
const watchMock = vi.spyOn(fs, 'watch').mockReturnValue({
|
||||||
close: closeMock,
|
close: closeMock,
|
||||||
@@ -216,15 +214,18 @@ describe('useGitBranchName', () => {
|
|||||||
const { unmount, rerender } = renderHook(() => useGitBranchName(CWD));
|
const { unmount, rerender } = renderHook(() => useGitBranchName(CWD));
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.runAllTimers();
|
|
||||||
rerender();
|
rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wait for watcher to be set up BEFORE unmounting
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(watchMock).toHaveBeenCalledWith(
|
||||||
|
GIT_LOGS_HEAD_PATH,
|
||||||
|
expect.any(Function),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
expect(watchMock).toHaveBeenCalledWith(
|
|
||||||
GIT_LOGS_HEAD_PATH,
|
|
||||||
expect.any(Function),
|
|
||||||
);
|
|
||||||
expect(closeMock).toHaveBeenCalled();
|
expect(closeMock).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,11 +41,13 @@ export function useGitBranchName(cwd: string): string | undefined {
|
|||||||
|
|
||||||
const gitLogsHeadPath = path.join(cwd, '.git', 'logs', 'HEAD');
|
const gitLogsHeadPath = path.join(cwd, '.git', 'logs', 'HEAD');
|
||||||
let watcher: fs.FSWatcher | undefined;
|
let watcher: fs.FSWatcher | undefined;
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
const setupWatcher = async () => {
|
const setupWatcher = async () => {
|
||||||
try {
|
try {
|
||||||
// Check if .git/logs/HEAD exists, as it might not in a new repo or orphaned head
|
// Check if .git/logs/HEAD exists, as it might not in a new repo or orphaned head
|
||||||
await fsPromises.access(gitLogsHeadPath, fs.constants.F_OK);
|
await fsPromises.access(gitLogsHeadPath, fs.constants.F_OK);
|
||||||
|
if (cancelled) return;
|
||||||
watcher = fs.watch(gitLogsHeadPath, (eventType: string) => {
|
watcher = fs.watch(gitLogsHeadPath, (eventType: string) => {
|
||||||
// Changes to .git/logs/HEAD (appends) indicate HEAD has likely changed
|
// Changes to .git/logs/HEAD (appends) indicate HEAD has likely changed
|
||||||
if (eventType === 'change' || eventType === 'rename') {
|
if (eventType === 'change' || eventType === 'rename') {
|
||||||
@@ -63,6 +65,7 @@ export function useGitBranchName(cwd: string): string | undefined {
|
|||||||
setupWatcher();
|
setupWatcher();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
watcher?.close();
|
watcher?.close();
|
||||||
};
|
};
|
||||||
}, [cwd, fetchBranchName]);
|
}, [cwd, fetchBranchName]);
|
||||||
|
|||||||
Reference in New Issue
Block a user