Download packages/core/src/utils/editor.test.ts from Jimmi42/openCLI: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/utils/editor.test.ts
- Command line
-
hf download hf://Jimmi42/openCLI/packages/core/src/utils/editor.test.ts
-
curl -L -o editor.test.ts https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/utils/editor.test.ts
11.5 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { | |
| vi, | |
| describe, | |
| it, | |
| expect, | |
| beforeEach, | |
| afterEach, | |
| type Mock, | |
| } from 'vitest'; | |
| import { | |
| checkHasEditorType, | |
| getDiffCommand, | |
| openDiff, | |
| allowEditorTypeInSandbox, | |
| isEditorAvailable, | |
| type EditorType, | |
| } from './editor.js'; | |
| import { execSync, spawn } from 'child_process'; | |
| vi.mock('child_process', () => ({ | |
| execSync: vi.fn(), | |
| spawn: vi.fn(), | |
| })); | |
| const originalPlatform = process.platform; | |
| describe('editor utils', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| delete process.env.SANDBOX; | |
| Object.defineProperty(process, 'platform', { | |
| value: originalPlatform, | |
| writable: true, | |
| }); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| delete process.env.SANDBOX; | |
| Object.defineProperty(process, 'platform', { | |
| value: originalPlatform, | |
| writable: true, | |
| }); | |
| }); | |
| 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' }, | |
| { editor: 'zed', command: 'zed', win32Command: 'zed' }, | |
| ]; | |
| 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 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 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 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); | |
| }); | |
| }); | |
| } | |
| }); | |
| describe('getDiffCommand', () => { | |
| const guiEditors: 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: 'zed', command: 'zed', win32Command: 'zed' }, | |
| ]; | |
| for (const { editor, command, win32Command } of guiEditors) { | |
| it(`should return the correct command for ${editor} on non-windows`, () => { | |
| Object.defineProperty(process, 'platform', { value: 'linux' }); | |
| const diffCommand = getDiffCommand('old.txt', 'new.txt', editor); | |
| expect(diffCommand).toEqual({ | |
| command, | |
| args: ['--wait', '--diff', 'old.txt', 'new.txt'], | |
| }); | |
| }); | |
| it(`should return the correct command for ${editor} on windows`, () => { | |
| Object.defineProperty(process, 'platform', { value: 'win32' }); | |
| const diffCommand = getDiffCommand('old.txt', 'new.txt', editor); | |
| expect(diffCommand).toEqual({ | |
| command: win32Command, | |
| 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).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 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', () => { | |
| const spawnEditors: EditorType[] = ['vscode', 'windsurf', 'cursor', 'zed']; | |
| for (const editor of spawnEditors) { | |
| it(`should call spawn for ${editor}`, async () => { | |
| const mockSpawn = { | |
| on: vi.fn((event, cb) => { | |
| if (event === 'close') { | |
| cb(0); | |
| } | |
| }), | |
| }; | |
| (spawn as Mock).mockReturnValue(mockSpawn); | |
| await openDiff('old.txt', 'new.txt', editor); | |
| const diffCommand = getDiffCommand('old.txt', 'new.txt', editor)!; | |
| expect(spawn).toHaveBeenCalledWith( | |
| diffCommand.command, | |
| diffCommand.args, | |
| { | |
| stdio: 'inherit', | |
| shell: true, | |
| }, | |
| ); | |
| expect(mockSpawn.on).toHaveBeenCalledWith( | |
| 'close', | |
| expect.any(Function), | |
| ); | |
| expect(mockSpawn.on).toHaveBeenCalledWith( | |
| 'error', | |
| expect.any(Function), | |
| ); | |
| }); | |
| it(`should reject if spawn for ${editor} 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', editor)).rejects.toThrow( | |
| 'spawn error', | |
| ); | |
| }); | |
| it(`should reject if ${editor} 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', editor)).rejects.toThrow( | |
| `${editor} exited with code 1`, | |
| ); | |
| }); | |
| } | |
| const execSyncEditors: EditorType[] = ['vim']; | |
| 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 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 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 a supported editor.', | |
| ); | |
| }); | |
| }); | |
| describe('allowEditorTypeInSandbox', () => { | |
| it('should allow vim in sandbox mode', () => { | |
| process.env.SANDBOX = 'sandbox'; | |
| expect(allowEditorTypeInSandbox('vim')).toBe(true); | |
| }); | |
| it('should allow vim when not in sandbox mode', () => { | |
| expect(allowEditorTypeInSandbox('vim')).toBe(true); | |
| }); | |
| const guiEditors: EditorType[] = ['vscode', 'windsurf', 'cursor', 'zed']; | |
| 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 ${editor} when not in sandbox mode`, () => { | |
| expect(allowEditorTypeInSandbox(editor)).toBe(true); | |
| }); | |
| } | |
| }); | |
| describe('isEditorAvailable', () => { | |
| 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 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 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 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); | |
| }); | |
| }); | |
| }); | |