UNPKG

memorybank

Version:

A command-line tool for parsing and displaying memory bank status from markdown files, with support for Roo Code's Memory Bank

199 lines (194 loc) 8.85 kB
import { describe, it, expect, beforeAll, beforeEach, afterAll, afterEach, vi } from "vitest"; import { promises as fs } from "node:fs"; import path from "node:path"; import os from "node:os"; // Mock modules before any imports vi.mock("node:child_process", () => ({ exec: vi.fn(), promisify: vi.fn((fn) => fn), })); import { toTildePath, getDocsPathValue, validateRepositories, getGitPath, processDocsDirectory, } from "../memorybank-status.js"; // Mock console methods const consoleMock = { log: vi.fn(), error: vi.fn(), }; // Replace console methods const originalConsole = global.console; global.console = { ...originalConsole, ...consoleMock }; // Mock process.exit const mockExit = vi.spyOn(process, "exit").mockImplementation(() => undefined); // Get the mocked exec function const mockExec = vi.mocked(await import("node:child_process")).exec; describe("memorybank-status", () => { afterEach(() => { vi.clearAllMocks(); }); afterAll(() => { global.console = originalConsole; vi.restoreAllMocks(); }); describe("toTildePath", () => { it("should expand ~ to home directory", () => { const homeDir = os.homedir(); expect(toTildePath("~/test")).toBe(path.join(homeDir, "test")); expect(toTildePath("~/folder/file.txt")).toBe(path.join(homeDir, "folder/file.txt")); }); it("should not modify paths without ~", () => { expect(toTildePath("/absolute/path")).toBe("/absolute/path"); expect(toTildePath("relative/path")).toBe("relative/path"); }); }); describe("getDocsPathValue", () => { it("should extract docs path from arguments", () => { const args = ["--other=value", "--docs-path=/test/path", "--flag"]; expect(getDocsPathValue(args)).toBe("/test/path"); }); it("should return undefined if no docs path provided", () => { const args = ["--other=value", "--flag"]; expect(getDocsPathValue(args)).toBeUndefined(); }); it("should handle docs path with equals sign in value", () => { const args = ["--docs-path=/path/with=equals"]; expect(getDocsPathValue(args)).toBe("/path/with=equals"); }); }); describe("getGitPath", () => { const originalPlatform = process.platform; beforeAll(() => { Object.defineProperty(process, "platform", { value: originalPlatform, writable: true, }); }); it("should return Windows git path on Windows", () => { Object.defineProperty(process, "platform", { value: "win32" }); expect(getGitPath()).toBe("C:\\Program Files\\Git\\bin\\git.cmd"); }); it("should return Unix git path on non-Windows", () => { Object.defineProperty(process, "platform", { value: "darwin" }); expect(getGitPath()).toBe("/usr/bin/git"); }); }); describe("validateRepositories", () => { const testDir = path.join(os.tmpdir(), "memorybank-test"); const requiredFiles = [ "productContext.md", "activeContext.md", "systemPatterns.md", "techContext.md", "progress.md", ]; beforeAll(async () => { await fs.mkdir(testDir, { recursive: true }); await Promise.all(requiredFiles.map((file) => fs.writeFile(path.join(testDir, file), "test content"))); }); it("should validate when all required files exist", async () => { await expect(validateRepositories(testDir)).resolves.not.toThrow(); }); it("should throw error when a required file is missing", async () => { const missingFile = requiredFiles[0]; await fs.unlink(path.join(testDir, missingFile)); await expect(validateRepositories(testDir)).rejects.toThrow(`Required file not found: ${missingFile}`); // Restore the file for other tests await fs.writeFile(path.join(testDir, missingFile), "test content"); }); it("should throw error for non-existent directory", async () => { const nonExistentDir = path.join(testDir, "non-existent"); await expect(validateRepositories(nonExistentDir)).rejects.toThrow(); }); }); describe("processDocsDirectory", () => { const testDir = path.join(os.tmpdir(), "memorybank-test"); // Helper function to setup git command mocks const setupGitMocks = (gitDirOutput, gitRemoteOutput) => { mockExec.mockImplementation((command, _options, callback) => { console.log("[DEBUG] Mocked exec command:", command); if (command.includes("rev-parse --git-dir")) { callback(null, gitDirOutput, ""); } else if (command.includes("remote get-url origin")) { console.log("[DEBUG] Mocked git remote output:", JSON.stringify(gitRemoteOutput)); callback(null, gitRemoteOutput, ""); } else { callback(new Error(`Unexpected command: ${command}`), "", ""); } return {}; }); }; beforeAll(async () => { // Create test directory with required files await fs.mkdir(testDir, { recursive: true }); // Use the same test content as test-progress.md const progressContent = `# Progress ## Implementation Status ### Core Features - ✅ Completed feature - ⚠️ In progress feature - ❌ Not started feature - Regular item without status ### Testing - ✅ Test suite setup - ⚠️ Integration tests in progress - ❌ E2E tests pending ## Priority Tasks ### High Priority - ❌ Important task`; await fs.writeFile(path.join(testDir, "progress.md"), progressContent); // Create other required files const otherFiles = ["productContext.md", "activeContext.md", "systemPatterns.md", "techContext.md"]; await Promise.all(otherFiles.map((file) => fs.writeFile(path.join(testDir, file), "test content"))); }); beforeEach(() => { // Reset all mocks before each test vi.resetAllMocks(); consoleMock.log.mockClear(); consoleMock.error.mockClear(); }); it("should handle non-existent directory", async () => { const nonExistentDir = path.join(testDir, "non-existent"); setupGitMocks(".git\n", ""); // Git setup doesn't matter for this test await processDocsDirectory(nonExistentDir, false); expect(consoleMock.error).toHaveBeenCalledWith("Error:", expect.stringContaining("Required file not found")); expect(mockExit).toHaveBeenCalledWith(1); }); it("should handle invalid progress.md content", async () => { setupGitMocks(".git\n", ""); // Git setup doesn't matter for this test // Create invalid progress.md await fs.writeFile(path.join(testDir, "progress.md"), "Invalid content"); await processDocsDirectory(testDir, false); expect(consoleMock.error).toHaveBeenCalled(); expect(mockExit).toHaveBeenCalledWith(1); // Restore valid content for other tests const validContent = `# Progress\n\n## Implementation Status\n\n### Core Features\n- ✅ Test`; await fs.writeFile(path.join(testDir, "progress.md"), validContent); }); }); describe("CLI argument handling", () => { const originalArgv = process.argv; afterEach(() => { process.argv = originalArgv; vi.clearAllMocks(); }); it("should handle --incomplete flag", async () => { process.argv = ["node", "memorybank-status.js", "--incomplete", "--docs-path=/test/path"]; const args = process.argv.slice(2); expect(args.includes("--incomplete")).toBe(true); expect(getDocsPathValue(args)).toBe("/test/path"); }); it("should handle direct file path", async () => { process.argv = ["node", "memorybank-status.js", "/test/file.md"]; const args = process.argv.slice(2); expect(args[0]).toBe("/test/file.md"); expect(getDocsPathValue(args)).toBeUndefined(); }); it("should handle missing path argument", async () => { process.argv = ["node", "memorybank-status.js"]; const args = process.argv.slice(2); expect(args.length).toBe(0); expect(getDocsPathValue(args)).toBeUndefined(); }); }); }); //# sourceMappingURL=memorybank-status.test.js.map