| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- type ExecFileCallback = (
- error: (Error & { code?: string | number }) | null,
- stdout: string,
- stderr: string,
- ) => void
- type ExecFileMock = (
- command: string,
- args: readonly string[],
- options: { encoding: string; signal: AbortSignal; windowsHide: boolean },
- callback: ExecFileCallback,
- ) => void
- const { execFileMock } = vi.hoisted(() => ({ execFileMock: vi.fn<ExecFileMock>() }))
- vi.mock('node:child_process', () => ({ execFile: execFileMock }))
- import { describe, expect, it, vi } from 'vitest'
- import { openNativePath, openNativeTextFile, type PathOpenerRunner } from '../src/native-path-opener.ts'
- const signal = () => new AbortController().signal
- describe('native path opener', () => {
- it('opens with macOS open(1)', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativePath('/Users/test/file.txt', signal(), { platform: 'darwin', run })
- expect(run).toHaveBeenCalledWith('open', ['/Users/test/file.txt'], expect.any(AbortSignal))
- })
- it('bypasses macOS file associations for text documents', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativeTextFile('/Users/test/settings.yaml', signal(), { platform: 'darwin', run })
- expect(run).toHaveBeenCalledWith('open', ['-t', '/Users/test/settings.yaml'], expect.any(AbortSignal))
- })
- it('uses the Linux desktop association for text documents', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativeTextFile('/tmp/settings.yaml', signal(), { platform: 'linux', run })
- expect(run).toHaveBeenCalledWith('xdg-open', ['/tmp/settings.yaml'], expect.any(AbortSignal))
- })
- it('opens with Windows Invoke-Item and escapes single quotes', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativePath("C:\\work\\o'reilly.txt", signal(), { platform: 'win32', run })
- expect(run).toHaveBeenCalledWith(
- 'powershell.exe',
- ['-NoProfile', '-Command', "Invoke-Item -LiteralPath 'C:\\work\\o''reilly.txt'"],
- expect.any(AbortSignal),
- )
- })
- it('uses the Windows desktop association for text documents', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativeTextFile('C:\\work\\settings.yaml', signal(), { platform: 'win32', run })
- expect(run).toHaveBeenCalledWith(
- 'powershell.exe',
- ['-NoProfile', '-Command', "Invoke-Item -LiteralPath 'C:\\work\\settings.yaml'"],
- expect.any(AbortSignal),
- )
- })
- it('opens with Linux xdg-open', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativePath('/tmp/a.txt', signal(), { platform: 'linux', run })
- expect(run).toHaveBeenCalledWith('xdg-open', ['/tmp/a.txt'], expect.any(AbortSignal))
- })
- it('rejects unsupported platforms', async () => {
- await expect(openNativePath('/x', signal(), { platform: 'freebsd' as NodeJS.Platform }))
- .rejects.toThrow('unsupported on freebsd')
- })
- it('uses the current process platform when no platform override is supplied', async () => {
- const run = vi.fn<PathOpenerRunner>(async () => ({ stdout: '', stderr: '' }))
- await openNativePath('/tmp/platform-default.txt', signal(), { run })
- const expected = process.platform === 'win32'
- ? 'powershell.exe'
- : process.platform === 'linux'
- ? 'xdg-open'
- : 'open'
- expect(run.mock.calls[0]?.[0]).toBe(expected)
- })
- it('runs the default command adapter without a shell and preserves command failures', async () => {
- execFileMock.mockImplementationOnce((_command, _args, _options, callback) => {
- callback(null, '', '')
- })
- await openNativePath('/tmp/default.txt', signal(), { platform: 'darwin' })
- const [command, args, options] = execFileMock.mock.calls[0]!
- expect(command).toBe('open')
- expect(args).toEqual(['/tmp/default.txt'])
- expect(options.encoding).toBe('utf8')
- expect(options.windowsHide).toBe(true)
- expect(options.signal).toBeInstanceOf(AbortSignal)
- const commandError = Object.assign(new Error('open failed'), { code: 1 })
- execFileMock.mockImplementationOnce((_command, _args, _options, callback) => {
- callback(commandError, 'partial output', 'failure details')
- })
- await expect(openNativePath('/tmp/missing.txt', signal(), { platform: 'darwin' })).rejects.toMatchObject({
- message: 'open failed', cause: commandError, code: 1,
- stdout: 'partial output', stderr: 'failure details',
- })
- })
- })
|