import Foundation import Testing @testable import ForgejoKit struct DiffParserTests { // MARK: - Empty and minimal input @Test func parseEmptyString() { let result = DiffParser.parse("") #expect(result.files.isEmpty) } @Test func parseGarbageInput() { let result = DiffParser.parse("this is not a diff") #expect(result.files.isEmpty) } // MARK: - Single file diff @Test func parseSingleFileAddition() { let diff = [ "diff --git a/hello.txt b/hello.txt", "new file mode 100644", "--- /dev/null", "+++ b/hello.txt", "@@ -0,0 +1,3 @@", "+line one", "+line two", "+line three", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 1) let file = result.files[0] #expect(file.oldName == "/dev/null") #expect(file.newName == "hello.txt") #expect(file.hunks.count == 1) let hunk = file.hunks[0] #expect(hunk.lines.count == 4) #expect(hunk.lines[0].type == .header) #expect(hunk.lines[1].type == .addition) #expect(hunk.lines[2].type == .addition) #expect(hunk.lines[3].type == .addition) } @Test func parseSingleFileDeletion() { let diff = [ "diff --git a/old.txt b/old.txt", "deleted file mode 100644", "--- a/old.txt", "+++ /dev/null", "@@ -1,2 +0,0 @@", "-removed line one", "-removed line two", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 1) let file = result.files[0] #expect(file.oldName == "old.txt") #expect(file.newName == "/dev/null") } @Test func parseSingleFileModification() { let diff = [ "diff --git a/file.txt b/file.txt", "--- a/file.txt", "+++ b/file.txt", "@@ -1,3 +1,3 @@", " context line", "-old line", "+new line", " another context", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 1) let hunk = result.files[0].hunks[0] #expect(hunk.lines.count == 5) let contextLine = hunk.lines[1] #expect(contextLine.type == .context) #expect(contextLine.oldLineNumber == 1) #expect(contextLine.newLineNumber == 1) let deletionLine = hunk.lines[2] #expect(deletionLine.type == .deletion) #expect(deletionLine.content == "old line") #expect(deletionLine.oldLineNumber == 2) #expect(deletionLine.newLineNumber == nil) let additionLine = hunk.lines[3] #expect(additionLine.type == .addition) #expect(additionLine.content == "new line") #expect(additionLine.oldLineNumber == nil) #expect(additionLine.newLineNumber == 2) let trailingContext = hunk.lines[4] #expect(trailingContext.type == .context) #expect(trailingContext.oldLineNumber == 3) #expect(trailingContext.newLineNumber == 3) } // MARK: - Multiple files @Test func parseMultipleFiles() { let diff = [ "diff --git a/a.txt b/a.txt", "--- a/a.txt", "+++ b/a.txt", "@@ -1 +1 @@", "-old a", "+new a", "diff --git a/b.txt b/b.txt", "--- a/b.txt", "+++ b/b.txt", "@@ -1 +1 @@", "-old b", "+new b", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 2) #expect(result.files[0].newName == "a.txt") #expect(result.files[1].newName == "b.txt") } // MARK: - Multiple hunks @Test func parseMultipleHunks() { let diff = [ "diff --git a/file.txt b/file.txt", "--- a/file.txt", "+++ b/file.txt", "@@ -1,3 +1,3 @@", " context", "-old first", "+new first", " context", "@@ -10,3 +10,3 @@", " context", "-old second", "+new second", " context", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files[0].hunks.count == 2) let hunk2 = result.files[0].hunks[1] let contextLine = hunk2.lines[1] #expect(contextLine.oldLineNumber == 10) #expect(contextLine.newLineNumber == 10) } // MARK: - Line number tracking @Test func lineNumbersTrackCorrectlyWithMultipleAdditions() { let diff = [ "diff --git a/file.txt b/file.txt", "--- a/file.txt", "+++ b/file.txt", "@@ -5,4 +5,6 @@", " context", "+added one", "+added two", " old context", "-removed", "+replaced", " trailing", ].joined(separator: "\n") let result = DiffParser.parse(diff) let lines = result.files[0].hunks[0].lines let ctx1 = lines[1] #expect(ctx1.oldLineNumber == 5) #expect(ctx1.newLineNumber == 5) let add1 = lines[2] #expect(add1.newLineNumber == 6) #expect(add1.oldLineNumber == nil) let add2 = lines[3] #expect(add2.newLineNumber == 7) let ctx2 = lines[4] #expect(ctx2.oldLineNumber == 6) #expect(ctx2.newLineNumber == 8) let del = lines[5] #expect(del.oldLineNumber == 7) #expect(del.newLineNumber == nil) let repl = lines[6] #expect(repl.newLineNumber == 9) #expect(repl.oldLineNumber == nil) let trailing = lines[7] #expect(trailing.oldLineNumber == 8) #expect(trailing.newLineNumber == 10) } // MARK: - Hunk header parsing @Test func hunkHeaderWithFunctionContext() { let diff = [ "diff --git a/main.swift b/main.swift", "--- a/main.swift", "+++ b/main.swift", "@@ -42,6 +42,7 @@ func doSomething() {", " context", "+new line", " context", ].joined(separator: "\n") let result = DiffParser.parse(diff) let firstLine = result.files[0].hunks[0].lines[1] #expect(firstLine.oldLineNumber == 42) #expect(firstLine.newLineNumber == 42) } // MARK: - File name extraction @Test func fileNameExtractedFromMinusPlus() { let diff = [ "diff --git a/src/app.swift b/src/app.swift", "--- a/src/app.swift", "+++ b/src/app.swift", "@@ -1 +1 @@", "-old", "+new", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files[0].oldName == "src/app.swift") #expect(result.files[0].newName == "src/app.swift") } @Test func fileNameFallsBackToGitHeader() { let diff = [ "diff --git a/image.png b/image.png", "Binary files differ", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 1) #expect(result.files[0].oldName == "image.png") #expect(result.files[0].newName == "image.png") } // MARK: - Commit diff (new file from Forgejo API) @Test func parseCommitDiffNewFile() { let diff = [ "diff --git a/src/main.py b/src/main.py", "new file mode 100644", "index 0000000..67e9324", "--- /dev/null", "+++ b/src/main.py", "@@ -0,0 +1 @@", "+def main():\\n print(\"Main module\")", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 1) let file = result.files[0] #expect(file.oldName == "/dev/null") #expect(file.newName == "src/main.py") #expect(file.hunks.count == 1) let hunk = file.hunks[0] #expect(hunk.lines[0].type == .header) #expect(hunk.lines[1].type == .addition) #expect(hunk.lines[1].newLineNumber == 1) #expect(hunk.lines[1].oldLineNumber == nil) } @Test func parseCommitDiffMultipleFiles() { let diff = [ "diff --git a/hello.py b/hello.py", "new file mode 100644", "--- /dev/null", "+++ b/hello.py", "@@ -0,0 +1,2 @@", "+print(\"hello\")", "+print(\"world\")", "diff --git a/README.md b/README.md", "--- a/README.md", "+++ b/README.md", "@@ -1,2 +1,3 @@", " # Project", "+", "+New section", ].joined(separator: "\n") let result = DiffParser.parse(diff) #expect(result.files.count == 2) #expect(result.files[0].newName == "hello.py") #expect(result.files[0].oldName == "/dev/null") #expect(result.files[0].hunks[0].lines.filter { $0.type == .addition }.count == 2) #expect(result.files[1].newName == "README.md") #expect(result.files[1].oldName == "README.md") let readmeHunk = result.files[1].hunks[0] #expect(readmeHunk.lines.filter { $0.type == .context }.count == 1) #expect(readmeHunk.lines.filter { $0.type == .addition }.count == 2) } // MARK: - No newline at end of file sentinel @Test func noNewlineAtEndOfFileSentinelIsSkipped() { let diff = [ "diff --git a/file.txt b/file.txt", "--- a/file.txt", "+++ b/file.txt", "@@ -1,2 +1,2 @@", " context", "-old line", "\\ No newline at end of file", "+new line", "\\ No newline at end of file", ].joined(separator: "\n") let result = DiffParser.parse(diff) let hunk = result.files[0].hunks[0] // Header + context + deletion + addition = 4 lines (sentinels skipped) #expect(hunk.lines.count == 4) #expect(hunk.lines[0].type == .header) #expect(hunk.lines[1].type == .context) #expect(hunk.lines[2].type == .deletion) #expect(hunk.lines[3].type == .addition) // diffPosition should be consecutive without gaps from the sentinel #expect(hunk.lines[1].diffPosition == 1) #expect(hunk.lines[2].diffPosition == 2) #expect(hunk.lines[3].diffPosition == 3) } // MARK: - DiffLine identity @Test func eachLineHasUniqueId() { let diff = [ "diff --git a/f.txt b/f.txt", "--- a/f.txt", "+++ b/f.txt", "@@ -1,2 +1,2 @@", "-a", "+b", ].joined(separator: "\n") let result = DiffParser.parse(diff) let ids = result.files[0].hunks[0].lines.map(\.id) let uniqueIds = Set(ids) #expect(ids.count == uniqueIds.count) } }