Files
brov-macbook/tests/DiffEngineTests.swift
2026-10-06 19:34:32 +03:00

223 lines
11 KiB
Swift
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.
import Foundation
@main
enum DiffEngineTests {
static var failures = 0
static func checkTrue(_ label: String, _ value: Bool) {
if value { print(" ✓ \(label)") }
else { print(" ✗ \(label)"); failures += 1 }
}
static func checkInt(_ label: String, _ got: Int, _ expected: Int) {
if got == expected { print(" ✓ \(label)") }
else { print(" ✗ \(label) got: \(got) expected: \(expected)"); failures += 1 }
}
static func main() {
// ── DiffEngine.fromEdit — additions ───────────────────────────────────
print("DiffEngine.fromEdit — additions")
do {
let d = DiffEngine.fromEdit(old: "", new: "hello\nworld\n", path: "/a/b.swift")
checkInt("added == 2", d.added, 2)
checkInt("removed == 0", d.removed, 0)
checkTrue("!tooLarge", !d.tooLarge)
checkTrue("1 hunk", d.hunks.count == 1)
checkTrue("name == b.swift", d.name == "b.swift")
}
// ── DiffEngine.fromEdit — removals ────────────────────────────────────
print("DiffEngine.fromEdit — removals")
do {
let d = DiffEngine.fromEdit(old: "hello\nworld\n", new: "", path: "/x.py")
checkInt("added == 0", d.added, 0)
checkInt("removed == 2", d.removed, 2)
checkTrue("1 hunk", d.hunks.count == 1)
}
// ── DiffEngine.fromEdit — replacement ─────────────────────────────────
print("DiffEngine.fromEdit — replacement")
do {
let d = DiffEngine.fromEdit(old: "foo\nbar\nbaz\n",
new: "foo\nqux\nbaz\n",
path: "/f.ts")
checkInt("added == 1", d.added, 1)
checkInt("removed == 1", d.removed, 1)
checkTrue("has context lines",
d.hunks.first?.lines.contains(where: { $0.kind == .context }) == true)
}
// ── DiffEngine.fromNew — new file ──────────────────────────────────────
print("DiffEngine.fromNew — new file")
do {
let d = DiffEngine.fromNew(content: "line1\nline2\nline3\n", path: "/new.rs")
checkInt("added == 3", d.added, 3)
checkInt("removed == 0", d.removed, 0)
checkTrue("all lines .added",
d.hunks.flatMap { $0.lines }.allSatisfy { $0.kind == .added })
}
// ── DiffEngine.fromEdit — MultiEdit (two edits) ────────────────────────
print("DiffEngine.fromEdit — MultiEdit (two edits)")
do {
let d1 = DiffEngine.fromEdit(old: "aaa\n", new: "bbb\n", path: "/m.kt")
let d2 = DiffEngine.fromEdit(old: "ccc\n", new: "ddd\n", path: "/m.kt")
checkInt("d1 added == 1", d1.added, 1)
checkInt("d1 removed == 1", d1.removed, 1)
checkInt("d2 added == 1", d2.added, 1)
checkInt("d2 removed == 1", d2.removed, 1)
}
// ── DiffEngine — tooLarge ──────────────────────────────────────────────
print("DiffEngine — tooLarge")
do {
// Generate > 200 KB combined content
let bigOld = String(repeating: "x", count: 150 * 1024)
let bigNew = String(repeating: "y", count: 60 * 1024)
let d = DiffEngine.fromEdit(old: bigOld, new: bigNew, path: "/big.swift")
checkTrue("tooLarge == true", d.tooLarge)
checkTrue("hunks.isEmpty", d.hunks.isEmpty)
}
// ── DiffEngine — CRLF ─────────────────────────────────────────────────
print("DiffEngine — CRLF")
do {
let d = DiffEngine.fromEdit(old: "a\r\nb\r\n", new: "a\r\nc\r\n", path: "/win.txt")
checkInt("added == 1", d.added, 1)
checkInt("removed == 1", d.removed, 1)
}
// ── DiffEngine — no trailing newline ──────────────────────────────────
print("DiffEngine — no trailing newline")
do {
let d = DiffEngine.fromEdit(old: "hello", new: "hello\nworld", path: "/t.txt")
checkInt("added == 1", d.added, 1)
checkInt("removed == 0", d.removed, 0)
}
// ── DiffEngine — 3-line context ───────────────────────────────────────
print("DiffEngine — 3-line context")
do {
// 10-line file, change line 5
let oldContent = (1...10).map { "line\($0)" }.joined(separator: "\n") + "\n"
let newContent = (1...10).map { $0 == 5 ? "changed" : "line\($0)" }.joined(separator: "\n") + "\n"
let d = DiffEngine.fromEdit(old: oldContent, new: newContent, path: "/ctx.swift")
checkInt("added == 1", d.added, 1)
checkInt("removed == 1", d.removed, 1)
checkTrue("1 hunk", d.hunks.count == 1)
let hunk = d.hunks[0]
let contextLines = hunk.lines.filter { $0.kind == .context }
checkTrue("has context before change", contextLines.count >= 3)
}
// ── DiffEngine — m*n > 1_000_000 → tooLarge ─────────────────────────
print("DiffEngine — m*n > 1_000_000 → tooLarge")
do {
// 1001 old lines × 1001 new lines = > 1M
let many = (0..<1001).map { "line\($0)" }.joined(separator: "\n")
let d = DiffEngine.fromEdit(old: many, new: many + "\nextra", path: "/big.swift")
checkTrue("tooLarge when m*n > 1M", d.tooLarge)
checkTrue("hunks empty", d.hunks.isEmpty)
}
// ── DiffEngine.fromNew — too large ────────────────────────────────────
print("DiffEngine.fromNew — too large")
do {
let bigContent = String(repeating: "x\n", count: FileDiff.maxLines + 1)
let d = DiffEngine.fromNew(content: bigContent, path: "/new.swift")
checkTrue("fromNew tooLarge", d.tooLarge)
checkTrue("fromNew isNewFile", d.isNewFile)
checkTrue("fromNew hunks empty", d.hunks.isEmpty)
checkTrue("fromNew added > 0", d.added > 0)
}
// ── String.makeDiffStep / parseDiffStep ───────────────────────────────
print("String.makeDiffStep / parseDiffStep")
do {
let s = String.makeDiffStep(filename: "foo.swift", added: 3, removed: 1, diffId: 7)
checkTrue("isDiffStep", s.isDiffStep)
let parsed = s.parseDiffStep()
checkTrue("parsed != nil", parsed != nil)
checkTrue("filename round-trips", parsed?.filename == "foo.swift")
checkTrue("added round-trips", parsed?.added == 3)
checkTrue("removed round-trips", parsed?.removed == 1)
checkTrue("diffId round-trips", parsed?.diffId == 7)
// id > 9 (multi-digit) round-trips correctly
let s2 = String.makeDiffStep(filename: "bar.ts", added: 0, removed: 2, diffId: 42)
checkTrue("diffId 42 round-trips", s2.parseDiffStep()?.diffId == 42)
// Non-diff step should not parse
checkTrue("normal step !isDiffStep", !"Edit foo.swift".isDiffStep)
checkTrue("normal step parseDiffStep == nil", "Edit foo.swift".parseDiffStep() == nil)
}
// ── DiffEngine.toOneLine ──────────────────────────────────────────────
print("DiffEngine.toOneLine")
do {
// multi-line joined with space
checkTrue("multi-line joined",
DiffEngine.toOneLine("line one\nline two\nline three") == "line one line two line three")
// bold stripped
checkTrue("bold stripped",
DiffEngine.toOneLine("**hello** world") == "hello world")
// heading stripped
checkTrue("heading stripped",
DiffEngine.toOneLine("## My Title\nsome text") == "My Title some text")
// empty input → empty
checkTrue("empty → empty", DiffEngine.toOneLine("").isEmpty)
// truncation
let long = DiffEngine.toOneLine(String(repeating: "x ", count: 200), maxChars: 10)
checkTrue("truncated to maxChars", long.count <= 10)
// stop at blank line
checkTrue("blank line → first para only",
DiffEngine.toOneLine("First para.\n\nSecond para.") == "First para.")
// stop at --- separator
checkTrue("--- separator → first para only",
DiffEngine.toOneLine("Done. Single commit 450a657 on github-pulse.\n\n---\n\nFiles touched (7)…")
== "Done. Single commit 450a657 on github-pulse.")
// stop at *** separator
checkTrue("*** separator → first para only",
DiffEngine.toOneLine("Summary line.\n***\nMore details.") == "Summary line.")
// stop at table row (|)
checkTrue("table row → first para only",
DiffEngine.toOneLine("Result:\n| Col1 | Col2 |\n|---|---|\n| A | B |") == "Result:")
// strip leading bullet -
checkTrue("strip bullet -",
DiffEngine.toOneLine("- item one\n- item two") == "item one item two")
// strip leading bullet *
checkTrue("strip bullet *",
DiffEngine.toOneLine("* first\n* second") == "first second")
// strip ordered list
checkTrue("strip ordered list",
DiffEngine.toOneLine("1. step one\n2. step two") == "step one step two")
// first paragraph empty → fall through to next
checkTrue("empty first para → next",
DiffEngine.toOneLine("\n\nActual content.") == "Actual content.")
}
// ── finish ─────────────────────────────────────────────────────────────
if failures == 0 {
print("\nAll tests passed.")
exit(0)
} else {
print("\n\(failures) test(s) failed.")
exit(1)
}
}
}