test(cli): add unit tests for editor fallback and improve env var handling

This commit is contained in:
A.K.M. Adib
2026-05-04 15:51:56 -04:00
parent a7d49971d2
commit 5ea9c0e3c0
3 changed files with 252 additions and 1 deletions
@@ -0,0 +1,126 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { act } from 'react';
import { renderHook } from '../../../test-utils/render.js';
import { useTextBuffer } from './text-buffer.js';
import {
openFileInEditor,
EditorNotConfiguredError,
} from '../../utils/editorUtils.js';
import { coreEvents, CoreEvent } from '@google/gemini-cli-core';
import fs from 'node:fs';
vi.mock('node:fs', () => ({
default: {
mkdtempSync: vi.fn().mockReturnValue('/tmp/gemini-edit-123'),
writeFileSync: vi.fn(),
readFileSync: vi.fn().mockReturnValue('updated text'),
unlinkSync: vi.fn(),
rmdirSync: vi.fn(),
},
}));
vi.mock('node:os', () => ({
default: {
tmpdir: vi.fn().mockReturnValue('/tmp'),
},
}));
vi.mock('../../utils/editorUtils.js', () => ({
openFileInEditor: vi.fn(),
EditorNotConfiguredError: class extends Error {
constructor() {
super('No external editor configured');
this.name = 'EditorNotConfiguredError';
}
},
}));
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const original =
await importOriginal<typeof import('@google/gemini-cli-core')>();
return {
...original,
coreEvents: {
emit: vi.fn(),
emitFeedback: vi.fn(),
},
};
});
describe('useTextBuffer external editor', () => {
const viewport = { width: 80, height: 24 };
beforeEach(() => {
vi.clearAllMocks();
});
it('should emit RequestEditorSelection when openFileInEditor throws EditorNotConfiguredError', async () => {
vi.mocked(openFileInEditor).mockRejectedValue(
new EditorNotConfiguredError(),
);
const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'some text',
viewport,
}),
);
await act(async () => {
await result.current.openInExternalEditor();
});
expect(coreEvents.emit).toHaveBeenCalledWith(
CoreEvent.RequestEditorSelection,
);
});
it('should update text when openFileInEditor succeeds', async () => {
vi.mocked(openFileInEditor).mockResolvedValue(undefined);
vi.mocked(fs.readFileSync).mockReturnValue('updated text from editor');
const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'initial text',
viewport,
}),
);
await act(async () => {
await result.current.openInExternalEditor();
});
expect(result.current.text).toBe('updated text from editor');
});
it('should log feedback error for other errors', async () => {
const unexpectedError = new Error('Some unexpected error');
vi.mocked(openFileInEditor).mockRejectedValue(unexpectedError);
const { result } = await renderHook(() =>
useTextBuffer({
initialText: 'some text',
viewport,
}),
);
await act(async () => {
await result.current.openInExternalEditor();
});
expect(coreEvents.emitFeedback).toHaveBeenCalledWith(
'error',
'[useTextBuffer] external editor error',
unexpectedError,
);
expect(coreEvents.emit).not.toHaveBeenCalledWith(
CoreEvent.RequestEditorSelection,
);
});
});