paths.spec.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. /** Display, durable, canonical, and temporary path rules. */
  2. import { mkdir, realpath, symlink } from 'node:fs/promises'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { afterEach, describe, expect, it } from 'vitest'
  6. import { canonicalPath, compareDisplay, displayPathOf, durablePathOf, isTemporaryPath, temporaryRoots } from '../src/paths.ts'
  7. import { scratchDir } from './support.ts'
  8. const cleanups: Array<() => Promise<unknown>> = []
  9. afterEach(async () => {
  10. for (const cleanup of cleanups.reverse()) await cleanup()
  11. cleanups.length = 0
  12. })
  13. const cwd = '/home/u/proj/pkg'
  14. const root = '/home/u/proj'
  15. const home = '/home/u'
  16. describe('displayPathOf', () => {
  17. it('prefers cwd-relative, then repository-relative, then home, then absolute', () => {
  18. expect(displayPathOf('/home/u/proj/pkg/src/a.ts', cwd, root, home)).toBe('src/a.ts')
  19. expect(displayPathOf('/home/u/proj/other/b.ts', cwd, root, home)).toBe('../other/b.ts')
  20. expect(displayPathOf('/home/u/.zshrc', cwd, root, home)).toBe('~/.zshrc')
  21. expect(displayPathOf('/etc/hosts', cwd, root, home)).toBe('/etc/hosts')
  22. expect(displayPathOf('/home/u/.zshrc', cwd, root, '')).toBe('/home/u/.zshrc')
  23. })
  24. })
  25. describe('durablePathOf', () => {
  26. it('keeps cwd-relative paths relative and everything else absolute', () => {
  27. expect(durablePathOf('/home/u/proj/pkg/a.ts', cwd)).toBe('a.ts')
  28. expect(durablePathOf('/home/u/proj/b.ts', cwd)).toBe('/home/u/proj/b.ts')
  29. })
  30. })
  31. describe('temporary paths', () => {
  32. it('matches the platform temp roots in raw and canonical form and keeps missing candidates lexical', async () => {
  33. const roots = await temporaryRoots()
  34. expect(roots).toContain('/tmp')
  35. expect(isTemporaryPath(join(tmpdir(), 'scratch.txt'), roots)).toBe(true)
  36. expect(isTemporaryPath('/tmp/x', roots)).toBe(true)
  37. expect(isTemporaryPath('/tmpfoo/x', roots)).toBe(false)
  38. expect(isTemporaryPath('/home/u/x', roots)).toBe(false)
  39. expect(await temporaryRoots(['/definitely/missing/root'])).toEqual(['/definitely/missing/root'])
  40. })
  41. })
  42. describe('canonicalPath', () => {
  43. it('resolves a missing file through the nearest existing ancestor, so its spelling is stable before and after creation', async () => {
  44. const root = await scratchDir('dsh-canonical-', cleanups)
  45. const real = join(root, 'real')
  46. await mkdir(join(real, 'nested'), { recursive: true })
  47. await symlink(real, join(root, 'link'))
  48. const resolvedReal = await realpath(real)
  49. expect(await canonicalPath(join(root, 'link', 'nested'))).toBe(join(resolvedReal, 'nested'))
  50. expect(await canonicalPath(join(root, 'link', 'nested', 'new.txt'))).toBe(join(resolvedReal, 'nested', 'new.txt'))
  51. expect(await canonicalPath(join(root, 'link', 'missing', 'deeper', 'new.txt'))).toBe(join(resolvedReal, 'missing', 'deeper', 'new.txt'))
  52. // Nothing but the root exists above this path, so its spelling is kept as given on every platform.
  53. expect(await canonicalPath('/definitely/missing/root/file')).toBe('/definitely/missing/root/file')
  54. expect(await canonicalPath(join(root, 'link', 'a'))).toBe(join(resolvedReal, 'a'))
  55. })
  56. })
  57. describe('compareDisplay', () => {
  58. it('orders by code units so parent and absolute paths lead', () => {
  59. const sorted = [{ display: 'src/b' }, { display: '~/x' }, { display: '../a' }, { display: '/etc/h' }, { display: 'src/a' }].sort(compareDisplay)
  60. expect(sorted.map(file => file.display)).toEqual(['../a', '/etc/h', 'src/a', 'src/b', '~/x'])
  61. expect(compareDisplay({ display: 'a' }, { display: 'a' })).toBe(0)
  62. })
  63. })