ForgejoKit/Tests/ForgejoKitTests/DiffParserTests.swift

363 lines
11 KiB
Swift
Raw Normal View History

2026-06-04 02:29:23 -07:00
@testable import ForgejoKit
import Foundation
import Testing
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")
2026-06-04 02:29:23 -07:00
#expect(result.files[0].hunks[0].lines.count(where: { $0.type == .addition }) == 2)
#expect(result.files[1].newName == "README.md")
#expect(result.files[1].oldName == "README.md")
let readmeHunk = result.files[1].hunks[0]
2026-06-04 02:29:23 -07:00
#expect(readmeHunk.lines.count(where: { $0.type == .context }) == 1)
#expect(readmeHunk.lines.count(where: { $0.type == .addition }) == 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)
}
}