Files
truthmark/tests/git/changes.test.ts
T

132 lines
5.2 KiB
TypeScript

import fs from "node:fs/promises";
import { describe, expect, it } from "vitest";
import { getUncommittedChanges } from "../../src/git/changes.js";
import { classifyPath } from "../../src/sync/classify.js";
import { createTempRepo } from "../helpers/temp-repo.js";
describe("getUncommittedChanges", () => {
it("returns staged, unstaged, and untracked paths without duplicates", async () => {
const repo = await createTempRepo();
try {
await repo.writeFile("src/changed.ts", "export const changed = 1;\n");
await repo.runGit(["add", "src/changed.ts"]);
await repo.runGit(["commit", "-m", "test: baseline tracked file"]);
await repo.writeFile("src/staged.ts", "export const staged = true;\n");
await repo.runGit(["add", "src/staged.ts"]);
await repo.writeFile("src/changed.ts", "export const changed = 2;\n");
await repo.runGit(["add", "src/changed.ts"]);
await repo.writeFile("src/changed.ts", "export const changed = 3;\n");
await repo.writeFile("src/untracked.ts", "export const untracked = true;\n");
const changes = await getUncommittedChanges(repo.rootDir);
expect(changes).toEqual(
expect.arrayContaining([
{
path: "src/changed.ts",
staged: true,
unstaged: true,
untracked: false,
deleted: false,
},
{
path: "src/staged.ts",
staged: true,
unstaged: false,
untracked: false,
deleted: false,
},
{
path: "src/untracked.ts",
staged: false,
unstaged: false,
untracked: true,
deleted: false,
},
]),
);
expect(changes.filter((change) => change.path === "src/changed.ts")).toHaveLength(1);
} finally {
await repo.cleanup();
}
});
it("includes deleted tracked files and marks them as deleted", async () => {
const repo = await createTempRepo();
try {
await repo.writeFile("src/deleted.ts", "export const deleted = true;\n");
await repo.runGit(["add", "src/deleted.ts"]);
await repo.runGit(["commit", "-m", "test: baseline deleted source"]);
await fs.rm(`${repo.rootDir}/src/deleted.ts`);
const changes = await getUncommittedChanges(repo.rootDir);
expect(changes).toEqual(
expect.arrayContaining([
{
path: "src/deleted.ts",
staged: false,
unstaged: true,
untracked: false,
deleted: true,
},
]),
);
} finally {
await repo.cleanup();
}
});
});
describe("classifyPath", () => {
it("treats markdown docs and config files as non-functional", () => {
expect(classifyPath("docs/guides/authentication.md", [])).toBe("markdown");
expect(classifyPath("TRUTHMARK.md", [])).toBe("markdown");
expect(classifyPath(".truthmark/config.yml", [])).toBe("config");
expect(classifyPath("package.json", [])).toBe("config");
expect(classifyPath("src/auth/session.ts", [])).toBe("functional-code");
});
it("respects ignore globs and treats generated Truthmark paths as derived", () => {
expect(classifyPath("dist/main.js", ["dist/**"])).toBe("ignored");
expect(classifyPath("vendor/lib/index.rb", ["vendor/**"])).toBe("ignored");
expect(classifyPath(".truthmark/cache/state.json", [])).toBe("derived");
expect(classifyPath(".truthmark/sync/report.md", [])).toBe("derived");
expect(classifyPath(".codex/skills/truthmark-sync/SKILL.md", [])).toBe("derived");
expect(classifyPath(".opencode/skills/truthmark-sync/SKILL.md", [])).toBe("derived");
expect(classifyPath("skills/truthmark-sync/SKILL.md", [])).toBe("markdown");
expect(classifyPath(".cursor/rules/truthmark.mdc", [])).toBe("derived");
expect(classifyPath(".github/copilot-instructions.md", [])).toBe("derived");
expect(classifyPath("CLAUDE.md", [])).toBe("derived");
expect(classifyPath("GEMINI.md", [])).toBe("derived");
expect(classifyPath(".gemini/commands/truthmark/sync.toml", [])).toBe("derived");
});
it("stays conservative for source-like paths in polyglot repos", () => {
expect(classifyPath("cmd/server.go", [])).toBe("functional-code");
expect(classifyPath("lib/session.rs", [])).toBe("functional-code");
expect(classifyPath("scripts/release.py", [])).toBe("functional-code");
expect(classifyPath("notes/todo.txt", [])).toBe("other");
});
it("classifies IaC, API schema, frontend, workflow, and monorepo surfaces as functional", () => {
expect(classifyPath("infra/main.tf", [])).toBe("functional-code");
expect(classifyPath("k8s/deployment.yaml", [])).toBe("functional-code");
expect(classifyPath("api/openapi.yaml", [])).toBe("functional-code");
expect(classifyPath("schema/user.graphql", [])).toBe("functional-code");
expect(classifyPath("proto/user.proto", [])).toBe("functional-code");
expect(classifyPath("frontend/components/Login.tsx", [])).toBe("functional-code");
expect(classifyPath(".github/workflows/ci.yml", [])).toBe("functional-code");
expect(classifyPath("apps/web/src/App.tsx", [])).toBe("functional-code");
expect(classifyPath("packages/auth/src/session.ts", [])).toBe("functional-code");
});
});