diff --git a/CHANGELOG.md b/CHANGELOG.md index 30599bd..609e069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ NEXT - TBA +0.1.5 +----- + +- Implemented `didHighlight()` and `didUnhighlight()` APIs for `CellViewModel`. ([@nuomi1](https://github.com/nuomi1), [#123](https://github.com/jessesquires/ReactiveCollectionsKit/pull/123)) + 0.1.4 ----- diff --git a/Sources/CellViewModel.swift b/Sources/CellViewModel.swift index 35ef828..41dfe36 100644 --- a/Sources/CellViewModel.swift +++ b/Sources/CellViewModel.swift @@ -44,6 +44,14 @@ public protocol CellViewModel: DiffableViewModel, ViewRegistrationProvider { /// Tells the view model that its cell was removed from the collection view. /// This corresponds to the delegate method `collectionView(_:didEndDisplaying:forItemAt:)`. func didEndDisplaying() + + /// Tells the view model that its cell was highlighted. + /// This corresponds to the delegate method `collectionView(_:didHighlightItemAt:)`. + func didHighlight() + + /// Tells the view model that the highlight was removed from its cell. + /// This corresponds to the delegate method `collectionView(_:didUnhighlightItemAt:)`. + func didUnhighlight() } extension CellViewModel { @@ -65,6 +73,12 @@ extension CellViewModel { /// Default implementation. Does nothing. public func didEndDisplaying() { } + + /// Default implementation. Does nothing. + public func didHighlight() { } + + /// Default implementation. Does nothing. + public func didUnhighlight() { } } extension CellViewModel { @@ -148,6 +162,16 @@ public struct AnyCellViewModel: CellViewModel { self._didEndDisplaying() } + /// :nodoc: + public func didHighlight() { + self._didHighlight() + } + + /// :nodoc: + public func didUnhighlight() { + self._didUnhighlight() + } + /// :nodoc: "override" the extension public let cellClass: AnyClass @@ -165,6 +189,8 @@ public struct AnyCellViewModel: CellViewModel { private let _didSelect: (CellEventCoordinator?) -> Void private let _willDisplay: () -> Void private let _didEndDisplaying: () -> Void + private let _didHighlight: () -> Void + private let _didUnhighlight: () -> Void // MARK: Init @@ -190,6 +216,8 @@ public struct AnyCellViewModel: CellViewModel { } self._willDisplay = viewModel.willDisplay self._didEndDisplaying = viewModel.didEndDisplaying + self._didHighlight = viewModel.didHighlight + self._didUnhighlight = viewModel.didUnhighlight self.cellClass = viewModel.cellClass self.reuseIdentifier = viewModel.reuseIdentifier } diff --git a/Sources/CollectionViewDriver.swift b/Sources/CollectionViewDriver.swift index 8e9488d..913f925 100644 --- a/Sources/CollectionViewDriver.swift +++ b/Sources/CollectionViewDriver.swift @@ -302,6 +302,18 @@ extension CollectionViewDriver: UICollectionViewDelegate { self.viewModel.cellViewModel(at: indexPath).shouldHighlight } + /// :nodoc: + public func collectionView(_ collectionView: UICollectionView, + didHighlightItemAt indexPath: IndexPath) { + self.viewModel.cellViewModel(at: indexPath).didHighlight() + } + + /// :nodoc: + public func collectionView(_ collectionView: UICollectionView, + didUnhighlightItemAt indexPath: IndexPath) { + self.viewModel.cellViewModel(at: indexPath).didUnhighlight() + } + // MARK: Managing context menus /// :nodoc: diff --git a/Tests/Fakes/FakeCellNibView.swift b/Tests/Fakes/FakeCellNibView.swift index 017c5a4..12ad217 100644 --- a/Tests/Fakes/FakeCellNibView.swift +++ b/Tests/Fakes/FakeCellNibView.swift @@ -54,6 +54,16 @@ struct FakeCellNibViewModel: CellViewModel { self.expectationDidEndDisplaying?.fulfillAndLog() } + var expectationDidHighlight: XCTestExpectation? + func didHighlight() { + self.expectationDidHighlight?.fulfillAndLog() + } + + var expectationDidUnhighlight: XCTestExpectation? + func didUnhighlight() { + self.expectationDidUnhighlight?.fulfillAndLog() + } + nonisolated static func == (left: Self, right: Self) -> Bool { left.id == right.id } diff --git a/Tests/Fakes/FakeCollectionViewModel.swift b/Tests/Fakes/FakeCollectionViewModel.swift index 6d601a8..f06db36 100644 --- a/Tests/Fakes/FakeCollectionViewModel.swift +++ b/Tests/Fakes/FakeCollectionViewModel.swift @@ -34,7 +34,9 @@ extension XCTestCase { expectDidSelectCell: Bool = false, expectConfigureCell: Bool = false, expectWillDisplay: Bool = false, - expectDidEndDisplaying: Bool = false + expectDidEndDisplaying: Bool = false, + expectDidHighlight: Bool = false, + expectDidUnhighlight: Bool = false ) -> CollectionViewModel { let sections = (0.. SectionViewModel { let cells = self.fakeCellViewModels( id: cellId, @@ -80,7 +86,9 @@ extension XCTestCase { expectDidSelectCell: expectDidSelectCell, expectConfigureCell: expectConfigureCell, expectWillDisplay: expectWillDisplay, - expectDidEndDisplaying: expectDidEndDisplaying + expectDidEndDisplaying: expectDidEndDisplaying, + expectDidHighlight: expectDidHighlight, + expectDidUnhighlight: expectDidUnhighlight ) var header = includeHeader ? FakeHeaderViewModel() : nil header?.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: "Header") @@ -114,7 +122,9 @@ extension XCTestCase { expectDidSelectCell: Bool = false, expectConfigureCell: Bool = false, expectWillDisplay: Bool = false, - expectDidEndDisplaying: Bool = false + expectDidEndDisplaying: Bool = false, + expectDidHighlight: Bool = false, + expectDidUnhighlight: Bool = false ) -> [AnyCellViewModel] { var cells = [AnyCellViewModel]() for cellIndex in 0.. AnyCellViewModel { if useNibs { var viewModel = FakeCellNibViewModel(id: id) @@ -148,6 +162,8 @@ extension XCTestCase { viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id) viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id) viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id) + viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id) + viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id) return viewModel.eraseToAnyViewModel() } @@ -157,6 +173,8 @@ extension XCTestCase { viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id) viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id) viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id) + viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id) + viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id) return viewModel.eraseToAnyViewModel() } @@ -165,6 +183,8 @@ extension XCTestCase { viewModel.expectationConfigureCell = self._cellConfigureExpectation(expect: expectConfigureCell, id: viewModel.id) viewModel.expectationWillDisplay = self._willDisplayExpectation(expect: expectWillDisplay, id: viewModel.id) viewModel.expectationDidEndDisplaying = self._didEndDisplayingExpectation(expect: expectDidEndDisplaying, id: viewModel.id) + viewModel.expectationDidHighlight = self._didHighlightExpectation(expect: expectDidHighlight, id: viewModel.id) + viewModel.expectationDidUnhighlight = self._didUnhighlightExpectation(expect: expectDidUnhighlight, id: viewModel.id) return viewModel.eraseToAnyViewModel() } @@ -187,6 +207,16 @@ extension XCTestCase { private func _didEndDisplayingExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? { expect ? self.expectation(description: "didEndDisplaying_\(id)") : nil } + + @MainActor + private func _didHighlightExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? { + expect ? self.expectation(description: "didHighlight_\(id)") : nil + } + + @MainActor + private func _didUnhighlightExpectation(expect: Bool, id: UniqueIdentifier) -> XCTestExpectation? { + expect ? self.expectation(description: "didUnhighlight_\(id)") : nil + } } // swiftlint:enable function_parameter_count diff --git a/Tests/Fakes/FakeNumberModel.swift b/Tests/Fakes/FakeNumberModel.swift index 0925edc..31de7b3 100644 --- a/Tests/Fakes/FakeNumberModel.swift +++ b/Tests/Fakes/FakeNumberModel.swift @@ -56,6 +56,16 @@ struct FakeNumberCellViewModel: CellViewModel { self.expectationDidEndDisplaying?.fulfillAndLog() } + var expectationDidHighlight: XCTestExpectation? + func didHighlight() { + self.expectationDidHighlight?.fulfillAndLog() + } + + var expectationDidUnhighlight: XCTestExpectation? + func didUnhighlight() { + self.expectationDidUnhighlight?.fulfillAndLog() + } + init(model: FakeNumberModel = FakeNumberModel()) { self.model = model } diff --git a/Tests/Fakes/FakeTextModel.swift b/Tests/Fakes/FakeTextModel.swift index d3a12c4..d25a360 100644 --- a/Tests/Fakes/FakeTextModel.swift +++ b/Tests/Fakes/FakeTextModel.swift @@ -55,6 +55,16 @@ struct FakeTextCellViewModel: CellViewModel { self.expectationDidEndDisplaying?.fulfillAndLog() } + var expectationDidHighlight: XCTestExpectation? + func didHighlight() { + self.expectationDidHighlight?.fulfillAndLog() + } + + var expectationDidUnhighlight: XCTestExpectation? + func didUnhighlight() { + self.expectationDidUnhighlight?.fulfillAndLog() + } + init( model: FakeTextModel = FakeTextModel(), shouldHighlight: Bool = true, diff --git a/Tests/TestCellViewModel.swift b/Tests/TestCellViewModel.swift index e4b80d6..7100d13 100644 --- a/Tests/TestCellViewModel.swift +++ b/Tests/TestCellViewModel.swift @@ -55,6 +55,12 @@ final class TestCellViewModel: XCTestCase { viewModel.expectationDidEndDisplaying = self.expectation(name: "did_end_displaying") viewModel.expectationDidEndDisplaying?.expectedFulfillmentCount = 2 + viewModel.expectationDidHighlight = self.expectation(name: "did_highlight") + viewModel.expectationDidHighlight?.expectedFulfillmentCount = 2 + + viewModel.expectationDidUnhighlight = self.expectation(name: "did_unhighlight") + viewModel.expectationDidUnhighlight?.expectedFulfillmentCount = 2 + let erased = viewModel.eraseToAnyViewModel() XCTAssertEqual(viewModel.hashValue, erased.hashValue) @@ -69,11 +75,15 @@ final class TestCellViewModel: XCTestCase { viewModel.didSelect(with: nil) viewModel.willDisplay() viewModel.didEndDisplaying() + viewModel.didHighlight() + viewModel.didUnhighlight() erased.configure(cell: FakeTextCollectionCell()) erased.didSelect(with: nil) erased.willDisplay() erased.didEndDisplaying() + erased.didHighlight() + erased.didUnhighlight() self.waitForExpectations() diff --git a/Tests/TestCollectionViewDriver.swift b/Tests/TestCollectionViewDriver.swift index 435a95c..0a97fae 100644 --- a/Tests/TestCollectionViewDriver.swift +++ b/Tests/TestCollectionViewDriver.swift @@ -199,6 +199,35 @@ final class TestCollectionViewDriver: UnitTestCase { self.keepDriverAlive(driver) } + @MainActor + func test_delegate_didHighlight_didUnhighlight_calls_cellViewModel() { + let sections = 2 + let cells = 5 + let model = self.fakeCollectionViewModel( + numSections: sections, + numCells: cells, + expectDidHighlight: true, + expectDidUnhighlight: true + ) + let driver = CollectionViewDriver( + view: self.collectionView, + viewModel: model, + options: .test() + ) + + for section in 0..