summaryrefslogtreecommitdiff
path: root/packages/core/src/utils/editor.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/utils/editor.test.ts')
-rw-r--r--packages/core/src/utils/editor.test.ts424
1 files changed, 245 insertions, 179 deletions
diff --git a/packages/core/src/utils/editor.test.ts b/packages/core/src/utils/editor.test.ts
index 8bc7a49c..84cca8b8 100644
--- a/packages/core/src/utils/editor.test.ts
+++ b/packages/core/src/utils/editor.test.ts
@@ -19,6 +19,7 @@ import {
openDiff,
allowEditorTypeInSandbox,
isEditorAvailable,
+ type EditorType,
} from './editor.js';
import { execSync, spawn } from 'child_process';
@@ -27,225 +28,290 @@ vi.mock('child_process', () => ({
spawn: vi.fn(),
}));
-describe('checkHasEditorType', () => {
+const originalPlatform = process.platform;
+
+describe('editor utils', () => {
beforeEach(() => {
vi.clearAllMocks();
- });
-
- it('should return true for vscode if "code" command exists', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
- expect(checkHasEditorType('vscode')).toBe(true);
- const expectedCommand =
- process.platform === 'win32' ? 'where.exe code.cmd' : 'command -v code';
- expect(execSync).toHaveBeenCalledWith(expectedCommand, {
- stdio: 'ignore',
+ delete process.env.SANDBOX;
+ Object.defineProperty(process, 'platform', {
+ value: originalPlatform,
+ writable: true,
});
});
- it('should return false for vscode if "code" command does not exist', () => {
- (execSync as Mock).mockImplementation(() => {
- throw new Error();
+ afterEach(() => {
+ vi.restoreAllMocks();
+ delete process.env.SANDBOX;
+ Object.defineProperty(process, 'platform', {
+ value: originalPlatform,
+ writable: true,
});
- expect(checkHasEditorType('vscode')).toBe(false);
});
- it('should return true for windsurf if "windsurf" command exists', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/windsurf'));
- expect(checkHasEditorType('windsurf')).toBe(true);
- expect(execSync).toHaveBeenCalledWith('command -v windsurf', {
- stdio: 'ignore',
- });
- });
+ describe('checkHasEditorType', () => {
+ const testCases: Array<{
+ editor: EditorType;
+ command: string;
+ win32Command: string;
+ }> = [
+ { editor: 'vscode', command: 'code', win32Command: 'code.cmd' },
+ { editor: 'windsurf', command: 'windsurf', win32Command: 'windsurf' },
+ { editor: 'cursor', command: 'cursor', win32Command: 'cursor' },
+ { editor: 'vim', command: 'vim', win32Command: 'vim' },
+ ];
- it('should return false for windsurf if "windsurf" command does not exist', () => {
- (execSync as Mock).mockImplementation(() => {
- throw new Error();
- });
- expect(checkHasEditorType('windsurf')).toBe(false);
- });
+ for (const { editor, command, win32Command } of testCases) {
+ describe(`${editor}`, () => {
+ it(`should return true if "${command}" command exists on non-windows`, () => {
+ Object.defineProperty(process, 'platform', { value: 'linux' });
+ (execSync as Mock).mockReturnValue(
+ Buffer.from(`/usr/bin/${command}`),
+ );
+ expect(checkHasEditorType(editor)).toBe(true);
+ expect(execSync).toHaveBeenCalledWith(`command -v ${command}`, {
+ stdio: 'ignore',
+ });
+ });
- it('should return true for cursor if "cursor" command exists', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/cursor'));
- expect(checkHasEditorType('cursor')).toBe(true);
- expect(execSync).toHaveBeenCalledWith('command -v cursor', {
- stdio: 'ignore',
- });
- });
+ it(`should return false if "${command}" command does not exist on non-windows`, () => {
+ Object.defineProperty(process, 'platform', { value: 'linux' });
+ (execSync as Mock).mockImplementation(() => {
+ throw new Error();
+ });
+ expect(checkHasEditorType(editor)).toBe(false);
+ });
- it('should return false for cursor if "cursor" command does not exist', () => {
- (execSync as Mock).mockImplementation(() => {
- throw new Error();
- });
- expect(checkHasEditorType('cursor')).toBe(false);
- });
+ it(`should return true if "${win32Command}" command exists on windows`, () => {
+ Object.defineProperty(process, 'platform', { value: 'win32' });
+ (execSync as Mock).mockReturnValue(
+ Buffer.from(`C:\\Program Files\\...\\${win32Command}`),
+ );
+ expect(checkHasEditorType(editor)).toBe(true);
+ expect(execSync).toHaveBeenCalledWith(`where.exe ${win32Command}`, {
+ stdio: 'ignore',
+ });
+ });
- it('should return true for vim if "vim" command exists', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/vim'));
- expect(checkHasEditorType('vim')).toBe(true);
- const expectedCommand =
- process.platform === 'win32' ? 'where.exe vim' : 'command -v vim';
- expect(execSync).toHaveBeenCalledWith(expectedCommand, {
- stdio: 'ignore',
- });
+ it(`should return false if "${win32Command}" command does not exist on windows`, () => {
+ Object.defineProperty(process, 'platform', { value: 'win32' });
+ (execSync as Mock).mockImplementation(() => {
+ throw new Error();
+ });
+ expect(checkHasEditorType(editor)).toBe(false);
+ });
+ });
+ }
});
- it('should return false for vim if "vim" command does not exist', () => {
- (execSync as Mock).mockImplementation(() => {
- throw new Error();
+ describe('getDiffCommand', () => {
+ it('should return the correct command for vscode', () => {
+ const command = getDiffCommand('old.txt', 'new.txt', 'vscode');
+ expect(command).toEqual({
+ command: 'code',
+ args: ['--wait', '--diff', 'old.txt', 'new.txt'],
+ });
});
- expect(checkHasEditorType('vim')).toBe(false);
- });
-});
-describe('getDiffCommand', () => {
- it('should return the correct command for vscode', () => {
- const command = getDiffCommand('old.txt', 'new.txt', 'vscode');
- expect(command).toEqual({
- command: 'code',
- args: ['--wait', '--diff', 'old.txt', 'new.txt'],
+ it('should return the correct command for windsurf', () => {
+ const command = getDiffCommand('old.txt', 'new.txt', 'windsurf');
+ expect(command).toEqual({
+ command: 'windsurf',
+ args: ['--wait', '--diff', 'old.txt', 'new.txt'],
+ });
});
- });
-
- it('should return the correct command for vim', () => {
- const command = getDiffCommand('old.txt', 'new.txt', 'vim');
- expect(command?.command).toBe('vim');
- expect(command?.args).toContain('old.txt');
- expect(command?.args).toContain('new.txt');
- });
-
- it('should return null for an unsupported editor', () => {
- // @ts-expect-error Testing unsupported editor
- const command = getDiffCommand('old.txt', 'new.txt', 'foobar');
- expect(command).toBeNull();
- });
-});
-describe('openDiff', () => {
- beforeEach(() => {
- vi.clearAllMocks();
- });
+ it('should return the correct command for cursor', () => {
+ const command = getDiffCommand('old.txt', 'new.txt', 'cursor');
+ expect(command).toEqual({
+ command: 'cursor',
+ args: ['--wait', '--diff', 'old.txt', 'new.txt'],
+ });
+ });
- it('should call spawn for vscode', async () => {
- const mockSpawn = {
- on: vi.fn((event, cb) => {
- if (event === 'close') {
- cb(0);
- }
- }),
- };
- (spawn as Mock).mockReturnValue(mockSpawn);
- await openDiff('old.txt', 'new.txt', 'vscode');
- expect(spawn).toHaveBeenCalledWith(
- 'code',
- ['--wait', '--diff', 'old.txt', 'new.txt'],
- { stdio: 'inherit' },
- );
- expect(mockSpawn.on).toHaveBeenCalledWith('close', expect.any(Function));
- expect(mockSpawn.on).toHaveBeenCalledWith('error', expect.any(Function));
- });
+ it('should return the correct command for vim', () => {
+ const command = getDiffCommand('old.txt', 'new.txt', 'vim');
+ expect(command).toEqual({
+ command: 'vim',
+ args: [
+ '-d',
+ '-i',
+ 'NONE',
+ '-c',
+ 'wincmd h | set readonly | wincmd l',
+ '-c',
+ 'highlight DiffAdd cterm=bold ctermbg=22 guibg=#005f00 | highlight DiffChange cterm=bold ctermbg=24 guibg=#005f87 | highlight DiffText ctermbg=21 guibg=#0000af | highlight DiffDelete ctermbg=52 guibg=#5f0000',
+ '-c',
+ 'set showtabline=2 | set tabline=[Instructions]\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)',
+ '-c',
+ 'wincmd h | setlocal statusline=OLD\\ FILE',
+ '-c',
+ 'wincmd l | setlocal statusline=%#StatusBold#NEW\\ FILE\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)',
+ '-c',
+ 'autocmd WinClosed * wqa',
+ 'old.txt',
+ 'new.txt',
+ ],
+ });
+ });
- it('should call execSync for vim', async () => {
- await openDiff('old.txt', 'new.txt', 'vim');
- expect(execSync).toHaveBeenCalled();
- const command = (execSync as Mock).mock.calls[0][0];
- expect(command).toContain('vim');
- expect(command).toContain('old.txt');
- expect(command).toContain('new.txt');
+ it('should return null for an unsupported editor', () => {
+ // @ts-expect-error Testing unsupported editor
+ const command = getDiffCommand('old.txt', 'new.txt', 'foobar');
+ expect(command).toBeNull();
+ });
});
- it('should handle spawn error for vscode', async () => {
- const mockSpawn = {
- on: vi.fn((event, cb) => {
- if (event === 'error') {
- cb(new Error('spawn error'));
- }
- }),
- };
- (spawn as Mock).mockReturnValue(mockSpawn);
- await expect(openDiff('old.txt', 'new.txt', 'vscode')).rejects.toThrow(
- 'spawn error',
- );
- });
-});
+ describe('openDiff', () => {
+ it('should call spawn for vscode', async () => {
+ const mockSpawn = {
+ on: vi.fn((event, cb) => {
+ if (event === 'close') {
+ cb(0);
+ }
+ }),
+ };
+ (spawn as Mock).mockReturnValue(mockSpawn);
+ await openDiff('old.txt', 'new.txt', 'vscode');
+ expect(spawn).toHaveBeenCalledWith(
+ 'code',
+ ['--wait', '--diff', 'old.txt', 'new.txt'],
+ { stdio: 'inherit' },
+ );
+ expect(mockSpawn.on).toHaveBeenCalledWith('close', expect.any(Function));
+ expect(mockSpawn.on).toHaveBeenCalledWith('error', expect.any(Function));
+ });
-describe('allowEditorTypeInSandbox', () => {
- afterEach(() => {
- delete process.env.SANDBOX;
- });
+ it('should reject if spawn for vscode fails', async () => {
+ const mockError = new Error('spawn error');
+ const mockSpawn = {
+ on: vi.fn((event, cb) => {
+ if (event === 'error') {
+ cb(mockError);
+ }
+ }),
+ };
+ (spawn as Mock).mockReturnValue(mockSpawn);
+ await expect(openDiff('old.txt', 'new.txt', 'vscode')).rejects.toThrow(
+ 'spawn error',
+ );
+ });
- it('should allow vim in sandbox mode', () => {
- process.env.SANDBOX = 'sandbox';
- expect(allowEditorTypeInSandbox('vim')).toBe(true);
- });
+ it('should reject if vscode exits with non-zero code', async () => {
+ const mockSpawn = {
+ on: vi.fn((event, cb) => {
+ if (event === 'close') {
+ cb(1);
+ }
+ }),
+ };
+ (spawn as Mock).mockReturnValue(mockSpawn);
+ await expect(openDiff('old.txt', 'new.txt', 'vscode')).rejects.toThrow(
+ 'VS Code exited with code 1',
+ );
+ });
- it('should allow vim when not in sandbox mode', () => {
- delete process.env.SANDBOX;
- expect(allowEditorTypeInSandbox('vim')).toBe(true);
- });
+ const execSyncEditors: EditorType[] = ['vim', 'windsurf', 'cursor'];
+ for (const editor of execSyncEditors) {
+ it(`should call execSync for ${editor} on non-windows`, async () => {
+ Object.defineProperty(process, 'platform', { value: 'linux' });
+ await openDiff('old.txt', 'new.txt', editor);
+ expect(execSync).toHaveBeenCalledTimes(1);
+ const diffCommand = getDiffCommand('old.txt', 'new.txt', editor)!;
+ const expectedCommand = `${
+ diffCommand.command
+ } ${diffCommand.args.map((arg) => `"${arg}"`).join(' ')}`;
+ expect(execSync).toHaveBeenCalledWith(expectedCommand, {
+ stdio: 'inherit',
+ encoding: 'utf8',
+ });
+ });
- it('should not allow vscode in sandbox mode', () => {
- process.env.SANDBOX = 'sandbox';
- expect(allowEditorTypeInSandbox('vscode')).toBe(false);
- });
+ it(`should call execSync for ${editor} on windows`, async () => {
+ Object.defineProperty(process, 'platform', { value: 'win32' });
+ await openDiff('old.txt', 'new.txt', editor);
+ expect(execSync).toHaveBeenCalledTimes(1);
+ const diffCommand = getDiffCommand('old.txt', 'new.txt', editor)!;
+ const expectedCommand = `${diffCommand.command} ${diffCommand.args.join(
+ ' ',
+ )}`;
+ expect(execSync).toHaveBeenCalledWith(expectedCommand, {
+ stdio: 'inherit',
+ encoding: 'utf8',
+ });
+ });
+ }
- it('should allow vscode when not in sandbox mode', () => {
- delete process.env.SANDBOX;
- expect(allowEditorTypeInSandbox('vscode')).toBe(true);
+ it('should log an error if diff command is not available', async () => {
+ const consoleErrorSpy = vi
+ .spyOn(console, 'error')
+ .mockImplementation(() => {});
+ // @ts-expect-error Testing unsupported editor
+ await openDiff('old.txt', 'new.txt', 'foobar');
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
+ 'No diff tool available. Install vim or vscode.',
+ );
+ });
});
- it('should not allow windsurf in sandbox mode', () => {
- process.env.SANDBOX = 'sandbox';
- expect(allowEditorTypeInSandbox('windsurf')).toBe(false);
- });
+ describe('allowEditorTypeInSandbox', () => {
+ it('should allow vim in sandbox mode', () => {
+ process.env.SANDBOX = 'sandbox';
+ expect(allowEditorTypeInSandbox('vim')).toBe(true);
+ });
- it('should allow windsurf when not in sandbox mode', () => {
- delete process.env.SANDBOX;
- expect(allowEditorTypeInSandbox('windsurf')).toBe(true);
- });
+ it('should allow vim when not in sandbox mode', () => {
+ expect(allowEditorTypeInSandbox('vim')).toBe(true);
+ });
- it('should not allow cursor in sandbox mode', () => {
- process.env.SANDBOX = 'sandbox';
- expect(allowEditorTypeInSandbox('cursor')).toBe(false);
- });
+ const guiEditors: EditorType[] = ['vscode', 'windsurf', 'cursor'];
+ for (const editor of guiEditors) {
+ it(`should not allow ${editor} in sandbox mode`, () => {
+ process.env.SANDBOX = 'sandbox';
+ expect(allowEditorTypeInSandbox(editor)).toBe(false);
+ });
- it('should allow cursor when not in sandbox mode', () => {
- delete process.env.SANDBOX;
- expect(allowEditorTypeInSandbox('cursor')).toBe(true);
+ it(`should allow ${editor} when not in sandbox mode`, () => {
+ expect(allowEditorTypeInSandbox(editor)).toBe(true);
+ });
+ }
});
-});
-describe('isEditorAvailable', () => {
- afterEach(() => {
- delete process.env.SANDBOX;
- });
+ describe('isEditorAvailable', () => {
+ it('should return false for undefined editor', () => {
+ expect(isEditorAvailable(undefined)).toBe(false);
+ });
- it('should return false for undefined editor', () => {
- expect(isEditorAvailable(undefined)).toBe(false);
- });
+ it('should return false for empty string editor', () => {
+ expect(isEditorAvailable('')).toBe(false);
+ });
- it('should return false for empty string editor', () => {
- expect(isEditorAvailable('')).toBe(false);
- });
+ it('should return false for invalid editor type', () => {
+ expect(isEditorAvailable('invalid-editor')).toBe(false);
+ });
- it('should return false for invalid editor type', () => {
- expect(isEditorAvailable('invalid-editor')).toBe(false);
- });
+ it('should return true for vscode when installed and not in sandbox mode', () => {
+ (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
+ expect(isEditorAvailable('vscode')).toBe(true);
+ });
- it('should return true for vscode when installed and not in sandbox mode', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
- expect(isEditorAvailable('vscode')).toBe(true);
- });
+ it('should return false for vscode when not installed and not in sandbox mode', () => {
+ (execSync as Mock).mockImplementation(() => {
+ throw new Error();
+ });
+ expect(isEditorAvailable('vscode')).toBe(false);
+ });
- it('should return false for vscode when not installed and not in sandbox mode', () => {
- (execSync as Mock).mockImplementation(() => {
- throw new Error();
+ it('should return false for vscode when installed and in sandbox mode', () => {
+ (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
+ process.env.SANDBOX = 'sandbox';
+ expect(isEditorAvailable('vscode')).toBe(false);
});
- expect(isEditorAvailable('vscode')).toBe(false);
- });
- it('should return false for vscode when installed and in sandbox mode', () => {
- (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
- process.env.SANDBOX = 'sandbox';
- expect(isEditorAvailable('vscode')).toBe(false);
+ it('should return true for vim when installed and in sandbox mode', () => {
+ (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/vim'));
+ process.env.SANDBOX = 'sandbox';
+ expect(isEditorAvailable('vim')).toBe(true);
+ });
});
});