Skip to content

Commit

Permalink
Implement didHighlight and didUnhighlight APIs (#123)
Browse files Browse the repository at this point in the history
Closes #95

---------

Co-authored-by: Jesse Squires <[email protected]>
  • Loading branch information
nuomi1 and jessesquires authored Aug 13, 2024
1 parent f74a0f4 commit 46e19c5
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 7 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----

Expand Down
28 changes: 28 additions & 0 deletions Sources/CellViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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
}
Expand Down
12 changes: 12 additions & 0 deletions Sources/CollectionViewDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions Tests/Fakes/FakeCellNibView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
44 changes: 37 additions & 7 deletions Tests/Fakes/FakeCollectionViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<numSections).map { sectionIndex in
self.fakeSectionViewModel(
Expand All @@ -50,7 +52,9 @@ extension XCTestCase {
expectDidSelectCell: expectDidSelectCell,
expectConfigureCell: expectConfigureCell,
expectWillDisplay: expectWillDisplay,
expectDidEndDisplaying: expectDidEndDisplaying
expectDidEndDisplaying: expectDidEndDisplaying,
expectDidHighlight: expectDidHighlight,
expectDidUnhighlight: expectDidUnhighlight
)
}
return CollectionViewModel(id: "collection_\(id)", sections: sections)
Expand All @@ -70,7 +74,9 @@ extension XCTestCase {
expectDidSelectCell: Bool = false,
expectConfigureCell: Bool = false,
expectWillDisplay: Bool = false,
expectDidEndDisplaying: Bool = false
expectDidEndDisplaying: Bool = false,
expectDidHighlight: Bool = false,
expectDidUnhighlight: Bool = false
) -> SectionViewModel {
let cells = self.fakeCellViewModels(
id: cellId,
Expand All @@ -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")
Expand Down Expand Up @@ -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..<count {
Expand All @@ -125,7 +135,9 @@ extension XCTestCase {
expectDidSelectCell: expectDidSelectCell,
expectConfigureCell: expectConfigureCell,
expectWillDisplay: expectWillDisplay,
expectDidEndDisplaying: expectDidEndDisplaying
expectDidEndDisplaying: expectDidEndDisplaying,
expectDidHighlight: expectDidHighlight,
expectDidUnhighlight: expectDidUnhighlight
)
cells.append(model)
}
Expand All @@ -140,14 +152,18 @@ extension XCTestCase {
expectDidSelectCell: Bool,
expectConfigureCell: Bool,
expectWillDisplay: Bool,
expectDidEndDisplaying: Bool
expectDidEndDisplaying: Bool,
expectDidHighlight: Bool,
expectDidUnhighlight: Bool
) -> AnyCellViewModel {
if useNibs {
var viewModel = FakeCellNibViewModel(id: id)
viewModel.expectationDidSelect = self._cellDidSelectExpectation(expect: expectDidSelectCell, id: viewModel.id)
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()
}

Expand All @@ -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()
}

Expand All @@ -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()
}

Expand All @@ -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
10 changes: 10 additions & 0 deletions Tests/Fakes/FakeNumberModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
10 changes: 10 additions & 0 deletions Tests/Fakes/FakeTextModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions Tests/TestCellViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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()

Expand Down
29 changes: 29 additions & 0 deletions Tests/TestCollectionViewDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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..<sections {
for item in 0..<cells {
let indexPath = IndexPath(item: item, section: section)
driver.collectionView(self.collectionView, didHighlightItemAt: indexPath)
driver.collectionView(self.collectionView, didUnhighlightItemAt: indexPath)
}
}

self.waitForExpectations()

self.keepDriverAlive(driver)
}

@MainActor
func test_dataSource_cellForItemAt_calls_cellViewModel_configure() async {
let viewController = FakeCollectionViewController()
Expand Down

0 comments on commit 46e19c5

Please sign in to comment.