Skip to content

Commit 6f4d568

Browse files
authored
Implement CustomDebugStringConvertible for CollectionViewDriver and CollectionViewDriverOptions (#139)
Closes #132 Implement `CustomDebugStringConvertible` for `CollectionViewDriver` and `CollectionViewDriverOptions`.
1 parent 703577a commit 6f4d568

6 files changed

+295
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The changelog for `ReactiveCollectionsKit`. Also see [the releases on GitHub](ht
55
NEXT
66
-----
77

8+
- Improve debug descriptions (i.e., `CustomDebugStringConvertible`) for various types. ([@nuomi1](https://github.com/nuomi1), [#139](https://github.com/jessesquires/ReactiveCollectionsKit/pull/139))
89
- TBA
910

1011
0.1.8

Sources/CollectionViewDriver.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,11 @@ extension CollectionViewDriver: UICollectionViewDelegateFlowLayout {
577577
?? .zero
578578
}
579579
}
580+
581+
extension CollectionViewDriver {
582+
override public var debugDescription: String {
583+
MainActor.assumeIsolated {
584+
driverDebugDescription(self, self._emptyViewProvider, self._cellEventCoordinator)
585+
}
586+
}
587+
}

Sources/CollectionViewDriverOptions.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,9 @@ public struct CollectionViewDriverOptions: Hashable {
4141
self.reloadDataOnReplacingViewModel = reloadDataOnReplacingViewModel
4242
}
4343
}
44+
45+
extension CollectionViewDriverOptions: CustomDebugStringConvertible {
46+
public var debugDescription: String {
47+
driverOptionsDebugDescription(self)
48+
}
49+
}

Sources/DebugDescriptions.swift

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import Foundation
1616
private enum Element {
1717
case type(Any.Type)
1818
case index(Int)
19-
case id(UniqueIdentifier)
2019
case header(AnySupplementaryViewModel?)
2120
case footer(AnySupplementaryViewModel?)
2221
case cells([AnyCellViewModel])
2322
case supplementaryViews([AnySupplementaryViewModel])
2423
case sections([SectionViewModel])
2524
case registrations(Set<ViewRegistration>)
26-
case isEmpty(Bool)
25+
case field(label: String, value: Any?)
26+
case options(CollectionViewDriverOptions)
27+
case viewModel(CollectionViewModel)
2728
case end
2829
}
2930

@@ -48,9 +49,6 @@ private func debugDescriptionBuilder<Target: TextOutputStream>(
4849
case let .index(index):
4950
buildString("[\(index)]:", indent: indent, to: &output)
5051

51-
case let .id(id):
52-
buildString("id: \(id)", indent: indent, to: &output)
53-
5452
case let .header(header):
5553
if let header {
5654
buildString("header: \(header.id) (\(header.reuseIdentifier))", indent: indent, to: &output)
@@ -98,12 +96,12 @@ private func debugDescriptionBuilder<Target: TextOutputStream>(
9896
debugDescriptionBuilder(
9997
elements: [
10098
(.index(index), indent + 2),
101-
(.id(section.id), indent + 4),
99+
(.field(label: "id", value: section.id), indent + 4),
102100
(.header(section.header), indent + 4),
103101
(.footer(section.footer), indent + 4),
104102
(.cells(section.cells), indent + 4),
105103
(.supplementaryViews(section.supplementaryViews), indent + 4),
106-
(.isEmpty(section.isEmpty), indent + 4)
104+
(.field(label: "isEmpty", value: section.isEmpty), indent + 4)
107105
],
108106
to: &output
109107
)
@@ -120,8 +118,40 @@ private func debugDescriptionBuilder<Target: TextOutputStream>(
120118
buildString("- \(registration.reuseIdentifier) (\(registration.viewType.kind))", indent: indent + 2, to: &output)
121119
}
122120

123-
case let .isEmpty(isEmpty):
124-
buildString("isEmpty: \(isEmpty)", indent: indent, to: &output)
121+
case let .field(label, value):
122+
if let value {
123+
buildString("\(label): \(value)", indent: indent, to: &output)
124+
} else {
125+
buildString("\(label): nil", indent: indent, to: &output)
126+
}
127+
128+
case let .options(options):
129+
buildString("options:", indent: indent, to: &output)
130+
131+
debugDescriptionBuilder(
132+
elements: [
133+
(.type(CollectionViewDriverOptions.self), indent + 2),
134+
(.field(label: "diffOnBackgroundQueue", value: options.diffOnBackgroundQueue), indent + 4),
135+
(.field(label: "reloadDataOnReplacingViewModel", value: options.reloadDataOnReplacingViewModel), indent + 4),
136+
(.end, indent + 2)
137+
],
138+
to: &output
139+
)
140+
141+
case let .viewModel(viewModel):
142+
buildString("viewModel:", indent: indent, to: &output)
143+
144+
debugDescriptionBuilder(
145+
elements: [
146+
(.type(CollectionViewModel.self), indent + 2),
147+
(.field(label: "id", value: viewModel.id), indent + 4),
148+
(.sections(viewModel.sections), indent + 4),
149+
(.registrations(viewModel.allRegistrations()), indent + 4),
150+
(.field(label: "isEmpty", value: viewModel.isEmpty), indent + 4),
151+
(.end, indent + 2)
152+
],
153+
to: &output
154+
)
125155

126156
case .end:
127157
buildString("}", indent: indent, to: &output)
@@ -134,10 +164,10 @@ func collectionDebugDescription(_ collection: CollectionViewModel) -> String {
134164
debugDescriptionBuilder(
135165
elements: [
136166
(.type(CollectionViewModel.self), 0),
137-
(.id(collection.id), 2),
167+
(.field(label: "id", value: collection.id), 2),
138168
(.sections(collection.sections), 2),
139169
(.registrations(collection.allRegistrations()), 2),
140-
(.isEmpty(collection.isEmpty), 2),
170+
(.field(label: "isEmpty", value: collection.isEmpty), 2),
141171
(.end, 0)
142172
],
143173
to: &output
@@ -150,13 +180,51 @@ func sectionDebugDescription(_ section: SectionViewModel) -> String {
150180
debugDescriptionBuilder(
151181
elements: [
152182
(.type(SectionViewModel.self), 0),
153-
(.id(section.id), 2),
183+
(.field(label: "id", value: section.id), 2),
154184
(.header(section.header), 2),
155185
(.footer(section.footer), 2),
156186
(.cells(section.cells), 2),
157187
(.supplementaryViews(section.supplementaryViews), 2),
158188
(.registrations(section.allRegistrations()), 2),
159-
(.isEmpty(section.isEmpty), 2),
189+
(.field(label: "isEmpty", value: section.isEmpty), 2),
190+
(.end, 0)
191+
],
192+
to: &output
193+
)
194+
return output
195+
}
196+
197+
func driverOptionsDebugDescription(_ options: CollectionViewDriverOptions) -> String {
198+
var output = ""
199+
debugDescriptionBuilder(
200+
elements: [
201+
(.type(CollectionViewDriverOptions.self), 0),
202+
(.field(label: "diffOnBackgroundQueue", value: options.diffOnBackgroundQueue), 2),
203+
(.field(label: "reloadDataOnReplacingViewModel", value: options.reloadDataOnReplacingViewModel), 2),
204+
(.end, 0)
205+
],
206+
to: &output
207+
)
208+
return output
209+
}
210+
211+
@MainActor
212+
func driverDebugDescription(
213+
_ driver: CollectionViewDriver,
214+
_ emptyViewProvider: EmptyViewProvider?,
215+
_ cellEventCoordinator: CellEventCoordinator?
216+
) -> String {
217+
var output = ""
218+
debugDescriptionBuilder(
219+
elements: [
220+
(.type(CollectionViewDriver.self), 0),
221+
(.options(driver.options), 2),
222+
(.viewModel(driver.viewModel), 2),
223+
(.field(label: "emptyViewProvider", value: emptyViewProvider), 2),
224+
(.field(label: "cellEventCoordinator", value: cellEventCoordinator), 2),
225+
(.field(label: "scrollViewDelegate", value: driver.scrollViewDelegate), 2),
226+
(.field(label: "flowLayoutDelegate", value: driver.flowLayoutDelegate), 2),
227+
(.field(label: "view", value: driver.view), 2),
160228
(.end, 0)
161229
],
162230
to: &output

Tests/TestCollectionViewDriverOptions.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,18 @@ final class TestCollectionViewDriverOptions: XCTestCase {
2222
XCTAssertFalse(options.diffOnBackgroundQueue)
2323
XCTAssertFalse(options.reloadDataOnReplacingViewModel)
2424
}
25+
26+
func test_debugDescription() {
27+
let options = CollectionViewDriverOptions()
28+
XCTAssertEqual(
29+
options.debugDescription,
30+
"""
31+
CollectionViewDriverOptions {
32+
diffOnBackgroundQueue: false
33+
reloadDataOnReplacingViewModel: false
34+
}
35+
36+
"""
37+
)
38+
}
2539
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//
2+
// Created by Jesse Squires
3+
// https://www.jessesquires.com
4+
//
5+
// Documentation
6+
// https://jessesquires.github.io/ReactiveCollectionsKit
7+
//
8+
// GitHub
9+
// https://github.com/jessesquires/ReactiveCollectionsKit
10+
//
11+
// Copyright © 2019-present Jesse Squires
12+
//
13+
14+
import Foundation
15+
@testable import ReactiveCollectionsKit
16+
import XCTest
17+
18+
final class TestDebugDescriptionDriver: XCTestCase {
19+
20+
private static let addressPattern = "0x[0-9a-fA-F]{8,12}" // 0x7b7c00003800 or 0x1509a2270
21+
private static let framePattern = #"\(\d+ \d+; \d+ \d+\)"# // (0 0; 402 874)
22+
private static let contentOffsetPattern = #"\{\d+, \d+\}"# // {0, 0}
23+
private static let contentSizePattern = #"\{\d+, \d+\}"# // {0, 0}
24+
private static let adjustedContentInsetPattern = #"\{\d+, \d+, \d+, \d+\}"# // {0, 0, 0, 0}
25+
26+
private let viewPattern = "<ReactiveCollectionsKitTests\\.FakeCollectionView: \(addressPattern); baseClass = UICollectionView; frame = \(framePattern); clipsToBounds = YES; gestureRecognizers = <NSArray: \(addressPattern)>; backgroundColor = <UIDynamicSystemColor: \(addressPattern); name = systemBackgroundColor>; layer = <CALayer: \(addressPattern)>; contentOffset: \(contentOffsetPattern); contentSize: \(contentSizePattern); adjustedContentInset: \(adjustedContentInsetPattern); layout: <ReactiveCollectionsKitTests\\.FakeCollectionLayout: \(addressPattern)>; dataSource: <ReactiveCollectionsKit\\.DiffableDataSource: \(addressPattern)>>"
27+
28+
private func XCTAssertEqualRegex(
29+
string: String,
30+
pattern: String,
31+
numMatches: Int = 1,
32+
_ message: @autoclosure () -> String = "",
33+
file: StaticString = #filePath,
34+
line: UInt = #line
35+
) throws {
36+
let regex = try NSRegularExpression(pattern: pattern)
37+
let count = regex.numberOfMatches(in: string, range: NSRange(string.startIndex..., in: string))
38+
XCTAssertEqual(count, numMatches, message(), file: file, line: line)
39+
}
40+
41+
@MainActor
42+
func test_empty() throws {
43+
let viewController = FakeCollectionViewController()
44+
let viewModel = self.fakeCollectionViewModel(
45+
id: "viewModel_1",
46+
numSections: 0,
47+
numCells: 0
48+
)
49+
let driver = CollectionViewDriver(
50+
view: viewController.collectionView,
51+
viewModel: viewModel
52+
)
53+
54+
let pattern =
55+
"""
56+
CollectionViewDriver \\{
57+
options:
58+
CollectionViewDriverOptions \\{
59+
diffOnBackgroundQueue: false
60+
reloadDataOnReplacingViewModel: false
61+
\\}
62+
viewModel:
63+
CollectionViewModel \\{
64+
id: viewModel_1
65+
sections: none
66+
registrations: none
67+
isEmpty: true
68+
\\}
69+
emptyViewProvider: nil
70+
cellEventCoordinator: nil
71+
scrollViewDelegate: nil
72+
flowLayoutDelegate: nil
73+
view: \(viewPattern)
74+
\\}
75+
76+
"""
77+
78+
try XCTAssertEqualRegex(string: driver.debugDescription, pattern: pattern)
79+
}
80+
81+
@MainActor
82+
func test_viewModel() throws {
83+
let viewController = FakeCollectionViewController()
84+
let viewModel = self.fakeCollectionViewModel(
85+
id: "viewModel_2",
86+
numSections: 1,
87+
numCells: 1,
88+
includeHeader: true,
89+
includeFooter: true,
90+
includeSupplementaryViews: true
91+
)
92+
let driver = CollectionViewDriver(
93+
view: viewController.collectionView,
94+
viewModel: viewModel
95+
)
96+
97+
let pattern =
98+
"""
99+
CollectionViewDriver \\{
100+
options:
101+
CollectionViewDriverOptions \\{
102+
diffOnBackgroundQueue: false
103+
reloadDataOnReplacingViewModel: false
104+
\\}
105+
viewModel:
106+
CollectionViewModel \\{
107+
id: viewModel_2
108+
sections:
109+
\\[0\\]:
110+
id: section_0
111+
header: Header \\(FakeHeaderViewModel\\)
112+
footer: Footer \\(FakeFooterViewModel\\)
113+
cells:
114+
\\[0\\]: cell_0_0 \\(FakeNumberCellViewModel\\)
115+
supplementary views:
116+
\\[0\\]: view_0_0 \\(FakeSupplementaryViewModel\\)
117+
isEmpty: false
118+
registrations:
119+
- FakeFooterViewModel \\(UICollectionElementKindSectionFooter\\)
120+
- FakeHeaderViewModel \\(UICollectionElementKindSectionHeader\\)
121+
- FakeNumberCellViewModel \\(cell\\)
122+
- FakeSupplementaryViewModel \\(FakeKind\\)
123+
isEmpty: false
124+
\\}
125+
emptyViewProvider: nil
126+
cellEventCoordinator: nil
127+
scrollViewDelegate: nil
128+
flowLayoutDelegate: nil
129+
view: \(viewPattern)
130+
\\}
131+
132+
"""
133+
134+
try XCTAssertEqualRegex(string: driver.debugDescription, pattern: pattern)
135+
}
136+
137+
@MainActor
138+
func test_delegate() throws {
139+
let viewController = FakeCollectionViewController()
140+
let viewModel = self.fakeCollectionViewModel(
141+
id: "viewModel_3",
142+
numSections: 0,
143+
numCells: 0
144+
)
145+
let emptyViewProvider = EmptyViewProvider {
146+
UIView()
147+
}
148+
let cellEventCoordinator = FakeCellEventCoordinator()
149+
let flowLayoutDelegate = FakeFlowLayoutDelegate()
150+
let driver = CollectionViewDriver(
151+
view: viewController.collectionView,
152+
viewModel: viewModel,
153+
emptyViewProvider: emptyViewProvider,
154+
cellEventCoordinator: cellEventCoordinator
155+
)
156+
driver.scrollViewDelegate = flowLayoutDelegate
157+
driver.flowLayoutDelegate = flowLayoutDelegate
158+
159+
let pattern =
160+
"""
161+
CollectionViewDriver \\{
162+
options:
163+
CollectionViewDriverOptions \\{
164+
diffOnBackgroundQueue: false
165+
reloadDataOnReplacingViewModel: false
166+
\\}
167+
viewModel:
168+
CollectionViewModel \\{
169+
id: viewModel_3
170+
sections: none
171+
registrations: none
172+
isEmpty: true
173+
\\}
174+
emptyViewProvider: EmptyViewProvider\\(viewBuilder: \\(Function\\)\\)
175+
cellEventCoordinator: ReactiveCollectionsKitTests\\.FakeCellEventCoordinator
176+
scrollViewDelegate: <ReactiveCollectionsKitTests\\.FakeFlowLayoutDelegate: \(Self.addressPattern)>
177+
flowLayoutDelegate: <ReactiveCollectionsKitTests\\.FakeFlowLayoutDelegate: \(Self.addressPattern)>
178+
view: \(viewPattern)
179+
\\}
180+
181+
"""
182+
183+
try XCTAssertEqualRegex(string: driver.debugDescription, pattern: pattern)
184+
}
185+
}

0 commit comments

Comments
 (0)