diff --git a/Bento.xcodeproj/project.pbxproj b/Bento.xcodeproj/project.pbxproj index 504e0f1..3c5bfed 100644 --- a/Bento.xcodeproj/project.pbxproj +++ b/Bento.xcodeproj/project.pbxproj @@ -36,8 +36,6 @@ 58FC4427207CF2BB00DA3614 /* AnyRenderableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3EF77F205D866F00D043AC /* AnyRenderableTests.swift */; }; 58FC4428207CF2BB00DA3614 /* TestId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A784702205EB61D00FA597E /* TestId.swift */; }; 58FC4429207CF2BB00DA3614 /* TestRenderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A784700205EB5E000FA597E /* TestRenderable.swift */; }; - 58FC442A207CF2BB00DA3614 /* NodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7846FC205EAF7C00FA597E /* NodeTests.swift */; }; - 58FC442B207CF2BB00DA3614 /* SectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7846FE205EAF8400FA597E /* SectionTests.swift */; }; 58FC442F207CF37700DA3614 /* script in Resources */ = {isa = PBXBuildFile; fileRef = 58FC442E207CF37700DA3614 /* script */; }; 58FC4430207CF3CD00DA3614 /* Bento.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58BA755C2016303B0050D5F1 /* Bento.framework */; }; 58FC4431207CF3CD00DA3614 /* Bento.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 58BA755C2016303B0050D5F1 /* Bento.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -76,7 +74,7 @@ 740921B820ACE5EC00B59F5C /* ConcatenationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 740921B720ACE5EC00B59F5C /* ConcatenationTests.swift */; }; 74208FA12083B1F00062CC8D /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74208FA22083B1F00062CC8D /* Nimble.framework */; }; 747523C72083A6660030CCAA /* FlexibleDiff.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 747523C82083A6660030CCAA /* FlexibleDiff.framework */; }; - 9A4DB3E1212CB3710079D5AE /* Changeset+MutationIndexPairs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4DB3E0212CB3710079D5AE /* Changeset+MutationIndexPairs.swift */; }; + 9A4DB3E1212CB3710079D5AE /* ChangesetExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4DB3E0212CB3710079D5AE /* ChangesetExtensions.swift */; }; 9AF4786A21205E3100F87E21 /* Supplement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF4786921205E3100F87E21 /* Supplement.swift */; }; 9AF4786C2120CA7500F87E21 /* BentoReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AF4786B2120CA7500F87E21 /* BentoReusableView.swift */; }; A67A873520B5D2D100BC2BAC /* RenderableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67A873420B5D2D100BC2BAC /* RenderableTests.swift */; }; @@ -170,6 +168,7 @@ 58FC4440207CFBD700DA3614 /* MovieComponentView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MovieComponentView.xib; sourceTree = ""; }; 58FC444B207CFBE100DA3614 /* BookAppointmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookAppointmentViewController.swift; sourceTree = ""; }; 58FC444C207CFBE200DA3614 /* MoviesListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoviesListViewController.swift; sourceTree = ""; }; + 5B175A6421C0634800590F34 /* ComponentContract.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = ComponentContract.md; sourceTree = ""; }; 61B40916208523F50063DE25 /* IntroViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroViewModel.swift; sourceTree = ""; }; 61B40917208523F50063DE25 /* IntroViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroViewController.swift; sourceTree = ""; }; 61B64BB02086561B0092082C /* IntroRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroRenderer.swift; sourceTree = ""; }; @@ -192,10 +191,8 @@ 74208FA22083B1F00062CC8D /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 747523C82083A6660030CCAA /* FlexibleDiff.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FlexibleDiff.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9A3EF77F205D866F00D043AC /* AnyRenderableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRenderableTests.swift; sourceTree = ""; }; - 9A4DB3E0212CB3710079D5AE /* Changeset+MutationIndexPairs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Changeset+MutationIndexPairs.swift"; sourceTree = ""; }; + 9A4DB3E0212CB3710079D5AE /* ChangesetExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangesetExtensions.swift; sourceTree = ""; }; 9A7846FA205D89C000FA597E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; - 9A7846FC205EAF7C00FA597E /* NodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeTests.swift; sourceTree = ""; }; - 9A7846FE205EAF8400FA597E /* SectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionTests.swift; sourceTree = ""; }; 9A784700205EB5E000FA597E /* TestRenderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRenderable.swift; sourceTree = ""; }; 9A784702205EB61D00FA597E /* TestId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestId.swift; sourceTree = ""; }; 9AF4786921205E3100F87E21 /* Supplement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Supplement.swift; sourceTree = ""; }; @@ -251,9 +248,10 @@ 581ED77E2022041C00EC9584 /* Diff */ = { isa = PBXGroup; children = ( + 5B175A6421C0634800590F34 /* ComponentContract.md */, 58467B02202B33F200577C77 /* TableViewSectionDiff.swift */, 61B64BB320873DA10092082C /* CollectionViewSectionDiff.swift */, - 9A4DB3E0212CB3710079D5AE /* Changeset+MutationIndexPairs.swift */, + 9A4DB3E0212CB3710079D5AE /* ChangesetExtensions.swift */, ); path = Diff; sourceTree = ""; @@ -374,8 +372,6 @@ children = ( A67A873420B5D2D100BC2BAC /* RenderableTests.swift */, 9A3EF77F205D866F00D043AC /* AnyRenderableTests.swift */, - 9A7846FC205EAF7C00FA597E /* NodeTests.swift */, - 9A7846FE205EAF8400FA597E /* SectionTests.swift */, 740921B520ACDDDA00B59F5C /* IfTests.swift */, 740921B720ACE5EC00B59F5C /* ConcatenationTests.swift */, 9A784700205EB5E000FA597E /* TestRenderable.swift */, @@ -660,7 +656,7 @@ 65E3ECAC2113591500869DF3 /* FocusableView.swift in Sources */, A9509880661501C40B50E453 /* TableViewHeaderFooterView.swift in Sources */, 515F94097C1F82B7E5C2DE0C /* Section.swift in Sources */, - 9A4DB3E1212CB3710079D5AE /* Changeset+MutationIndexPairs.swift in Sources */, + 9A4DB3E1212CB3710079D5AE /* ChangesetExtensions.swift in Sources */, 65E3ECA421133BF700869DF3 /* FocusEligibilitySource.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -670,11 +666,9 @@ buildActionMask = 2147483647; files = ( 58FC4427207CF2BB00DA3614 /* AnyRenderableTests.swift in Sources */, - 58FC442A207CF2BB00DA3614 /* NodeTests.swift in Sources */, 58FC4428207CF2BB00DA3614 /* TestId.swift in Sources */, 58FC4429207CF2BB00DA3614 /* TestRenderable.swift in Sources */, 740921B620ACDDDA00B59F5C /* IfTests.swift in Sources */, - 58FC442B207CF2BB00DA3614 /* SectionTests.swift in Sources */, 740921B820ACE5EC00B59F5C /* ConcatenationTests.swift in Sources */, A67A873520B5D2D100BC2BAC /* RenderableTests.swift in Sources */, ); diff --git a/Bento/Bento/Node.swift b/Bento/Bento/Node.swift index b7b2a61..107b8f5 100644 --- a/Bento/Bento/Node.swift +++ b/Bento/Bento/Node.swift @@ -1,6 +1,6 @@ import UIKit -public struct Node: Equatable { +public struct Node { public let id: Identifier let component: AnyRenderable @@ -13,10 +13,6 @@ public struct Node: Equatable { self.init(id: id, component: AnyRenderable(component)) } - public static func == (lhs: Node, rhs: Node) -> Bool { - return lhs.id == rhs.id && lhs.component == rhs.component - } - public func component(as type: T.Type) -> T? { return component.cast(to: type) } diff --git a/Bento/Bento/Section.swift b/Bento/Bento/Section.swift index b56f2a6..3727e6b 100644 --- a/Bento/Bento/Section.swift +++ b/Bento/Bento/Section.swift @@ -1,6 +1,6 @@ import UIKit -public struct Section: Equatable { +public struct Section { public typealias Item = Node public let id: SectionID @@ -70,16 +70,6 @@ public struct Section: Equatable { return supplements[supplement]?.sizeBoundTo(size: size, inheritedMargins: inheritedMargins) } - public static func hasEqualMetadata(_ lhs: Section, _ rhs: Section) -> Bool { - return lhs.supplements == rhs.supplements - } - - public static func == (lhs: Section, rhs: Section) -> Bool { - return lhs.id == rhs.id - && hasEqualMetadata(lhs, rhs) - && lhs.items == rhs.items - } - public static func |---+ (lhs: Section, rhs: Item) -> Section { return Section(id: lhs.id, items: lhs.items + [rhs], supplements: lhs.supplements) } diff --git a/Bento/Bento/UITableViewExtensions.swift b/Bento/Bento/UITableViewExtensions.swift index dc7a8dc..5390b40 100644 --- a/Bento/Bento/UITableViewExtensions.swift +++ b/Bento/Bento/UITableViewExtensions.swift @@ -59,3 +59,16 @@ extension UITableView { return objc_getAssociatedObject(self, AssociatedKey.adapter) as AnyObject? } } + +extension UITableView { + var visibleSections: Set { + let isVisible = (0 ..< numberOfSections) + .map(rect(forSection:)) + .map(bounds.intersects) + + return Set( + zip(isVisible, 0 ..< numberOfSections) + .compactMap { $0 ? $1 : nil } + ) + } +} diff --git a/Bento/Diff/Changeset+MutationIndexPairs.swift b/Bento/Diff/Changeset+MutationIndexPairs.swift deleted file mode 100644 index f4e9154..0000000 --- a/Bento/Diff/Changeset+MutationIndexPairs.swift +++ /dev/null @@ -1,10 +0,0 @@ -import FlexibleDiff - -extension Changeset { - var mutationIndexPairs: FlattenCollection<[[(source: Int, destination: Int)]]> { - return [ - mutations.map { ($0, $0) }, - moves.compactMap { $0.isMutated ? ($0.source, $0.destination) : nil } - ].joined() - } -} diff --git a/Bento/Diff/ChangesetExtensions.swift b/Bento/Diff/ChangesetExtensions.swift new file mode 100644 index 0000000..37a3a37 --- /dev/null +++ b/Bento/Diff/ChangesetExtensions.swift @@ -0,0 +1,19 @@ +import FlexibleDiff + +extension Changeset { + /// Retrieve the positions of all mutated items, in place or moved. The changes are filtered + /// by the specified collection of pre-removal visible item indices. + /// + /// - parameters: + /// - visibleItems: The pre-removal indices of the visible items. + func positionsOfMutations(amongVisible visibleItems: Indices) -> FlattenCollection<[[(source: Int, destination: Int)]]> where Indices.Element == Int { + return [ + mutations.lazy + .filter(visibleItems.contains) + .map { (source: $0, destination: $0) }, + moves.lazy + .filter { visibleItems.contains($0.source) } + .map { (source: $0.source, destination: $0.destination) } + ].joined() + } +} diff --git a/Bento/Diff/CollectionViewSectionDiff.swift b/Bento/Diff/CollectionViewSectionDiff.swift index 8e211b0..468aaba 100644 --- a/Bento/Diff/CollectionViewSectionDiff.swift +++ b/Bento/Diff/CollectionViewSectionDiff.swift @@ -15,13 +15,16 @@ struct CollectionViewSectionDiff { } func apply(to collectionView: UICollectionView, completion: (() -> Void)? = nil) { + /// Since we are going to always rebind visible components, there is no point to evaluate + /// component equation. However, we still force all instances of components to be treated as + /// unequal, so as to preserve all positional information for in-place updates to visible cells. let diff = SectionedChangeset(previous: oldSections, current: newSections, sectionIdentifier: { $0.id }, - areMetadataEqual: Section.hasEqualMetadata, + areMetadataEqual: const(false), items: { $0.items }, itemIdentifier: { $0.id }, - areItemsEqual: ==) + areItemsEqual: const(false)) apply(diff: diff, to: collectionView, completion: completion) } @@ -40,7 +43,7 @@ struct CollectionViewSectionDiff { by: { $0.section } ) - for (source, destination) in diff.sections.mutationIndexPairs { + for (source, destination) in diff.sections.positionsOfMutations(amongVisible: groups.keys) { if let indexPaths = groups[source] { for indexPath in indexPaths { let view = collectionView.supplementaryView(forElementKind: elementKind, at: indexPath) @@ -95,8 +98,13 @@ extension UICollectionView { deleteItems(at: sectionMutation.deletedIndexPaths) insertItems(at: sectionMutation.insertedIndexPaths) perform(moves: sectionMutation.movedCollectionIndexPaths) + + let visibleItems = Set( + indexPathsForVisibleItems + .compactMap { $0.section == sectionMutation.source ? $0.row : nil } + ) - for (source, destination) in sectionMutation.changeset.mutationIndexPairs { + for (source, destination) in sectionMutation.changeset.positionsOfMutations(amongVisible: visibleItems) { if let cell = cellForItem(at: [sectionMutation.source, source]) as? CollectionViewContainerCell { let component = newSections[sectionMutation.destination] .items[destination] diff --git a/Bento/Diff/ComponentContract.md b/Bento/Diff/ComponentContract.md new file mode 100644 index 0000000..507ca40 --- /dev/null +++ b/Bento/Diff/ComponentContract.md @@ -0,0 +1,17 @@ +## Bento Component Contract +Applicable to both UICollectionView and UITableView. + +### Components must conform to `Renderable`. +`Renderable` represents a set of requirements which a component must satisfy for Bento to integrate it. + +### Components need not be evaluable for equation. +Bento enforces no restriction in what types of properties the component can comprise of. Moreover, there are also prevalent uses of non-equatable properties, for example, user interaction callbacks. Therefore, Bento does not require any component to be evaluable for equation. + +### Any given ID path may be bound with any kind and instance of components at any time. +Bento is designed to be very flexible in terms of component types. Users should require no extra precaution in populating their `Box`es with different permutations of ID paths and components. + +### Visible ID paths may be aggressively rebound. +[As Bento cannot rely on component equation](#components-need-not-be-evaluable-for-equation), Bento aggressively rebinds all visible ID paths with the new component at the same ID path in any new `Box` being applied. + +### Components should avoid deriving animations from rebinding occurrences. +[Given potential occurrences of aggressive rebinding](#visible-id-paths-may-be-aggressively-rebound), rebinding is generally not a good indiction of when animations should happen. Components might want to instead have data-driven, fine-grained animations, or rely on mechanisms like `ViewLifecycleAware`. diff --git a/Bento/Diff/TableViewSectionDiff.swift b/Bento/Diff/TableViewSectionDiff.swift index e7e1b05..1df6105 100644 --- a/Bento/Diff/TableViewSectionDiff.swift +++ b/Bento/Diff/TableViewSectionDiff.swift @@ -15,19 +15,25 @@ struct TableViewSectionDiff { } func apply(to tableView: UITableView) { + /// Since we are going to always rebind visible components, there is no point to evaluate + /// component equation. However, we still force all instances of components to be treated as + /// unequal, so as to preserve all positional information for in-place updates to visible cells. let diff = SectionedChangeset(previous: oldSections, current: newSections, sectionIdentifier: { $0.id }, - areMetadataEqual: Section.hasEqualMetadata, + areMetadataEqual: const(false), items: { $0.items }, itemIdentifier: { $0.id }, - areItemsEqual: ==) + areItemsEqual: const(false)) apply(diff: diff, to: tableView) } private func apply(diff: SectionedChangeset, to tableView: UITableView) { tableView.beginUpdates() - for (source, destination) in diff.sections.mutationIndexPairs { + + let visibleSections = tableView.visibleSections + + for (source, destination) in diff.sections.positionsOfMutations(amongVisible: visibleSections) { if let headerView = tableView.headerView(forSection: source) { let component = newSections[destination].supplements[.header] (headerView as? BentoReusableView)?.bind(component) @@ -38,6 +44,7 @@ struct TableViewSectionDiff { (footerView as? BentoReusableView)?.bind(component) } } + tableView.insertSections(diff.sections.inserts, with: animation.sectionInsertion) tableView.deleteSections(diff.sections.removals, with: animation.sectionDeletion) tableView.moveSections(diff.sections.moves, animation: animation) @@ -48,14 +55,22 @@ struct TableViewSectionDiff { private func apply(sectionMutations: [SectionedChangeset.MutatedSection], to tableView: UITableView, with animation: TableViewAnimation) { + let visibleIndexPaths = tableView.indexPathsForVisibleRows ?? [] + for sectionMutation in sectionMutations { tableView.deleteRows(at: sectionMutation.deletedIndexPaths, with: animation.rowDeletion) tableView.insertRows(at: sectionMutation.insertedIndexPaths, with: animation.rowInsertion) tableView.perform(moves: sectionMutation.movedIndexPaths) - for (source, destination) in sectionMutation.changeset.mutationIndexPairs { - if let cell = tableView.cellForRow(at: [sectionMutation.source, source]) as? BentoReusableView { - let node = newSections[sectionMutation.destination].items[destination] + let visibleRows = Set( + visibleIndexPaths.lazy + .filter { $0.section == sectionMutation.source } + .map { $0.item } + ) + + for (source, destination) in sectionMutation.changeset.positionsOfMutations(amongVisible: visibleRows) { + if let cell = tableView.cellForRow(at: IndexPath(item: source, section: sectionMutation.source)) as? BentoReusableView { + let node = newSections[sectionMutation.destination].items[destination] cell.bind(node.component) } } diff --git a/Bento/Helpers.swift b/Bento/Helpers.swift index a96b908..365d7fe 100644 --- a/Bento/Helpers.swift +++ b/Bento/Helpers.swift @@ -42,3 +42,7 @@ internal func search(from leaf: UIView, type: T.Type) -> T? { } return cell as! T? } + +func const(_ value: Value) -> (_: T, _: U) -> Value { + return { _, _ in value } +} diff --git a/Bento/Renderable/AnyRenderable.swift b/Bento/Renderable/AnyRenderable.swift index 24bb543..ec730f9 100644 --- a/Bento/Renderable/AnyRenderable.swift +++ b/Bento/Renderable/AnyRenderable.swift @@ -58,10 +58,6 @@ struct AnyRenderable: Renderable { return view } - - static func ==(lhs: AnyRenderable, rhs: AnyRenderable) -> Bool { - return lhs.base.equals(to: rhs.base) - } } private class AnyRenderableBox: AnyRenderableBoxBase where Base.View: UIView { @@ -91,12 +87,6 @@ private class AnyRenderableBox: AnyRenderableBoxBase where Bas override func cast(to type: T.Type) -> T? { return base as? T } - - override func equals(to other: AnyRenderableBoxBase) -> Bool { - guard let other = other as? AnyRenderableBox - else { return false } - return self.base == other.base - } } private class AnyRenderableBoxBase { @@ -108,6 +98,5 @@ private class AnyRenderableBoxBase { func render(in view: UIView) { fatalError() } func generate() -> UIView { fatalError() } - func equals(to other: AnyRenderableBoxBase) -> Bool { fatalError() } func cast(to type: T.Type) -> T? { fatalError() } } diff --git a/Bento/Renderable/Renderable.swift b/Bento/Renderable/Renderable.swift index c1e2dd5..113997f 100644 --- a/Bento/Renderable/Renderable.swift +++ b/Bento/Renderable/Renderable.swift @@ -1,6 +1,6 @@ import UIKit -public protocol Renderable: Equatable { +public protocol Renderable { associatedtype View var reuseIdentifier: String { get } @@ -9,12 +9,6 @@ public protocol Renderable: Equatable { func render(in view: View) } -public extension Renderable where Self: AnyObject { - static func == (lhs: Self, rhs: Self) -> Bool { - return lhs === rhs - } -} - public extension Renderable { var reuseIdentifier: String { return String(reflecting: View.self) diff --git a/BentoTests/AnyRenderableTests.swift b/BentoTests/AnyRenderableTests.swift index 812e414..b50969a 100644 --- a/BentoTests/AnyRenderableTests.swift +++ b/BentoTests/AnyRenderableTests.swift @@ -22,23 +22,6 @@ class AnyRenderableTests: XCTestCase { renderable.render(in: testView) expect(testView.hasInvoked) == true } - - func testDefaultEqualityImplementation() { - let base = AnyRenderable(TestDefaultEqualityRenderable()) - - expect(base) == base - expect(base) != AnyRenderable(TestDefaultEqualityRenderable()) - expect(base) != AnyRenderable(TestCustomEqualityRenderable(value: 0)) - } - - func testComponentCustomEquality() { - let base = AnyRenderable(TestCustomEqualityRenderable(value: 0)) - - expect(base) == base - expect(base) == AnyRenderable(TestCustomEqualityRenderable(value: 0)) - expect(base) != AnyRenderable(TestCustomEqualityRenderable(value: 1)) - expect(base) != AnyRenderable(TestDefaultEqualityRenderable()) - } } private class TestView: UIView { diff --git a/BentoTests/NodeTests.swift b/BentoTests/NodeTests.swift deleted file mode 100644 index 967e765..0000000 --- a/BentoTests/NodeTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Nimble -import XCTest -import UIKit -@testable import Bento - -class NodeTests: XCTestCase { - func testEqaulity() { - expect(template) == template - } - - func testEqualityMutatedComponent() { - expect(template) != Node(id: TestRowId.first, - component: TestCustomEqualityRenderable(value: 1)) - } - - func testEqualityMutatedId() { - expect(template) != Node(id: TestRowId.second, - component: TestCustomEqualityRenderable(value: 0)) - } -} - -private var template: Node { - return Node(id: TestRowId.first, - component: TestCustomEqualityRenderable(value: 0)) -} diff --git a/BentoTests/SectionTests.swift b/BentoTests/SectionTests.swift deleted file mode 100644 index 0fa11d7..0000000 --- a/BentoTests/SectionTests.swift +++ /dev/null @@ -1,220 +0,0 @@ -import Nimble -import XCTest -import UIKit -@testable import Bento - -class SectionTests: XCTestCase { - func testMetadataEqualitySelfEquality() { - expect(Section.hasEqualMetadata(template, template)) == true - } - - func testMetadataEqualityDifferentFooter() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestCustomEqualityRenderable(value: 2), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testMetadataEqualityDifferentHeader() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: -1), - footer: TestCustomEqualityRenderable(value: 1), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testMetadataEqualityDifferentHeaderType() { - let section = Section( - id: TestSectionId.first, - header: TestDefaultEqualityRenderable(), - footer: TestCustomEqualityRenderable(value: 1), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testMetadataEqualityDifferentFooterType() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestDefaultEqualityRenderable(), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testMetadataEqualityOmittedFooter() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testMetadataEqualityOmittedHeader() { - let section = Section( - id: TestSectionId.first, - footer: TestCustomEqualityRenderable(value: 1), - items: [] - ) - - expect(Section.hasEqualMetadata(section, section)) == true - expect(Section.hasEqualMetadata(template, section)) == false - } - - func testEqualitySelfEquality() { - expect(templateWithNodes) == templateWithNodes - } - - func testEqualityMutatedFooter() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestCustomEqualityRenderable(value: .max), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityMutatedHeader() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: .max), - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityMutatedNodes() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: .max), - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3)), - Node(id: .second, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - - func testEqualityMutatedFooterWithDifferentComponentType() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestDefaultEqualityRenderable(), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityMutatedHeaderWithDifferentComponentType() { - let section = Section( - id: TestSectionId.first, - header: TestDefaultEqualityRenderable(), - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityMutatedNodesWithDifferentComponentType() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: .max), - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestDefaultEqualityRenderable())] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityOmittedFooter() { - let section = Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityOmittedHeader() { - let section = Section( - id: TestSectionId.first, - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityOmittedHeaderFooter() { - let section = Section( - id: TestSectionId.first, - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) - - expect(section) == section - expect(templateWithNodes) != section - } - - func testEqualityOmittedNodes() { - let section = Section( - id: TestSectionId.second, - header: TestCustomEqualityRenderable(value: 0), - footer: TestCustomEqualityRenderable(value: 1), - items: [] - ) - - expect(section) == section - expect(templateWithNodes) != section - } -} - -private var template: Section { - return Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestCustomEqualityRenderable(value: 1), - items: [] - ) -} - -private var templateWithNodes: Section { - return Section( - id: TestSectionId.first, - header: TestCustomEqualityRenderable(value: 0), - footer: TestCustomEqualityRenderable(value: 1), - items: [Node(id: .first, component: TestCustomEqualityRenderable(value: 3))] - ) -}