Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354import { describe, expect, it } from "vitest";import { LineOp, type NiceDiff } from "./diff";import { renderFormatPatch, renderUnifiedDiff } from "./rawdiff";
const modifiedFile: NiceDiff = { diff: [ { name: { old: "foo.go", new: "foo.go" }, text_fragments: [ { Comment: "func main()", OldPosition: 1, OldLines: 3, NewPosition: 1, NewLines: 3, Lines: [ { Op: LineOp.Context, Line: "package main\n" }, { Op: LineOp.Delete, Line: "// old comment\n" }, { Op: LineOp.Add, Line: "// new comment\n" }, { Op: LineOp.Context, Line: "func main() {}\n" } ] } ] } ]};
describe("renderUnifiedDiff", () => { it("renders nothing for a missing diff", () => { expect(renderUnifiedDiff(null)).toBe(""); expect(renderUnifiedDiff(undefined)).toBe(""); });
it("renders a modified file with the hunk comment", () => { expect(renderUnifiedDiff(modifiedFile)).toBe( "diff --git a/foo.go b/foo.go\n" + "--- a/foo.go\n" + "+++ b/foo.go\n" + "@@ -1,3 +1,3 @@ func main()\n" + " package main\n" + "-// old comment\n" + "+// new comment\n" + " func main() {}\n" ); });
it("renders a new file", () => { const got = renderUnifiedDiff({ diff: [ { name: { old: "", new: "new.go" }, is_new: true, text_fragments: [ { OldPosition: 0, OldLines: 0, NewPosition: 1, NewLines: 2, Lines: [ { Op: LineOp.Add, Line: "package main\n" }, { Op: LineOp.Add, Line: "func main() {}\n" } ] } ] } ] }); expect(got).toBe( "diff --git a/new.go b/new.go\n" + "new file mode 100644\n" + "--- /dev/null\n" + "+++ b/new.go\n" + "@@ -0,0 +1,2 @@\n" + "+package main\n" + "+func main() {}\n" ); });
it("renders a deleted file", () => { const got = renderUnifiedDiff({ diff: [ { name: { old: "old.go", new: "" }, is_delete: true, text_fragments: [ { OldPosition: 1, OldLines: 2, NewPosition: 0, NewLines: 0, Lines: [ { Op: LineOp.Delete, Line: "package main\n" }, { Op: LineOp.Delete, Line: "func main() {}\n" } ] } ] } ] }); expect(got).toBe( "diff --git a/old.go b/old.go\n" + "deleted file mode 100644\n" + "--- a/old.go\n" + "+++ /dev/null\n" + "@@ -1,2 +0,0 @@\n" + "-package main\n" + "-func main() {}\n" ); });
it("renders a renamed file with multiple fragments", () => { const got = renderUnifiedDiff({ diff: [ { name: { old: "old.go", new: "renamed.go" }, is_rename: true, text_fragments: [ { OldPosition: 1, OldLines: 2, NewPosition: 1, NewLines: 2, Lines: [ { Op: LineOp.Context, Line: "package main\n" }, { Op: LineOp.Delete, Line: "func old() {}\n" }, { Op: LineOp.Add, Line: "func renamed() {}\n" } ] }, { Comment: "func init()", OldPosition: 10, OldLines: 1, NewPosition: 10, NewLines: 1, Lines: [{ Op: LineOp.Context, Line: "var x = 1\n" }] } ] } ] }); expect(got).toBe( "diff --git a/old.go b/renamed.go\n" + "rename from old.go\n" + "rename to renamed.go\n" + "--- a/old.go\n" + "+++ b/renamed.go\n" + "@@ -1,2 +1,2 @@\n" + " package main\n" + "-func old() {}\n" + "+func renamed() {}\n" + "@@ -10,1 +10,1 @@ func init()\n" + " var x = 1\n" ); });
it("renders multiple files", () => { const file = (name: string, oldLine: string, newLine: string) => ({ name: { old: name, new: name }, text_fragments: [ { OldPosition: 1, OldLines: 1, NewPosition: 1, NewLines: 1, Lines: [ { Op: LineOp.Delete, Line: `${oldLine}\n` }, { Op: LineOp.Add, Line: `${newLine}\n` } ] } ] }); const got = renderUnifiedDiff({ diff: [file("a.go", "old a", "new a"), file("b.go", "old b", "new b")] }); expect(got).toBe( "diff --git a/a.go b/a.go\n" + "--- a/a.go\n" + "+++ b/a.go\n" + "@@ -1,1 +1,1 @@\n" + "-old a\n" + "+new a\n" + "diff --git a/b.go b/b.go\n" + "--- a/b.go\n" + "+++ b/b.go\n" + "@@ -1,1 +1,1 @@\n" + "-old b\n" + "+new b\n" ); });
it("marks a missing trailing newline on delete and context lines", () => { const got = renderUnifiedDiff({ diff: [ { name: { old: "foo.go", new: "foo.go" }, text_fragments: [ { OldPosition: 1, OldLines: 2, NewPosition: 1, NewLines: 2, Lines: [ { Op: LineOp.Delete, Line: "old" }, { Op: LineOp.Add, Line: "new\n" }, { Op: LineOp.Context, Line: "tail" } ] } ] } ] }); expect(got).toBe( "diff --git a/foo.go b/foo.go\n" + "--- a/foo.go\n" + "+++ b/foo.go\n" + "@@ -1,2 +1,2 @@\n" + "-old\n" + "\\ No newline at end of file\n" + "+new\n" + " tail\n" + "\\ No newline at end of file\n" ); });});
describe("renderFormatPatch", () => { it("renders nothing for a missing diff", () => { expect(renderFormatPatch(null)).toBe(""); expect(renderFormatPatch(undefined)).toBe(""); });
it("renders the full patch for a rename and a delete", () => { const got = renderFormatPatch({ commit: { hash: [0xab, 0xc1, 0x23, 0x45, 0x67, 0x89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], message: "Fix the bug\n\nThis patch resolves the long-standing issue.\n\n\n", author: { Name: "Alice Dev", Email: "alice@example.com", When: "2024-03-15T10:30:00Z" } }, stat: { files_changed: 2, insertions: 1, deletions: 3 }, diff: [ { name: { old: "old.go", new: "renamed.go" }, is_rename: true, text_fragments: [ { OldPosition: 1, OldLines: 2, NewPosition: 1, NewLines: 2, LinesAdded: 1, LinesDeleted: 1, Lines: [ { Op: LineOp.Context, Line: "package main\n" }, { Op: LineOp.Delete, Line: "func old() {}\n" }, { Op: LineOp.Add, Line: "func renamed() {}\n" } ] } ] }, { name: { old: "gone.go", new: "" }, is_delete: true, text_fragments: [ { OldPosition: 1, OldLines: 2, NewPosition: 0, NewLines: 0, LinesAdded: 0, LinesDeleted: 2, Lines: [ { Op: LineOp.Delete, Line: "package main\n" }, { Op: LineOp.Delete, Line: "func main() {}\n" } ] } ] } ] }); expect(got).toBe( "From abc1234567890000000000000000000000000000 Mon Sep 17 00:00:00 2001\n" + "From: Alice Dev <alice@example.com>\n" + "Date: Fri, 15 Mar 2024 10:30:00 +0000\n" + "Subject: [PATCH] Fix the bug\n" + "\n" + "This patch resolves the long-standing issue.\n" + "---\n" + " renamed.go | 2 +-\n" + " gone.go | 2 --\n" + " 2 file(s) changed, 1 insertion(s)(+), 3 deletion(s)(-)\n" + "\n" + "diff --git a/old.go b/renamed.go\n" + "rename from old.go\n" + "rename to renamed.go\n" + "--- a/old.go\n" + "+++ b/renamed.go\n" + "@@ -1,2 +1,2 @@\n" + " package main\n" + "-func old() {}\n" + "+func renamed() {}\n" + "diff --git a/gone.go b/gone.go\n" + "deleted file mode 100644\n" + "--- a/gone.go\n" + "+++ /dev/null\n" + "@@ -1,2 +0,0 @@\n" + "-package main\n" + "-func main() {}\n" + "\n--\ntangled.sh\n" ); });
it("omits the body for a single-line message and uses the zero time for a missing When", () => { const got = renderFormatPatch({ commit: { message: "Single line commit", author: { Name: "", Email: "", When: "" } } }); expect(got).toBe( "From Mon Sep 17 00:00:00 2001\n" + "From: <>\n" + "Date: Mon, 01 Jan 0001 00:00:00 +0000\n" + "Subject: [PATCH] Single line commit\n" + "\n" + "---\n" + " 0 file(s) changed, 0 insertion(s)(+), 0 deletion(s)(-)\n" + "\n" + "\n--\ntangled.sh\n" ); });
it("uses the zero time for an unparseable When", () => { const got = renderFormatPatch({ commit: { this: "abc123", message: "x", author: { Name: "A", Email: "a@b.c", When: "not a date" } } }); expect(got).toContain("Date: Mon, 01 Jan 0001 00:00:00 +0000\n"); });
it("pads the year to four digits", () => { const got = renderFormatPatch({ commit: { this: "abc123", message: "x", author: { Name: "A", Email: "a@b.c", When: "0999-06-15T10:30:00Z" } } }); expect(got).toContain("Date: Sat, 15 Jun 0999 10:30:00 +0000\n"); });});