import * as path from "path"; import * as os from "os"; jest.mock("fs"); import * as fs from "fs"; import { findWorkspace, getWorkspaceState, syncWorkspaceState, } from "../src/workspace-detector"; const mockExistsSync = fs.existsSync as jest.Mock; function buildDeepPath(depth: number): string { // Build /a/b/c/.../z with `depth` segments below root const segments = Array.from({ length: depth }, (_, i) => String.fromCharCode(97 + (i % 26)) + i); return path.join("/", ...segments); } beforeEach(() => { jest.clearAllMocks(); mockExistsSync.mockReturnValue(false); // Reset the module-level _cached singleton by forcing a sync with an inactive path syncWorkspaceState("/tmp/reset-cache"); // Clear calls accumulated by the reset sync so tests start with a clean history mockExistsSync.mockClear(); }); // ─── findWorkspace() ────────────────────────────────────────────────────────── describe("findWorkspace()", () => { it("returns active when .pisces exists in startCwd", () => { mockExistsSync.mockImplementation((p: string) => p === "/workspace/project/.pisces"); const result = findWorkspace("/workspace/project"); expect(result).toEqual({ isActive: true, root: "/workspace/project", ageGroup: null }); }); it("returns active when .pisces found two levels up", () => { mockExistsSync.mockImplementation( (p: string) => p === "/workspace/.pisces" ); const result = findWorkspace("/workspace/project/src"); expect(result).toEqual({ isActive: true, root: "/workspace", ageGroup: null }); }); it("returns inactive when no .pisces found anywhere", () => { mockExistsSync.mockReturnValue(false); const result = findWorkspace("/workspace/project"); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); }); it("stops walking at HOME and does not traverse above it", () => { const home = os.homedir(); const projectDir = path.join(home, "workspace", "project"); // .pisces is above HOME — must never be found mockExistsSync.mockReturnValue(false); const result = findWorkspace(projectDir); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); // Ensure existsSync was never called with a path above HOME const checkedPaths: string[] = mockExistsSync.mock.calls.map((c) => c[0] as string); for (const p of checkedPaths) { expect(p.startsWith(home)).toBe(true); } }); it("stops at filesystem root when parent === dir", () => { mockExistsSync.mockReturnValue(false); // On POSIX, path.dirname("/") === "/" (parent === dir) const result = findWorkspace("/deeply/nested/path"); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); // Should have walked up to root without hanging expect(mockExistsSync).toHaveBeenCalled(); }); it("respects the 15-level depth limit", () => { // Build a path 16 segments deep so without the limit it would walk 16 times const deepPath = buildDeepPath(16); mockExistsSync.mockReturnValue(false); findWorkspace(deepPath); // existsSync is called once per level; we should see at most 15 calls // (levels 0..14 inclusive) expect(mockExistsSync.mock.calls.length).toBeLessThanOrEqual(15); }); it("returns the ancestor directory as root, not the child where search started", () => { mockExistsSync.mockImplementation( (p: string) => p === "/workspace/.pisces" ); const result = findWorkspace("/workspace/project/src/lib"); expect(result.root).toBe("/workspace"); }); }); // ─── getWorkspaceState() ────────────────────────────────────────────────────── describe("getWorkspaceState()", () => { it("lazily initialises from process.cwd() on first call after reset", () => { // After reset via syncWorkspaceState("/tmp/reset-cache"), the cache is // { isActive: false, root: null }. A subsequent getWorkspaceState() returns // that cached value without re-evaluating from process.cwd(). const result = getWorkspaceState(); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); }); it("returns cached result without re-reading the filesystem", () => { // Seed the cache with a known state mockExistsSync.mockImplementation( (p: string) => p === "/workspace/.pisces" ); syncWorkspaceState("/workspace/project"); mockExistsSync.mockClear(); // Repeated reads must not call existsSync again getWorkspaceState(); getWorkspaceState(); expect(mockExistsSync).not.toHaveBeenCalled(); }); it("lazy init uses process.cwd() when _cached is null", () => { // Use jest.isolateModules to get a fresh module with _cached === null jest.isolateModules(() => { jest.mock("fs"); const freshFs = require("fs") as { existsSync: jest.Mock }; freshFs.existsSync.mockReturnValue(false); const { getWorkspaceState: freshGet } = require("../src/workspace-detector") as typeof import("../src/workspace-detector"); const result = freshGet(); // Should have called existsSync with a path derived from process.cwd() expect(freshFs.existsSync).toHaveBeenCalledWith( expect.stringContaining(process.cwd().split(path.sep)[1] ?? "") ); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); }); }); }); // ─── syncWorkspaceState() ───────────────────────────────────────────────────── describe("syncWorkspaceState()", () => { it("updates the cache to active when .pisces found", () => { mockExistsSync.mockImplementation( (p: string) => p === "/workspace/.pisces" ); const result = syncWorkspaceState("/workspace"); expect(result).toEqual({ isActive: true, root: "/workspace", ageGroup: null }); expect(getWorkspaceState()).toEqual({ isActive: true, root: "/workspace", ageGroup: null }); }); it("updates the cache to inactive when .pisces not found", () => { // First sync to active mockExistsSync.mockImplementation( (p: string) => p === "/workspace/.pisces" ); syncWorkspaceState("/workspace"); // Now sync to a different path with no .pisces mockExistsSync.mockReturnValue(false); const result = syncWorkspaceState("/other/project"); expect(result).toEqual({ isActive: false, root: null, ageGroup: null }); expect(getWorkspaceState()).toEqual({ isActive: false, root: null, ageGroup: null }); }); it("subsequent getWorkspaceState() returns the synced value", () => { mockExistsSync.mockImplementation( (p: string) => p === "/my/workspace/.pisces" ); syncWorkspaceState("/my/workspace/project"); mockExistsSync.mockClear(); const state = getWorkspaceState(); expect(state).toEqual({ isActive: true, root: "/my/workspace", ageGroup: null }); expect(mockExistsSync).not.toHaveBeenCalled(); }); });