Commit dada3cd
authored
Add MockView and SplitTreeTests (ghostty-org#10778)
This PR introduces unit tests and a supporting Mock NSView for testing
the SplitTree implementation in Swift. It includes 51 tests which
achieve approximately 93.13% (949/1019) coverage of SplitTree.swift's
branches.
<details>
<summary>Coverage</summary>
<pre>
./ghostty/macos/Sources/Features/Splits/SplitTree.swift 93.13%
(949/1019)
SplitTree.Path.isEmpty.getter 100.00% (1/1)
SplitTree.isEmpty.getter 100.00% (3/3)
SplitTree.isSplit.getter 100.00% (3/3)
SplitTree.init() 100.00% (3/3)
SplitTree.init(view:) 100.00% (3/3)
SplitTree.contains(_:) 100.00% (4/4)
SplitTree.inserting(view:at:direction:) 100.00% (6/6)
SplitTree.find(id:) 100.00% (4/4)
SplitTree.removing(_:) 93.75% (15/16)
SplitTree.replacing(node:with:) 93.75% (15/16)
SplitTree.focusTarget(for:from:) 82.14% (46/56)
closure #1 in SplitTree.focusTarget(for:from:) 100.00% (1/1)
closure #2 in SplitTree.focusTarget(for:from:) 100.00% (1/1)
closure #3 in SplitTree.focusTarget(for:from:) 100.00% (3/3)
implicit closure #1 in SplitTree.focusTarget(for:from:) 0.00% (0/1)
SplitTree.equalized() 100.00% (5/5)
SplitTree.resizing(node:by:in:with:) 92.00% (69/75)
closure #1 in SplitTree.resizing(node:by:in:with:) 100.00% (1/1)
SplitTree.viewBounds() 100.00% (4/4)
SplitTree.init(from:) 76.00% (19/25)
SplitTree.encode(to:) 100.00% (15/15)
SplitTree.Node.find(id:) 100.00% (13/13)
SplitTree.Node.node(view:) 88.89% (16/18)
SplitTree.Node.path(to:) 100.00% (32/32)
search #1 <A>(_:) in SplitTree.Node.path(to:) 100.00% (27/27)
SplitTree.Node.node(at:) 89.47% (17/19)
SplitTree.Node.inserting(view:at:direction:) 86.84% (33/38)
SplitTree.Node.replacingNode(at:with:) 100.00% (43/43)
replaceInner #1 <A>(current:pathOffset:) in
SplitTree.Node.replacingNode(at:with:) 96.67% (29/30)
SplitTree.Node.remove(_:) 70.27% (26/37)
implicit closure #1 in SplitTree.Node.remove(_:) 100.00% (1/1)
SplitTree.Node.resizing(to:) 100.00% (16/16)
SplitTree.Node.leftmostLeaf() 87.50% (7/8)
SplitTree.Node.rightmostLeaf() 87.50% (7/8)
SplitTree.Node.equalize() 100.00% (4/4)
SplitTree.Node.equalizeWithWeight() 100.00% (30/30)
SplitTree.Node.weightForDirection(_:) 83.33% (10/12)
SplitTree.Node.calculateViewBounds(in:) 100.00% (50/50)
SplitTree.Node.viewBounds() 100.00% (26/26)
SplitTree.Node.spatial(within:) 100.00% (18/18)
SplitTree.Node.dimensions() 80.77% (21/26)
SplitTree.Node.spatialSlots(in:) 100.00% (53/53)
SplitTree.Spatial.slots(in:from:) 100.00% (47/47)
closure #1 in SplitTree.Spatial.slots(in:from:) 100.00% (1/1)
distance #1 <A>(from:to:) in SplitTree.Spatial.slots(in:from:) 100.00%
(6/6)
closure #2 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #2 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #3 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #4 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #4 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #5 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #6 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #6 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #7 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
closure #8 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
implicit closure #1 in closure #8 in SplitTree.Spatial.slots(in:from:)
100.00% (1/1)
closure #9 in SplitTree.Spatial.slots(in:from:) 100.00% (3/3)
SplitTree.Spatial.doesBorder(side:from:) 100.00% (20/20)
closure #1 in SplitTree.Spatial.doesBorder(side:from:) 100.00% (1/1)
closure #2 in SplitTree.Spatial.doesBorder(side:from:) 100.00% (3/3)
static SplitTree.Node.== infix(_:_:) 100.00% (13/13)
SplitTree.Node.init(from:) 66.67% (12/18)
SplitTree.Node.encode(to:) 100.00% (11/11)
SplitTree.Node.leaves() 100.00% (9/9)
SplitTree.makeIterator() 100.00% (3/3)
implicit closure #1 in SplitTree.makeIterator() 100.00% (1/1)
SplitTree.Node.makeIterator() 0.00% (0/3)
SplitTree.startIndex.getter 100.00% (3/3)
SplitTree.endIndex.getter 100.00% (3/3)
implicit closure #1 in SplitTree.endIndex.getter 100.00% (1/1)
SplitTree.subscript.getter 100.00% (5/5)
implicit closure #1 in SplitTree.subscript.getter 100.00% (1/1)
implicit closure #2 in implicit closure #1 in SplitTree.subscript.getter
100.00% (1/1)
implicit closure #3 in SplitTree.subscript.getter 0.00% (0/1)
implicit closure #4 in SplitTree.subscript.getter 0.00% (0/1)
SplitTree.index(after:) 100.00% (4/4)
implicit closure #1 in SplitTree.index(after:) 100.00% (1/1)
implicit closure #2 in SplitTree.index(after:) 0.00% (0/1)
SplitTree.Node.structuralIdentity.getter 100.00% (3/3)
SplitTree.Node.StructuralIdentity.init(_:) 100.00% (3/3)
static SplitTree.Node.StructuralIdentity.== infix(_:_:) 100.00% (3/3)
SplitTree.Node.StructuralIdentity.hash(into:) 100.00% (3/3)
SplitTree.Node.isStructurallyEqual(to:) 100.00% (18/18)
implicit closure #1 in SplitTree.Node.isStructurallyEqual(to:) 100.00%
(1/1)
implicit closure #2 in SplitTree.Node.isStructurallyEqual(to:) 100.00%
(1/1)
SplitTree.Node.hashStructure(into:) 100.00% (14/14)
SplitTree.structuralIdentity.getter 100.00% (3/3)
SplitTree.StructuralIdentity.init(_:) 100.00% (4/4)
static SplitTree.StructuralIdentity.== infix(_:_:) 100.00% (4/4)
implicit closure #1 in static SplitTree.StructuralIdentity.==
infix(_:_:) 100.00% (1/1)
SplitTree.StructuralIdentity.hash(into:) 80.00% (8/10)
static SplitTree.StructuralIdentity.areNodesStructurallyEqual(_:_:)
90.00% (9/10)
</pre>
</details>
I chose this as a good place to start contributing to Ghostty because I
was curious about the macOS implementation, and there was a specific
request for help with testing (ghostty-org#7879).
My process for writing the tests was basically reading
[SplitTree.swift](./macos/Sources/Features/Splits/SplitTree.swift) to
understand it, then writing tests for each high-level method and
checking against code coverage to capture all the code paths:
## Running
```bash
rm -rf /tmp/ghostty-test.xcresult
xcodebuild -project macos/Ghostty.xcodeproj \
-scheme GhosttyTest \
-configuration Debug \
test \
-destination 'platform=macOS' \
-enableCodeCoverage YES \
-resultBundlePath /tmp/ghostty-test.xcresult \
-only-testing:GhosttyTests/SplitTreeTests \
2>&1 | xcbeautify
```
## Coverage
```bash
xcrun xccov view --report /tmp/ghostty-test.xcresult | grep 'SplitTree\.'
```
This was originally implemented in [~38
commits](https://github.com/pouwerkerk/ghostty/pull/1/commits), but I
squashed them down to 1 commit for easier review.
## AI Disclosure
The tests were written by me, but I used Opus 4.6 to explain some parts
of the code, and then finally to provide feedback on the tests. It
suggested tests for `nodeStructuralIdentityInSet` and
`nodeStructuralIdentityDistinguishesLeaves` as well as [the
Parameterized
test](pouwerkerk@6a0bca4),
`resizingAdjustsRatio`, which seemed like a clever way to collapse 12
individual tests into 3 parameterized ones that still run 12 cases
total. I didn't know this feature existed, and it seems like a great way
to write tests that are more maintainable. I read this relatively new
feature in the [Swift
Docs](https://developer.apple.com/documentation/testing/parameterizedtesting).
I find this to be a particularly useful feature of Claude/related
agents, where it can suggest better ways of writing something in a more
idiomatic way, and it taught me something new, which is always fun.
I'm more than happy to continue work on tests for ghostty-org#7879 and always
welcome to any feedback you have.1 file changed
+666
-0
lines changed
0 commit comments