Files
apes-sidebar/tests/paths.spec.ts
T
Menghuan1918 db189e3852 feat: 统一路径处理——UNC/软链接分类/html 路由平台守卫(#65 #67 #43 #79 #115)
- fs-tree: requireAbsolute 改用 path.isAbsolute(win32 UNC 放行、C:foo 仍拒);
  目录列表仅对 symlink 行 stat 一次,暴露 isSymlink/broken,目录软链接可展开
- html-route: UNC 路径经 '//' 标记编码往返;标记仅在 Windows 风格会话启用,
  POSIX '//' 绝对路径不再被误判(平台守卫)
- client: isAbsolutePath/isWindowsStylePath 镜像 + resolveSidebarPath 识别 UNC,
  htmlUrl 以会话 cwd 形态传递平台信号;FsEntry 增加 isSymlink/broken
- explorer: 软链接行链接徽标、悬空链接标红 + tooltip(issue #65)
- 测试: tests/platform.ts 守卫助手;win32 专属用例改为 skipIf 可见跳过,
  补 POSIX 反向分支与 UNC 矩阵;新增 fs-tree-symlink.spec
2026-08-16 23:24:43 +08:00

90 lines
4.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { isAbsolutePath, isWindowsStylePath, relativeTo } from '../src/client/paths.ts'
import { resolveSidebarPath } from '../src/client/produced-files.ts'
import { htmlUrl } from '../src/client/api.ts'
describe('path helpers', () => {
it('derives relative paths under the cwd (and "." for the cwd itself)', () => {
expect(relativeTo('/Users/me/code', '/Users/me/code/src/main.ts')).toBe('src/main.ts')
expect(relativeTo('/Users/me/code', '/Users/me/code')).toBe('.')
expect(relativeTo('/Users/me/code/', '/Users/me/code/src/a/b.ts')).toBe('src/a/b.ts')
})
it('falls back to the path unchanged when it lies outside the cwd', () => {
expect(relativeTo('/Users/me/code', '/Users/other/x.ts')).toBe('/Users/other/x.ts')
expect(relativeTo('/Users/me/code', '/Users/me/codex/y.ts')).toBe('/Users/me/codex/y.ts')
})
it('handles windows roots and mixed separators', () => {
expect(relativeTo('C:\\Users\\me', 'C:\\Users\\me\\src\\a.ts')).toBe('src/a.ts')
expect(relativeTo('C:\\Users\\me', 'C:/Users/me/src/a.ts')).toBe('src/a.ts')
expect(relativeTo('C:\\Users\\me\\', 'C:\\Users\\me')).toBe('.')
})
it('containment is case-insensitive (windows/macOS case-insensitive volumes)', () => {
expect(relativeTo('C:\\Users\\Me', 'c:/users/me/src/a.ts')).toBe('src/a.ts')
expect(relativeTo('/Users/Me/code', '/users/me/code/src/main.ts')).toBe('src/main.ts')
// The returned relative text keeps the caller's own casing.
expect(relativeTo('C:\\Users\\me', 'C:\\Users\\Me\\SRC\\a.ts')).toBe('SRC/a.ts')
})
it('resolves produced paths against windows cwds', () => {
expect(resolveSidebarPath('C:\\work\\proj', 'src/a.ts')).toBe('C:\\work\\proj\\src/a.ts')
expect(resolveSidebarPath('C:\\work\\proj', 'C:\\abs\\x.ts')).toBe('C:\\abs\\x.ts')
expect(resolveSidebarPath('C:\\work\\proj\\', 'C:\\abs\\x.ts')).toBe('C:\\abs\\x.ts')
})
it('keeps UNC produced paths absolute instead of joining them onto the cwd', () => {
// Pure client function: UNC detection is platform-independent, so these
// assertions run on every host without a platform guard.
expect(resolveSidebarPath('C:\\work\\proj', '\\\\server\\share\\abs\\x.ts'))
.toBe('\\\\server\\share\\abs\\x.ts')
expect(resolveSidebarPath('C:\\work\\proj', '//server/share/abs/x.ts'))
.toBe('//server/share/abs/x.ts')
// A relative path under a UNC cwd joins with backslashes.
expect(resolveSidebarPath('\\\\server\\share\\proj', 'src/a.ts'))
.toBe('\\\\server\\share\\proj\\src/a.ts')
})
it('mirrors the host absolute-path notion without node:path', () => {
expect(isAbsolutePath('/abs/x.ts')).toBe(true)
expect(isAbsolutePath('C:\\abs\\x.ts')).toBe(true)
expect(isAbsolutePath('C:/abs/x.ts')).toBe(true)
expect(isAbsolutePath('\\\\server\\share\\x.ts')).toBe(true)
expect(isAbsolutePath('//server/share/x.ts')).toBe(true)
expect(isAbsolutePath('C:relative.ts')).toBe(false)
expect(isAbsolutePath('rel/x.ts')).toBe(false)
})
it('detects windows-style session paths (platform signal for the html route)', () => {
expect(isWindowsStylePath('C:\\work\\proj')).toBe(true)
expect(isWindowsStylePath('C:/work/proj')).toBe(true)
expect(isWindowsStylePath('\\\\server\\share\\proj')).toBe(true)
expect(isWindowsStylePath('//server/share/proj')).toBe(false) // ambiguous form needs the drive/backslash signal
expect(isWindowsStylePath('/Users/me/code')).toBe(false)
})
it('htmlUrl marks UNC paths from the file path itself (cwd-independent signal)', () => {
// cwd not hydrated yet: the unambiguous backslash UNC path must still
// keep its '//' marker, or win32 would decode /server/... against the
// current drive and the isWithin fence would reject the preview (403).
expect(htmlUrl({ sessionId: 's' }, '\\\\server\\share\\proj\\a.html'))
.toBe('/sidebar/html/s//server/share/proj/a.html')
// A forward-slash-form cwd (still Windows) does not mask the backslash path.
expect(htmlUrl({ sessionId: 's', cwd: '//server/share/proj' }, '\\\\server\\share\\proj\\a.html'))
.toBe('/sidebar/html/s//server/share/proj/a.html')
// Both cwd and path in the forward-slash UNC form: the '//' path matches
// the '//' cwd, so it is UNC and keeps the marker (win32 round-trips).
expect(htmlUrl({ sessionId: 's', cwd: '//server/share/proj' }, '//server/share/proj/a.html'))
.toBe('/sidebar/html/s//server/share/proj/a.html')
// A Windows drive cwd marks forward-slash UNC paths.
expect(htmlUrl({ sessionId: 's', cwd: 'C:\\work' }, '//server/share/proj/a.html'))
.toBe('/sidebar/html/s//server/share/proj/a.html')
// A POSIX session keeps forward-slash paths marker-free.
expect(htmlUrl({ sessionId: 's', cwd: '/home/me' }, '//server/share/a.html'))
.toBe('/sidebar/html/s/server/share/a.html')
expect(htmlUrl({ sessionId: 's', cwd: '/home/me' }, '/home/me/index.html'))
.toBe('/sidebar/html/s/home/me/index.html')
})
})