- 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
90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
/**
|
|
* fs-tree symlink listing: the explorer must show a symlink as what it
|
|
* points at (a symlink to a directory expands like a directory) and flag
|
|
* links whose target is missing. The host listing keeps the probe cheap:
|
|
* only entries that are actually symlinks are stat'ed.
|
|
*/
|
|
import { describe, expect, it } from 'vitest'
|
|
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { listDirectory } from '../src/fs-tree.ts'
|
|
|
|
/**
|
|
* Symlink creation needs extra privileges on Windows; skip the suite there
|
|
* rather than fail (mirror of the smoke spec's platform-tolerant fixtures).
|
|
*/
|
|
const canSymlink = (() => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-sidebar-symlink-probe-'))
|
|
try {
|
|
symlinkSync('target', join(dir, 'link'))
|
|
return true
|
|
} catch {
|
|
return false
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})()
|
|
|
|
/** A scratch level with real entries plus three symlinks (dir/file/dangling). */
|
|
function makeFixture(): string {
|
|
const dir = mkdtempSync(join(tmpdir(), 'dsh-sidebar-symlink-'))
|
|
mkdirSync(join(dir, 'real-dir'))
|
|
writeFileSync(join(dir, 'real-file.txt'), 'content')
|
|
symlinkSync(join(dir, 'real-dir'), join(dir, 'link-to-dir'))
|
|
symlinkSync(join(dir, 'real-file.txt'), join(dir, 'link-to-file'))
|
|
symlinkSync(join(dir, 'missing-target'), join(dir, 'broken-link'))
|
|
return dir
|
|
}
|
|
|
|
describe.skipIf(!canSymlink)('fs-tree symlink listing', () => {
|
|
it('reports a symlink to a directory as an expandable directory', async () => {
|
|
const dir = makeFixture()
|
|
try {
|
|
const listing = await listDirectory(dir)
|
|
const row = listing.entries.find(entry => entry.name === 'link-to-dir')
|
|
expect(row).toMatchObject({ isDir: true, isSymlink: true, broken: false })
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('reports a symlink to a file as a file with a link badge', async () => {
|
|
const dir = makeFixture()
|
|
try {
|
|
const listing = await listDirectory(dir)
|
|
const row = listing.entries.find(entry => entry.name === 'link-to-file')
|
|
expect(row).toMatchObject({ isDir: false, isSymlink: true, broken: false })
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('flags a dangling symlink as broken', async () => {
|
|
const dir = makeFixture()
|
|
try {
|
|
const listing = await listDirectory(dir)
|
|
const row = listing.entries.find(entry => entry.name === 'broken-link')
|
|
expect(row).toMatchObject({ isDir: false, isSymlink: true, broken: true })
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('leaves regular entries unmarked and sorts symlinked dirs with the directories', async () => {
|
|
const dir = makeFixture()
|
|
try {
|
|
const listing = await listDirectory(dir)
|
|
const realDir = listing.entries.find(entry => entry.name === 'real-dir')
|
|
const realFile = listing.entries.find(entry => entry.name === 'real-file.txt')
|
|
expect(realDir).toMatchObject({ isDir: true, isSymlink: false, broken: false })
|
|
expect(realFile).toMatchObject({ isDir: false, isSymlink: false, broken: false })
|
|
// Directory-first ordering counts a symlinked directory as a directory.
|
|
const dirs = listing.entries.filter(entry => entry.isDir).map(entry => entry.name)
|
|
expect(dirs).toEqual(['link-to-dir', 'real-dir'])
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
})
|