Skip to content

Commit 6ad1c5b

Browse files
committed
test: add performance and improve snapshot testing
Add `DiffLayouterPerformanceTests.swift` to test text layout performance with varying word counts. Convert existing snapshot tests to use XCTest, enhancing compatibility and extendability. Introduce snapshot artifacts directory configuration to manage artifacts. Update tests to specify the snapshot test name explicitly. Modify `verticalInsetIsNonNegativeAndMonotonic` test to ensure non-negativity and check monotonicity of vertical insets, improving test rigor.
1 parent 9229e37 commit 6ad1c5b

18 files changed

Lines changed: 186 additions & 76 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ DerivedData/
66
.swiftpm/configuration/registries.json
77
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
88
.netrc
9+
.snapshot-artifacts/
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import AppKit
2+
import XCTest
3+
@testable import TextDiff
4+
5+
// swift test --filter DiffLayouterPerformanceTests 2>&1 | xcsift
6+
7+
final class DiffLayouterPerformanceTests: XCTestCase {
8+
func testLayoutPerformance200Words() {
9+
runLayoutPerformanceTest(wordCount: 200)
10+
}
11+
12+
func testLayoutPerformance500Words() {
13+
runLayoutPerformanceTest(wordCount: 500)
14+
}
15+
16+
func testLayoutPerformance1000Words() {
17+
runLayoutPerformanceTest(wordCount: 1000)
18+
}
19+
20+
private func runLayoutPerformanceTest(wordCount: Int) {
21+
let style = TextDiffStyle.default
22+
let verticalInset = DiffTextLayoutMetrics.verticalTextInset(for: style)
23+
let contentInsets = NSEdgeInsets(top: verticalInset, left: 0, bottom: verticalInset, right: 0)
24+
let availableWidth: CGFloat = 520
25+
26+
let original = Self.largeText(wordCount: wordCount)
27+
let updated = Self.replacingLastWord(in: original)
28+
let segments = TextDiffEngine.diff(original: original, updated: updated, mode: .character)
29+
30+
measure(metrics: [XCTClockMetric()]) {
31+
let layout = DiffTokenLayouter.layout(
32+
segments: segments,
33+
style: style,
34+
availableWidth: availableWidth,
35+
contentInsets: contentInsets
36+
)
37+
XCTAssertFalse(layout.runs.isEmpty)
38+
}
39+
}
40+
41+
private static func largeText(wordCount: Int) -> String {
42+
let vocabulary = [
43+
"alpha", "beta", "gamma", "delta", "epsilon", "theta", "lambda", "sigma",
44+
"swift", "layout", "render", "token", "word", "segment", "measure", "width"
45+
]
46+
var words: [String] = []
47+
words.reserveCapacity(wordCount)
48+
49+
for index in 0..<wordCount {
50+
words.append(vocabulary[index % vocabulary.count])
51+
}
52+
53+
return words.joined(separator: " ")
54+
}
55+
56+
private static func replacingLastWord(in text: String) -> String {
57+
guard let lastSpace = text.lastIndex(of: " ") else {
58+
return "changed"
59+
}
60+
return String(text[..<lastSpace]) + " changed"
61+
}
62+
}

Tests/TextDiffTests/NSTextDiffSnapshotTests.swift

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,61 @@
11
import AppKit
22
import SnapshotTesting
3-
import Testing
43
import TextDiff
4+
import XCTest
55

6-
@Suite(.snapshots(record: .missing))
7-
@MainActor
8-
struct NSTextDiffSnapshotTests {
9-
@Test
10-
func token_basic_replacement() {
6+
final class NSTextDiffSnapshotTests: XCTestCase {
7+
override func invokeTest() {
8+
withSnapshotTesting(record: .missing) {
9+
super.invokeTest()
10+
}
11+
}
12+
13+
@MainActor
14+
func testTokenBasicReplacement() {
1115
assertNSTextDiffSnapshot(
1216
original: "Apply old value in this sentence.",
1317
updated: "Apply new value in this sentence.",
1418
mode: .token,
15-
size: CGSize(width: 500, height: 120)
19+
size: CGSize(width: 500, height: 120),
20+
testName: "token_basic_replacement()"
1621
)
1722
}
1823

19-
@Test
20-
func character_suffix_refinement() {
24+
@MainActor
25+
func testCharacterSuffixRefinement() {
2126
assertNSTextDiffSnapshot(
2227
original: "Add a diff",
2328
updated: "Added a diff",
2429
mode: .character,
25-
size: CGSize(width: 320, height: 110)
30+
size: CGSize(width: 320, height: 110),
31+
testName: "character_suffix_refinement()"
2632
)
2733
}
2834

29-
@Test
30-
func punctuation_replacement() {
35+
@MainActor
36+
func testPunctuationReplacement() {
3137
assertNSTextDiffSnapshot(
3238
original: "Wait!",
3339
updated: "Wait.",
3440
mode: .token,
35-
size: CGSize(width: 320, height: 100)
41+
size: CGSize(width: 320, height: 100),
42+
testName: "punctuation_replacement()"
3643
)
3744
}
3845

39-
@Test
40-
func multiline_insertion_wrap() {
46+
@MainActor
47+
func testMultilineInsertionWrap() {
4148
assertNSTextDiffSnapshot(
4249
original: "line1\nline2",
4350
updated: "line1\nlineX\nline2",
4451
mode: .token,
45-
size: CGSize(width: 300, height: 150)
52+
size: CGSize(width: 300, height: 150),
53+
testName: "multiline_insertion_wrap()"
4654
)
4755
}
4856

49-
@Test
50-
func custom_style_spacing_strikethrough() {
57+
@MainActor
58+
func testCustomStyleSpacingStrikethrough() {
5159
var style = TextDiffStyle.default
5260
style.removalsStyle.strikethrough = true
5361
style.interChipSpacing = 1
@@ -57,7 +65,8 @@ struct NSTextDiffSnapshotTests {
5765
updated: sampleUpdatedSentence,
5866
mode: .character,
5967
style: style,
60-
size: CGSize(width: 300, height: 180)
68+
size: CGSize(width: 300, height: 180),
69+
testName: "custom_style_spacing_strikethrough()"
6170
)
6271
}
6372

Tests/TextDiffTests/SnapshotTestSupport.swift

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ func assertTextDiffSnapshot(
2020
line: UInt = #line,
2121
column: UInt = #column
2222
) {
23+
configureSnapshotArtifactsDirectory(filePath: filePath)
24+
2325
let rootView = TextDiffView(
2426
original: original,
2527
updated: updated,
@@ -36,19 +38,21 @@ func assertTextDiffSnapshot(
3638

3739
let snapshotImage = renderSnapshotImage1x(view: hostingView, size: size)
3840

39-
assertSnapshot(
40-
of: snapshotImage,
41-
as: .image(
42-
precision: snapshotPrecision,
43-
perceptualPrecision: snapshotPerceptualPrecision
44-
),
45-
named: name,
46-
fileID: fileID,
47-
file: filePath,
48-
testName: testName,
49-
line: line,
50-
column: column
51-
)
41+
withSnapshotTesting(diffTool: .ksdiff) {
42+
assertSnapshot(
43+
of: snapshotImage,
44+
as: .image(
45+
precision: snapshotPrecision,
46+
perceptualPrecision: snapshotPerceptualPrecision
47+
),
48+
named: name,
49+
fileID: fileID,
50+
file: filePath,
51+
testName: testName,
52+
line: line,
53+
column: column
54+
)
55+
}
5256
}
5357

5458
@MainActor
@@ -65,6 +69,8 @@ func assertNSTextDiffSnapshot(
6569
line: UInt = #line,
6670
column: UInt = #column
6771
) {
72+
configureSnapshotArtifactsDirectory(filePath: filePath)
73+
6874
let diffView = NSTextDiffView(
6975
original: original,
7076
updated: updated,
@@ -84,19 +90,31 @@ func assertNSTextDiffSnapshot(
8490

8591
let snapshotImage = renderSnapshotImage1x(view: container, size: size)
8692

87-
assertSnapshot(
88-
of: snapshotImage,
89-
as: .image(
90-
precision: snapshotPrecision,
91-
perceptualPrecision: snapshotPerceptualPrecision
92-
),
93-
named: name,
94-
fileID: fileID,
95-
file: filePath,
96-
testName: testName,
97-
line: line,
98-
column: column
99-
)
93+
withSnapshotTesting(diffTool: .ksdiff) {
94+
assertSnapshot(
95+
of: snapshotImage,
96+
as: .image(
97+
precision: snapshotPrecision,
98+
perceptualPrecision: snapshotPerceptualPrecision
99+
),
100+
named: name,
101+
fileID: fileID,
102+
file: filePath,
103+
testName: testName,
104+
line: line,
105+
column: column
106+
)
107+
}
108+
}
109+
110+
private func configureSnapshotArtifactsDirectory(filePath: StaticString) {
111+
let fileURL = URL(fileURLWithPath: "\(filePath)")
112+
let repoRootURL = fileURL
113+
.deletingLastPathComponent() // TextDiffTests
114+
.deletingLastPathComponent() // Tests
115+
.deletingLastPathComponent() // repo root
116+
let artifactsPath = repoRootURL.appendingPathComponent(".snapshot-artifacts", isDirectory: true).path
117+
setenv("SNAPSHOT_ARTIFACTS", artifactsPath, 1)
100118
}
101119

102120
@MainActor

Tests/TextDiffTests/TextDiffEngineTests.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,12 +332,21 @@ func layouterUsesRemovalStrikethroughFromRemovalStyle() throws {
332332
}
333333

334334
@Test
335-
func verticalInsetScalesWithChipInsets() {
336-
#expect(DiffTextLayoutMetrics.verticalTextInset(for: .default) == 3)
337-
338-
var style = TextDiffStyle.default
339-
style.chipInsets = NSEdgeInsets(top: 6, left: 2, bottom: 1, right: 2)
340-
#expect(DiffTextLayoutMetrics.verticalTextInset(for: style) == 8)
335+
func verticalInsetIsNonNegativeAndMonotonic() {
336+
var base = TextDiffStyle.default
337+
base.chipInsets = NSEdgeInsets(top: 0, left: 2, bottom: 0, right: 2)
338+
let baseInset = DiffTextLayoutMetrics.verticalTextInset(for: base)
339+
#expect(baseInset >= 0)
340+
341+
var largerTop = base
342+
largerTop.chipInsets = NSEdgeInsets(top: 6, left: 2, bottom: 0, right: 2)
343+
let largerTopInset = DiffTextLayoutMetrics.verticalTextInset(for: largerTop)
344+
#expect(largerTopInset >= baseInset)
345+
346+
var largerBottom = base
347+
largerBottom.chipInsets = NSEdgeInsets(top: 0, left: 2, bottom: 7, right: 2)
348+
let largerBottomInset = DiffTextLayoutMetrics.verticalTextInset(for: largerBottom)
349+
#expect(largerBottomInset >= baseInset)
341350
}
342351

343352
@Test

0 commit comments

Comments
 (0)