/*
Unit tests for workspace tree index covering buildWorkspaceTreeIndex; filterTreeIndex. It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- Fixtures stay inline and minimal so each case states its own setup.
- RFC-1311: Compass header sweep — declares the workspace suite's contract for test-file scaffold coverage.
*/
// @ai-invariant fixtures must not couple to kernel internals beyond the public surface under test
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test, expect, describe } from "vitest";
import { buildWorkspaceTreeIndex, filterTreeIndex } from "../workspace-tree-index.ts";
describe("buildWorkspaceTreeIndex", () => {
test("indexes files in a simple workspace", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "a.md"), "a");
await fs.writeFile(path.join(ws, "b.ts"), "b");
const index = await buildWorkspaceTreeIndex(ws);
expect(index.size).toBe(2);
expect(index.has("a.md")).toBe(true);
expect(index.has("b.ts")).toBe(true);
});
test("entries have mtimeMs and size", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "file.md"), "hello world");
const index = await buildWorkspaceTreeIndex(ws);
const entry = index.get("file.md");
expect(entry).toBeDefined();
expect(typeof entry!.mtimeMs).toBe("number");
expect(entry!.size).toBe(11);
});
test("excludes .git and node_modules by default", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "visible.md"), "v");
await fs.mkdir(path.join(ws, ".git"));
await fs.writeFile(path.join(ws, ".git", "config"), "c");
await fs.mkdir(path.join(ws, "node_modules"));
await fs.writeFile(path.join(ws, "node_modules", "pkg.json"), "{}");
const index = await buildWorkspaceTreeIndex(ws);
expect(index.has("visible.md")).toBe(true);
expect(index.has(".git/config")).toBe(false);
expect(index.has("node_modules/pkg.json")).toBe(false);
});
test("does not exclude dist/ or missions/", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.mkdir(path.join(ws, "dist"));
await fs.writeFile(path.join(ws, "dist", "output.html"), "html");
await fs.mkdir(path.join(ws, "missions"));
await fs.writeFile(path.join(ws, "missions", "mission.md"), "m");
const index = await buildWorkspaceTreeIndex(ws);
expect(index.has("dist/output.html")).toBe(true);
expect(index.has("missions/mission.md")).toBe(true);
});
test("indexes nested directories", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.mkdir(path.join(ws, "src", "commands"), { recursive: true });
await fs.writeFile(path.join(ws, "src", "commands", "cmd.ts"), "c");
await fs.writeFile(path.join(ws, "src", "index.ts"), "i");
const index = await buildWorkspaceTreeIndex(ws);
expect(index.has("src/commands/cmd.ts")).toBe(true);
expect(index.has("src/index.ts")).toBe(true);
});
test("custom excludeDirs override defaults", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "keep.md"), "k");
await fs.mkdir(path.join(ws, ".git"));
await fs.writeFile(path.join(ws, ".git", "config"), "c");
await fs.mkdir(path.join(ws, "exclude-me"));
await fs.writeFile(path.join(ws, "exclude-me", "file.md"), "f");
const index = await buildWorkspaceTreeIndex(ws, [".git", "exclude-me"]);
expect(index.has("keep.md")).toBe(true);
expect(index.has(".git/config")).toBe(false);
expect(index.has("exclude-me/file.md")).toBe(false);
});
});
describe("filterTreeIndex", () => {
test("matches simple file pattern", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "a.md"), "a");
await fs.writeFile(path.join(ws, "b.ts"), "b");
const index = await buildWorkspaceTreeIndex(ws);
const matched = filterTreeIndex(index, ["a.md"], ws, ws);
expect(matched).toEqual([path.join(ws, "a.md")]);
});
test("matches glob patterns", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "a.md"), "a");
await fs.writeFile(path.join(ws, "b.md"), "b");
await fs.writeFile(path.join(ws, "c.ts"), "c");
const index = await buildWorkspaceTreeIndex(ws);
const matched = filterTreeIndex(index, ["*.md"], ws, ws);
expect(matched).toHaveLength(2);
expect(matched).toContain(path.join(ws, "a.md"));
expect(matched).toContain(path.join(ws, "b.md"));
});
test("matches nested glob patterns", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.mkdir(path.join(ws, "src", "commands"), { recursive: true });
await fs.writeFile(path.join(ws, "src", "commands", "a.ts"), "a");
await fs.writeFile(path.join(ws, "src", "commands", "b.ts"), "b");
await fs.writeFile(path.join(ws, "src", "index.ts"), "i");
const index = await buildWorkspaceTreeIndex(ws);
const matched = filterTreeIndex(index, ["src/commands/*.ts"], ws, ws);
expect(matched).toHaveLength(2);
});
test("resolves app token relative to baseDir", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.mkdir(path.join(ws, "sites", "myapp"), { recursive: true });
await fs.writeFile(path.join(ws, "sites", "myapp", "content.md"), "c");
const index = await buildWorkspaceTreeIndex(ws);
const baseDir = path.join(ws, "sites", "myapp");
const matched = filterTreeIndex(index, ["/content.md"], baseDir, ws);
expect(matched).toEqual([path.join(ws, "sites", "myapp", "content.md")]);
});
test("returns sorted results", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "c.md"), "c");
await fs.writeFile(path.join(ws, "a.md"), "a");
await fs.writeFile(path.join(ws, "b.md"), "b");
const index = await buildWorkspaceTreeIndex(ws);
const matched = filterTreeIndex(index, ["*.md"], ws, ws);
expect(matched).toEqual([path.join(ws, "a.md"), path.join(ws, "b.md"), path.join(ws, "c.md")]);
});
test("returns empty array for no matches", async () => {
const ws = await fs.mkdtemp(path.join(os.tmpdir(), "tree-index-"));
await fs.writeFile(path.join(ws, "a.md"), "a");
const index = await buildWorkspaceTreeIndex(ws);
const matched = filterTreeIndex(index, ["*.ts"], ws, ws);
expect(matched).toEqual([]);
});
});