Files
IGListKit/Tests/IGListAdapterTests.m
Andrew Monshizadeh a4e5ad862e Move section index to property
Summary:
This pull request removes the `sectionForSectionController:` method from the `IGListCollectionContext` protocol so that the protocol is exclusively for presentation methods.

This should not add new functionality, but rather makes the index directly accessible on the section controllers themselves. This change makes sense because at no time will there be an update to the list that the list adapter is unaware of and so it will always be able to set and update any indexes for a section controller that has changed.

Issue fixed: #609

- [X] All tests pass. Demo project builds and runs.
- [X] I added tests, an experiment, or detailed why my change isn't tested.
- [X] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [X] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Closes https://github.com/Instagram/IGListKit/pull/671

Reviewed By: jessesquires

Differential Revision: D4942159

Pulled By: amonshiz

fbshipit-source-id: d648cfdd381cbf1d9ee7ff549ae27d2972a84622
2017-05-01 07:28:27 -07:00

1218 lines
62 KiB
Objective-C

/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/runtime.h>
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <IGListKit/IGListKit.h>
#import "IGListAdapterInternal.h"
#import "IGListTestAdapterDataSource.h"
#import "IGListTestAdapterHorizontalDataSource.h"
#import "IGListTestSection.h"
#import "IGTestSupplementarySource.h"
#import "IGTestNibSupplementaryView.h"
#import "IGListTestCase.h"
@interface IGListAdapterTests : IGListTestCase
@end
@implementation IGListAdapterTests
- (void)setUp {
self.dataSource = [IGListTestAdapterDataSource new];
self.updater = [IGListReloadDataUpdater new];
[super setUp];
// test case doesn't use -setupWithObjects for more control over update events
self.adapter.collectionView = self.collectionView;
self.adapter.dataSource = self.dataSource;
}
- (void)test_whenAdapterNotUpdated_withDataSourceUpdated_thatAdapterHasNoSectionControllers {
self.dataSource.objects = @[@0, @1, @2];
XCTAssertNil([self.adapter sectionControllerForObject:@0]);
XCTAssertNil([self.adapter sectionControllerForObject:@1]);
XCTAssertNil([self.adapter sectionControllerForObject:@2]);
}
- (void)test_whenAdapterUpdated_thatAdapterHasSectionControllers {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTAssertNotNil([self.adapter sectionControllerForObject:@0]);
XCTAssertNotNil([self.adapter sectionControllerForObject:@1]);
XCTAssertNotNil([self.adapter sectionControllerForObject:@2]);
}
- (void)test_whenAdapterReloaded_thatAdapterHasSectionControllers {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertNotNil([self.adapter sectionControllerForObject:@0]);
XCTAssertNotNil([self.adapter sectionControllerForObject:@1]);
XCTAssertNotNil([self.adapter sectionControllerForObject:@2]);
}
- (void)test_whenAdapterUpdated_thatSectionControllerHasSection {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * list = [self.adapter sectionControllerForObject:@1];
XCTAssertEqual([self.adapter sectionForSectionController:list], 1);
}
- (void)test_whenAdapterUpdated_withUnknownItem_thatSectionControllerHasNoSection {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * randomList = [[IGListTestSection alloc] init];
XCTAssertEqual([self.adapter sectionForSectionController:randomList], NSNotFound);
}
- (void)test_whenQueryingAdapter_withUnknownItem_thatSectionControllerIsNil {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTAssertNil([self.adapter sectionControllerForObject:@3]);
}
- (void)test_whenAdapterUpdated_thatSectionControllerHasCorrectObject {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * list = [self.adapter sectionControllerForObject:@1];
XCTAssertEqual([self.adapter objectForSectionController:list], @1);
}
- (void)test_whenQueryingAdapter_withUnknownItem_thatObjectForSectionControllerIsNil {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * randomList = [[IGListTestSection alloc] init];
XCTAssertNil([self.adapter objectForSectionController:randomList]);
}
- (void)test_whenQueryingIndexPaths_withSectionController_thatPathsAreEqual {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * second = [self.adapter sectionControllerForObject:@1];
NSArray *paths0 = [self.adapter indexPathsFromSectionController:second
indexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 4)]
usePreviousIfInUpdateBlock:NO];
NSArray *expected = @[
[NSIndexPath indexPathForItem:2 inSection:1],
[NSIndexPath indexPathForItem:3 inSection:1],
[NSIndexPath indexPathForItem:4 inSection:1],
[NSIndexPath indexPathForItem:5 inSection:1],
];
XCTAssertEqualObjects(paths0, expected);
}
- (void)test_whenQueryingIndexPaths_insideBatchUpdateBlock_thatPathsAreEqual {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter performUpdatesAnimated:YES completion:nil];
IGListSectionController * second = [self.adapter sectionControllerForObject:@1];
__block BOOL executed = NO;
[self.adapter performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
NSArray *paths = [self.adapter indexPathsFromSectionController:second
indexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(2, 2)]
usePreviousIfInUpdateBlock:YES];
NSArray *expected = @[
[NSIndexPath indexPathForItem:2 inSection:1],
[NSIndexPath indexPathForItem:3 inSection:1],
];
XCTAssertEqualObjects(paths, expected);
executed = YES;
} completion:nil];
XCTAssertTrue(executed);
}
- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassName {
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, nil);
XCTAssertEqualObjects(identifier, @"UICollectionViewCell");
}
- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassNameAndSupplimentaryKind {
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, UICollectionElementKindSectionFooter);
XCTAssertEqualObjects(identifier, @"UICollectionElementKindSectionFooterUICollectionViewCell");
}
- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassNameAndNibName {
NSString *nibName = @"IGNibName";
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nibName, nil);
XCTAssertEqualObjects(identifier, @"IGNibNameUICollectionViewCell");
}
- (void)test_whenDataSourceChanges_thatBackgroundViewVisibilityChanges {
self.dataSource.objects = @[@1];
UIView *background = [[UIView alloc] init];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = background;
__block BOOL executed = NO;
[self.adapter reloadDataWithCompletion:^(BOOL finished) {
XCTAssertTrue(self.adapter.collectionView.backgroundView.hidden, @"Background view should be hidden");
XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned");
self.dataSource.objects = @[];
[self.adapter reloadDataWithCompletion:^(BOOL finished2) {
XCTAssertFalse(self.adapter.collectionView.backgroundView.hidden, @"Background view should be visible");
XCTAssertEqualObjects(background, self.adapter.collectionView.backgroundView, @"Background view not correctly assigned");
executed = YES;
}];
}];
XCTAssertTrue(executed);
}
- (void)test_whenReloadingData_thatNewSectionControllersAreCreated {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *oldSectionController = [self.adapter sectionControllerForObject:@1];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *newSectionController = [self.adapter sectionControllerForObject:@1];
XCTAssertNotEqual(oldSectionController, newSectionController);
}
- (void)test_whenSettingCollectionView_thenSettingDataSource_thatViewControllerIsSet {
self.dataSource.objects = @[@0, @1, @2];
UIViewController *controller = [UIViewController new];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:controller];
adapter.collectionView = self.collectionView;
adapter.dataSource = self.dataSource;
IGListSectionController *sectionController = [adapter sectionControllerForObject:@1];
XCTAssertEqual(controller, sectionController.viewController);
}
- (void)test_whenSettingCollectionView_thenSettingDataSource_thatCellExists {
self.dataSource.objects = @[@1];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:nil];
adapter.collectionView = self.collectionView;
adapter.dataSource = self.dataSource;
[self.collectionView layoutIfNeeded];
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
}
- (void)test_whenSettingDataSource_thenSettingCollectionView_thatCellExists {
self.dataSource.objects = @[@1];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:nil];
adapter.dataSource = self.dataSource;
adapter.collectionView = self.collectionView;
[self.collectionView layoutIfNeeded];
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
}
- (void)test_whenChangingCollectionViews_thatCellsExist {
self.dataSource.objects = @[@1];
IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil];
adapter.dataSource = self.dataSource;
adapter.collectionView = self.collectionView;
[self.collectionView layoutIfNeeded];
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
UICollectionView *otherCollectionView = [[UICollectionView alloc] initWithFrame:self.collectionView.frame collectionViewLayout:self.collectionView.collectionViewLayout];
adapter.collectionView = otherCollectionView;
[otherCollectionView layoutIfNeeded];
XCTAssertNotNil([otherCollectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
}
- (void)test_whenChangingCollectionViewsToACollectionViewInUseByAnotherAdapter_thatCollectionViewDelegateIsUpdated {
IGListTestAdapterDataSource *dataSource1 = [[IGListTestAdapterDataSource alloc] init];
dataSource1.objects = @[@1];
IGListAdapterUpdater *updater1 = [[IGListAdapterUpdater alloc] init];
IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:updater1 viewController:nil];
adapter1.dataSource = dataSource1;
IGListTestAdapterDataSource *dataSource2 = [[IGListTestAdapterDataSource alloc] init];
dataSource1.objects = @[@1];
IGListAdapterUpdater *updater2 = [[IGListAdapterUpdater alloc] init];
IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:updater2 viewController:nil];
adapter1.dataSource = dataSource2;
// associate collection view with adapter1
adapter1.collectionView = self.collectionView;
XCTAssertEqual(self.collectionView.dataSource, adapter1);
// associate collection view with adapter2
adapter2.collectionView = self.collectionView;
XCTAssertEqual(self.collectionView.dataSource, adapter2);
// associate collection view with adapter1
adapter1.collectionView = self.collectionView;
XCTAssertEqual(self.collectionView.dataSource, adapter1);
}
- (void)test_whenCellsExtendBeyondBounds_thatVisibleSectionControllersAreLimited {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 12);
NSArray *visibleSectionControllers = [self.adapter visibleSectionControllers];
// UIWindow is 100x100, each cell is 100x10 so should have the following section/cell count: 1 + 2 + 3 + 4 = 10 (100 tall)
XCTAssertEqual(visibleSectionControllers.count, 4);
XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@1]]);
XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@2]]);
XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@3]]);
XCTAssertTrue([visibleSectionControllers containsObject:[self.adapter sectionControllerForObject:@4]]);
}
- (void)test_whenCellsExtendBeyondBounds_thatVisibleCellsExistForSectionControllers {
self.dataSource.objects = @[@2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
id sectionController2 = [self.adapter sectionControllerForObject:@2];
id sectionController3 = [self.adapter sectionControllerForObject:@3];
id sectionController4 = [self.adapter sectionControllerForObject:@4];
id sectionController5 = [self.adapter sectionControllerForObject:@5];
id sectionController6 = [self.adapter sectionControllerForObject:@6];
XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController2].count, 2);
XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController3].count, 3);
XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController4].count, 4);
XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController5].count, 1);
XCTAssertEqual([self.adapter visibleCellsForSectionController:sectionController6].count, 0);
}
- (void)test_whenCellsExtendBeyondBounds_thatVisibleIndexPathsExistForSectionControllers {
self.dataSource.objects = @[@2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
id sectionController2 = [self.adapter sectionControllerForObject:@2];
id sectionController3 = [self.adapter sectionControllerForObject:@3];
id sectionController4 = [self.adapter sectionControllerForObject:@4];
id sectionController5 = [self.adapter sectionControllerForObject:@5];
id sectionController6 = [self.adapter sectionControllerForObject:@6];
XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController2].count, 2);
XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController3].count, 3);
XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController4].count, 4);
XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController5].count, 1);
XCTAssertEqual([self.adapter visibleIndexPathsForSectionController:sectionController6].count, 0);
}
- (void)test_whenDataSourceAddsItems_thatEmptyViewBecomesVisible {
self.dataSource.objects = @[];
UIView *background = [UIView new];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = background;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual(self.collectionView.backgroundView, background);
XCTAssertFalse(self.collectionView.backgroundView.hidden);
self.dataSource.objects = @[@2];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertTrue(self.collectionView.backgroundView.hidden);
}
- (void)test_whenInsertingIntoEmptySection_thatEmptyViewBecomesHidden {
self.dataSource.objects = @[@0];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = [UIView new];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertFalse(self.collectionView.backgroundView.hidden);
IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)];
sectionController.items = 1;
[self.adapter insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
XCTAssertTrue(self.collectionView.backgroundView.hidden);
}
- (void)test_whenDeletingAllItemsFromSection_thatEmptyViewBecomesVisible {
self.dataSource.objects = @[@1];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = [UIView new];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertTrue(self.collectionView.backgroundView.hidden);
IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(1)];
sectionController.items = 0;
[self.adapter deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
XCTAssertFalse(self.collectionView.backgroundView.hidden);
}
- (void)test_whenEmptySectionAddsItems_thatEmptyViewBecomesHidden {
self.dataSource.objects = @[@0];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = [UIView new];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertFalse(self.collectionView.backgroundView.hidden);
IGListTestSection *sectionController = [self.adapter sectionControllerForObject:@(0)];
sectionController.items = 2;
[self.adapter reloadSectionController:sectionController];
XCTAssertTrue(self.collectionView.backgroundView.hidden);
}
- (void)test_whenSectionItemsAreDeletedAsBatch_thatEmptyViewBecomesVisible {
self.dataSource.objects = @[@1, @2];
((IGListTestAdapterDataSource *)self.dataSource).backgroundView = [UIView new];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertTrue(self.collectionView.backgroundView.hidden);
IGListTestSection *firstSectionController = [self.adapter sectionControllerForObject:@(1)];
IGListTestSection *secondSectionController = [self.adapter sectionControllerForObject:@(2)];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
[self.adapter performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
firstSectionController.items = 0;
[self.adapter deleteInSectionController:firstSectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
secondSectionController.items = 0;
NSIndexSet *indexesToDelete = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, 2)];
[self.adapter deleteInSectionController:secondSectionController atIndexes:indexesToDelete];
} completion:^(BOOL finished) {
XCTAssertFalse(self.collectionView.backgroundView.hidden);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenScrollViewDelegateSet_thatDelegateReceivesEvents {
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)];
self.adapter.collectionViewDelegate = nil;
self.adapter.scrollViewDelegate = mockDelegate;
[[mockDelegate expect] scrollViewDidScroll:self.collectionView];
[self.adapter scrollViewDidScroll:self.collectionView];
[mockDelegate verify];
}
- (void)test_whenCollectionViewDelegateSet_thatDelegateReceivesEvents {
// silence display handler asserts
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;
self.adapter.scrollViewDelegate = nil;
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:path];
[[mockDelegate expect] collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path];
[self.adapter collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path];
[mockDelegate verify];
}
- (void)test_whenCollectionViewDelegateSet_withScrollViewDelegateSet_thatDelegatesReceiveUniqueEvents {
// silence display handler asserts
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockCollectionViewDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
id mockScrollViewDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)];
self.adapter.collectionViewDelegate = mockCollectionViewDelegate;
self.adapter.scrollViewDelegate = mockScrollViewDelegate;
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:path];
[[mockScrollViewDelegate expect] scrollViewDidScroll:self.collectionView];
[[mockCollectionViewDelegate reject] scrollViewDidScroll:self.collectionView];
[[mockCollectionViewDelegate expect] collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path];
[self.adapter scrollViewDidScroll:self.collectionView];
[self.adapter collectionView:self.collectionView didEndDisplayingCell:cell forItemAtIndexPath:path];
[mockScrollViewDelegate verify];
[mockCollectionViewDelegate verify];
}
- (void)test_whenSupplementarySourceSupportsFooter_thatHeaderViewsAreNil {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
}
- (void)test_whenSupplementarySourceSupportsFooter_withNibs_thatHeaderViewsAreNil {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.dequeueFromNib = YES;
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
id view = [self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertTrue([view isKindOfClass:IGTestNibSupplementaryView.class]);
XCTAssertEqualObjects([[(IGTestNibSupplementaryView *)view label] text], @"Foo bar baz");
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
}
- (void)test_whenAdapterReleased_withSectionControllerStrongRefToCell_thatSectionControllersRelease {
__weak id weakCollectionView = nil, weakAdapter = nil, weakSectionController = nil;
@autoreleasepool {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)
collectionViewLayout:layout];
weakCollectionView = collectionView;
IGListTestAdapterDataSource *dataSource = [[IGListTestAdapterDataSource alloc] init];
dataSource.objects = @[@0, @1, @2];
IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil];
adapter.collectionView = collectionView;
adapter.dataSource = dataSource;
weakAdapter = adapter;
IGListSectionController *sectionController = [adapter sectionControllerForObject:@1];
weakSectionController = sectionController;
// force the collection view to layout and generate cells
[collectionView layoutIfNeeded];
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]];
XCTAssertNotNil(cell);
// strongly attach the cell to an section controller
objc_setAssociatedObject(sectionController, @"some_random_key", cell, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// weak refs should exist at this point
XCTAssertNotNil(weakCollectionView);
XCTAssertNotNil(weakAdapter);
XCTAssertNotNil(weakSectionController);
}
XCTAssertNil(weakCollectionView);
XCTAssertNil(weakAdapter);
XCTAssertNil(weakSectionController);
}
- (void)test_whenAdapterReleased_withSectionControllerStrongRefToCollectionView_thatSectionControllersRelease {
__weak id weakCollectionView = nil, weakAdapter = nil, weakSectionController = nil;
@autoreleasepool {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)
collectionViewLayout:layout];
weakCollectionView = collectionView;
IGListTestAdapterDataSource *dataSource = [[IGListTestAdapterDataSource alloc] init];
dataSource.objects = @[@0, @1, @2];
IGListReloadDataUpdater *updater = [[IGListReloadDataUpdater alloc] init];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil];
adapter.collectionView = collectionView;
adapter.dataSource = dataSource;
weakAdapter = adapter;
IGListSectionController *sectionController = [adapter sectionControllerForObject:@1];
weakSectionController = sectionController;
// force the collection view to layout and generate cells
[collectionView layoutIfNeeded];
// strongly attach the cell to an section controller
objc_setAssociatedObject(sectionController, @"some_random_key", collectionView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// weak refs should exist at this point
XCTAssertNotNil(weakCollectionView);
XCTAssertNotNil(weakAdapter);
XCTAssertNotNil(weakSectionController);
}
XCTAssertNil(weakCollectionView);
XCTAssertNil(weakAdapter);
XCTAssertNil(weakSectionController);
}
- (void)test_whenAdapterUpdatedTwice_withThreeSections_thatSectionsUpdatedFirstLast {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertTrue([[self.adapter sectionControllerForObject:@0] isFirstSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@1] isFirstSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@2] isFirstSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@0] isLastSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@1] isLastSection]);
XCTAssertTrue([[self.adapter sectionControllerForObject:@2] isLastSection]);
// update and shift objects to test that first/last flags are also updated
self.dataSource.objects = @[@2, @0, @1];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertFalse([[self.adapter sectionControllerForObject:@0] isFirstSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@1] isFirstSection]);
XCTAssertTrue([[self.adapter sectionControllerForObject:@2] isFirstSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@0] isLastSection]);
XCTAssertTrue([[self.adapter sectionControllerForObject:@1] isLastSection]);
XCTAssertFalse([[self.adapter sectionControllerForObject:@2] isLastSection]);
}
- (void)test_whenAdapterUpdated_withObjectsOverflow_thatVisibleObjectsIsSubsetOfAllObjects {
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
self.collectionView.contentOffset = CGPointMake(0, 30);
[self.collectionView layoutIfNeeded];
NSArray *visibleObjects = [[self.adapter visibleObjects] sortedArrayUsingSelector:@selector(compare:)];
NSArray *expectedObjects = @[@3, @4, @5];
XCTAssertEqualObjects(visibleObjects, expectedObjects);
}
- (void)test_whenAdapterUpdated_thatVisibleCellsForObjectAreFound {
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@2, @10, @5];
[self.adapter reloadDataWithCompletion:nil];
self.collectionView.contentOffset = CGPointMake(0, 80);
[self.collectionView layoutIfNeeded];
UICollectionView *collectionView = self.collectionView;
NSArray *visibleCellsForObject = [[self.adapter visibleCellsForObject:@10] sortedArrayUsingComparator:^NSComparisonResult(UICollectionViewCell* lhs, UICollectionViewCell* rhs) {
NSIndexPath *lhsIndexPath = [collectionView indexPathForCell:lhs];
NSIndexPath *rhsIndexPath = [collectionView indexPathForCell:rhs];
if (lhsIndexPath.section == rhsIndexPath.section) {
return lhsIndexPath.item > rhsIndexPath.item;
}
return lhsIndexPath.section > rhsIndexPath.section;
}];
XCTAssertEqual(visibleCellsForObject.count, 4);
XCTAssertEqual([self.collectionView indexPathForCell:visibleCellsForObject[0]].item, 6);
XCTAssertEqual([self.collectionView indexPathForCell:visibleCellsForObject[1]].item, 7);
XCTAssertEqual([self.collectionView indexPathForCell:visibleCellsForObject[2]].item, 8);
XCTAssertEqual([self.collectionView indexPathForCell:visibleCellsForObject[3]].item, 9);
NSArray *visibleCellsForObjectTwo = [self.adapter visibleCellsForObject:@5];
XCTAssertEqual(visibleCellsForObjectTwo.count, 5);
}
- (void)test_whenAdapterUpdated_thatVisibleCellsForNilObjectIsEmpty {
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@2, @10, @5];
[self.adapter reloadDataWithCompletion:nil];
self.collectionView.contentOffset = CGPointMake(0, 80);
[self.collectionView layoutIfNeeded];
NSArray *visibleCellsForObject = [self.adapter visibleCellsForObject:@3];
XCTAssertEqual(visibleCellsForObject.count, 0);
}
- (void)test_whenScrollVerticallyToItem {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 30);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 150);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 105);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 60);
}
- (void)test_whenScrollHorizontallyToItem {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
IGListTestAdapterHorizontalDataSource *dataSource = [[IGListTestAdapterHorizontalDataSource alloc] init];
self.adapter.dataSource = dataSource;
dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 10, 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 30, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 150, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 105, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 60, 0);
self.layout.scrollDirection = UICollectionViewScrollDirectionVertical;
self.adapter.dataSource = self.dataSource;
}
- (void)test_whenScrollToItem_thatSupplementarySourceSupportsSingleHeader {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
[self.adapter scrollToObject:@1 supplementaryKinds:@[UICollectionElementKindSectionHeader] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:@[UICollectionElementKindSectionHeader] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 20);
}
- (void)test_whenScrollToItem_thatSupplementarySourceSupportsHeaderAndFooter {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
[self.adapter scrollToObject:@1 supplementaryKinds:@[UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:@[UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 30);
}
- (void)test_whenScrollVerticallyToItem_thatFeedIsEmpty {
self.dataSource.objects = @[];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
}
- (void)test_whenScrollVerticallyToItem_thatItemNotInFeed {
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
self.dataSource.objects = @[@1, @2, @3, @4];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@5 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@5 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
}
- (void)test_whenQueryingIndexPath_withOOBSectionController_thatNilReturned {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id randomSectionController = [IGListSectionController new];
XCTAssertNil([self.adapter indexPathForSectionController:randomSectionController index:0 usePreviousIfInUpdateBlock:NO]);
}
- (void)test_whenQueryingSectionForObject_thatSectionReturned {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.adapter sectionForObject:@0], 0);
XCTAssertEqual([self.adapter sectionForObject:@1], 1);
XCTAssertEqual([self.adapter sectionForObject:@2], 2);
XCTAssertEqual([self.adapter sectionForObject:@3], NSNotFound);
}
- (void)test_whenQueryingSectionControllerForSection_thatControllerReturned {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.adapter sectionControllerForSection:0], [self.adapter sectionControllerForObject:@0]);
XCTAssertEqual([self.adapter sectionControllerForSection:1], [self.adapter sectionControllerForObject:@1]);
XCTAssertEqual([self.adapter sectionControllerForSection:2], [self.adapter sectionControllerForObject:@2]);
}
- (void)test_whenReloadingData_withNoDataSource_thatCompletionCalledWithNO {
self.dataSource.objects = @[@1];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:nil];
adapter.collectionView = self.collectionView;
__block BOOL executed = NO;
[adapter reloadDataWithCompletion:^(BOOL finished) {
executed = YES;
XCTAssertFalse(finished);
}];
XCTAssertTrue(executed);
}
- (void)test_whenReloadingData_withNoCollectionView_thatCompletionCalledWithNO {
self.dataSource.objects = @[@1];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:nil];
adapter.dataSource = self.dataSource;
__block BOOL executed = NO;
[adapter reloadDataWithCompletion:^(BOOL finished) {
executed = YES;
XCTAssertFalse(finished);
}];
XCTAssertTrue(executed);
}
- (void)test_whenSectionControllerDeleting_withEmptyIndexes_thatNoUpdatesHappen {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListUpdatingDelegate)];
[[mockDelegate reject] deleteItemsFromCollectionView:[OCMArg any] indexPaths:[OCMArg any]];
self.adapter.updater = mockDelegate;
id sectionController = [self.adapter sectionControllerForObject:@1];
[self.adapter deleteInSectionController:sectionController atIndexes:[NSIndexSet new]];
[mockDelegate verify];
}
- (void)test_whenSectionControllerInserting_withEmptyIndexes_thatNoUpdatesHappen {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListUpdatingDelegate)];
[[mockDelegate reject] insertItemsIntoCollectionView:[OCMArg any] indexPaths:[OCMArg any]];
self.adapter.updater = mockDelegate;
id sectionController = [self.adapter sectionControllerForObject:@1];
[self.adapter insertInSectionController:sectionController atIndexes:[NSIndexSet new]];
[mockDelegate verify];
}
- (void)test_whenReloading_withSectionControllerNotFound_thatNoUpdatesHappen {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListUpdatingDelegate)];
[[mockDelegate reject] reloadCollectionView:[OCMArg any] sections:[OCMArg any]];
self.adapter.updater = mockDelegate;
id sectionController = [IGListSectionController new];
[self.adapter reloadSectionController:sectionController];
[mockDelegate verify];
}
- (void)test_whenSelectingCell_thatCollectionViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[[mockDelegate expect] collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
// simulates the collectionview telling its delegate that it was tapped
[self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenSelectingCell_thatSectionControllerReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
// simulates the collectionview telling its delegate that it was tapped
[self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];
XCTAssertTrue(s0.wasSelected);
XCTAssertFalse(s1.wasSelected);
XCTAssertFalse(s2.wasSelected);
}
- (void)test_whenDisplayingCell_thatCollectionViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
UICollectionViewCell *cell = [UICollectionViewCell new];
[[mockDelegate expect] collectionView:self.collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
// simulates the collectionview telling its delegate that a cell will be displayed
[self.adapter collectionView:self.collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenWillBeginDragging_thatScrollViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockCollectionDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
id mockScrollDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)];
self.adapter.collectionViewDelegate = mockCollectionDelegate;
self.adapter.scrollViewDelegate = mockScrollDelegate;
[[mockCollectionDelegate reject] scrollViewWillBeginDragging:self.collectionView];
[[mockScrollDelegate expect] scrollViewWillBeginDragging:self.collectionView];
// simulates the scrollview delegate telling the adapter that it will begin dragging
[self.adapter scrollViewWillBeginDragging:self.collectionView];
[mockCollectionDelegate verify];
[mockScrollDelegate verify];
}
- (void)test_whenDidEndDragging_thatScrollViewDelegateReceivesMethod {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockCollectionDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
id mockScrollDelegate = [OCMockObject mockForProtocol:@protocol(UIScrollViewDelegate)];
self.adapter.collectionViewDelegate = mockCollectionDelegate;
self.adapter.scrollViewDelegate = mockScrollDelegate;
[[mockCollectionDelegate reject] scrollViewDidEndDragging:self.collectionView willDecelerate:NO];
[[mockScrollDelegate expect] scrollViewDidEndDragging:self.collectionView willDecelerate:NO];
// simulates the scrollview delegate telling the adapter that it will end dragging
[self.adapter scrollViewDidEndDragging:self.collectionView willDecelerate:NO];
[mockCollectionDelegate verify];
[mockScrollDelegate verify];
}
- (void)test_whenReloadingObjectsThatDontExist_thatAdapterContinues {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
[self.adapter reloadObjects:@[@1, @3]];
XCTAssertEqual(self.collectionView.numberOfSections, 3);
}
- (void)test_whenDeselectingThroughContext_thatCellDeselected {
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
[self.collectionView selectItemAtIndexPath:path animated:NO scrollPosition:UICollectionViewScrollPositionTop];
XCTAssertTrue([[self.collectionView cellForItemAtIndexPath:path] isSelected]);
id section = [self.adapter sectionControllerForObject:@1];
[self.adapter deselectItemAtIndex:0 sectionController:section animated:NO];
XCTAssertFalse([[self.collectionView cellForItemAtIndexPath:path] isSelected]);
}
- (void)test_whenScrollingToIndex_withSectionController_thatPositionCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18, @19];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *section = [self.adapter sectionControllerForObject:@8];
[section.collectionContext scrollToSectionController:section atIndex:0 scrollPosition:UICollectionViewScrollPositionTop animated:NO];
XCTAssertEqual(self.collectionView.contentOffset.x, 0);
XCTAssertEqual(self.collectionView.contentOffset.y, 280);
}
- (void)test_whenDisplayingSectionController_withOnlySupplementaryView_thatDisplayEventStillSent {
self.dataSource.objects = @[@0];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@0];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
id mockDisplayDelegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)];
[[mockDisplayDelegate expect] listAdapter:self.adapter willDisplaySectionController:controller];
[[mockDisplayDelegate reject] listAdapter:self.adapter willDisplaySectionController:controller cell:[OCMArg any] atIndex:0];
controller.displayDelegate = mockDisplayDelegate;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
[mockDisplayDelegate verify];
}
- (void)test_whenEndingDisplayOfSectionController_withOnlySupplementaryView_thatDisplayEventStillSent {
self.dataSource.objects = @[@0];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionHeader];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@0];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNotNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
id mockDisplayDelegate = [OCMockObject mockForProtocol:@protocol(IGListDisplayDelegate)];
[[mockDisplayDelegate expect] listAdapter:self.adapter didEndDisplayingSectionController:controller];
[[mockDisplayDelegate reject] listAdapter:self.adapter didEndDisplayingSectionController:controller cell:[OCMArg any] atIndex:0];
controller.displayDelegate = mockDisplayDelegate;
controller.supplementaryViewSource = nil;
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNil([self.collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
[mockDisplayDelegate verify];
}
- (void)test_whenWillDisplaySupplementaryView_thatCollectionViewDelegateReceivesEvents {
// silence display handler asserts
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;
UICollectionReusableView *view = [UICollectionReusableView new];
NSString *kind = @"kind";
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
[[mockDelegate expect] collectionView:self.collectionView willDisplaySupplementaryView:view forElementKind:kind atIndexPath:path];
[self.adapter collectionView:self.collectionView willDisplaySupplementaryView:view forElementKind:kind atIndexPath:path];
[mockDelegate verify];
}
- (void)test_whenEndDisplayingSupplementaryView_thatCollectionViewDelegateReceivesEvents {
// silence display handler asserts
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
id mockDelegate = [OCMockObject mockForProtocol:@protocol(UICollectionViewDelegate)];
self.adapter.collectionViewDelegate = mockDelegate;
UICollectionReusableView *view = [UICollectionReusableView new];
NSString *kind = @"kind";
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
[[mockDelegate expect] collectionView:self.collectionView didEndDisplayingSupplementaryView:view forElementOfKind:kind atIndexPath:path];
[self.adapter collectionView:self.collectionView didEndDisplayingSupplementaryView:view forElementOfKind:kind atIndexPath:path];
[mockDelegate verify];
}
- (void)test_whenDataSourceDoesntHandleObject_thatObjectIsDropped {
// IGListTestAdapterDataSource does not handle NSStrings
self.dataSource.objects = @[@1, @"dog", @2];
[self.adapter reloadDataWithCompletion:nil];
NSArray *expected = @[@1, @2];
XCTAssertEqualObjects(self.adapter.objects, expected);
}
- (void)test_whenSectionEdgeInsetIsNotZero {
// IGListTestAdapterDataSource does not handle NSStrings
self.dataSource.objects = @[@42];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@42];
IGAssertEqualSize([self.adapter containerSizeForSectionController:controller], 98, 98);
}
- (void)test_whenSectionControllerReturnsNegativeSize_thatAdapterReturnsZero {
self.dataSource.objects = @[@1];
IGListTestSection *section = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
section.size = CGSizeMake(-1, -1);
const CGSize size = [self.adapter sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertEqual(size.width, 0.0);
XCTAssertEqual(size.height, 0.0);
}
- (void)test_whenSupplementarySourceReturnsNegativeSize_thatAdapterReturnsZero {
self.dataSource.objects = @[@1];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter];
supplementarySource.size = CGSizeMake(-1, -1);
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
const CGSize size = [self.adapter sizeForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertEqual(size.width, 0.0);
XCTAssertEqual(size.height, 0.0);
}
- (void)test_whenQueryingContainerInset_thatMatchesCollectionView {
self.dataSource.objects = @[@2];
[self.adapter reloadDataWithCompletion:nil];
self.collectionView.contentInset = UIEdgeInsetsMake(1, 2, 3, 4);
IGListSectionController *controller = [self.adapter sectionControllerForObject:@2];
const UIEdgeInsets inset = [controller.collectionContext containerInset];
XCTAssertEqual(inset.top, 1);
XCTAssertEqual(inset.left, 2);
XCTAssertEqual(inset.bottom, 3);
XCTAssertEqual(inset.right, 4);
}
- (void)test_whenQueryingInsetContainerSize_thatResultIsBoundsInsetByContent {
self.dataSource.objects = @[@2];
[self.adapter reloadDataWithCompletion:nil];
self.collectionView.contentInset = UIEdgeInsetsMake(1, 2, 3, 4);
IGListSectionController *controller = [self.adapter sectionControllerForObject:@2];
const CGSize size = [controller.collectionContext insetContainerSize];
XCTAssertEqual(size.width, 94);
XCTAssertEqual(size.height, 96);
}
- (void)test_whenInsertingAtBeginning_thatAllSectionControllerIndexesUpdateCorrectly_forInsertAtHead {
NSNumber *zero = @0;
NSNumber *one = @1;
NSNumber *two = @2;
NSNumber *three = @3;
self.dataSource.objects = @[one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *controller1a = [self.adapter sectionControllerForObject:one];
XCTAssertEqual(controller1a.sectionIndex, 0);
XCTAssertTrue(controller1a.isFirstSection);
XCTAssertEqual([self.adapter sectionControllerForObject:two].sectionIndex, 1);
XCTAssertEqual([self.adapter sectionControllerForObject:three].sectionIndex, 2);
XCTAssertTrue([self.adapter sectionControllerForObject:three].isLastSection);
self.dataSource.objects = @[zero, one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *controller0 = [self.adapter sectionControllerForObject:zero];
XCTAssertEqual(controller0.sectionIndex, 0);
XCTAssertTrue(controller0.isFirstSection);
IGListSectionController *controller1b = [self.adapter sectionControllerForObject:one];
XCTAssertEqual(controller1b.sectionIndex, 1);
XCTAssertFalse(controller1b.isFirstSection);
XCTAssertEqual([self.adapter sectionControllerForObject:two].sectionIndex, 2);
XCTAssertEqual([self.adapter sectionControllerForObject:three].sectionIndex, 3);
XCTAssertTrue([self.adapter sectionControllerForObject:three].isLastSection);
}
- (void)test_whenRemovingFromHead_thatAllSectionControllerIndexesUpdateCorrectly_RemovedSectionControllerIsNotFound {
NSNumber *zero = @0;
NSNumber *one = @1;
NSNumber *two = @2;
NSNumber *three = @3;
self.dataSource.objects = @[zero, one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *zeroController = [self.adapter sectionControllerForSection:0];
XCTAssertEqual(zeroController.sectionIndex, 0);
XCTAssertTrue(zeroController.isFirstSection);
IGListSectionController *oneController = [self.adapter sectionControllerForSection:1];
XCTAssertEqual(oneController.sectionIndex, 1);
XCTAssertFalse(oneController.isFirstSection);
IGListSectionController *threeController = [self.adapter sectionControllerForSection:3];
XCTAssertEqual(threeController.sectionIndex, 3);
XCTAssertTrue(threeController.isLastSection);
self.dataSource.objects = @[one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertEqual(zeroController.sectionIndex, NSNotFound);
XCTAssertFalse(zeroController.isFirstSection);
XCTAssertEqual(oneController.sectionIndex, 0);
XCTAssertTrue(oneController.isFirstSection);
XCTAssertEqual(threeController.sectionIndex, 2);
XCTAssertTrue(threeController.isLastSection);
}
- (void)test_whenRemovingFromMiddle_thatAllSectionControllerIndexesUpdateCorrectly_removedSectionControllerIsNotFound {
NSNumber *zero = @0;
NSNumber *one = @1;
NSNumber *two = @2;
NSNumber *three = @3;
self.dataSource.objects = @[zero, one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *zeroController = [self.adapter sectionControllerForSection:0];
XCTAssertEqual(zeroController.sectionIndex, 0);
XCTAssertTrue(zeroController.isFirstSection);
IGListSectionController *oneController = [self.adapter sectionControllerForSection:1];
XCTAssertEqual(oneController.sectionIndex, 1);
XCTAssertFalse(oneController.isFirstSection);
IGListSectionController *threeController = [self.adapter sectionControllerForSection:3];
XCTAssertEqual(threeController.sectionIndex, 3);
XCTAssertTrue(threeController.isLastSection);
self.dataSource.objects = @[zero, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertEqual(zeroController.sectionIndex, 0);
XCTAssertTrue(zeroController.isFirstSection);
XCTAssertEqual(oneController.sectionIndex, NSNotFound);
XCTAssertFalse(oneController.isFirstSection);
XCTAssertEqual(threeController.sectionIndex, 2);
XCTAssertTrue(threeController.isLastSection);
}
- (void)test_withStrongRefToSectionController_thatAdapterSectionIndexIsZero_thatSectionControllerIndexDoesNotChange {
IGListSectionController *sc = nil;
// hold a weak reference to simulate what would happen to the collectionContext object on a section controller
// if the section controller were held strongly by an async block and the rest of the infra was deallocated
__weak IGListAdapter *wAdapter = nil;
@autoreleasepool {
IGListTestAdapterDataSource *dataSource = [IGListTestAdapterDataSource new];
IGListReloadDataUpdater *updater = [IGListReloadDataUpdater new];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater
viewController:nil];
adapter.dataSource = dataSource;
adapter.collectionView = self.collectionView;
wAdapter = adapter;
dataSource.objects = @[@0, @1, @2];
[adapter performUpdatesAnimated:NO completion:nil];
sc = [adapter sectionControllerForSection:1];
XCTAssertEqual(sc.sectionIndex, 1);
}
XCTAssertEqual(sc.sectionIndex, 1);
XCTAssertEqual([wAdapter sectionForSectionController:sc], 0);
}
@end