| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /**
- * store-paths.test.ts - Comprehensive unit tests for Windows path support
- *
- * Tests all path-related utility functions for cross-platform compatibility:
- * - isAbsolutePath() - Unix, Windows (C:\, C:/), and Git Bash (/c/) paths
- * - normalizePathSeparators() - backslash to forward slash conversion
- * - getRelativePathFromPrefix() - relative path extraction
- * - resolve() - path resolution with Unix and Windows paths
- *
- * Run with: bun test store-paths.test.ts
- */
- import { describe, test, expect, beforeEach, afterEach } from "vitest";
- import {
- isAbsolutePath,
- normalizePathSeparators,
- getRelativePathFromPrefix,
- resolve,
- } from "../src/store.js";
- // =============================================================================
- // Test Utilities
- // =============================================================================
- let originalPWD: string | undefined;
- let originalProcessCwd: () => string;
- beforeEach(() => {
- // Save original environment
- originalPWD = process.env.PWD;
- originalProcessCwd = process.cwd;
- });
- afterEach(() => {
- // Restore original environment
- if (originalPWD !== undefined) {
- process.env.PWD = originalPWD;
- } else {
- delete process.env.PWD;
- }
- process.cwd = originalProcessCwd;
- });
- /**
- * Mock the current working directory for testing.
- * Sets both process.env.PWD and process.cwd() to simulate different environments.
- */
- function mockPWD(path: string): void {
- process.env.PWD = path;
- process.cwd = () => path;
- }
- // =============================================================================
- // Path Utilities - Cross-platform Support
- // =============================================================================
- describe("Path utilities - Cross-platform support", () => {
-
- // ===========================================================================
- // isAbsolutePath
- // ===========================================================================
-
- describe("isAbsolutePath", () => {
- test("Unix absolute paths", () => {
- expect(isAbsolutePath("/path/to/file")).toBe(true);
- expect(isAbsolutePath("/")).toBe(true);
- expect(isAbsolutePath("/home/user/documents")).toBe(true);
- expect(isAbsolutePath("/usr/local/bin")).toBe(true);
- });
- test("Unix relative paths", () => {
- expect(isAbsolutePath("path/to/file")).toBe(false);
- expect(isAbsolutePath("./path/to/file")).toBe(false);
- expect(isAbsolutePath("../path/to/file")).toBe(false);
- expect(isAbsolutePath("./file")).toBe(false);
- expect(isAbsolutePath("../file")).toBe(false);
- expect(isAbsolutePath("file.txt")).toBe(false);
- });
- test("Windows absolute paths (native) - forward slash", () => {
- expect(isAbsolutePath("C:/path/to/file")).toBe(true);
- expect(isAbsolutePath("C:/")).toBe(true);
- expect(isAbsolutePath("D:/Users/Documents")).toBe(true);
- expect(isAbsolutePath("Z:/")).toBe(true);
- expect(isAbsolutePath("c:/lowercase")).toBe(true);
- });
- test("Windows absolute paths (native) - backslash", () => {
- expect(isAbsolutePath("C:\\path\\to\\file")).toBe(true);
- expect(isAbsolutePath("C:\\")).toBe(true);
- expect(isAbsolutePath("D:\\Users\\Documents")).toBe(true);
- expect(isAbsolutePath("Z:\\")).toBe(true);
- expect(isAbsolutePath("c:\\lowercase")).toBe(true);
- });
- test("Windows relative paths", () => {
- expect(isAbsolutePath("path\\to\\file")).toBe(false);
- expect(isAbsolutePath(".\\path\\to\\file")).toBe(false);
- expect(isAbsolutePath("..\\path\\to\\file")).toBe(false);
- expect(isAbsolutePath(".\\file")).toBe(false);
- expect(isAbsolutePath("..\\file")).toBe(false);
- expect(isAbsolutePath("file.txt")).toBe(false);
- });
- test("Git Bash style paths", () => {
- expect(isAbsolutePath("/c/Users/name/file")).toBe(true);
- expect(isAbsolutePath("/C/Users/name/file")).toBe(true);
- expect(isAbsolutePath("/d/Projects")).toBe(true);
- expect(isAbsolutePath("/D/Projects")).toBe(true);
- expect(isAbsolutePath("/z/")).toBe(true);
- });
- test("Edge cases", () => {
- expect(isAbsolutePath("")).toBe(false);
- expect(isAbsolutePath("C:")).toBe(true); // Drive letter only
- expect(isAbsolutePath("C")).toBe(false); // Just a letter
- expect(isAbsolutePath(":")).toBe(false);
- expect(isAbsolutePath("/a")).toBe(true); // Short Unix path
- expect(isAbsolutePath("/1/")).toBe(true); // Number after slash (not Git Bash)
- });
- });
- // ===========================================================================
- // normalizePathSeparators
- // ===========================================================================
-
- describe("normalizePathSeparators", () => {
- test("Windows paths with backslashes", () => {
- expect(normalizePathSeparators("C:\\Users\\name\\file.txt"))
- .toBe("C:/Users/name/file.txt");
- expect(normalizePathSeparators("D:\\Projects\\qmd\\src"))
- .toBe("D:/Projects/qmd/src");
- expect(normalizePathSeparators("\\path\\to\\file"))
- .toBe("/path/to/file");
- });
- test("Mixed separators", () => {
- expect(normalizePathSeparators("C:\\Users/name\\file.txt"))
- .toBe("C:/Users/name/file.txt");
- expect(normalizePathSeparators("path\\to/file/here"))
- .toBe("path/to/file/here");
- });
- test("Unix paths (should remain unchanged)", () => {
- expect(normalizePathSeparators("/path/to/file"))
- .toBe("/path/to/file");
- expect(normalizePathSeparators("/usr/local/bin"))
- .toBe("/usr/local/bin");
- expect(normalizePathSeparators("relative/path"))
- .toBe("relative/path");
- });
- test("Multiple consecutive backslashes", () => {
- expect(normalizePathSeparators("path\\\\to\\\\file"))
- .toBe("path//to//file");
- expect(normalizePathSeparators("C:\\\\Users\\\\name"))
- .toBe("C://Users//name");
- });
- test("Edge cases", () => {
- expect(normalizePathSeparators("")).toBe("");
- expect(normalizePathSeparators("\\")).toBe("/");
- expect(normalizePathSeparators("\\\\")).toBe("//");
- expect(normalizePathSeparators("file.txt")).toBe("file.txt");
- });
- });
- // ===========================================================================
- // getRelativePathFromPrefix
- // ===========================================================================
-
- describe("getRelativePathFromPrefix", () => {
- test("Exact match (path equals prefix)", () => {
- expect(getRelativePathFromPrefix("/home/user", "/home/user")).toBe("");
- expect(getRelativePathFromPrefix("C:/Users/name", "C:/Users/name")).toBe("");
- expect(getRelativePathFromPrefix("/path", "/path")).toBe("");
- });
- test("Path under prefix", () => {
- expect(getRelativePathFromPrefix("/home/user/documents", "/home/user"))
- .toBe("documents");
- expect(getRelativePathFromPrefix("/home/user/documents/file.txt", "/home/user"))
- .toBe("documents/file.txt");
- expect(getRelativePathFromPrefix("C:/Users/name/Documents/file.txt", "C:/Users/name"))
- .toBe("Documents/file.txt");
- });
- test("Path not under prefix", () => {
- expect(getRelativePathFromPrefix("/home/other", "/home/user")).toBeNull();
- expect(getRelativePathFromPrefix("/usr/local", "/home/user")).toBeNull();
- expect(getRelativePathFromPrefix("C:/Users/other", "D:/Users")).toBeNull();
- });
- test("Windows paths with normalized separators", () => {
- // Backslashes should be normalized
- expect(getRelativePathFromPrefix("C:\\Users\\name\\Documents", "C:\\Users\\name"))
- .toBe("Documents");
- expect(getRelativePathFromPrefix("C:\\Users\\name\\Documents\\file.txt", "C:/Users/name"))
- .toBe("Documents/file.txt");
- });
- test("Prefix with trailing slash", () => {
- expect(getRelativePathFromPrefix("/home/user/documents", "/home/user/"))
- .toBe("documents");
- expect(getRelativePathFromPrefix("C:/Users/name/Documents", "C:/Users/name/"))
- .toBe("Documents");
- });
- test("Prefix without trailing slash", () => {
- expect(getRelativePathFromPrefix("/home/user/documents", "/home/user"))
- .toBe("documents");
- expect(getRelativePathFromPrefix("C:/Users/name/Documents", "C:/Users/name"))
- .toBe("Documents");
- });
- test("Edge cases", () => {
- // Empty prefix
- expect(getRelativePathFromPrefix("/path/to/file", "")).toBeNull();
-
- // Path is prefix substring but not in hierarchy
- expect(getRelativePathFromPrefix("/home/username", "/home/user")).toBeNull();
-
- // Root prefix
- expect(getRelativePathFromPrefix("/home/user", "/")).toBe("home/user");
- });
- });
- // ===========================================================================
- // resolve - Unix environment
- // ===========================================================================
-
- describe("resolve - Unix environment", () => {
- beforeEach(() => {
- mockPWD("/home/user");
- });
- test("Unix relative paths", () => {
- expect(resolve("/base", "relative")).toBe("/base/relative");
- expect(resolve("/base", "a/b/c")).toBe("/base/a/b/c");
- expect(resolve("/home", "user/documents")).toBe("/home/user/documents");
- });
- test("Unix absolute paths", () => {
- expect(resolve("/base", "/absolute")).toBe("/absolute");
- expect(resolve("/home/user", "/usr/local")).toBe("/usr/local");
- expect(resolve("/any", "/")).toBe("/");
- });
- test("Path with .. and .", () => {
- expect(resolve("/base", "../other")).toBe("/other");
- expect(resolve("/base/sub", "..")).toBe("/base");
- expect(resolve("/base", "./file")).toBe("/base/file");
- expect(resolve("/base/a/b", "../../c")).toBe("/base/c");
- });
- test("Multiple path segments", () => {
- expect(resolve("/a", "b", "c")).toBe("/a/b/c");
- expect(resolve("/a", "b", "../c")).toBe("/a/c");
- expect(resolve("/a", "b", "/c")).toBe("/c");
- });
- test("Relative path without base (uses PWD)", () => {
- expect(resolve("relative")).toBe("/home/user/relative");
- expect(resolve("a/b/c")).toBe("/home/user/a/b/c");
- expect(resolve("./file")).toBe("/home/user/file");
- });
- test("Absolute path alone", () => {
- expect(resolve("/absolute/path")).toBe("/absolute/path");
- expect(resolve("/")).toBe("/");
- });
- });
- // ===========================================================================
- // resolve - Windows environment
- // ===========================================================================
-
- describe("resolve - Windows environment", () => {
- beforeEach(() => {
- mockPWD("C:/Users/name");
- });
- test("Windows relative paths", () => {
- expect(resolve("C:/base", "relative")).toBe("C:/base/relative");
- expect(resolve("C:/base", "a/b/c")).toBe("C:/base/a/b/c");
- expect(resolve("D:/Projects", "qmd/src")).toBe("D:/Projects/qmd/src");
- });
- test("Windows absolute paths", () => {
- expect(resolve("C:/base", "D:/other")).toBe("D:/other");
- expect(resolve("C:/Users", "C:/Program Files")).toBe("C:/Program Files");
- expect(resolve("D:/any", "E:/other")).toBe("E:/other");
- });
- test("Windows with backslashes", () => {
- expect(resolve("C:\\base", "relative")).toBe("C:/base/relative");
- expect(resolve("C:\\Users\\name", "Documents")).toBe("C:/Users/name/Documents");
- expect(resolve("C:\\base", "a\\b\\c")).toBe("C:/base/a/b/c");
- });
- test("Path with .. and .", () => {
- expect(resolve("C:/base", "../other")).toBe("C:/other");
- expect(resolve("C:/base/sub", "..")).toBe("C:/base");
- expect(resolve("C:/base", "./file")).toBe("C:/base/file");
- expect(resolve("C:/base/a/b", "../../c")).toBe("C:/base/c");
- });
- test("Multiple path segments", () => {
- expect(resolve("C:/a", "b", "c")).toBe("C:/a/b/c");
- expect(resolve("C:/a", "b", "../c")).toBe("C:/a/c");
- expect(resolve("C:/a", "b", "D:/c")).toBe("D:/c");
- });
- test("Relative path without base (uses PWD)", () => {
- expect(resolve("relative")).toBe("C:/Users/name/relative");
- expect(resolve("a/b/c")).toBe("C:/Users/name/a/b/c");
- expect(resolve(".\\file")).toBe("C:/Users/name/file");
- });
- test("Drive letter only", () => {
- expect(resolve("C:")).toBe("C:/");
- expect(resolve("D:")).toBe("D:/");
- });
- });
- // ===========================================================================
- // resolve - Git Bash style paths
- // ===========================================================================
-
- describe("resolve - Git Bash style paths", () => {
- test("Git Bash to Windows conversion", () => {
- expect(resolve("/c/Users/name")).toBe("C:/Users/name");
- expect(resolve("/C/Users/name")).toBe("C:/Users/name");
- expect(resolve("/d/Projects")).toBe("D:/Projects");
- expect(resolve("/D/Projects")).toBe("D:/Projects");
- });
- test("Git Bash with relative paths", () => {
- expect(resolve("/c/base", "relative")).toBe("C:/base/relative");
- expect(resolve("/d/Projects", "qmd/src")).toBe("D:/Projects/qmd/src");
- });
- test("Git Bash with .. and .", () => {
- expect(resolve("/c/base", "../other")).toBe("C:/other");
- expect(resolve("/c/base/sub", "..")).toBe("C:/base");
- expect(resolve("/c/base", "./file")).toBe("C:/base/file");
- });
- test("Multiple Git Bash segments", () => {
- expect(resolve("/c/a", "b", "c")).toBe("C:/a/b/c");
- expect(resolve("/c/a", "b", "/d/c")).toBe("D:/c");
- });
- });
- // ===========================================================================
- // resolve - Edge cases and mixed scenarios
- // ===========================================================================
-
- describe("resolve - Edge cases", () => {
- test("Empty path segments are filtered", () => {
- expect(resolve("/base", "", "file")).toBe("/base/file");
- expect(resolve("C:/base", "", "file")).toBe("C:/base/file");
- });
- test("Multiple consecutive slashes", () => {
- expect(resolve("/base//path///file")).toBe("/base/path/file");
- expect(resolve("C:/base//path///file")).toBe("C:/base/path/file");
- });
- test("Trailing slashes", () => {
- expect(resolve("/base/", "file")).toBe("/base/file");
- expect(resolve("C:/base/", "file")).toBe("C:/base/file");
- });
- test("Complex .. navigation", () => {
- expect(resolve("/a/b/c/d", "../../../e")).toBe("/a/e");
- expect(resolve("C:/a/b/c/d", "../../../e")).toBe("C:/a/e");
- });
- test("Too many .. (should not go above root)", () => {
- expect(resolve("/base", "../../../../other")).toBe("/other");
- expect(resolve("C:/base", "../../../../other")).toBe("C:/other");
- });
- test("Mixed Unix and Windows (normalized)", () => {
- mockPWD("C:/Users/name");
- expect(resolve("/unix/path")).toBe("/unix/path");
- expect(resolve("relative")).toBe("C:/Users/name/relative");
- });
- test("Error on no arguments", () => {
- expect(() => resolve()).toThrow("resolve: at least one path segment is required");
- });
- });
- });
|