| 1 | // Cross-platform path identity used by chat file recognition. The flavor rules |
| 2 | // here are what keep a Windows drive path and a POSIX path with a literal |
| 3 | // backslash from collapsing into each other. |
| 4 | import assert from "node:assert/strict"; |
| 5 | import { |
| 6 | declaredPathFlavor, fileIdentity, fileURLToPath, isAbsolutePath, isUncPath, |
| 7 | isWindowsDrivePath, pathBasename, pathExtension, |
| 8 | } from "../lib/filePaths"; |
| 9 | |
| 10 | // ── Windows drive, UNC, and separators ────────────────────────────────────── |
| 11 | assert.equal(fileIdentity("C:\\Users\\me\\out.svg"), "C:/Users/me/out.svg"); |
| 12 | assert.equal(fileIdentity("c:/users/me/out.svg"), "C:/users/me/out.svg", "drive letter folds case"); |
| 13 | assert.equal(fileIdentity("C:\\Users\\me\\out.svg"), fileIdentity("c:/Users/me/out.svg")); |
| 14 | assert.equal(fileIdentity("C:\\a\\..\\b\\c.go"), "C:/b/c.go"); |
| 15 | assert.equal(fileIdentity("C:\\..\\..\\b.go"), "C:/b.go", "a drive root is never folded away"); |
| 16 | assert.equal(fileIdentity("C:\\.."), "C:/"); |
| 17 | assert.equal(fileIdentity("\\\\server\\share\\dir\\a.txt"), "//server/share/dir/a.txt"); |
| 18 | assert.equal(fileIdentity("//server/share/dir/a.txt"), "//server/share/dir/a.txt"); |
| 19 | assert.equal(fileIdentity("\\\\server\\share\\a\\..\\b.txt"), "//server/share/b.txt"); |
| 20 | assert.equal(fileIdentity("\\\\server\\share\\..\\..\\b.txt"), "//server/share/b.txt", "a UNC share root is preserved"); |
| 21 | assert(isUncPath("\\\\server\\share"), "UNC detection"); |
| 22 | assert(isUncPath("//server/share"), "UNC forward-slash detection"); |
| 23 | assert(isWindowsDrivePath("C:/x"), "forward-slash drive path detection"); |
| 24 | assert.equal(declaredPathFlavor("C:\\x"), "windows"); |
| 25 | assert.equal(declaredPathFlavor("/home/x"), "posix"); |
| 26 | assert.equal(declaredPathFlavor("src/x.ts"), undefined, "relative paths declare no flavor"); |
| 27 | |
| 28 | // ── POSIX, including the backslash rule ───────────────────────────────────── |
| 29 | assert.equal(fileIdentity("/home/me/out.svg"), "/home/me/out.svg"); |
| 30 | assert.equal(fileIdentity("/home/me/./sub/../out.svg"), "/home/me/out.svg"); |
| 31 | assert.equal(fileIdentity("/../../etc/passwd"), "/etc/passwd", ".. never escapes the POSIX root"); |
| 32 | assert.equal(fileIdentity("/tmp/a\\b.svg"), "/tmp/a\\b.svg", "a POSIX path keeps literal backslashes"); |
| 33 | assert.notEqual(fileIdentity("/tmp/a\\b.svg"), fileIdentity("/tmp/a/b.svg")); |
| 34 | assert.equal(fileIdentity("out/a.svg", "posix"), "out/a.svg"); |
| 35 | assert.equal(fileIdentity("out\\a.svg", "posix"), "out\\a.svg", "a POSIX host keeps the backslash"); |
| 36 | assert.equal(fileIdentity("out\\a.svg", "windows"), "out/a.svg"); |
| 37 | assert.equal(fileIdentity("out\\a.svg"), "out\\a.svg", "an ambiguous relative path preserves a literal backslash"); |
| 38 | assert.notEqual(fileIdentity("out\\a.svg"), fileIdentity("out/a.svg"), "the renderer never merges host-ambiguous paths"); |
| 39 | |
| 40 | // ── file:// URLs decode once, ordinary paths never decode ─────────────────── |
| 41 | assert.equal(fileURLToPath("file:///home/me/a%20b.svg"), "/home/me/a b.svg"); |
| 42 | assert.equal(fileURLToPath("file:///C:/Users/me/a.svg"), "C:/Users/me/a.svg"); |
| 43 | assert.equal(fileURLToPath("file://localhost/home/me/a.svg"), "/home/me/a.svg"); |
| 44 | assert.equal(fileURLToPath("file://server/share/a.svg"), "//server/share/a.svg", "an authority form is a UNC file URL"); |
| 45 | assert.equal(fileURLToPath("file:///home/me/a.svg?v=1"), undefined); |
| 46 | assert.equal(fileURLToPath("file:////./PhysicalDrive0"), undefined, "a device authority is never a file path"); |
| 47 | assert.equal(fileURLToPath("FILE:///home/me/a.svg"), undefined, "the scheme match is case-sensitive like the link allowlist"); |
| 48 | assert.equal(fileIdentity("file:///home/me/a%20b.svg"), "/home/me/a b.svg"); |
| 49 | assert.equal(fileIdentity("file://server/share/a.svg"), "//server/share/a.svg", "a UNC file URL keeps its share root"); |
| 50 | assert.equal(fileIdentity("/home/me/a%20b.svg"), "/home/me/a%20b.svg", "a literal %20 is not an escape"); |
| 51 | assert.notEqual(fileIdentity("/home/me/a%20b.svg"), fileIdentity("/home/me/a b.svg")); |
| 52 | assert.equal(fileIdentity(" /tmp/x.svg "), "/tmp/x.svg", "surrounding whitespace is trimmed"); |
| 53 | |
| 54 | // ── Space, CJK, parentheses, and other legal filename bytes survive ───────── |
| 55 | assert.equal(fileIdentity("/Users/me/我的 项目/图 (1).svg"), "/Users/me/我的 项目/图 (1).svg"); |
| 56 | assert.equal(fileIdentity("C:\\我的 项目\\图 (1).svg"), "C:/我的 项目/图 (1).svg"); |
| 57 | assert.equal(pathBasename("/Users/me/我的 项目/图 (1).svg"), "图 (1).svg"); |
| 58 | |
| 59 | // ── Case is preserved, so a case-sensitive volume is never merged ─────────── |
| 60 | assert.notEqual(fileIdentity("/Users/me/Out.svg"), fileIdentity("/Users/me/out.svg")); |
| 61 | assert.notEqual(fileIdentity("/home/me/Readme.md"), fileIdentity("/home/me/readme.md")); |
| 62 | |
| 63 | // ── Basename, extension, and absoluteness ─────────────────────────────────── |
| 64 | assert.equal(pathBasename("C:\\a\\b\\c.tar.gz"), "c.tar.gz"); |
| 65 | assert.equal(pathBasename("/a/b/"), "b"); |
| 66 | assert.equal(pathBasename("file:///a/b/c.svg"), "c.svg"); |
| 67 | assert.equal(pathExtension("a/b/c.TAR.GZ"), "gz"); |
| 68 | assert.equal(pathExtension("/a/.gitignore"), "", "a dotfile has no extension"); |
| 69 | assert.equal(pathExtension("a/b."), ""); |
| 70 | assert(isAbsolutePath("/home/x")); |
| 71 | assert(isAbsolutePath("C:\\x")); |
| 72 | assert(isAbsolutePath("\\\\server\\share\\x")); |
| 73 | assert(isAbsolutePath("file:///home/x")); |
| 74 | assert(!isAbsolutePath("src/x.ts")); |
| 75 | assert(!isAbsolutePath("src\\x.ts"), "a relative path is not absolute on either flavor"); |
| 76 | |
| 77 | // ── Same-file aliases and same-name ambiguity ─────────────────────────────── |
| 78 | assert.equal( |
| 79 | fileIdentity("./out/diagram.svg"), |
| 80 | fileIdentity("out/diagram.svg"), |
| 81 | "a ./ prefix names the same relative file", |
| 82 | ); |
| 83 | assert.equal( |
| 84 | fileIdentity("C:\\repo\\out\\diagram.svg"), |
| 85 | fileIdentity("c:/repo/./out/../out/diagram.svg"), |
| 86 | ); |
| 87 | assert.equal( |
| 88 | fileIdentity("/repo/out/diagram.svg"), |
| 89 | fileIdentity("/repo/out/../out/diagram.svg"), |
| 90 | ); |
| 91 | assert.notEqual(fileIdentity("out/diagram.svg"), fileIdentity("/repo/out/diagram.svg"), "a relative and an absolute spelling stay distinct until the host resolves them"); |
| 92 | assert.equal(pathBasename("/a/diagram.svg"), pathBasename("/b/diagram.svg"), "two files can share a basename"); |
| 93 | |
| 94 | console.log("file paths: three-platform identity and URL decoding passed"); |
| 95 |