| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { |
| 3 | fileBasename, |
| 4 | workspaceRelativeFilePath, |
| 5 | isExplicitRelativePath, |
| 6 | isUncPath, |
| 7 | isWindowsAbsolutePath, |
| 8 | isWindowsDrivePath, |
| 9 | newProjectFolderName, |
| 10 | normalizeProjectPathForComparison, |
| 11 | normalizeProjectPathForDispatch, |
| 12 | resolveWorkspaceFilePath, |
| 13 | } from "./path.ts"; |
| 14 | |
| 15 | describe("path helpers", () => { |
| 16 | it.each([ |
| 17 | ["report:123", "/workspace", "/workspace/report:123"], |
| 18 | ["/workspace/report:12:3", "/other", "/workspace/report:12:3"], |
| 19 | ["src/file", "C:\\Workspace\\", "C:\\Workspace\\src\\file"], |
| 20 | ["C:/Workspace/file", "/other", "C:/Workspace/file"], |
| 21 | ["file", "\\\\host\\share\\", "\\\\host\\share\\file"], |
| 22 | ["file", "/", "/file"], |
| 23 | ["~/report:123", "/workspace", "~/report:123"], |
| 24 | ])("resolves literal path %s within %s", (path, root, expected) => { |
| 25 | expect(resolveWorkspaceFilePath(path, root)).toBe(expected); |
| 26 | }); |
| 27 | |
| 28 | it("detects windows drive paths", () => { |
| 29 | expect(isWindowsDrivePath("C:\\repo")).toBe(true); |
| 30 | expect(isWindowsDrivePath("D:/repo")).toBe(true); |
| 31 | expect(isWindowsDrivePath("/repo")).toBe(false); |
| 32 | }); |
| 33 | |
| 34 | it("detects UNC paths", () => { |
| 35 | expect(isUncPath("\\\\server\\share\\repo")).toBe(true); |
| 36 | expect(isUncPath("C:\\repo")).toBe(false); |
| 37 | }); |
| 38 | |
| 39 | it("detects windows absolute paths", () => { |
| 40 | expect(isWindowsAbsolutePath("C:\\repo")).toBe(true); |
| 41 | expect(isWindowsAbsolutePath("\\\\server\\share\\repo")).toBe(true); |
| 42 | expect(isWindowsAbsolutePath("./repo")).toBe(false); |
| 43 | }); |
| 44 | |
| 45 | it("detects explicit relative paths", () => { |
| 46 | expect(isExplicitRelativePath(".")).toBe(true); |
| 47 | expect(isExplicitRelativePath("..")).toBe(true); |
| 48 | expect(isExplicitRelativePath("./repo")).toBe(true); |
| 49 | expect(isExplicitRelativePath("..\\repo")).toBe(true); |
| 50 | expect(isExplicitRelativePath("~/repo")).toBe(false); |
| 51 | }); |
| 52 | |
| 53 | it("normalizes a bare Windows drive root the same as one with a trailing separator", () => { |
| 54 | // `C:`, `C:\` and `C:/` all refer to the drive root and must compare equal. |
| 55 | expect(normalizeProjectPathForDispatch("C:")).toBe("C:\\"); |
| 56 | expect(normalizeProjectPathForComparison("C:")).toBe("c:\\"); |
| 57 | expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:\\")); |
| 58 | expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:/")); |
| 59 | // Non-root drive paths keep their trailing separator trimmed as before. |
| 60 | expect(normalizeProjectPathForDispatch("C:\\repo\\")).toBe("C:\\repo"); |
| 61 | }); |
| 62 | |
| 63 | it("names a new project's folder from any typed name", () => { |
| 64 | expect(newProjectFolderName("Pinball Stats")).toBe("pinball-stats"); |
| 65 | expect(newProjectFolderName(" Café & Crème! ")).toBe("cafe-creme"); |
| 66 | expect(newProjectFolderName("../../etc")).toBe("etc"); |
| 67 | // Nothing usable left, so the server falls back to a fixed name. |
| 68 | expect(newProjectFolderName("🎱🎱")).toBe("project"); |
| 69 | expect(newProjectFolderName(`${"a".repeat(63)} b`)).toBe("a".repeat(63)); |
| 70 | // Windows cannot make folders with device names. |
| 71 | expect(newProjectFolderName("Con")).toBe("con-project"); |
| 72 | expect(newProjectFolderName("LPT1")).toBe("lpt1-project"); |
| 73 | expect(newProjectFolderName("console")).toBe("console"); |
| 74 | }); |
| 75 | }); |
| 76 | |
| 77 | describe("fileBasename", () => { |
| 78 | it.each([ |
| 79 | ["/tmp/favicons/", "favicons"], |
| 80 | ["C:\\Users\\kelchm\\.claude\\", ".claude"], |
| 81 | ["/tmp/", "tmp"], |
| 82 | ["AGENTS.md", "AGENTS.md"], |
| 83 | ["/", "/"], |
| 84 | ])("labels %s as %s", (path, basename) => { |
| 85 | expect(fileBasename(path)).toBe(basename); |
| 86 | }); |
| 87 | }); |
| 88 | |
| 89 | describe("workspaceRelativeFilePath", () => { |
| 90 | it.each([ |
| 91 | ["/repo/project/src/main.ts", "/repo/project", "src/main.ts"], |
| 92 | ["/repo/project/src/main.ts", "/repo/project/", "src/main.ts"], |
| 93 | ["C:\\Users\\mike\\t3code\\apps\\web\\a.ts", "C:/Users/mike/t3code", "apps/web/a.ts"], |
| 94 | ["/C:/Users/mike/t3code/apps/web/a.ts", "C:/Users/mike/t3code", "apps/web/a.ts"], |
| 95 | ["/Repo/Project/src/main.ts", "/repo/project", null], |
| 96 | ["/tmp/case/project/probe.txt", "/tmp/case/Project", null], |
| 97 | ["//tmp/case/project/probe.txt", "//tmp/case/Project", null], |
| 98 | ["/tmp/case/Project/probe.txt", "/tmp/case/Project", "probe.txt"], |
| 99 | ["C:/USERS/mike/t3code/main.ts", "c:/users/MIKE/t3code", "main.ts"], |
| 100 | ["/C:/USERS/mike/t3code/main.ts", "/c:/users/MIKE/t3code", "main.ts"], |
| 101 | ["\\\\server\\share\\PROJECT\\main.ts", "\\\\Server\\Share\\Project", "main.ts"], |
| 102 | ["/tmp/repo/file.ts", "/", "tmp/repo/file.ts"], |
| 103 | ["C:/Users/MIKE/main.ts", "c:/", "Users/MIKE/main.ts"], |
| 104 | ["\\\\server\\SHARE\\file.ts", "\\\\Server\\Share\\", "file.ts"], |
| 105 | ["/tmp/repo/file.ts ", "/tmp/repo", "file.ts "], |
| 106 | ["/tmp/report.ts", "/repo/project", null], |
| 107 | ["/repo/project-two/a.ts", "/repo/project", null], |
| 108 | ["/repo/project/a.ts", undefined, null], |
| 109 | ])("relates %s to %s", (path, workspaceRoot, relativePath) => { |
| 110 | expect(workspaceRelativeFilePath(path, workspaceRoot)).toBe(relativePath); |
| 111 | }); |
| 112 | }); |