36 | ) {
37 | self.expectationWillEndDrag?.fulfillAndLog()
38 | }
39 |
40 | var expectationDidEndDrag: XCTestExpectation?
41 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
42 | self.expectationDidEndDrag?.fulfillAndLog()
43 | }
44 |
45 | var expectationShouldScrollToTop: XCTestExpectation?
46 | func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
47 | self.expectationShouldScrollToTop?.fulfillAndLog()
48 | return true
49 | }
50 |
51 | var expectationDidScrollToTop: XCTestExpectation?
52 | func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
53 | self.expectationDidScrollToTop?.fulfillAndLog()
54 | }
55 |
56 | var expectationWillBeginDecelerate: XCTestExpectation?
57 | func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
58 | self.expectationWillBeginDecelerate?.fulfillAndLog()
59 | }
60 |
61 | var expectationDidEndDecelerate: XCTestExpectation?
62 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
63 | self.expectationDidEndDecelerate?.fulfillAndLog()
64 | }
65 |
66 | var expectationViewForZoom: XCTestExpectation?
67 | func viewForZooming(in scrollView: UIScrollView) -> UIView? {
68 | self.expectationViewForZoom?.fulfillAndLog()
69 | return nil
70 | }
71 |
72 | var expectationWillBeginZoom: XCTestExpectation?
73 | func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
74 | self.expectationWillBeginZoom?.fulfillAndLog()
75 | }
76 |
77 | var expectationDidEndZoom: XCTestExpectation?
78 | func scrollViewDidEndZooming(_ scrollView: UIScrollView,
79 | with view: UIView?,
80 | atScale scale: CGFloat) {
81 | self.expectationDidEndZoom?.fulfillAndLog()
82 | }
83 |
84 | var expectationDidZoom: XCTestExpectation?
85 | func scrollViewDidZoom(_ scrollView: UIScrollView) {
86 | self.expectationDidZoom?.fulfillAndLog()
87 | }
88 |
89 | var expectationDidEndScrollAnimation: XCTestExpectation?
90 | func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
91 | self.expectationDidEndScrollAnimation?.fulfillAndLog()
92 | }
93 |
94 | var expectationDidChangeInset: XCTestExpectation?
95 | func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
96 | self.expectationDidChangeInset?.fulfillAndLog()
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Tests/Fakes/FakeSupplementaryNib.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Tests/Fakes/FakeSupplementaryNibView.swift:
--------------------------------------------------------------------------------
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 UIKit
17 | import XCTest
18 |
19 | final class FakeSupplementaryNibView: UICollectionReusableView {
20 | @IBOutlet var label: UILabel!
21 | }
22 |
23 | struct FakeSupplementaryNibViewModel: SupplementaryViewModel {
24 | static let kind = "FakeKindWithNib"
25 |
26 | let id: UniqueIdentifier = String.random
27 |
28 | var registration: ViewRegistration {
29 | ViewRegistration(
30 | reuseIdentifier: self.reuseIdentifier,
31 | viewType: .supplementary(kind: Self.kind),
32 | method: .nib(
33 | name: "FakeSupplementaryNib",
34 | bundle: .testBundle
35 | )
36 | )
37 | }
38 |
39 | var expectationConfigureView: XCTestExpectation?
40 | func configure(view: FakeSupplementaryNibView) {
41 | self.expectationConfigureView?.fulfillAndLog()
42 | }
43 |
44 | static func == (left: Self, right: Self) -> Bool {
45 | left.id == right.id
46 | }
47 |
48 | func hash(into hasher: inout Hasher) {
49 | hasher.combine(self.id)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Tests/Fakes/FakeSupplementaryViewModel.swift:
--------------------------------------------------------------------------------
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 | struct FakeSupplementaryViewModel: SupplementaryViewModel {
19 | static let kind = "FakeKind"
20 |
21 | let title: String
22 |
23 | var id: UniqueIdentifier { self.title }
24 |
25 | var registration: ViewRegistration {
26 | ViewRegistration(
27 | reuseIdentifier: self.reuseIdentifier,
28 | supplementaryViewClass: FakeSupplementaryView.self,
29 | kind: Self.kind
30 | )
31 | }
32 |
33 | var expectationConfigureView: XCTestExpectation?
34 | func configure(view: FakeSupplementaryView) {
35 | self.expectationConfigureView?.fulfillAndLog()
36 | }
37 |
38 | var expectationWillDisplay: XCTestExpectation?
39 | func willDisplay() {
40 | self.expectationWillDisplay?.fulfillAndLog()
41 | }
42 |
43 | var expectationDidEndDisplaying: XCTestExpectation?
44 | func didEndDisplaying() {
45 | self.expectationDidEndDisplaying?.fulfillAndLog()
46 | }
47 |
48 | init(title: String = .random) {
49 | self.title = title
50 | }
51 |
52 | static func == (left: Self, right: Self) -> Bool {
53 | left.title == right.title
54 | }
55 |
56 | func hash(into hasher: inout Hasher) {
57 | hasher.combine(self.title)
58 | }
59 | }
60 |
61 | final class FakeSupplementaryView: UICollectionViewCell { }
62 |
63 | struct FakeHeaderViewModel: SupplementaryHeaderViewModel {
64 | let title: String
65 |
66 | var id: UniqueIdentifier { "Header" }
67 |
68 | var expectationConfigureView: XCTestExpectation?
69 | func configure(view: FakeCollectionHeaderView) {
70 | self.expectationConfigureView?.fulfillAndLog()
71 | }
72 |
73 | var expectationWillDisplay: XCTestExpectation?
74 | func willDisplay() {
75 | self.expectationWillDisplay?.fulfillAndLog()
76 | }
77 |
78 | var expectationDidEndDisplaying: XCTestExpectation?
79 | func didEndDisplaying() {
80 | self.expectationDidEndDisplaying?.fulfillAndLog()
81 | }
82 |
83 | init(title: String = .random) {
84 | self.title = title
85 | }
86 |
87 | static func == (left: Self, right: Self) -> Bool {
88 | left.title == right.title
89 | }
90 |
91 | func hash(into hasher: inout Hasher) {
92 | hasher.combine(self.title)
93 | }
94 | }
95 |
96 | final class FakeCollectionHeaderView: UICollectionReusableView { }
97 |
98 | struct FakeFooterViewModel: SupplementaryFooterViewModel {
99 | let title: String
100 |
101 | var id: UniqueIdentifier { "Footer" }
102 |
103 | var expectationConfigureView: XCTestExpectation?
104 | func configure(view: FakeCollectionFooterView) {
105 | self.expectationConfigureView?.fulfillAndLog()
106 | }
107 |
108 | var expectationWillDisplay: XCTestExpectation?
109 | func willDisplay() {
110 | self.expectationWillDisplay?.fulfillAndLog()
111 | }
112 |
113 | var expectationDidEndDisplaying: XCTestExpectation?
114 | func didEndDisplaying() {
115 | self.expectationDidEndDisplaying?.fulfillAndLog()
116 | }
117 |
118 | init(title: String = .random) {
119 | self.title = title
120 | }
121 |
122 | static func == (left: Self, right: Self) -> Bool {
123 | left.title == right.title
124 | }
125 |
126 | func hash(into hasher: inout Hasher) {
127 | hasher.combine(self.title)
128 | }
129 | }
130 |
131 | final class FakeCollectionFooterView: UICollectionReusableView { }
132 |
--------------------------------------------------------------------------------
/Tests/Fakes/FakeTextModel.swift:
--------------------------------------------------------------------------------
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 | import ReactiveCollectionsKit
16 | import UIKit
17 | import XCTest
18 |
19 | struct FakeTextModel: Hashable {
20 | let text: String
21 |
22 | init(text: String = .random) {
23 | self.text = text
24 | }
25 | }
26 |
27 | struct FakeTextCellViewModel: CellViewModel {
28 | let model: FakeTextModel
29 |
30 | var id: UniqueIdentifier {
31 | self.model.text
32 | }
33 |
34 | var shouldSelect = true
35 |
36 | var shouldDeselect = true
37 |
38 | var shouldHighlight = true
39 |
40 | var contextMenuConfiguration: UIContextMenuConfiguration?
41 |
42 | var expectationConfigureCell: XCTestExpectation?
43 | func configure(cell: FakeTextCollectionCell) {
44 | self.expectationConfigureCell?.fulfillAndLog()
45 | }
46 |
47 | var expectationDidSelect: XCTestExpectation?
48 | func didSelect(with coordinator: (any CellEventCoordinator)?) {
49 | self.expectationDidSelect?.fulfillAndLog()
50 | }
51 |
52 | var expectationDidDeselect: XCTestExpectation?
53 | func didDeselect(with coordinator: (any CellEventCoordinator)?) {
54 | self.expectationDidDeselect?.fulfillAndLog()
55 | }
56 |
57 | var expectationWillDisplay: XCTestExpectation?
58 | func willDisplay() {
59 | self.expectationWillDisplay?.fulfillAndLog()
60 | }
61 |
62 | var expectationDidEndDisplaying: XCTestExpectation?
63 | func didEndDisplaying() {
64 | self.expectationDidEndDisplaying?.fulfillAndLog()
65 | }
66 |
67 | var expectationDidHighlight: XCTestExpectation?
68 | func didHighlight() {
69 | self.expectationDidHighlight?.fulfillAndLog()
70 | }
71 |
72 | var expectationDidUnhighlight: XCTestExpectation?
73 | func didUnhighlight() {
74 | self.expectationDidUnhighlight?.fulfillAndLog()
75 | }
76 |
77 | init(
78 | model: FakeTextModel = FakeTextModel(),
79 | shouldSelect: Bool = true,
80 | shouldDeselect: Bool = true,
81 | shouldHighlight: Bool = true,
82 | contextMenuConfiguration: UIContextMenuConfiguration? = nil
83 | ) {
84 | self.model = model
85 | self.shouldSelect = shouldSelect
86 | self.shouldDeselect = shouldDeselect
87 | self.shouldHighlight = shouldHighlight
88 | self.contextMenuConfiguration = contextMenuConfiguration
89 | }
90 |
91 | static func == (left: Self, right: Self) -> Bool {
92 | left.model == right.model
93 | }
94 |
95 | func hash(into hasher: inout Hasher) {
96 | hasher.combine(self.model)
97 | }
98 | }
99 |
100 | final class FakeTextCollectionCell: UICollectionViewCell { }
101 |
--------------------------------------------------------------------------------
/Tests/TestCellEventCoordinator.swift:
--------------------------------------------------------------------------------
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 TestCellEventCoordinator: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_underlyingViewController() {
22 | class CustomVC: UIViewController, CellEventCoordinator { }
23 | let controller = CustomVC()
24 | XCTAssertEqual(controller.underlyingViewController, controller)
25 |
26 | let coordinator = FakeCellEventCoordinator()
27 | XCTAssertNil(coordinator.underlyingViewController)
28 | }
29 |
30 | @MainActor
31 | func test_didSelectCell_getsCalled() {
32 | let cell = FakeCellViewModel()
33 | let section = SectionViewModel(id: "id", cells: [cell])
34 | let model = CollectionViewModel(id: "id", sections: [section])
35 |
36 | let coordinator = FakeCellEventCoordinator()
37 | coordinator.expectationDidSelect = self.expectation()
38 |
39 | let driver = CollectionViewDriver(
40 | view: self.collectionView,
41 | viewModel: model,
42 | options: .test(),
43 | cellEventCoordinator: coordinator
44 | )
45 |
46 | let indexPath = IndexPath(item: 0, section: 0)
47 | driver.collectionView(self.collectionView, didSelectItemAt: indexPath)
48 |
49 | XCTAssertEqual(coordinator.selectedCell as! FakeCellViewModel, cell)
50 |
51 | self.waitForExpectations()
52 |
53 | self.keepDriverAlive(driver)
54 | }
55 |
56 | @MainActor
57 | func test_didDeselectCell_getsCalled() {
58 | let cell = FakeCellViewModel()
59 | let section = SectionViewModel(id: "id", cells: [cell])
60 | let model = CollectionViewModel(id: "id", sections: [section])
61 |
62 | let coordinator = FakeCellEventCoordinator()
63 | coordinator.expectationDidDeselect = self.expectation()
64 |
65 | let driver = CollectionViewDriver(
66 | view: self.collectionView,
67 | viewModel: model,
68 | options: .test(),
69 | cellEventCoordinator: coordinator
70 | )
71 |
72 | let indexPath = IndexPath(item: 0, section: 0)
73 | driver.collectionView(self.collectionView, didDeselectItemAt: indexPath)
74 |
75 | XCTAssertEqual(coordinator.deselectedCell as! FakeCellViewModel, cell)
76 |
77 | self.waitForExpectations()
78 |
79 | self.keepDriverAlive(driver)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Tests/TestCollectionExtensions.swift:
--------------------------------------------------------------------------------
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 TestCollectionExtensions: XCTestCase {
19 |
20 | func test_isNotEmpty() {
21 | XCTAssertTrue([1, 2, 3].isNotEmpty)
22 | XCTAssertFalse([].isNotEmpty)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/TestCollectionViewConstants.swift:
--------------------------------------------------------------------------------
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 UIKit
17 | import XCTest
18 |
19 | final class TestCollectionViewConstants: XCTestCase {
20 |
21 | @MainActor
22 | func test_header() {
23 | XCTAssertEqual(CollectionViewConstants.headerKind, UICollectionView.elementKindSectionHeader)
24 | }
25 |
26 | @MainActor
27 | func test_footer() {
28 | XCTAssertEqual(CollectionViewConstants.footerKind, UICollectionView.elementKindSectionFooter)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Tests/TestCollectionViewDriverOptions.swift:
--------------------------------------------------------------------------------
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 TestCollectionViewDriverOptions: XCTestCase {
19 |
20 | func test_defaultValues() {
21 | let options = CollectionViewDriverOptions()
22 | XCTAssertFalse(options.diffOnBackgroundQueue)
23 | XCTAssertFalse(options.reloadDataOnReplacingViewModel)
24 | }
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 | let options2 = CollectionViewDriverOptions(
39 | diffOnBackgroundQueue: true,
40 | reloadDataOnReplacingViewModel: true
41 | )
42 | XCTAssertEqual(
43 | options2.debugDescription,
44 | """
45 | CollectionViewDriverOptions {
46 | diffOnBackgroundQueue: true
47 | reloadDataOnReplacingViewModel: true
48 | }
49 | """
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Tests/TestCollectionViewDriverReconfigure.swift:
--------------------------------------------------------------------------------
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 TestCollectionViewDriverReconfigure: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_reconfigure_item() async {
22 | var uniqueCell = MyStaticCellViewModel(name: "initial")
23 | uniqueCell.expectation = self.expectation(field: .configure, id: uniqueCell.name)
24 |
25 | let numberCells = (1...5).map { _ in
26 | var viewModel = FakeNumberCellViewModel()
27 | viewModel.expectationConfigureCell = self.expectation(field: .configure, id: viewModel.id)
28 | return viewModel
29 | }
30 |
31 | let section1 = SectionViewModel(id: "one", cells: numberCells)
32 | let section2 = SectionViewModel(id: "two", cells: [uniqueCell])
33 | let section3 = self.fakeSectionViewModel(id: "three")
34 | let model = CollectionViewModel(id: "id", sections: [section1, section2, section3])
35 |
36 | let viewController = FakeCollectionViewController()
37 | let driver = CollectionViewDriver(view: viewController.collectionView, viewModel: model)
38 | self.simulateAppearance(viewController: viewController)
39 | self.waitForExpectations()
40 |
41 | // Update one cell to be reconfigured
42 | uniqueCell = MyStaticCellViewModel(name: "updated")
43 | uniqueCell.expectation = self.expectation(field: .configure, id: uniqueCell.name)
44 | let updatedSection = SectionViewModel(id: "two", cells: [uniqueCell])
45 | let updatedModel = CollectionViewModel(id: "id", sections: [section1, updatedSection])
46 | await driver.update(viewModel: updatedModel)
47 | self.waitForExpectations()
48 |
49 | self.keepDriverAlive(driver)
50 | }
51 |
52 | @MainActor
53 | func test_reconfigure_header_footer() {
54 | let viewController = FakeCollectionViewController()
55 | viewController.collectionView.setCollectionViewLayout(
56 | UICollectionViewCompositionalLayout.fakeLayout(addSupplementaryViews: false),
57 | animated: false
58 | )
59 |
60 | let driver = CollectionViewDriver(view: viewController.collectionView, options: .test())
61 |
62 | // Initial header and footer
63 | var header = FakeHeaderViewModel()
64 | header.expectationConfigureView = self.expectation(field: .configure, id: "initial_header")
65 | var footer = FakeFooterViewModel()
66 | footer.expectationConfigureView = self.expectation(field: .configure, id: "initial_footer")
67 | let cells = [FakeNumberCellViewModel()]
68 | let section = SectionViewModel(id: "id", cells: cells, header: header, footer: footer)
69 | let model = CollectionViewModel(id: "id", sections: [section])
70 |
71 | driver.update(viewModel: model)
72 | self.simulateAppearance(viewController: viewController)
73 | self.waitForExpectations()
74 |
75 | // Update header and footer to be reconfigured
76 | var updatedHeader = FakeHeaderViewModel()
77 | updatedHeader.expectationConfigureView = self.expectation(field: .configure, id: "updated_header")
78 | var updatedFooter = FakeFooterViewModel()
79 | updatedFooter.expectationConfigureView = self.expectation(field: .configure, id: "updated_footer")
80 | let updatedSection = SectionViewModel(id: "id", cells: cells, header: updatedHeader, footer: updatedFooter)
81 | let updatedModel = CollectionViewModel(id: "id", sections: [updatedSection])
82 |
83 | driver.update(viewModel: updatedModel)
84 | self.waitForExpectations()
85 |
86 | self.keepDriverAlive(driver)
87 | }
88 | }
89 |
90 | private struct MyStaticCellViewModel: CellViewModel {
91 | let id: UniqueIdentifier = "MyCellViewModel"
92 | let name: String
93 |
94 | var expectation: XCTestExpectation?
95 | func configure(cell: FakeCollectionCell) {
96 | expectation?.fulfillAndLog()
97 | }
98 |
99 | static func == (left: Self, right: Self) -> Bool {
100 | left.name == right.name
101 | }
102 |
103 | func hash(into hasher: inout Hasher) {
104 | hasher.combine(self.name)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Tests/TestDiffableSnapshot.swift:
--------------------------------------------------------------------------------
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 TestDiffableSnapshot: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_init() {
22 | let model = self.fakeCollectionViewModel()
23 | let sectionIds = Set(model.allSectionsByIdentifier().keys)
24 | let itemIds = Set(model.allCellsByIdentifier().keys)
25 |
26 | let snapshot = DiffableSnapshot(viewModel: model)
27 | XCTAssertEqual(Set(snapshot.sectionIdentifiers), sectionIds)
28 | XCTAssertEqual(Set(snapshot.itemIdentifiers), itemIds)
29 |
30 | for sectionId in snapshot.sectionIdentifiers {
31 | let section = model.sectionViewModel(for: sectionId)
32 | XCTAssertNotNil(section)
33 |
34 | for itemId in snapshot.itemIdentifiers(inSection: sectionId) {
35 | let cell = section?.cellViewModel(for: itemId)
36 | XCTAssertNotNil(cell)
37 | }
38 | }
39 | }
40 |
41 | @MainActor
42 | func test_init_empty() {
43 | let snapshot = DiffableSnapshot(viewModel: .empty)
44 | XCTAssertTrue(snapshot.sectionIdentifiers.isEmpty)
45 | XCTAssertTrue(snapshot.itemIdentifiers.isEmpty)
46 | }
47 |
48 | @MainActor
49 | func test_init_perf() {
50 | let model = self.fakeCollectionViewModel(numSections: 10, numCells: 10_000)
51 | self.measure {
52 | let snapshot = DiffableSnapshot(viewModel: model)
53 | XCTAssertEqual(snapshot.numberOfItems, 100_000)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Tests/TestEmptyView.swift:
--------------------------------------------------------------------------------
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 TestEmptyView: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_provider() {
22 | let view = FakeEmptyView()
23 | let provider = EmptyViewProvider {
24 | view
25 | }
26 |
27 | XCTAssertIdentical(provider.view, view)
28 | }
29 |
30 | @MainActor
31 | func test_driver_displaysEmptyView() {
32 | let emptyView = FakeEmptyView()
33 | let provider = EmptyViewProvider {
34 | emptyView
35 | }
36 |
37 | let viewController = FakeCollectionViewController()
38 | let driver = CollectionViewDriver(
39 | view: viewController.collectionView,
40 | emptyViewProvider: provider
41 | )
42 |
43 | XCTAssertTrue(driver.viewModel.isEmpty)
44 |
45 | self.simulateAppearance(viewController: viewController)
46 |
47 | // Begin in empty state
48 | XCTAssertTrue(driver.view.subviews.contains(where: { $0 === emptyView }))
49 |
50 | // Update to non-empty model
51 | let nonEmptyExpectation = self.expectation(name: "non_empty")
52 | let model = self.fakeCollectionViewModel()
53 | driver.update(viewModel: model, animated: true) { _ in
54 | nonEmptyExpectation.fulfillAndLog()
55 | }
56 | self.waitForExpectations()
57 | XCTAssertTrue(driver.viewModel.isNotEmpty)
58 | XCTAssertFalse(driver.view.subviews.contains(where: { $0 === emptyView }))
59 |
60 | // Update to empty model
61 | let animationExpectation = self.expectation(name: "animation")
62 | driver.update(viewModel: .empty, animated: true) { _ in
63 | animationExpectation.fulfillAndLog()
64 | }
65 | self.waitForExpectations()
66 | XCTAssertTrue(driver.viewModel.isEmpty)
67 | XCTAssertTrue(driver.view.subviews.contains(where: { $0 === emptyView }))
68 |
69 | // Update to empty model "again"
70 | // already displaying empty view, should return early
71 | // also test completion block
72 | let completionExpectation = self.expectation(name: "completion")
73 | driver.update(viewModel: .empty, animated: false) { _ in
74 | completionExpectation.fulfillAndLog()
75 | }
76 | self.waitForExpectations()
77 | XCTAssertTrue(driver.viewModel.isEmpty)
78 | let emptyViews = driver.view.subviews.filter { $0 is FakeEmptyView }
79 | XCTAssertEqual(emptyViews.count, 1)
80 |
81 | self.keepDriverAlive(driver)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Tests/TestFlowLayoutDelegate.swift:
--------------------------------------------------------------------------------
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 TestFlowLayoutDelegate: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_forwardsEvents_to_flowLayoutDelegate() {
22 | let model = self.fakeCollectionViewModel()
23 | let driver = CollectionViewDriver(
24 | view: self.collectionView,
25 | viewModel: model,
26 | options: .test()
27 | )
28 |
29 | let flowLayoutDelegate = FakeFlowLayoutDelegate()
30 | driver.flowLayoutDelegate = flowLayoutDelegate
31 |
32 | // Setup all expectations
33 | flowLayoutDelegate.expectationSizeForItem = self.expectation(name: "size_for_item")
34 | flowLayoutDelegate.expectationInsetForSection = self.expectation(name: "inset_for_section")
35 | flowLayoutDelegate.expectationMinimumLineSpacing = self.expectation(name: "line_spacing")
36 | flowLayoutDelegate.expectationMinimumInteritemSpacing = self.expectation(name: "inter_item_spacing")
37 | flowLayoutDelegate.expectationSizeForHeader = self.expectation(name: "size_for_header")
38 | flowLayoutDelegate.expectationSizeForFooter = self.expectation(name: "size_for_footer")
39 |
40 | // Call all delegate methods
41 | let collectionView = self.collectionView
42 | let layout = self.layout
43 | let indexPath = IndexPath(item: 0, section: 0)
44 |
45 | _ = driver.collectionView(collectionView, layout: layout, sizeForItemAt: indexPath)
46 | _ = driver.collectionView(collectionView, layout: layout, insetForSectionAt: 0)
47 | _ = driver.collectionView(collectionView, layout: layout, minimumLineSpacingForSectionAt: 0)
48 | _ = driver.collectionView(collectionView, layout: layout, minimumInteritemSpacingForSectionAt: 0)
49 | _ = driver.collectionView(collectionView, layout: layout, referenceSizeForHeaderInSection: 0)
50 | _ = driver.collectionView(collectionView, layout: layout, referenceSizeForFooterInSection: 0)
51 |
52 | // Verify expectations
53 | self.waitForExpectations()
54 | }
55 |
56 | @MainActor
57 | func test_delegateMethods_returnLayoutProperties_whenNoDelegateIsSet() {
58 | let model = self.fakeCollectionViewModel()
59 | let driver = CollectionViewDriver(
60 | view: self.collectionView,
61 | viewModel: model,
62 | options: .test()
63 | )
64 |
65 | self.layout.itemSize = CGSize(width: 100, height: 100)
66 | self.layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
67 | self.layout.minimumLineSpacing = 42
68 | self.layout.minimumInteritemSpacing = 42
69 | self.layout.headerReferenceSize = CGSize(width: 400, height: 200)
70 | self.layout.footerReferenceSize = CGSize(width: 400, height: 100)
71 |
72 | let collectionView = self.collectionView
73 | let layout = collectionView.collectionViewLayout
74 | let indexPath = IndexPath(item: 0, section: 0)
75 |
76 | let size = driver.collectionView(collectionView, layout: layout, sizeForItemAt: indexPath)
77 | XCTAssertEqual(size, self.layout.itemSize)
78 |
79 | let inset = driver.collectionView(collectionView, layout: layout, insetForSectionAt: 0)
80 | XCTAssertEqual(inset, self.layout.sectionInset)
81 |
82 | let lineSpacing = driver.collectionView(collectionView, layout: layout, minimumLineSpacingForSectionAt: 0)
83 | XCTAssertEqual(lineSpacing, self.layout.minimumLineSpacing)
84 |
85 | let itemSpacing = driver.collectionView(collectionView, layout: layout, minimumInteritemSpacingForSectionAt: 0)
86 | XCTAssertEqual(itemSpacing, self.layout.minimumInteritemSpacing)
87 |
88 | let headerSize = driver.collectionView(collectionView, layout: layout, referenceSizeForHeaderInSection: 0)
89 | XCTAssertEqual(headerSize, self.layout.headerReferenceSize)
90 |
91 | let footerSize = driver.collectionView(collectionView, layout: layout, referenceSizeForFooterInSection: 0)
92 | XCTAssertEqual(footerSize, self.layout.footerReferenceSize)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Tests/TestPlans/UnitTests.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "C9B16F27-B6EF-4110-8C39-2889F7D56870",
5 | "name" : "Default",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 | "codeCoverage" : {
13 | "targets" : [
14 | {
15 | "containerPath" : "container:ReactiveCollectionsKit.xcodeproj",
16 | "identifier" : "88FA48D92363A6160061F8B2",
17 | "name" : "ReactiveCollectionsKit"
18 | }
19 | ]
20 | },
21 | "testExecutionOrdering" : "random",
22 | "testRepetitionMode" : "retryOnFailure",
23 | "testTimeoutsEnabled" : true,
24 | "threadSanitizerEnabled" : true,
25 | "undefinedBehaviorSanitizerEnabled" : true
26 | },
27 | "testTargets" : [
28 | {
29 | "target" : {
30 | "containerPath" : "container:ReactiveCollectionsKit.xcodeproj",
31 | "identifier" : "88FA48E22363A6160061F8B2",
32 | "name" : "ReactiveCollectionsKitTests"
33 | }
34 | }
35 | ],
36 | "version" : 1
37 | }
38 |
--------------------------------------------------------------------------------
/Tests/TestScrollViewDelegate.swift:
--------------------------------------------------------------------------------
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 TestScrollViewDelegate: UnitTestCase, @unchecked Sendable {
19 |
20 | @MainActor
21 | func test_forwardsEvents_to_scrollViewDelegate() {
22 | let model = self.fakeCollectionViewModel()
23 | let driver = CollectionViewDriver(
24 | view: self.collectionView,
25 | viewModel: model,
26 | options: .test()
27 | )
28 |
29 | let scrollViewDelegate = FakeScrollViewDelegate()
30 | driver.scrollViewDelegate = scrollViewDelegate
31 |
32 | // Setup all expectations
33 | scrollViewDelegate.expectationDidScroll = self.expectation(name: "did_scroll")
34 | scrollViewDelegate.expectationWillBeginDrag = self.expectation(name: "will_begin_drag")
35 | scrollViewDelegate.expectationWillEndDrag = self.expectation(name: "will_end_drag")
36 | scrollViewDelegate.expectationDidEndDrag = self.expectation(name: "did_end_drag")
37 | scrollViewDelegate.expectationShouldScrollToTop = self.expectation(name: "should_scroll_top")
38 | scrollViewDelegate.expectationDidScrollToTop = self.expectation(name: "did_scroll_top")
39 | scrollViewDelegate.expectationWillBeginDecelerate = self.expectation(name: "will_begin_decelerate")
40 | scrollViewDelegate.expectationDidEndDecelerate = self.expectation(name: "did_end_decelerate")
41 | scrollViewDelegate.expectationViewForZoom = self.expectation(name: "view_for_zoom")
42 | scrollViewDelegate.expectationWillBeginZoom = self.expectation(name: "will_begin_zoom")
43 | scrollViewDelegate.expectationDidEndZoom = self.expectation(name: "did_end_zoom")
44 | scrollViewDelegate.expectationDidZoom = self.expectation(name: "did_zoom")
45 | scrollViewDelegate.expectationDidEndScrollAnimation = self.expectation(name: "did_end_scroll_animation")
46 | scrollViewDelegate.expectationDidChangeInset = self.expectation(name: "did_change_inset")
47 |
48 | // Call all delegate methods
49 | let scrollView = self.collectionView
50 | driver.scrollViewDidScroll(scrollView)
51 | driver.scrollViewWillBeginDragging(scrollView)
52 |
53 | var offset = CGPoint.zero
54 | withUnsafeMutablePointer(to: &offset) { pointer in
55 | driver.scrollViewWillEndDragging(scrollView, withVelocity: .zero, targetContentOffset: pointer)
56 | }
57 |
58 | driver.scrollViewDidEndDragging(scrollView, willDecelerate: true)
59 | _ = driver.scrollViewShouldScrollToTop(scrollView)
60 | driver.scrollViewDidScrollToTop(scrollView)
61 | driver.scrollViewWillBeginDecelerating(scrollView)
62 | driver.scrollViewDidEndDecelerating(scrollView)
63 | _ = driver.viewForZooming(in: scrollView)
64 | driver.scrollViewWillBeginZooming(scrollView, with: nil)
65 | driver.scrollViewDidEndZooming(scrollView, with: nil, atScale: 1)
66 | driver.scrollViewDidZoom(scrollView)
67 | driver.scrollViewDidEndScrollingAnimation(scrollView)
68 | driver.scrollViewDidChangeAdjustedContentInset(scrollView)
69 |
70 | // Verify expectations
71 | self.waitForExpectations()
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Tests/TestSupplementaryViewModel.swift:
--------------------------------------------------------------------------------
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 TestSupplementaryViewModel: XCTestCase {
19 |
20 | @MainActor
21 | func test_SupplementaryViewModel_protocol_extension() {
22 | let viewModel = FakeSupplementaryViewModel()
23 | XCTAssert(viewModel.viewClass == FakeSupplementaryView.self)
24 | XCTAssertEqual(viewModel.reuseIdentifier, "FakeSupplementaryViewModel")
25 | }
26 |
27 | // swiftlint:disable xct_specific_matcher
28 | @MainActor
29 | func test_eraseToAnyViewModel() {
30 | var viewModel = FakeSupplementaryViewModel()
31 | viewModel.expectationConfigureView = self.expectation(field: .configure, id: viewModel.id)
32 | viewModel.expectationConfigureView?.expectedFulfillmentCount = 2
33 |
34 | viewModel.expectationWillDisplay = self.expectation(field: .willDisplay, id: viewModel.id)
35 | viewModel.expectationWillDisplay?.expectedFulfillmentCount = 2
36 |
37 | viewModel.expectationDidEndDisplaying = self.expectation(field: .didEndDisplaying, id: viewModel.id)
38 | viewModel.expectationDidEndDisplaying?.expectedFulfillmentCount = 2
39 |
40 | let erased = viewModel.eraseToAnyViewModel()
41 | XCTAssertEqual(erased.id, viewModel.id)
42 | XCTAssertEqual(erased.hashValue, viewModel.hashValue)
43 | XCTAssertEqual(erased.registration, viewModel.registration)
44 | XCTAssertTrue(erased.viewClass == viewModel.viewClass)
45 | XCTAssertEqual(erased.reuseIdentifier, viewModel.reuseIdentifier)
46 |
47 | viewModel.configure(view: FakeSupplementaryView())
48 | viewModel.willDisplay()
49 | viewModel.didEndDisplaying()
50 |
51 | erased.configure(view: FakeSupplementaryView())
52 | erased.willDisplay()
53 | erased.didEndDisplaying()
54 |
55 | self.waitForExpectations()
56 |
57 | let erased2 = viewModel.eraseToAnyViewModel()
58 | XCTAssertEqual(erased, erased2)
59 | XCTAssertEqual(erased.hashValue, erased2.hashValue)
60 |
61 | XCTAssertNotEqual(erased, FakeSupplementaryViewModel().eraseToAnyViewModel())
62 | XCTAssertNotEqual(erased.hashValue, FakeSupplementaryViewModel().eraseToAnyViewModel().hashValue)
63 |
64 | let erased3 = viewModel.eraseToAnyViewModel().eraseToAnyViewModel()
65 | XCTAssertEqual(erased3, erased)
66 | XCTAssertEqual(erased3.hashValue, erased.hashValue)
67 | XCTAssertEqual(erased3.id, viewModel.id)
68 | XCTAssertEqual(erased3.hashValue, viewModel.hashValue)
69 | XCTAssertEqual(erased3.registration, viewModel.registration)
70 | XCTAssertTrue(erased3.viewClass == viewModel.viewClass)
71 | XCTAssertEqual(erased3.reuseIdentifier, viewModel.reuseIdentifier)
72 |
73 | let erased4 = (viewModel.eraseToAnyViewModel() as (any SupplementaryViewModel)).eraseToAnyViewModel()
74 | XCTAssertEqual(erased4, erased)
75 | XCTAssertEqual(erased4.hashValue, erased.hashValue)
76 | XCTAssertEqual(erased4.id, viewModel.id)
77 | XCTAssertEqual(erased4.hashValue, viewModel.hashValue)
78 | XCTAssertEqual(erased4.registration, viewModel.registration)
79 | XCTAssertTrue(erased4.viewClass == viewModel.viewClass)
80 | XCTAssertEqual(erased4.reuseIdentifier, viewModel.reuseIdentifier)
81 |
82 | let anyViewModel5 = AnySupplementaryViewModel(erased2)
83 | XCTAssertEqual(erased, anyViewModel5)
84 | XCTAssertEqual(erased.hashValue, anyViewModel5.hashValue)
85 |
86 | let anyViewModel6 = AnySupplementaryViewModel(erased3)
87 | XCTAssertEqual(erased, anyViewModel6)
88 | XCTAssertEqual(erased.hashValue, anyViewModel6.hashValue)
89 |
90 | let anyViewModel7 = AnySupplementaryViewModel(erased4)
91 | XCTAssertEqual(erased, anyViewModel7)
92 | XCTAssertEqual(erased.hashValue, anyViewModel7.hashValue)
93 | }
94 | // swiftlint:enable xct_specific_matcher
95 |
96 | @MainActor
97 | func test_header() {
98 | XCTAssertEqual(FakeHeaderViewModel.kind, UICollectionView.elementKindSectionHeader)
99 |
100 | let viewModel = FakeHeaderViewModel()
101 | XCTAssertEqual(viewModel._kind, UICollectionView.elementKindSectionHeader)
102 |
103 | let expected = ViewRegistration(
104 | reuseIdentifier: "FakeHeaderViewModel",
105 | supplementaryViewClass: FakeCollectionHeaderView.self,
106 | kind: UICollectionView.elementKindSectionHeader
107 | )
108 | XCTAssertEqual(viewModel.registration, expected)
109 | }
110 |
111 | @MainActor
112 | func test_footer() {
113 | XCTAssertEqual(FakeFooterViewModel.kind, UICollectionView.elementKindSectionFooter)
114 |
115 | let viewModel = FakeFooterViewModel()
116 | XCTAssertEqual(viewModel._kind, UICollectionView.elementKindSectionFooter)
117 |
118 | let expected = ViewRegistration(
119 | reuseIdentifier: "FakeFooterViewModel",
120 | supplementaryViewClass: FakeCollectionFooterView.self,
121 | kind: UICollectionView.elementKindSectionFooter
122 | )
123 | XCTAssertEqual(viewModel.registration, expected)
124 | }
125 |
126 | @MainActor
127 | func test_debugDescription() {
128 | let cell = FakeSupplementaryViewModel().eraseToAnyViewModel()
129 | print(cell.debugDescription)
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Tests/TestViewRegistration.swift:
--------------------------------------------------------------------------------
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 TestViewRegistration: XCTestCase {
19 |
20 | private class TestView: UICollectionReusableView { }
21 |
22 | @MainActor
23 | func test_convenience_init_class_cell() {
24 | let id = "test"
25 | let registration = ViewRegistration(reuseIdentifier: id, cellClass: TestView.self)
26 |
27 | XCTAssertEqual(registration.reuseIdentifier, id)
28 | XCTAssertEqual(registration.viewType, .cell)
29 | XCTAssertEqual(registration.method, .viewClass(TestView.self))
30 | }
31 |
32 | @MainActor
33 | func test_convenience_init_nib_cell() {
34 | let id = "test"
35 | let nib = "nib"
36 | let registration = ViewRegistration(reuseIdentifier: id, cellNibName: nib)
37 |
38 | XCTAssertEqual(registration.reuseIdentifier, id)
39 | XCTAssertEqual(registration.viewType, .cell)
40 | XCTAssertEqual(registration.method, .nib(name: nib, bundle: nil))
41 | }
42 |
43 | @MainActor
44 | func test_convenience_init_class_supplementary() {
45 | let id = "test"
46 | let kind = "kind"
47 | let registration = ViewRegistration(reuseIdentifier: id, supplementaryViewClass: TestView.self, kind: kind)
48 |
49 | XCTAssertEqual(registration.reuseIdentifier, id)
50 | XCTAssertEqual(registration.viewType, .supplementary(kind: kind))
51 | XCTAssertEqual(registration.method, .viewClass(TestView.self))
52 | }
53 |
54 | @MainActor
55 | func test_convenience_init_nib_supplementary() {
56 | let id = "test"
57 | let nib = "nib"
58 | let kind = "kind"
59 | let registration = ViewRegistration(reuseIdentifier: id, supplementaryViewNibName: nib, kind: kind)
60 |
61 | XCTAssertEqual(registration.reuseIdentifier, id)
62 | XCTAssertEqual(registration.viewType, .supplementary(kind: kind))
63 | XCTAssertEqual(registration.method, .nib(name: nib, bundle: nil))
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Tests/TestViewRegistrationMethod.swift:
--------------------------------------------------------------------------------
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 TestViewRegistrationMethod: XCTestCase {
19 |
20 | func test_viewClassName_nibName_nibBundle() {
21 | let method1 = ViewRegistrationMethod.viewClass(UICollectionViewListCell.self)
22 |
23 | XCTAssertEqual(method1._viewClassName, "UICollectionViewListCell")
24 | XCTAssertNil(method1._nibName)
25 | XCTAssertNil(method1._nibBundle)
26 |
27 | let method2 = ViewRegistrationMethod.nib(name: "name", bundle: .main)
28 | XCTAssertNil(method2._viewClassName)
29 | XCTAssertEqual(method2._nibName, "name")
30 | XCTAssertEqual(method2._nibBundle, .main)
31 | }
32 |
33 | func test_equatable_viewClass() {
34 | let method1 = ViewRegistrationMethod.viewClass(UICollectionViewListCell.self)
35 | let method2 = ViewRegistrationMethod.viewClass(UICollectionViewListCell.self)
36 | let method3 = ViewRegistrationMethod.viewClass(UIView.self)
37 |
38 | XCTAssertEqual(method1, method1)
39 | XCTAssertEqual(method1, method2)
40 | XCTAssertNotEqual(method1, method3)
41 |
42 | let set = Set([method1, method2, method3])
43 | XCTAssertEqual(set.count, 2)
44 | XCTAssertEqual(set, [method1, method3])
45 | }
46 |
47 | func test_equatable_nib() {
48 | let method1 = ViewRegistrationMethod.nib(name: "one", bundle: .main)
49 | let method2 = ViewRegistrationMethod.nib(name: "one", bundle: .main)
50 | let method3 = ViewRegistrationMethod.nib(name: "one", bundle: nil)
51 | let method4 = ViewRegistrationMethod.nib(name: "two", bundle: .main)
52 | let method5 = ViewRegistrationMethod.nib(name: "two", bundle: nil)
53 | let method6 = ViewRegistrationMethod.nib(name: "two", bundle: nil)
54 |
55 | XCTAssertEqual(method1, method1)
56 | XCTAssertEqual(method1, method2)
57 | XCTAssertNotEqual(method1, method3)
58 | XCTAssertNotEqual(method4, method5)
59 | XCTAssertEqual(method5, method6)
60 |
61 | let set = Set([method1, method2, method3, method4, method5, method6])
62 | XCTAssertEqual(set.count, 4)
63 | XCTAssertEqual(set, [method1, method3, method4, method5])
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Tests/TestViewRegistrationViewType.swift:
--------------------------------------------------------------------------------
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 TestViewRegistrationViewType: XCTestCase {
19 |
20 | func test_kind() {
21 | let cell = ViewRegistrationViewType.cell
22 | XCTAssertEqual(cell.kind, "cell")
23 |
24 | let view = ViewRegistrationViewType.supplementary(kind: "kind")
25 | XCTAssertEqual(view.kind, "kind")
26 | }
27 |
28 | func test_isCell() {
29 | let cell = ViewRegistrationViewType.cell
30 | XCTAssertTrue(cell.isCell)
31 | XCTAssertFalse(cell.isSupplementary)
32 | XCTAssertFalse(cell.isHeader)
33 | XCTAssertFalse(cell.isFooter)
34 |
35 | let view = ViewRegistrationViewType.supplementary(kind: "kind")
36 | XCTAssertFalse(view.isCell)
37 | }
38 |
39 | func test_isSupplementary() {
40 | let view = ViewRegistrationViewType.supplementary(kind: "kind")
41 | XCTAssertTrue(view.isSupplementary)
42 | XCTAssertFalse(view.isHeader)
43 | XCTAssertFalse(view.isFooter)
44 | XCTAssertFalse(view.isCell)
45 |
46 | let cell = ViewRegistrationViewType.cell
47 | XCTAssertFalse(cell.isSupplementary)
48 | }
49 |
50 | func test_isHeader() {
51 | let view = ViewRegistrationViewType.supplementary(kind: CollectionViewConstants.headerKind)
52 |
53 | XCTAssertTrue(view.isHeader)
54 | XCTAssertTrue(view.isSupplementary)
55 |
56 | XCTAssertFalse(view.isFooter)
57 | XCTAssertFalse(view.isCell)
58 | }
59 |
60 | func test_isFooter() {
61 | let view = ViewRegistrationViewType.supplementary(kind: CollectionViewConstants.footerKind)
62 | XCTAssertTrue(view.isFooter)
63 | XCTAssertTrue(view.isSupplementary)
64 |
65 | XCTAssertFalse(view.isHeader)
66 | XCTAssertFalse(view.isCell)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Tests/Utils/CollectionViewDriverOptions+Extensions.swift:
--------------------------------------------------------------------------------
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 | import ReactiveCollectionsKit
16 |
17 | extension CollectionViewDriverOptions {
18 | static func test() -> Self {
19 | .init(diffOnBackgroundQueue: false, reloadDataOnReplacingViewModel: true)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/Utils/String+Extensions.swift:
--------------------------------------------------------------------------------
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 |
16 | extension String {
17 | static var random: String {
18 | String(UUID().uuidString)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/Utils/TestBundle.swift:
--------------------------------------------------------------------------------
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 |
16 | extension Bundle {
17 | static var testBundle: Bundle {
18 | let bundle = Bundle(for: BundleFinder.self)
19 |
20 | // building and testing via Package.swift
21 | let swiftPackageBundleName = "ReactiveCollectionsKit_ReactiveCollectionsKitTests.bundle"
22 | if let swiftPackageBundleURL = bundle.resourceURL?.appendingPathComponent(swiftPackageBundleName),
23 | let swiftPackageBundle = Bundle(url: swiftPackageBundleURL) {
24 | return swiftPackageBundle
25 | }
26 |
27 | // building and testing via Xcode Project
28 | return bundle
29 | }
30 | }
31 |
32 | private final class BundleFinder { }
33 |
--------------------------------------------------------------------------------
/Tests/Utils/TestExpectationField.swift:
--------------------------------------------------------------------------------
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 |
16 | enum TestExpectationField: String, Hashable {
17 | case configure
18 | case didSelect
19 | case didDeselect
20 | case willDisplay
21 | case didEndDisplaying
22 | case didHighlight
23 | case didUnhighlight
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Utils/UnitTestCase.swift:
--------------------------------------------------------------------------------
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 | // swiftlint:disable:next final_test_case
19 | class UnitTestCase: XCTestCase, @unchecked Sendable {
20 |
21 | private static let frame = CGRect(x: 0, y: 0, width: 320, height: 600)
22 |
23 | @MainActor var collectionView: FakeCollectionView {
24 | FakeCollectionView(
25 | frame: Self.frame,
26 | collectionViewLayout: self.layout
27 | )
28 | }
29 |
30 | @MainActor let layout = FakeCollectionLayout()
31 |
32 | var keepAliveDrivers = [CollectionViewDriver]()
33 |
34 | var keepAliveWindows = [UIWindow]()
35 |
36 | override func setUp() async throws {
37 | try await super.setUp()
38 | await self.collectionView.layoutSubviews()
39 | await self.collectionView.reloadData()
40 |
41 | self.keepAliveDrivers.removeAll()
42 | self.keepAliveWindows.removeAll()
43 | }
44 |
45 | @MainActor
46 | func simulateAppearance(viewController: UIViewController) {
47 | viewController.beginAppearanceTransition(true, animated: false)
48 | viewController.endAppearanceTransition()
49 |
50 | let window = UIWindow()
51 | window.frame = Self.frame
52 | window.rootViewController = viewController
53 | window.makeKeyAndVisible()
54 |
55 | self.keepAliveWindows.append(window)
56 | }
57 |
58 | @MainActor
59 | func keepDriverAlive(_ driver: CollectionViewDriver) {
60 | self.keepAliveDrivers.append(driver)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/Utils/XCTestCase+Extensions.swift:
--------------------------------------------------------------------------------
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 | import ReactiveCollectionsKit
16 | import XCTest
17 |
18 | extension XCTestCase {
19 | var defaultTimeout: TimeInterval { 5 }
20 |
21 | @MainActor
22 | func waitForExpectations() {
23 | self.waitForExpectations(timeout: self.defaultTimeout, handler: nil)
24 | }
25 |
26 | func expectation(function: String = #function, name: String? = nil) -> XCTestExpectation {
27 | self.expectation(description: [function, name].compactMap { $0 }.joined(separator: "-"))
28 | }
29 |
30 | func expectation(field: TestExpectationField, id: UniqueIdentifier, function: String = #function) -> XCTestExpectation {
31 | self.expectation(function: function, name: "\(field.rawValue)_\(id)")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Tests/Utils/XCTestExpectation+Expectations.swift:
--------------------------------------------------------------------------------
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 | import XCTest
16 |
17 | extension XCTestExpectation {
18 | func setInvertedAndLog() {
19 | self.isInverted = true
20 | print("Inverted expectation: \(self.expectationDescription)")
21 | }
22 |
23 | func fulfillAndLog() {
24 | self.fulfill()
25 | print("Fulfilled expectation: \(self.expectationDescription)")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docs/badge.svg:
--------------------------------------------------------------------------------
1 |
29 |
--------------------------------------------------------------------------------
/docs/css/highlight.css:
--------------------------------------------------------------------------------
1 | /*! Jazzy - https://github.com/realm/jazzy
2 | * Copyright Realm Inc.
3 | * SPDX-License-Identifier: MIT
4 | */
5 | /* Credit to https://gist.github.com/wataru420/2048287 */
6 | .highlight .c {
7 | color: #999988;
8 | font-style: italic; }
9 |
10 | .highlight .err {
11 | color: #a61717;
12 | background-color: #e3d2d2; }
13 |
14 | .highlight .k {
15 | color: #000000;
16 | font-weight: bold; }
17 |
18 | .highlight .o {
19 | color: #000000;
20 | font-weight: bold; }
21 |
22 | .highlight .cm {
23 | color: #999988;
24 | font-style: italic; }
25 |
26 | .highlight .cp {
27 | color: #999999;
28 | font-weight: bold; }
29 |
30 | .highlight .c1 {
31 | color: #999988;
32 | font-style: italic; }
33 |
34 | .highlight .cs {
35 | color: #999999;
36 | font-weight: bold;
37 | font-style: italic; }
38 |
39 | .highlight .gd {
40 | color: #000000;
41 | background-color: #ffdddd; }
42 |
43 | .highlight .gd .x {
44 | color: #000000;
45 | background-color: #ffaaaa; }
46 |
47 | .highlight .ge {
48 | color: #000000;
49 | font-style: italic; }
50 |
51 | .highlight .gr {
52 | color: #aa0000; }
53 |
54 | .highlight .gh {
55 | color: #999999; }
56 |
57 | .highlight .gi {
58 | color: #000000;
59 | background-color: #ddffdd; }
60 |
61 | .highlight .gi .x {
62 | color: #000000;
63 | background-color: #aaffaa; }
64 |
65 | .highlight .go {
66 | color: #888888; }
67 |
68 | .highlight .gp {
69 | color: #555555; }
70 |
71 | .highlight .gs {
72 | font-weight: bold; }
73 |
74 | .highlight .gu {
75 | color: #aaaaaa; }
76 |
77 | .highlight .gt {
78 | color: #aa0000; }
79 |
80 | .highlight .kc {
81 | color: #000000;
82 | font-weight: bold; }
83 |
84 | .highlight .kd {
85 | color: #000000;
86 | font-weight: bold; }
87 |
88 | .highlight .kp {
89 | color: #000000;
90 | font-weight: bold; }
91 |
92 | .highlight .kr {
93 | color: #000000;
94 | font-weight: bold; }
95 |
96 | .highlight .kt {
97 | color: #445588; }
98 |
99 | .highlight .m {
100 | color: #009999; }
101 |
102 | .highlight .s {
103 | color: #d14; }
104 |
105 | .highlight .na {
106 | color: #008080; }
107 |
108 | .highlight .nb {
109 | color: #0086B3; }
110 |
111 | .highlight .nc {
112 | color: #445588;
113 | font-weight: bold; }
114 |
115 | .highlight .no {
116 | color: #008080; }
117 |
118 | .highlight .ni {
119 | color: #800080; }
120 |
121 | .highlight .ne {
122 | color: #990000;
123 | font-weight: bold; }
124 |
125 | .highlight .nf {
126 | color: #990000; }
127 |
128 | .highlight .nn {
129 | color: #555555; }
130 |
131 | .highlight .nt {
132 | color: #000080; }
133 |
134 | .highlight .nv {
135 | color: #008080; }
136 |
137 | .highlight .ow {
138 | color: #000000;
139 | font-weight: bold; }
140 |
141 | .highlight .w {
142 | color: #bbbbbb; }
143 |
144 | .highlight .mf {
145 | color: #009999; }
146 |
147 | .highlight .mh {
148 | color: #009999; }
149 |
150 | .highlight .mi {
151 | color: #009999; }
152 |
153 | .highlight .mo {
154 | color: #009999; }
155 |
156 | .highlight .sb {
157 | color: #d14; }
158 |
159 | .highlight .sc {
160 | color: #d14; }
161 |
162 | .highlight .sd {
163 | color: #d14; }
164 |
165 | .highlight .s2 {
166 | color: #d14; }
167 |
168 | .highlight .se {
169 | color: #d14; }
170 |
171 | .highlight .sh {
172 | color: #d14; }
173 |
174 | .highlight .si {
175 | color: #d14; }
176 |
177 | .highlight .sx {
178 | color: #d14; }
179 |
180 | .highlight .sr {
181 | color: #009926; }
182 |
183 | .highlight .s1 {
184 | color: #d14; }
185 |
186 | .highlight .ss {
187 | color: #990073; }
188 |
189 | .highlight .bp {
190 | color: #999999; }
191 |
192 | .highlight .vc {
193 | color: #008080; }
194 |
195 | .highlight .vg {
196 | color: #008080; }
197 |
198 | .highlight .vi {
199 | color: #008080; }
200 |
201 | .highlight .il {
202 | color: #009999; }
203 |
--------------------------------------------------------------------------------
/docs/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jessesquires/ReactiveCollectionsKit/afde76889320f3c7dcbf53156cde214f1a585000/docs/img/carat.png
--------------------------------------------------------------------------------
/docs/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jessesquires/ReactiveCollectionsKit/afde76889320f3c7dcbf53156cde214f1a585000/docs/img/dash.png
--------------------------------------------------------------------------------
/docs/img/gh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jessesquires/ReactiveCollectionsKit/afde76889320f3c7dcbf53156cde214f1a585000/docs/img/gh.png
--------------------------------------------------------------------------------
/docs/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jessesquires/ReactiveCollectionsKit/afde76889320f3c7dcbf53156cde214f1a585000/docs/img/spinner.gif
--------------------------------------------------------------------------------
/docs/js/jazzy.js:
--------------------------------------------------------------------------------
1 | // Jazzy - https://github.com/realm/jazzy
2 | // Copyright Realm Inc.
3 | // SPDX-License-Identifier: MIT
4 |
5 | window.jazzy = {'docset': false}
6 | if (typeof window.dash != 'undefined') {
7 | document.documentElement.className += ' dash'
8 | window.jazzy.docset = true
9 | }
10 | if (navigator.userAgent.match(/xcode/i)) {
11 | document.documentElement.className += ' xcode'
12 | window.jazzy.docset = true
13 | }
14 |
15 | function toggleItem($link, $content) {
16 | var animationDuration = 300;
17 | $link.toggleClass('token-open');
18 | $content.slideToggle(animationDuration);
19 | }
20 |
21 | function itemLinkToContent($link) {
22 | return $link.parent().parent().next();
23 | }
24 |
25 | // On doc load + hash-change, open any targeted item
26 | function openCurrentItemIfClosed() {
27 | if (window.jazzy.docset) {
28 | return;
29 | }
30 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token');
31 | $content = itemLinkToContent($link);
32 | if ($content.is(':hidden')) {
33 | toggleItem($link, $content);
34 | }
35 | }
36 |
37 | $(openCurrentItemIfClosed);
38 | $(window).on('hashchange', openCurrentItemIfClosed);
39 |
40 | // On item link ('token') click, toggle its discussion
41 | $('.token').on('click', function(event) {
42 | if (window.jazzy.docset) {
43 | return;
44 | }
45 | var $link = $(this);
46 | toggleItem($link, itemLinkToContent($link));
47 |
48 | // Keeps the document from jumping to the hash.
49 | var href = $link.attr('href');
50 | if (history.pushState) {
51 | history.pushState({}, '', href);
52 | } else {
53 | location.hash = href;
54 | }
55 | event.preventDefault();
56 | });
57 |
58 | // Clicks on links to the current, closed, item need to open the item
59 | $("a:not('.token')").on('click', function() {
60 | if (location == this.href) {
61 | openCurrentItemIfClosed();
62 | }
63 | });
64 |
65 | // KaTeX rendering
66 | if ("katex" in window) {
67 | $($('.math').each( (_, element) => {
68 | katex.render(element.textContent, element, {
69 | displayMode: $(element).hasClass('m-block'),
70 | throwOnError: false,
71 | trust: true
72 | });
73 | }))
74 | }
75 |
--------------------------------------------------------------------------------
/docs/js/jazzy.search.js:
--------------------------------------------------------------------------------
1 | // Jazzy - https://github.com/realm/jazzy
2 | // Copyright Realm Inc.
3 | // SPDX-License-Identifier: MIT
4 |
5 | $(function(){
6 | var $typeahead = $('[data-typeahead]');
7 | var $form = $typeahead.parents('form');
8 | var searchURL = $form.attr('action');
9 |
10 | function displayTemplate(result) {
11 | return result.name;
12 | }
13 |
14 | function suggestionTemplate(result) {
15 | var t = '';
16 | t += '' + result.name + '';
17 | if (result.parent_name) {
18 | t += '' + result.parent_name + '';
19 | }
20 | t += '
';
21 | return t;
22 | }
23 |
24 | $typeahead.one('focus', function() {
25 | $form.addClass('loading');
26 |
27 | $.getJSON(searchURL).then(function(searchData) {
28 | const searchIndex = lunr(function() {
29 | this.ref('url');
30 | this.field('name');
31 | this.field('abstract');
32 | for (const [url, doc] of Object.entries(searchData)) {
33 | this.add({url: url, name: doc.name, abstract: doc.abstract});
34 | }
35 | });
36 |
37 | $typeahead.typeahead(
38 | {
39 | highlight: true,
40 | minLength: 3,
41 | autoselect: true
42 | },
43 | {
44 | limit: 10,
45 | display: displayTemplate,
46 | templates: { suggestion: suggestionTemplate },
47 | source: function(query, sync) {
48 | const lcSearch = query.toLowerCase();
49 | const results = searchIndex.query(function(q) {
50 | q.term(lcSearch, { boost: 100 });
51 | q.term(lcSearch, {
52 | boost: 10,
53 | wildcard: lunr.Query.wildcard.TRAILING
54 | });
55 | }).map(function(result) {
56 | var doc = searchData[result.ref];
57 | doc.url = result.ref;
58 | return doc;
59 | });
60 | sync(results);
61 | }
62 | }
63 | );
64 | $form.removeClass('loading');
65 | $typeahead.trigger('focus');
66 | });
67 | });
68 |
69 | var baseURL = searchURL.slice(0, -"search.json".length);
70 |
71 | $typeahead.on('typeahead:select', function(e, result) {
72 | window.location = baseURL + result.url;
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/docs/undocumented.json:
--------------------------------------------------------------------------------
1 | {
2 | "warnings": [
3 |
4 | ],
5 | "source_directory": "/Users/jsq/Developer/GitHub/ReactiveCollectionsKit"
6 | }
--------------------------------------------------------------------------------
/scripts/build_docs.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Created by Jesse Squires
4 | # https://www.jessesquires.com
5 | #
6 | # Copyright © 2020-present Jesse Squires
7 | #
8 | # Jazzy: https://github.com/realm/jazzy/releases/latest
9 | # Generates documentation using jazzy and checks for installation.
10 |
11 | VERSION="0.15.2"
12 |
13 | FOUND=$(jazzy --version)
14 | LINK="https://github.com/realm/jazzy"
15 | INSTALL="gem install jazzy"
16 |
17 | PROJECT="ReactiveCollectionsKit"
18 |
19 | if which jazzy >/dev/null; then
20 | jazzy \
21 | --clean \
22 | --author "Jesse Squires" \
23 | --author_url "https://jessesquires.com" \
24 | --github_url "https://github.com/jessesquires/$PROJECT" \
25 | --module "$PROJECT" \
26 | --source-directory . \
27 | --readme "README.md" \
28 | --documentation "Guides/*.md" \
29 | --output docs/
30 | else
31 | echo "
32 | Error: Jazzy not installed!
33 |
34 | Download: $LINK
35 | Install: $INSTALL
36 | "
37 | exit 1
38 | fi
39 |
40 | if [ "$FOUND" != "jazzy version: $VERSION" ]; then
41 | echo "
42 | Warning: incorrect Jazzy installed! Please upgrade.
43 | Expected: $VERSION
44 | Found: $FOUND
45 |
46 | Download: $LINK
47 | Install: $INSTALL
48 | "
49 | fi
50 |
51 | exit
52 |
--------------------------------------------------------------------------------
/scripts/lint.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Created by Jesse Squires
4 | # https://www.jessesquires.com
5 | #
6 | # Copyright © 2020-present Jesse Squires
7 | #
8 | # SwiftLint: https://github.com/realm/SwiftLint/releases/latest
9 | #
10 | # Runs SwiftLint and checks for installation of correct version.
11 |
12 | set -e
13 | export PATH="$PATH:/opt/homebrew/bin"
14 |
15 | if [[ "${GITHUB_ACTIONS}" ]]; then
16 | # ignore on GitHub Actions
17 | exit 0
18 | fi
19 |
20 | LINK="https://github.com/realm/SwiftLint"
21 | INSTALL="brew install swiftlint"
22 |
23 | if ! which swiftlint >/dev/null; then
24 | echo "
25 | Error: SwiftLint not installed!
26 |
27 | Download: $LINK
28 | Install: $INSTALL
29 | "
30 | exit 0
31 | fi
32 |
33 | PROJECT="ReactiveCollectionsKit.xcodeproj"
34 | SCHEME="ReactiveCollectionsKit"
35 |
36 | VERSION="0.57.0"
37 | FOUND=$(swiftlint version)
38 | CONFIG="./.swiftlint.yml"
39 |
40 | echo "Running swiftlint..."
41 | echo ""
42 |
43 | # no arguments, just lint without fixing
44 | if [[ $# -eq 0 ]]; then
45 | swiftlint --config $CONFIG
46 | echo ""
47 | fi
48 |
49 | for argval in "$@"
50 | do
51 | # run --fix
52 | if [[ "$argval" == "fix" ]]; then
53 | echo "Auto-correcting lint errors..."
54 | echo ""
55 | swiftlint --fix --progress --config $CONFIG && swiftlint --config $CONFIG
56 | echo ""
57 | # run analyze
58 | elif [[ "$argval" == "analyze" ]]; then
59 | LOG="xcodebuild.log"
60 | echo "Running anaylze..."
61 | echo ""
62 | xcodebuild -scheme $SCHEME -project $PROJECT clean build-for-testing > $LOG
63 | swiftlint analyze --fix --progress --format --strict --config $CONFIG --compiler-log-path $LOG
64 | rm $LOG
65 | echo ""
66 | else
67 | echo "Error: invalid arguments."
68 | echo "Usage: $0 [fix] [analyze]"
69 | echo ""
70 | fi
71 | done
72 |
73 | if [ $FOUND != $VERSION ]; then
74 | echo "
75 | Warning: incorrect SwiftLint installed! Please upgrade.
76 | Expected: $VERSION
77 | Found: $FOUND
78 |
79 | Download: $LINK
80 | Install: $INSTALL
81 | "
82 | fi
83 |
84 | exit 0
85 |
--------------------------------------------------------------------------------