Skip to content

Commit 46e19c5

Browse files
nuomi1jessesquires
andauthored
Implement didHighlight and didUnhighlight APIs (#123)
Closes #95 --------- Co-authored-by: Jesse Squires <jesse@jessesquires.com>
1 parent f74a0f4 commit 46e19c5

9 files changed

+151
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ NEXT
77

88
- TBA
99

10+
0.1.5
11+
-----
12+
13+
- Implemented `didHighlight()` and `didUnhighlight()` APIs for `CellViewModel`. ([@nuomi1](https://github.com/nuomi1), [#123](https://github.com/jessesquires/ReactiveCollectionsKit/pull/123))
14+
1015
0.1.4
1116
-----
1217

Sources/CellViewModel.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public protocol CellViewModel: DiffableViewModel, ViewRegistrationProvider {
4444
/// Tells the view model that its cell was removed from the collection view.
4545
/// This corresponds to the delegate method `collectionView(_:didEndDisplaying:forItemAt:)`.
4646
func didEndDisplaying()
47+
48+
/// Tells the view model that its cell was highlighted.
49+
/// This corresponds to the delegate method `collectionView(_:didHighlightItemAt:)`.
50+
func didHighlight()
51+
52+
/// Tells the view model that the highlight was removed from its cell.
53+
/// This corresponds to the delegate method `collectionView(_:didUnhighlightItemAt:)`.
54+
func didUnhighlight()
4755
}
4856

4957
extension CellViewModel {
@@ -65,6 +73,12 @@ extension CellViewModel {
6573

6674
/// Default implementation. Does nothing.
6775
public func didEndDisplaying() { }
76+
77+
/// Default implementation. Does nothing.
78+
public func didHighlight() { }
79+
80+
/// Default implementation. Does nothing.
81+
public func didUnhighlight() { }
6882
}
6983

7084
extension CellViewModel {
@@ -148,6 +162,16 @@ public struct AnyCellViewModel: CellViewModel {
148162
self._didEndDisplaying()
149163
}
150164

165+
/// :nodoc:
166+
public func didHighlight() {
167+
self._didHighlight()
168+
}
169+
170+
/// :nodoc:
171+
public func didUnhighlight() {
172+
self._didUnhighlight()
173+
}
174+
151175
/// :nodoc: "override" the extension
152176
public let cellClass: AnyClass
153177

@@ -165,6 +189,8 @@ public struct AnyCellViewModel: CellViewModel {
165189
private let _didSelect: (CellEventCoordinator?) -> Void
166190
private let _willDisplay: () -> Void
167191
private let _didEndDisplaying: () -> Void
192+
private let _didHighlight: () -> Void
193+
private let _didUnhighlight: () -> Void
168194

169195
// MARK: Init
170196

@@ -190,6 +216,8 @@ public struct AnyCellViewModel: CellViewModel {
190216
}
191217
self._willDisplay = viewModel.willDisplay
192218
self._didEndDisplaying = viewModel.didEndDisplaying
219+
self._didHighlight = viewModel.didHighlight
220+
self._didUnhighlight = viewModel.didUnhighlight
193221
self.cellClass = viewModel.cellClass
194222
self.reuseIdentifier = viewModel.reuseIdentifier
195223
}

Sources/CollectionViewDriver.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,18 @@ extension CollectionViewDriver: UICollectionViewDelegate {
302302
self.viewModel.cellViewModel(at: indexPath).shouldHighlight
303303
}
304304

305+
/// :nodoc:
306+
public func collectionView(_ collectionView: UICollectionView,
307+
didHighlightItemAt indexPath: IndexPath) {
308+
self.viewModel.cellViewModel(at: indexPath).didHighlight()
309+
}
310+
311+
/// :nodoc:
312+
public func collectionView(_ collectionView: UICollectionView,
313+
didUnhighlightItemAt indexPath: IndexPath) {
314+
self.viewModel.cellViewModel(at: indexPath).didUnhighlight()
315+
}
316+
305317
// MARK: Managing context menus
306318

307319
/// :nodoc:

Tests/Fakes/FakeCellNibView.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ struct FakeCellNibViewModel: CellViewModel {
5454
self.expectationDidEndDisplaying?.fulfillAndLog()
5555
}
5656

57+
var expectationDidHighlight: XCTestExpectation?
58+
func didHighlight() {
59+
self.expectationDidHighlight?.fulfillAndLog()
60+
}
61+
62+
var expectationDidUnhighlight: XCTestExpectation?
63+
func didUnhighlight() {
64+
self.expectationDidUnhighlight?.fulfillAndLog()
65+
}
66+
5767
nonisolated static func == (left: Self, right: Self) -> Bool {
5868
left.id == right.id
5969
}

Tests/Fakes/FakeCollectionViewModel.swift

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ extension XCTestCase {
3434
expectDidSelectCell: Bool = false,
3535
expectConfigureCell: Bool = false,
3636
expectWillDisplay: Bool = false,
37-
expectDidEndDisplaying: Bool = false
37+
expectDidEndDisplaying: Bool = false,
38+
expectDidHighlight: Bool = false,
39+
expectDidUnhighlight: Bool = false
3840
) -> CollectionViewModel {
3941
let sections = (0..<numSections).map { sectionIndex in
4042
self.fakeSectionViewModel(
@@ -50,7 +52,9 @@ extension XCTestCase {
5052
expectDidSelectCell: expectDidSelectCell,
5153
expectConfigureCell: expectConfigureCell,
5254
expectWillDisplay: expectWillDisplay,
53-
expectDidEndDisplaying: expectDidEndDisplaying
55+
expectDidEndDisplaying: expectDidEndDisplaying,
56+
expectDidHighlight: expectDidHighlight,
57+
expectDidUnhighlight: expectDidUnhighlight
5458
)
5559
}
5660
return CollectionViewModel(id: "collection_\(id)", sections: sections)
@@ -70,7 +74,9 @@ extension XCTestCase {
7074
expectDidSelectCell: Bool = false,
7175
expectConfigureCell: Bool = false,
7276
expectWillDisplay: Bool = false,
73-
expectDidEndDisplaying: Bool = false
77+
expectDidEndDisplaying: Bool = false,
78+
expectDidHighlight: Bool = false,
79+
expectDidUnhighlight: Bool = false
7480
) -> SectionViewModel {
7581
let cells = self.fakeCellViewModels(
7682
id: cellId,
@@ -80,7 +86,9 @@ extension XCTestCase {
8086
expectDidSelectCell: expectDidSelectCell,
8187
expectConfigureCell: expectConfigureCell,
8288
expectWillDisplay: expectWillDisplay,
83-
expectDidEndDisplaying: expectDidEndDisplaying
89+
expectDidEndDisplaying: expectDidEndDisplaying,
90+
expectDidHighlight: expectDidHighlight,
91+
expectDidUnhighlight: expectDidUnhighlight
8492
)
8593
var header = includeHeader ? FakeHeaderViewModel() : nil
8694
header?.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: "Header")
@@ -114,7 +122,9 @@ extension XCTestCase {
114122
expectDidSelectCell: Bool = false,
115123
expectConfigureCell: Bool = false,
116124
expectWillDisplay: Bool = false,
117-
expectDidEndDisplaying: Bool = false
125+
expectDidEndDisplaying: Bool = false,
126+
expectDidHighlight: Bool = false,
127+
expectDidUnhighlight: Bool = false
118128
) -> [AnyCellViewModel] {
119129
var cells = [AnyCellViewModel]()
120130
for cellIndex in 0..<count {
@@ -125,7 +135,9 @@ extension XCTestCase {
125135
expectDidSelectCell: expectDidSelectCell,
126136
expectConfigureCell: expectConfigureCell,
127137
expectWillDisplay: expectWillDisplay,
128-
expectDidEndDisplaying: expectDidEndDisplaying
138+
expectDidEndDisplaying: expectDidEndDisplaying,
139+
expectDidHighlight: expectDidHighlight,
140+
expectDidUnhighlight: expectDidUnhighlight
129141
)
130142
cells.append(model)
131143
}
@@ -140,14 +152,18 @@ extension XCTestCase {
140152
expectDidSelectCell: Bool,
141153
expectConfigureCell: Bool,
142154
expectWillDisplay: Bool,
143-
expectDidEndDisplaying: Bool
155+
expectDidEndDisplaying: Bool,
156+
expectDidHighlight: Bool,
157+
expectDidUnhighlight: Bool
144158
) -> AnyCellViewModel {
145159
if useNibs {
146160
var viewModel = FakeCellNibViewModel(id: id)
147161
viewModel.expectationDidSelect = self._cellDidSelectExpectation(expect: expectDidSelectCell, id: viewModel.id)
148162
viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id)
149163
viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id)
150164
viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id)
165+
viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id)
166+
viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id)
151167
return viewModel.eraseToAnyViewModel()
152168
}
153169

@@ -157,6 +173,8 @@ extension XCTestCase {
157173
viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id)
158174
viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id)
159175
viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id)
176+
viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id)
177+
viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id)
160178
return viewModel.eraseToAnyViewModel()
161179
}
162180

@@ -165,6 +183,8 @@ extension XCTestCase {
165183
viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id)
166184
viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id)
167185
viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id)
186+
viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id)
187+
viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id)
168188
return viewModel.eraseToAnyViewModel()
169189
}
170190

@@ -187,6 +207,16 @@ extension XCTestCase {
187207
private func _didEndDisplayingExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? {
188208
expect ? self.expectation(description: "didEndDisplaying_\(id)") : nil
189209
}
210+
211+
@MainActor
212+
private func _didHighlightExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? {
213+
expect ? self.expectation(description: "didHighlight_\(id)") : nil
214+
}
215+
216+
@MainActor
217+
private func _didUnhighlightExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? {
218+
expect ? self.expectation(description: "didUnhighlight_\(id)") : nil
219+
}
190220
}
191221

192222
// swiftlint:enable function_parameter_count

Tests/Fakes/FakeNumberModel.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ struct FakeNumberCellViewModel: CellViewModel {
5656
self.expectationDidEndDisplaying?.fulfillAndLog()
5757
}
5858

59+
var expectationDidHighlight: XCTestExpectation?
60+
func didHighlight() {
61+
self.expectationDidHighlight?.fulfillAndLog()
62+
}
63+
64+
var expectationDidUnhighlight: XCTestExpectation?
65+
func didUnhighlight() {
66+
self.expectationDidUnhighlight?.fulfillAndLog()
67+
}
68+
5969
init(model: FakeNumberModel = FakeNumberModel()) {
6070
self.model = model
6171
}

Tests/Fakes/FakeTextModel.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ struct FakeTextCellViewModel: CellViewModel {
5555
self.expectationDidEndDisplaying?.fulfillAndLog()
5656
}
5757

58+
var expectationDidHighlight: XCTestExpectation?
59+
func didHighlight() {
60+
self.expectationDidHighlight?.fulfillAndLog()
61+
}
62+
63+
var expectationDidUnhighlight: XCTestExpectation?
64+
func didUnhighlight() {
65+
self.expectationDidUnhighlight?.fulfillAndLog()
66+
}
67+
5868
init(
5969
model: FakeTextModel = FakeTextModel(),
6070
shouldHighlight: Bool = true,

Tests/TestCellViewModel.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ final class TestCellViewModel: XCTestCase {
5555
viewModel.expectationDidEndDisplaying = self.expectation(name: "did_end_displaying")
5656
viewModel.expectationDidEndDisplaying?.expectedFulfillmentCount = 2
5757

58+
viewModel.expectationDidHighlight = self.expectation(name: "did_highlight")
59+
viewModel.expectationDidHighlight?.expectedFulfillmentCount = 2
60+
61+
viewModel.expectationDidUnhighlight = self.expectation(name: "did_unhighlight")
62+
viewModel.expectationDidUnhighlight?.expectedFulfillmentCount = 2
63+
5864
let erased = viewModel.eraseToAnyViewModel()
5965
XCTAssertEqual(viewModel.hashValue, erased.hashValue)
6066

@@ -69,11 +75,15 @@ final class TestCellViewModel: XCTestCase {
6975
viewModel.didSelect(with: nil)
7076
viewModel.willDisplay()
7177
viewModel.didEndDisplaying()
78+
viewModel.didHighlight()
79+
viewModel.didUnhighlight()
7280

7381
erased.configure(cell: FakeTextCollectionCell())
7482
erased.didSelect(with: nil)
7583
erased.willDisplay()
7684
erased.didEndDisplaying()
85+
erased.didHighlight()
86+
erased.didUnhighlight()
7787

7888
self.waitForExpectations()
7989

Tests/TestCollectionViewDriver.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,35 @@ final class TestCollectionViewDriver: UnitTestCase {
199199
self.keepDriverAlive(driver)
200200
}
201201

202+
@MainActor
203+
func test_delegate_didHighlight_didUnhighlight_calls_cellViewModel() {
204+
let sections = 2
205+
let cells = 5
206+
let model = self.fakeCollectionViewModel(
207+
numSections: sections,
208+
numCells: cells,
209+
expectDidHighlight: true,
210+
expectDidUnhighlight: true
211+
)
212+
let driver = CollectionViewDriver(
213+
view: self.collectionView,
214+
viewModel: model,
215+
options: .test()
216+
)
217+
218+
for section in 0..<sections {
219+
for item in 0..<cells {
220+
let indexPath = IndexPath(item: item, section: section)
221+
driver.collectionView(self.collectionView, didHighlightItemAt: indexPath)
222+
driver.collectionView(self.collectionView, didUnhighlightItemAt: indexPath)
223+
}
224+
}
225+
226+
self.waitForExpectations()
227+
228+
self.keepDriverAlive(driver)
229+
}
230+
202231
@MainActor
203232
func test_dataSource_cellForItemAt_calls_cellViewModel_configure() async {
204233
let viewController = FakeCollectionViewController()

0 commit comments

Comments
 (0)