Files
hapi/ios/Packages/HapiKit/Tests/HapiUITests/DiffModelTests.swift
T

312 lines
9.5 KiB
Swift

import Foundation
import HapiUI
import Testing
/// Unified-diff parser fixtures: modify / add / delete / rename / binary /
/// bare hunks. Line semantics follow the web's `CodexDiffView.parseUnifiedDiff`
/// (empty in-hunk line = context, `\` marker is not content); rename/binary
/// semantics follow `gitParsers.ts` (R/C → renamed, binary → 0/0 counts).
@Suite("UnifiedDiffParser")
struct DiffModelTests {
// MARK: - Modify
private let modifyDiff = """
diff --git a/src/app.ts b/src/app.ts
index 1234567..89abcde 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,3 +1,4 @@ import section
import a
-import b
+import b2
+import c
import d
@@ -10,2 +11,2 @@
x
y
"""
@Test func parsesModifiedFile() throws {
let files = UnifiedDiffParser.parse(modifyDiff)
#expect(files.count == 1)
let file = try #require(files.first)
#expect(file.kind == .modified)
#expect(file.oldPath == "src/app.ts")
#expect(file.newPath == "src/app.ts")
#expect(file.isBinary == false)
#expect(file.hunks.count == 2)
#expect(file.additions == 2)
#expect(file.deletions == 1)
#expect(file.displayPath == "src/app.ts")
}
@Test func parsesHunkHeaderNumbersAndHeading() throws {
let file = try #require(UnifiedDiffParser.parse(modifyDiff).first)
let first = try #require(file.hunks.first)
#expect(first.oldStart == 1)
#expect(first.oldCount == 3)
#expect(first.newStart == 1)
#expect(first.newCount == 4)
#expect(first.sectionHeading == "import section")
#expect(first.header == "@@ -1,3 +1,4 @@ import section")
let second = try #require(file.hunks.last)
#expect(second.sectionHeading == nil)
}
@Test func computesLineNumbers() throws {
let file = try #require(UnifiedDiffParser.parse(modifyDiff).first)
let lines = try #require(file.hunks.first).lines
#expect(lines.count == 5)
#expect(lines[0] == DiffLine(kind: .context, text: "import a", oldNumber: 1, newNumber: 1))
#expect(lines[1] == DiffLine(kind: .deletion, text: "import b", oldNumber: 2))
#expect(lines[2] == DiffLine(kind: .addition, text: "import b2", newNumber: 2))
#expect(lines[3] == DiffLine(kind: .addition, text: "import c", newNumber: 3))
#expect(lines[4] == DiffLine(kind: .context, text: "import d", oldNumber: 3, newNumber: 4))
let secondHunk = try #require(file.hunks.last).lines
#expect(secondHunk[0].oldNumber == 10)
#expect(secondHunk[0].newNumber == 11)
#expect(secondHunk[1].oldNumber == 11)
#expect(secondHunk[1].newNumber == 12)
}
// MARK: - Add / delete
@Test func parsesAddedFile() throws {
let diff = """
diff --git a/new.txt b/new.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/new.txt
@@ -0,0 +1,2 @@
+hello
+world
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.kind == .added)
#expect(file.oldPath == nil)
#expect(file.newPath == "new.txt")
#expect(file.additions == 2)
#expect(file.deletions == 0)
#expect(file.hunks.first?.lines.map(\.newNumber) == [1, 2])
#expect(file.displayPath == "new.txt")
}
@Test func parsesDeletedFile() throws {
let diff = """
diff --git a/old.txt b/old.txt
deleted file mode 100644
index e69de29..0000000
--- a/old.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-hello
-world
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.kind == .deleted)
#expect(file.oldPath == "old.txt")
#expect(file.newPath == nil)
#expect(file.additions == 0)
#expect(file.deletions == 2)
#expect(file.hunks.first?.lines.map(\.oldNumber) == [1, 2])
#expect(file.displayPath == "old.txt")
}
// MARK: - Rename
@Test func parsesPureRename() throws {
let diff = """
diff --git a/lib/a.ts b/lib/b.ts
similarity index 100%
rename from lib/a.ts
rename to lib/b.ts
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.kind == .renamed)
#expect(file.oldPath == "lib/a.ts")
#expect(file.newPath == "lib/b.ts")
#expect(file.hunks.isEmpty)
#expect(file.additions == 0)
#expect(file.displayPath == "lib/a.ts → lib/b.ts")
}
@Test func parsesRenameWithEdits() throws {
let diff = """
diff --git a/lib/a.ts b/lib/b.ts
similarity index 90%
rename from lib/a.ts
rename to lib/b.ts
index 111..222 100644
--- a/lib/a.ts
+++ b/lib/b.ts
@@ -1,2 +1,2 @@
keep
-old
+new
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.kind == .renamed)
#expect(file.hunks.count == 1)
#expect(file.additions == 1)
#expect(file.deletions == 1)
}
@Test func copyCountsAsRenamed() throws {
let diff = """
diff --git a/x.ts b/y.ts
copy from x.ts
copy to y.ts
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.kind == .renamed)
#expect(file.oldPath == "x.ts")
#expect(file.newPath == "y.ts")
}
// MARK: - Binary
@Test func parsesBinaryFile() throws {
let diff = """
diff --git a/img.png b/img.png
index 1234567..89abcde 100644
Binary files a/img.png and b/img.png differ
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
#expect(file.isBinary)
#expect(file.hunks.isEmpty)
// gitParsers numstat parity: binary reports 0/0.
#expect(file.additions == 0)
#expect(file.deletions == 0)
}
// MARK: - Bare hunks (agent unified_diff inputs)
@Test func parsesHeaderlessHunk() throws {
let diff = """
@@ -1,2 +1,2 @@
a
-b
+c
"""
let files = UnifiedDiffParser.parse(diff)
#expect(files.count == 1)
let file = try #require(files.first)
#expect(file.oldPath == nil)
#expect(file.newPath == nil)
#expect(file.kind == .modified)
#expect(file.hunks.count == 1)
#expect(file.hunks.first?.lines.count == 3)
#expect(file.additions == 1)
#expect(file.deletions == 1)
#expect(file.displayPath == "diff")
}
@Test func parsesOmittedCountsAsOne() throws {
let diff = """
@@ -3 +7 @@
-x
+y
"""
let hunk = try #require(UnifiedDiffParser.parse(diff).first?.hunks.first)
#expect(hunk.oldStart == 3)
#expect(hunk.oldCount == 1)
#expect(hunk.newStart == 7)
#expect(hunk.newCount == 1)
#expect(hunk.lines[0].oldNumber == 3)
#expect(hunk.lines[1].newNumber == 7)
}
@Test func emptyInHunkLineIsContext() throws {
let diff = """
--- a/f.txt
+++ b/f.txt
@@ -1,3 +1,3 @@
a
b
"""
let lines = try #require(UnifiedDiffParser.parse(diff).first?.hunks.first).lines
#expect(lines.count == 3)
#expect(lines[1] == DiffLine(kind: .context, text: "", oldNumber: 2, newNumber: 2))
}
@Test func noNewlineMarkerIsNotContent() throws {
let diff = """
--- a/f.txt
+++ b/f.txt
@@ -1 +1 @@
-old
\\ No newline at end of file
+new
\\ No newline at end of file
"""
let file = try #require(UnifiedDiffParser.parse(diff).first)
let lines = try #require(file.hunks.first).lines
#expect(lines.count == 4)
#expect(lines[1].kind == .noNewlineMarker)
#expect(lines[3].kind == .noNewlineMarker)
#expect(lines[1].oldNumber == nil)
#expect(lines[1].newNumber == nil)
#expect(file.additions == 1)
#expect(file.deletions == 1)
}
// MARK: - Multi-file and junk
@Test func parsesMultiFileDiff() {
let diff = """
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1 +1 @@
-x
+y
diff --git a/b.txt b/b.txt
--- a/b.txt
+++ b/b.txt
@@ -1 +1 @@
-p
+q
"""
let files = UnifiedDiffParser.parse(diff)
#expect(files.count == 2)
#expect(files.map(\.displayPath) == ["a.txt", "b.txt"])
#expect(files.allSatisfy { $0.hunks.count == 1 })
}
@Test func ignoresProseAndStrayMarkers() {
let text = """
Here is a summary of changes.
--- notes below
nothing else
"""
#expect(UnifiedDiffParser.parse(text).isEmpty)
#expect(UnifiedDiffParser.parse("").isEmpty)
}
@Test func closesHunkWhenCountsLie() throws {
// Declared counts promise more lines than delivered; the next file
// header must still start a fresh file.
let diff = """
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,5 +1,5 @@
-x
+y
diff --git a/b.txt b/b.txt
--- a/b.txt
+++ b/b.txt
@@ -1 +1 @@
-p
+q
"""
let files = UnifiedDiffParser.parse(diff)
#expect(files.count == 2)
#expect(files.last?.newPath == "b.txt")
#expect(files.first?.hunks.count == 1)
}
}