Files
omp-ralpi/tests/diff.test.ts

238 lines
7.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Tests for the noise-filtered diff engine (src/diff.ts).
* Covers: per-file +/ parsing, excluded-file split, totals excluding noise,
* malformed-chunk guard, isExcluded reasons, and configurable overrides.
*/
import { describe, test, expect } from "bun:test";
import {
parseDiff,
filterNoise,
isExcluded,
compileIgnorePatterns,
EXCLUDED_PATTERNS,
} from "../src/diff";
/** A synthetic unified diff mixing code, a lockfile, a minified file, and a binary. */
const SYNTH_DIFF = [
"diff --git a/src/index.ts b/src/index.ts",
"index 111..222 100644",
"--- a/src/index.ts",
"+++ b/src/index.ts",
"@@ -1,2 +1,4 @@",
' import { foo } from "./foo";',
"+export const baz = 1;",
"+export const qux = 2;",
"-foo();",
"+bar();",
"",
"diff --git a/package-lock.json b/package-lock.json",
"index 000..111 100644",
"--- a/package-lock.json",
"+++ b/package-lock.json",
"@@ -0,0 +1,3 @@",
"+{",
'+ "name": "x"',
"+}",
"",
"diff --git a/dist/foo.min.js b/dist/foo.min.js",
"index 111..222 100644",
"--- a/dist/foo.min.js",
"+++ b/dist/foo.min.js",
"@@ -1 +1 @@",
"-var a=1;",
"+var a=2;",
"",
"diff --git a/assets/logo.png b/assets/logo.png",
"index 111..222 100644",
"Binary files differ",
].join("\n");
describe("parseDiff", () => {
test("splits included vs excluded files and totals only included", () => {
const summary = parseDiff(SYNTH_DIFF);
// Included: only src/index.ts (code). Lockfile, minified, binary excluded.
expect(summary.files).toHaveLength(1);
expect(summary.files[0]).toEqual({
path: "src/index.ts",
linesAdded: 3,
linesRemoved: 1,
ext: "ts",
});
expect(summary.excluded).toHaveLength(3);
const byPath = new Map(
summary.excluded.map((f) => [f.path, f]),
);
expect(byPath.get("package-lock.json")).toMatchObject({
linesAdded: 3,
linesRemoved: 0,
reason: "lockfile",
});
expect(byPath.get("dist/foo.min.js")).toMatchObject({
linesAdded: 1,
linesRemoved: 1,
reason: "minified asset",
});
expect(byPath.get("assets/logo.png")).toMatchObject({
linesAdded: 0,
linesRemoved: 0,
reason: "binary/media asset",
});
// Totals exclude the noise files.
expect(summary.totalAdded).toBe(3);
expect(summary.totalRemoved).toBe(1);
});
test("returns empty summary for an empty diff", () => {
const summary = parseDiff("");
expect(summary.files).toHaveLength(0);
expect(summary.excluded).toHaveLength(0);
expect(summary.totalAdded).toBe(0);
expect(summary.totalRemoved).toBe(0);
});
test("skips malformed chunks without a/… b/ header without crashing", () => {
const malformed =
"diff --git weird-line\nindex 111..222\n--- a/x\n+++ b/x\n+x\n" +
"\n" +
"diff --git a/src/ok.ts b/src/ok.ts\n--- a/src/ok.ts\n+++ b/src/ok.ts\n+ok\n";
const summary = parseDiff(malformed);
// Only the well-formed chunk is counted.
expect(summary.files).toHaveLength(1);
expect(summary.files[0].path).toBe("src/ok.ts");
expect(summary.totalAdded).toBe(1);
});
test("does not count +++/--- header lines as additions/removals", () => {
const diff = [
"diff --git a/src/a.ts b/src/a.ts",
"--- a/src/a.ts",
"+++ b/src/a.ts",
"@@ -0,0 +1,2 @@",
"+one",
"+two",
].join("\n");
const summary = parseDiff(diff);
expect(summary.files[0].linesAdded).toBe(2);
expect(summary.files[0].linesRemoved).toBe(0);
});
});
describe("isExcluded", () => {
test("returns the right reason per pattern", () => {
expect(isExcluded("package-lock.json")).toBe("lockfile");
expect(isExcluded("yarn.lock")).toBe("lockfile");
expect(isExcluded("src/app.min.js")).toBe("minified asset");
expect(isExcluded("src/styles.min.css")).toBe("minified asset");
expect(isExcluded("build/out.js")).toBe("build output");
expect(isExcluded("node_modules/foo/index.js")).toBe("dependency");
expect(isExcluded("vendor/lib.bundle.js")).toBe("vendored dependency");
expect(isExcluded("assets/icon.svg")).toBe("binary/media asset");
expect(isExcluded("src/api.generated.ts")).toBe("generated file");
expect(isExcluded("test/__snapshots__/x.snap")).toBe("snapshot");
expect(isExcluded("dist/x.js.map")).toBe("source map");
});
test("returns undefined for review-relevant files", () => {
expect(isExcluded("src/foo.ts")).toBeUndefined();
expect(isExcluded("src/index.ts")).toBeUndefined();
});
test("merges caller-supplied extra patterns", () => {
expect(isExcluded("src/data.foo", [/\.foo$/])).toBe("extra ignore pattern");
expect(isExcluded("src/data.foo")).toBeUndefined();
});
test("EXCLUDED_PATTERNS covers lockfiles, min, generated, snap, map, build, vendor, binaries", () => {
for (const pat of [
"package-lock.json",
"src/app.min.js",
"src/thing.generated.ts",
"x.snap",
"x.js.map",
"dist/bundle.js",
"node_modules/a/b.js",
"vendor/x",
"a.png",
"f.woff2",
]) {
const hit = EXCLUDED_PATTERNS.some((r) => r.pattern.test(pat));
expect(hit, `${pat} should be covered by a default rule`).toBe(true);
}
});
});
describe("filterNoise", () => {
test("re-emits only included-file chunks", () => {
const filtered = filterNoise(SYNTH_DIFF);
expect(filtered).toContain("diff --git a/src/index.ts");
expect(filtered).not.toContain("package-lock.json");
expect(filtered).not.toContain("foo.min.js");
expect(filtered).not.toContain("logo.png");
});
test("returns empty when every file is noise", () => {
const onlyNoise = [
"diff --git a/package-lock.json b/package-lock.json",
"--- a/package-lock.json",
"+++ b/package-lock.json",
"+x",
].join("\n");
expect(filterNoise(onlyNoise)).toBe("");
});
});
describe("configurable noise rules", () => {
test("extraPatterns excludes a matching file from the review diff", () => {
const diff = [
"diff --git a/src/foo.ts b/src/foo.ts",
"--- a/src/foo.ts",
"+++ b/src/foo.ts",
"+keep",
"diff --git a/src/data.foo b/src/data.foo",
"--- a/src/data.foo",
"+++ b/src/data.foo",
"+drop",
].join("\n");
const opts = { extraPatterns: compileIgnorePatterns(["\\.foo$"]) };
const summary = parseDiff(diff, opts);
expect(summary.files.map((f) => f.path)).toEqual(["src/foo.ts"]);
expect(summary.excluded.map((f) => [f.path, f.reason])).toEqual([
["src/data.foo", "extra ignore pattern"],
]);
expect(filterNoise(diff, opts)).not.toContain("data.foo");
});
test("ignorePaths keeps an excluded-by-default file in scope", () => {
const diff = [
"diff --git a/package-lock.json b/package-lock.json",
"--- a/package-lock.json",
"+++ b/package-lock.json",
"+a",
"+b",
"+c",
].join("\n");
const opts = { ignorePaths: ["package-lock.json"] };
const summary = parseDiff(diff, opts);
expect(summary.files).toHaveLength(1);
expect(summary.files[0].path).toBe("package-lock.json");
expect(summary.excluded).toHaveLength(0);
expect(summary.totalAdded).toBe(3);
expect(filterNoise(diff, opts)).toContain("package-lock.json");
});
test("default behavior unchanged when overrides are unset", () => {
const summary = parseDiff(SYNTH_DIFF);
expect(summary.files[0].path).toBe("src/index.ts");
expect(summary.totalAdded).toBe(3);
});
test("compileIgnorePatterns skips invalid regexes", () => {
const compiled = compileIgnorePatterns(["\\.foo$", "(", "ok$"]);
expect(compiled.length).toBe(2);
});
});