resolve.spec.ts 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { delimiter, join } from 'node:path'
  4. import { afterEach, describe, expect, it } from 'vitest'
  5. import { canExecute, hasLinuxChooserBinary } from '../src/probe.ts'
  6. import { resolveDirectoryPickerBackend } from '../src/resolve.ts'
  7. import type { DirectoryPickerHostFacts } from '../src/resolve.ts'
  8. /** Baseline facts that resolve to `native`; each case overrides one signal (darwin never consults `linuxChooser`). */
  9. const attended: DirectoryPickerHostFacts = {
  10. bindHost: '127.0.0.1',
  11. platform: 'darwin',
  12. env: {},
  13. linuxChooser: false,
  14. }
  15. describe('resolveDirectoryPickerBackend', () => {
  16. it('resolves native for a loopback bind on a display platform', () => {
  17. expect(resolveDirectoryPickerBackend(attended)).toBe('native')
  18. expect(resolveDirectoryPickerBackend({ ...attended, platform: 'win32' })).toBe('native')
  19. })
  20. it('resolves browse for an all-interfaces bind regardless of other signals', () => {
  21. expect(resolveDirectoryPickerBackend({ ...attended, bindHost: '0.0.0.0' })).toBe('browse')
  22. })
  23. it('resolves browse under an SSH launch (either env marker)', () => {
  24. expect(resolveDirectoryPickerBackend({ ...attended, env: { SSH_CONNECTION: '10.0.0.2 55 10.0.0.9 22' } })).toBe('browse')
  25. expect(resolveDirectoryPickerBackend({ ...attended, env: { SSH_TTY: '/dev/pts/3' } })).toBe('browse')
  26. })
  27. it('requires a display session and a chooser binary on linux', () => {
  28. const linux: DirectoryPickerHostFacts = { ...attended, platform: 'linux', linuxChooser: true }
  29. expect(resolveDirectoryPickerBackend(linux)).toBe('browse')
  30. expect(resolveDirectoryPickerBackend({ ...linux, env: { DISPLAY: ':0' } })).toBe('native')
  31. expect(resolveDirectoryPickerBackend({ ...linux, env: { WAYLAND_DISPLAY: 'wayland-1' } })).toBe('native')
  32. expect(resolveDirectoryPickerBackend({ ...linux, env: { DISPLAY: ':0' }, linuxChooser: false })).toBe('browse')
  33. })
  34. it('resolves browse on platforms the native backend cannot serve, display or not', () => {
  35. expect(resolveDirectoryPickerBackend({ ...attended, platform: 'freebsd', env: { DISPLAY: ':0' }, linuxChooser: true })).toBe('browse')
  36. expect(resolveDirectoryPickerBackend({ ...attended, platform: 'openbsd', env: { WAYLAND_DISPLAY: 'wayland-1' } })).toBe('browse')
  37. })
  38. it('treats blank env exports as unset', () => {
  39. expect(resolveDirectoryPickerBackend({ ...attended, env: { SSH_CONNECTION: '', SSH_TTY: '' } })).toBe('native')
  40. expect(resolveDirectoryPickerBackend({
  41. ...attended, platform: 'linux', linuxChooser: true, env: { DISPLAY: '', WAYLAND_DISPLAY: '' },
  42. })).toBe('browse')
  43. })
  44. })
  45. let probeRoot: string | undefined
  46. afterEach(() => {
  47. if (probeRoot !== undefined) rmSync(probeRoot, { recursive: true, force: true })
  48. probeRoot = undefined
  49. })
  50. describe('hasLinuxChooserBinary', () => {
  51. it('finds a chooser binary in any PATH segment, skipping empty segments', () => {
  52. const seen: string[] = []
  53. const path = ['', '/opt/none', '/usr/local/bin'].join(delimiter)
  54. const found = hasLinuxChooserBinary(path, (candidate) => {
  55. seen.push(candidate)
  56. return candidate === join('/usr/local/bin', 'kdialog')
  57. })
  58. expect(found).toBe(true)
  59. expect(seen).toEqual([
  60. join('/opt/none', 'zenity'), join('/opt/none', 'kdialog'),
  61. join('/usr/local/bin', 'zenity'), join('/usr/local/bin', 'kdialog'),
  62. ])
  63. })
  64. it('reports absence when no segment holds a chooser binary', () => {
  65. expect(hasLinuxChooserBinary(['/a', '/b'].join(delimiter), () => false)).toBe(false)
  66. expect(hasLinuxChooserBinary('', () => true)).toBe(false)
  67. expect(hasLinuxChooserBinary(undefined, () => true)).toBe(false)
  68. })
  69. })
  70. describe('canExecute', () => {
  71. it('accepts an executable file and rejects an absent one', () => {
  72. probeRoot = mkdtempSync(join(tmpdir(), 'dsh-picker-probe-'))
  73. const binary = join(probeRoot, 'zenity')
  74. writeFileSync(binary, '#!/bin/sh\n')
  75. chmodSync(binary, 0o755)
  76. expect(canExecute(binary)).toBe(true)
  77. expect(canExecute(join(probeRoot, 'kdialog'))).toBe(false)
  78. })
  79. })