IGListKit/Tests/IGListAdapterTests.m
Tim Oliver 2b0ca81c0b Added more coverage cases to list adapter tests
Summary: I reenabled a couple of tests that had previously been marked disabled, but work properly now, and I added additional tests to cover some of the coverage cases that had previously been missed.

Reviewed By: candance

Differential Revision: D45004625

fbshipit-source-id: dacf49b2f0f420f269e3071ad4fc76762c95365c
2023-04-17 20:41:49 -07:00

2355 lines
130 KiB
Objective-C

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <objc/runtime.h>
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <IGListDiffKit/IGListExperiments.h>
#import <IGListKit/IGListKit.h>
#import "IGListAdapterInternal.h"
#import "IGListTestAdapterDataSource.h"
#import "IGListTestAdapterHorizontalDataSource.h"
#import "IGListTestAdapterReorderingDataSource.h"
#import "IGListTestCase.h"
#import "IGListTestOffsettingLayout.h"
#import "IGListTestSection.h"
#import "IGTestNibSupplementaryView.h"
#import "IGTestReorderableSection.h"
#import "IGTestSupplementarySource.h"
#import "UICollectionViewLayout+InteractiveReordering.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;
if (@available(iOS 11.0, tvOS 11.0, *)) {
self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
- (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_whenReloadingEmptyIndexSet_whenInvalidatingLayout_insideBatchUpdateBlock_thatOperationExitsGracefully {
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) {
[batchContext reloadInSectionController:second atIndexes:[NSIndexSet indexSet]];
[batchContext invalidateLayoutInSectionController:second atIndexes:[NSIndexSet indexSet]];
executed = YES;
} completion:nil];
XCTAssertTrue(executed);
}
- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassName {
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, nil);
XCTAssertEqualObjects(identifier, @"UICollectionViewCell");
}
- (void)test_whenQueryingReusableIdentifierWithGivenIdentifier_thatIdentifierEqualsGivenIdentifierAndClassName {
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, nil, @"MyCoolID");
XCTAssertEqualObjects(identifier, @"MyCoolIDUICollectionViewCell");
}
- (void)test_whenQueryingReusableIdentifier_thatIdentifierEqualsClassNameAndSupplimentaryKind {
NSString *identifier = IGListReusableViewIdentifier(UICollectionViewCell.class, UICollectionElementKindSectionFooter, nil);
XCTAssertEqualObjects(identifier, @"UICollectionElementKindSectionFooterUICollectionViewCell");
}
- (void)test_whenDequeueingTwoCellsOfTheSameClassWithDifferentReuseIdentifiers_thatBothReuseIdentifiersReturnCells {
self.dataSource.objects = @[@1, @2];
[self.adapter reloadDataWithCompletion:nil];
UICollectionViewCell *cell1 = [self.adapter dequeueReusableCellOfClass:[UICollectionViewCell class]
withReuseIdentifier:@"reuse-1"
forSectionController:[self.adapter sectionControllerForObject:@1]
atIndex:0];
UICollectionViewCell *cell2 = [self.adapter dequeueReusableCellOfClass:[UICollectionViewCell class]
withReuseIdentifier:@"reuse-2"
forSectionController:[self.adapter sectionControllerForObject:@1]
atIndex:0];
XCTAssertNotNil(cell1);
XCTAssertNotNil(cell2);
}
- (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) {
UIView *backgroundViewAfterReload = self.adapter.collectionView.backgroundView;
XCTAssertTrue(!backgroundViewAfterReload || backgroundViewAfterReload.hidden, @"Background view should be hidden");
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_withEmptySectionPlusFooter_thatVisibleSectionControllersAreCorrect {
self.dataSource.objects = @[@0];
[self.adapter reloadDataWithCompletion:nil];
IGTestSupplementarySource *supplementarySource = [IGTestSupplementarySource new];
supplementarySource.dequeueFromNib = YES;
supplementarySource.collectionContext = self.adapter;
supplementarySource.supportedElementKinds = @[UICollectionElementKindSectionFooter];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@0];
controller.supplementaryViewSource = supplementarySource;
supplementarySource.sectionController = controller;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSArray<IGListSectionController *> *visibleSectionControllers = [self.adapter visibleSectionControllers];
XCTAssertTrue([visibleSectionControllers count] == 1);
XCTAssertTrue(visibleSectionControllers.firstObject.supplementaryViewSource == supplementarySource);
}
- (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];
UIView *backgroundView = self.adapter.collectionView.backgroundView;
XCTAssertTrue(!backgroundView || 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];
UIView *backgroundView = self.adapter.collectionView.backgroundView;
XCTAssertTrue(!backgroundView || 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_withSkipViewSectionControllerMap_withObjectsOverflow_thatVisibleObjectsIsSubsetOfAllObjects {
self.adapter.experiments |= IGListExperimentSkipViewSectionControllerMap;
// 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_withSkipViewSectionControllerMap_fetchingCellIsValid {
self.adapter.experiments |= IGListExperimentSkipViewSectionControllerMap;
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
[self.collectionView layoutIfNeeded];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@2];
UICollectionViewCell *cell = [self.adapter cellForItemAtIndex:0 sectionController:controller];
XCTAssertNotNil(cell);
}
- (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_whenAdapterUpdated_thatFullyVisibleCellsIsCorrect {
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@2, @10, @5];
[self.adapter reloadDataWithCompletion:nil];
[self.collectionView layoutIfNeeded];
// each row is 10 pixels high, and the first object has 2 rows.
// the window is 100 pixels highm so the number of visible cells in the second section should be 8
IGListSectionController *controller = [self.adapter sectionControllerForObject:@10];
NSArray *visibleCells = [self.adapter fullyVisibleCellsForSectionController:controller];
XCTAssertEqual(visibleCells.count, 8);
}
- (void)test_whenAdapterUpdated_thatFullyVisibleIndexPathsIsCorrect {
// each section controller returns n items sized 100x10
self.dataSource.objects = @[@2, @10, @5];
[self.adapter reloadDataWithCompletion:nil];
[self.collectionView layoutIfNeeded];
// each row is 10 pixels high, and the first object has 2 rows.
// the window is 100 pixels highm so the number of visible cells in the second section should be 8
IGListSectionController *controller = [self.adapter sectionControllerForObject:@10];
NSArray *visibleCells = [self.adapter visibleIndexPathsForSectionController:controller];
XCTAssertEqual(visibleCells.count, 8);
}
- (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 additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 30);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content height minus collection view height is 110, can't scroll more than that
IGAssertEqualPoint([self.collectionView contentOffset], 0, 110);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionCenteredVertically additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 110);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 110);
}
- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsAndNoSupplymentaryView {
self.dataSource.objects = @[@1, @0, @300];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@0 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@300 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
}
- (void)test_whenScrollVerticallyToItemInASectionWithNoCellsButAHeaderSupplymentaryView {
self.dataSource.objects = @[@1, @0, @300];
[self.adapter reloadDataWithCompletion:nil];
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];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@0 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@0 supplementaryKinds:@[UICollectionElementKindSectionHeader] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionTop additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@300 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 20);
}
- (void)test_whenScrollVerticallyToItemWithPositionning {
self.dataSource.objects = @[@1, @100, @200];
[self.adapter reloadDataWithCompletion:nil];
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionTop additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionCenteredVertically additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionTop additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 10);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionCenteredVertically additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 460);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 910);
[self.adapter scrollToObject:@200 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, self.collectionView.contentSize.height - self.collectionView.frame.size.height);
}
- (void)test_whenScrollVerticallyToBottom_withContentInsets_thatBottomFlushWithCollectionViewBounds {
self.dataSource.objects = @[@100];
[self.adapter reloadDataWithCompletion:nil];
// no insets
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 900);
// top 100
self.collectionView.contentInset = UIEdgeInsetsMake(100, 0, 0, 0);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 900);
// bottom 100
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 100, 0);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 900 + 100);
// top 50, bottom 100
self.collectionView.contentInset = UIEdgeInsetsMake(50, 0, 100, 0);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionBottom additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 900 + 100);
}
- (void)test_whenScrollHorizontalToRight_withContentInsets_thatRightFlushWithCollectionViewBounds {
self.dataSource.objects = @[@100];
[self.adapter reloadDataWithCompletion:nil];
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[layout invalidateLayout];
// no insets
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 900, 0);
// left 100
self.collectionView.contentInset = UIEdgeInsetsMake(0, 100, 0, 0);
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 900, 0);
// right 100
self.collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 100);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 900 + 100, 0);
// left 50, right 100
self.collectionView.contentInset = UIEdgeInsetsMake(0, 50, 0, 100);
[self.collectionView layoutIfNeeded];
[self.adapter scrollToObject:@100 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 900 + 100, 0);
}
- (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:UICollectionViewScrollPositionLeft additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 10, 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 30, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content width minus collection view width is 110, can't scroll more than that
IGAssertEqualPoint([self.collectionView contentOffset], 110, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionCenteredHorizontally additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 110, 0);
[self.adapter scrollToObject:@6 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionRight additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 110, 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 additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:@[UICollectionElementKindSectionHeader] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content height smaller than collection view height, won't scroll
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
}
- (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 additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:@[UICollectionElementKindSectionHeader, UICollectionElementKindSectionFooter] scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content height smaller than collection view height, won't scroll
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
}
- (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 additionalOffset:0 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 additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@5 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@2 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content height is smaller than collection view height, can't scroll
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
[self.adapter scrollToObject:@5 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
// Content height is smaller than collection view height, can't scroll
IGAssertEqualPoint([self.collectionView contentOffset], 0, 0);
}
- (void)test_whenScrollToItem_thatNonVisibleCellsDidNotAppear {
// Regenerate the source with existing objects
self.dataSource = [IGListTestAdapterDataSource new];
self.dataSource.objects = @[@20, @22];
self.adapter.dataSource = self.dataSource;
// # of items for each object == [item integerValue], so @2 has 2 items (cells)
// Assumptions: UICollectionView size is (100,100), each cell size is (100,10)
[self.adapter scrollToObject:@22 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionTop additionalOffset:0 animated:NO];
// Force the layout, which creates the cells
[self.collectionView layoutIfNeeded];
IGListTestSection *firstSection = [self.adapter sectionControllerForObject:@20];
XCTAssertNotNil(firstSection);
IGListTestSection *lastSection = [self.adapter sectionControllerForObject:@22];
XCTAssertNotNil(lastSection);
XCTAssertFalse(firstSection.wasDisplayed);
XCTAssertTrue(lastSection.wasDisplayed);
}
- (void)test_whenScrolledVertically_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 8) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 12) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(0, 35) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 40) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 59) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledVerticallyWithContentInset_thatIndexPathForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 3);
[self.collectionView setContentOffset:CGPointMake(0, 0 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 8 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(0, 12 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(0, 35 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 40 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(0, 59 - inset) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithSingleRow_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(590, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithSingleRowWithContentInset_thatIndexPathForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(400 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:1 inSection:2]);
[self.collectionView setContentOffset:CGPointMake(590 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:2 inSection:2]);
}
- (void)test_whenScrolledHorizontallyWithStackedSections_thatIndexPathForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:0]);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:1]);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:3]);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:4]);
[self.collectionView setContentOffset:CGPointMake(590, 0) animated:NO];
XCTAssertEqual([self.adapter indexPathForFirstVisibleItem], [NSIndexPath indexPathForItem:0 inSection:5]);
}
- (void)test_whenScrolledVertically_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 8) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 8);
[self.collectionView setContentOffset:CGPointMake(0, 12) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 2);
[self.collectionView setContentOffset:CGPointMake(0, 35) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 5);
[self.collectionView setContentOffset:CGPointMake(0, 40) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 59) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrolledVerticallyWithContentInset_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 8 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 8);
[self.collectionView setContentOffset:CGPointMake(0, 12 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 2);
[self.collectionView setContentOffset:CGPointMake(0, 35 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 5);
[self.collectionView setContentOffset:CGPointMake(0, 40 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.collectionView setContentOffset:CGPointMake(0, 59 - inset) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrolledHorizontallyWithSingleRow_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrolledHorizontallyWithSingleRowWithContentInset_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.dataSource.objects = @[@1, @2, @3, @4];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.collectionView.frame = CGRectMake(0, 0, 300, 10);
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 4);
[self.collectionView setContentOffset:CGPointMake(0 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599 - inset, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrolledHorizontallyWithStackedSections_thatOffsetForFirstVisibleItemIsCorrect {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6];
self.layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
[self.adapter reloadDataWithCompletion:nil];
XCTAssertEqual([self.collectionView numberOfSections], 6);
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(80, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 80);
[self.collectionView setContentOffset:CGPointMake(120, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 20);
[self.collectionView setContentOffset:CGPointMake(350, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 50);
[self.collectionView setContentOffset:CGPointMake(400, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.collectionView setContentOffset:CGPointMake(599, 0) animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (void)test_whenScrollToIndexPathWithAdditionalOffsetVerticially_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 5;
self.collectionView.contentInset = UIEdgeInsetsMake(inset, 0, inset, 0);
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 additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:7 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 7);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:3 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 3);
[self.adapter scrollToObject:@4 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionVertical scrollPosition:UICollectionViewScrollPositionNone additionalOffset:9 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionVertical], 9);
}
- (void)test_whenScrollToIndexPathWithAdditionalOffsetHorizontally_thatOffsetForFirstVisibleItemIsCorrect {
const CGFloat inset = 172;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
self.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 additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.adapter scrollToObject:@1 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:73 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 73);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:0 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 0);
[self.adapter scrollToObject:@3 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:36 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 36);
[self.adapter scrollToObject:@4 supplementaryKinds:nil scrollDirection:UICollectionViewScrollDirectionHorizontal scrollPosition:UICollectionViewScrollPositionNone additionalOffset:99 animated:NO];
XCTAssertEqual([self.adapter offsetForFirstVisibleItemWithScrollDirection:UICollectionViewScrollDirectionHorizontal], 99);
}
- (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 collection view telling its delegate that it was tapped
[self.adapter collectionView:self.collectionView didSelectItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenSelectingCell_thatSelectionConfirmationDelegatesReturnValues {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
XCTAssertTrue([self.adapter collectionView:self.collectionView shouldSelectItemAtIndexPath:indexPath]);
XCTAssertTrue([self.adapter collectionView:self.collectionView shouldDeselectItemAtIndexPath:indexPath]);
}
- (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_whenDeselectingCell_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 didDeselectItemAtIndexPath:indexPath];
// simulates the collectionview telling its delegate that it was tapped
[self.adapter collectionView:self.collectionView didDeselectItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenDeselectingCell_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 didDeselectItemAtIndexPath:indexPath];
IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];
XCTAssertTrue(s0.wasDeselected);
XCTAssertFalse(s1.wasDeselected);
XCTAssertFalse(s2.wasDeselected);
}
- (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_whenDidEndDecelerating_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;
id mockSectionControllerScrollDelegate = [OCMockObject mockForProtocol:@protocol(IGListScrollDelegate)];
IGListSectionController *firstController = [self.adapter.visibleSectionControllers firstObject];
firstController.scrollDelegate = mockSectionControllerScrollDelegate;
[[mockCollectionDelegate reject] scrollViewDidEndDecelerating:self.collectionView];
[[mockScrollDelegate expect] scrollViewDidEndDecelerating:self.collectionView];
[[mockSectionControllerScrollDelegate expect] listAdapter:self.adapter didEndDeceleratingSectionController:firstController];
// simulates the scrollview delegate telling the adapter that it ended decelerating
[self.adapter scrollViewDidEndDecelerating:self.collectionView];
[mockCollectionDelegate verify];
[mockScrollDelegate verify];
[mockSectionControllerScrollDelegate 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_whenSelectingThroughContext_thatCellSelected {
self.dataSource.objects = @[@1, @2, @3];
[self.adapter reloadDataWithCompletion:nil];
NSIndexPath *path = [NSIndexPath indexPathForItem:0 inSection:0];
[self.adapter selectItemAtIndex:0 sectionController:[self.adapter sectionControllerForObject:@1] animated:NO scrollPosition:UICollectionViewScrollPositionTop];
XCTAssertTrue([[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_whenDisplayingSectionController_fetchingCellIndexIsValid {
self.dataSource.objects = @[@0, @1, @2];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@1];
UICollectionViewCell *cell = [self.adapter cellForItemAtIndex:0 sectionController:controller];
XCTAssertNotNil(cell);
NSInteger index = [self.adapter indexForCell:cell sectionController:controller];
XCTAssertNotEqual(index, NSNotFound);
XCTAssertEqual(index, 0);
}
- (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_whenEndDisplayingSupplementaryView_withSkipViewSectionControllerMap_thatCollectionViewDelegateReceivesEvents {
self.adapter.experiments |= IGListExperimentSkipViewSectionControllerMap;
// 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_whenHighlightingCell_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 didHighlightItemAtIndexPath:indexPath];
// simulates the collectionview telling its delegate that it was highlighted
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenHighlightingCell_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 highlighted
[self.adapter collectionView:self.collectionView didHighlightItemAtIndexPath:indexPath];
IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];
XCTAssertTrue(s0.wasHighlighted);
XCTAssertFalse(s1.wasHighlighted);
XCTAssertFalse(s2.wasHighlighted);
}
- (void)test_whenUnhighlightingCell_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 didUnhighlightItemAtIndexPath:indexPath];
// simulates the collectionview telling its delegate that it was unhighlighted
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:indexPath];
[mockDelegate verify];
}
- (void)test_whenUnlighlightingCell_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 unhighlighted
[self.adapter collectionView:self.collectionView didUnhighlightItemAtIndexPath:indexPath];
IGListTestSection *s0 = [self.adapter sectionControllerForObject:@0];
IGListTestSection *s1 = [self.adapter sectionControllerForObject:@1];
IGListTestSection *s2 = [self.adapter sectionControllerForObject:@2];
XCTAssertTrue(s0.wasUnhighlighted);
XCTAssertFalse(s1.wasUnhighlighted);
XCTAssertFalse(s2.wasUnhighlighted);
}
- (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_whenQueryingContainerContentOffset_thatResultIsCorrectlyOffset {
self.dataSource.objects = @[@1, @2, @3, @4, @5, @6, @7, @8, @9, @10];
[self.adapter reloadDataWithCompletion:nil];
IGListSectionController *controller = [self.adapter sectionControllerForObject:@3];
[self.adapter scrollToSectionController:controller atIndex:0 scrollPosition:UICollectionViewScrollPositionTop animated:NO];
const CGPoint offset = [controller.collectionContext containerContentOffset];
XCTAssertEqual(offset.x, 0);
XCTAssertEqual(offset.y, 30);
}
- (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.section, 0);
XCTAssertTrue(controller1a.isFirstSection);
XCTAssertEqual([self.adapter sectionControllerForObject:two].section, 1);
XCTAssertEqual([self.adapter sectionControllerForObject:three].section, 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.section, 0);
XCTAssertTrue(controller0.isFirstSection);
IGListSectionController *controller1b = [self.adapter sectionControllerForObject:one];
XCTAssertEqual(controller1b.section, 1);
XCTAssertFalse(controller1b.isFirstSection);
XCTAssertEqual([self.adapter sectionControllerForObject:two].section, 2);
XCTAssertEqual([self.adapter sectionControllerForObject:three].section, 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.section, 0);
XCTAssertTrue(zeroController.isFirstSection);
IGListSectionController *oneController = [self.adapter sectionControllerForSection:1];
XCTAssertEqual(oneController.section, 1);
XCTAssertFalse(oneController.isFirstSection);
IGListSectionController *threeController = [self.adapter sectionControllerForSection:3];
XCTAssertEqual(threeController.section, 3);
XCTAssertTrue(threeController.isLastSection);
self.dataSource.objects = @[one, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertEqual(zeroController.section, NSNotFound);
XCTAssertFalse(zeroController.isFirstSection);
XCTAssertEqual(oneController.section, 0);
XCTAssertTrue(oneController.isFirstSection);
XCTAssertEqual(threeController.section, 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.section, 0);
XCTAssertTrue(zeroController.isFirstSection);
IGListSectionController *oneController = [self.adapter sectionControllerForSection:1];
XCTAssertEqual(oneController.section, 1);
XCTAssertFalse(oneController.isFirstSection);
IGListSectionController *threeController = [self.adapter sectionControllerForSection:3];
XCTAssertEqual(threeController.section, 3);
XCTAssertTrue(threeController.isLastSection);
self.dataSource.objects = @[zero, two, three];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertEqual(zeroController.section, 0);
XCTAssertTrue(zeroController.isFirstSection);
XCTAssertEqual(oneController.section, NSNotFound);
XCTAssertFalse(oneController.isFirstSection);
XCTAssertEqual(threeController.section, 2);
XCTAssertTrue(threeController.isLastSection);
}
- (void)test_withStrongRefToSectionController_thatAdaptersectionIsZero_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.section, 1);
}
XCTAssertEqual(sc.section, NSNotFound);
XCTAssertEqual([wAdapter sectionForSectionController:sc], 0);
}
- (void)test_whenSwappingCollectionViews_withMultipleAdapters_thatDoesntNilOtherAdaptersCollectionView {
IGListTestAdapterDataSource *dataSource1 = [IGListTestAdapterDataSource new];
IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
adapter1.dataSource = dataSource1;
IGListTestAdapterDataSource *dataSource2 = [IGListTestAdapterDataSource new];
IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
adapter2.dataSource = dataSource2;
UICollectionView *collectionView1 = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[UICollectionViewFlowLayout new]];
UICollectionView *collectionView2 = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[UICollectionViewFlowLayout new]];
adapter1.collectionView = collectionView1;
adapter2.collectionView = collectionView2;
XCTAssertEqual(adapter1.collectionView, collectionView1);
XCTAssertEqual(collectionView1.dataSource, adapter1);
XCTAssertEqual(adapter2.collectionView, collectionView2);
XCTAssertEqual(collectionView2.dataSource, adapter2);
adapter2.collectionView = collectionView1;
XCTAssertEqual(adapter2.collectionView, collectionView1);
XCTAssertEqual(collectionView1.dataSource, adapter2);
XCTAssertNil(adapter1.collectionView);
adapter1.collectionView = collectionView2;
XCTAssertEqual(adapter1.collectionView, collectionView2);
XCTAssertEqual(collectionView2.dataSource, adapter1);
XCTAssertEqual(adapter2.collectionView, collectionView1);
XCTAssertEqual(collectionView1.dataSource, adapter2);
}
- (void)test_whenPrefetchingEnabled_thatSetterDisables {
if (@available(iOS 10.0, *)) {
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[UICollectionViewFlowLayout new]];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
adapter.collectionView = collectionView;
XCTAssertFalse(collectionView.prefetchingEnabled);
}
}
- (void)test_whenSectionControllerReorderDisabled_thatAdapterReorderDisabled {
BOOL isReorderable = NO;
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1, @2];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
IGTestReorderableSection *section = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section.isReorderable = isReorderable;
[self.adapter performUpdatesAnimated:NO completion:nil];
BOOL canMove = [self.adapter collectionView:self.collectionView canMoveItemAtIndexPath:indexPath];
XCTAssertFalse(canMove);
}
- (void)test_whenSectionControllerReorderEnabled_thatAdapterReorderEnabled {
BOOL isReorderable = YES;
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1, @2];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
IGTestReorderableSection *section = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section.isReorderable = isReorderable;
[self.adapter performUpdatesAnimated:NO completion:nil];
BOOL canMove = [self.adapter collectionView:self.collectionView canMoveItemAtIndexPath:indexPath];
XCTAssertTrue(canMove);
}
- (NSIndexPath *)interpretedIndexPathFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
UICollectionViewLayout *layout = self.collectionView.collectionViewLayout;
NSIndexPath *updatedIndexPath = [layout updatedTargetForInteractivelyMovingItem:fromIndexPath
toIndexPath:toIndexPath
adapter:self.adapter];
if (!updatedIndexPath) {
return toIndexPath;
}
return updatedIndexPath;
}
- (void)test_whenSectionIsInteractivelyReordered_thatIndexesUpdateCorrectly {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1, @2];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
IGTestReorderableSection *section0 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
IGTestReorderableSection *section1 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:1];
IGTestReorderableSection *section2 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:2];
section0.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
section1.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
section2.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
section2.isReorderable = YES;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath, *toIndexPath, *interpretedPath;
// move the last section into the first position, dropping into the end of the first section
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:2];
toIndexPath = [NSIndexPath indexPathForItem:1 inSection:0];
interpretedPath = [self interpretedIndexPathFromIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:interpretedPath];
XCTAssertEqual(section0, [self.adapter sectionControllerForSection:0]);
XCTAssertEqual(section2, [self.adapter sectionControllerForSection:1]);
XCTAssertEqual(section1, [self.adapter sectionControllerForSection:2]);
// move the last section into the first position, dropping into the start of the first section
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:2];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
interpretedPath = [self interpretedIndexPathFromIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:interpretedPath];
XCTAssertEqual(section1, [self.adapter sectionControllerForSection:0]);
XCTAssertEqual(section0, [self.adapter sectionControllerForSection:1]);
XCTAssertEqual(section2, [self.adapter sectionControllerForSection:2]);
// move the first section into the last position, dropping into the start of the last section
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:2];
interpretedPath = [self interpretedIndexPathFromIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:interpretedPath];
XCTAssertEqual(section0, [self.adapter sectionControllerForSection:0]);
XCTAssertEqual(section1, [self.adapter sectionControllerForSection:1]);
XCTAssertEqual(section2, [self.adapter sectionControllerForSection:2]);
// move the first section into the last position, dropping into the end of the last section
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
toIndexPath = [NSIndexPath indexPathForItem:1 inSection:2];
interpretedPath = [self interpretedIndexPathFromIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:interpretedPath];
XCTAssertEqual(section1, [self.adapter sectionControllerForSection:0]);
XCTAssertEqual(section2, [self.adapter sectionControllerForSection:1]);
XCTAssertEqual(section0, [self.adapter sectionControllerForSection:2]);
}
- (void)test_whenItemsInSectionAreInteractivelyReordered_thatIndexesUpdateCorrectly {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSArray *sectionObjects = @[@0, @1, @2];
IGTestReorderableSection *section = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:sectionObjects];
section.isReorderable = YES;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath, *toIndexPath;
// move the last item into the first position
fromIndexPath = [NSIndexPath indexPathForItem:2 inSection:0];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
XCTAssertEqual(sectionObjects[2], section.sectionObject.objects[0]);
XCTAssertEqual(sectionObjects[0], section.sectionObject.objects[1]);
XCTAssertEqual(sectionObjects[1], section.sectionObject.objects[2]);
// move the last item into the middle position
fromIndexPath = [NSIndexPath indexPathForItem:2 inSection:0];
toIndexPath = [NSIndexPath indexPathForItem:1 inSection:0];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
XCTAssertEqual(sectionObjects[2], section.sectionObject.objects[0]);
XCTAssertEqual(sectionObjects[1], section.sectionObject.objects[1]);
XCTAssertEqual(sectionObjects[0], section.sectionObject.objects[2]);
}
- (void)test_whenItemsInSectionAreInteractivelyReordered_andReorderingIsDisallowed_thatIndexesUpdateCorrectly {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSArray *sectionObjects = @[@0, @1, @2];
IGTestReorderableSection *section = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:sectionObjects];
section.isReorderable = NO;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath, *toIndexPath;
// move the last item into the first position
fromIndexPath = [NSIndexPath indexPathForItem:2 inSection:0];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
XCTAssertEqual(sectionObjects[0], section.sectionObject.objects[0]);
XCTAssertEqual(sectionObjects[1], section.sectionObject.objects[1]);
XCTAssertEqual(sectionObjects[2], section.sectionObject.objects[2]);
}
- (void)test_whenItemsAreInteractivelyReorderedAcrossSections_thatIndexesRevertToOriginalState {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSArray *section0Objects = @[@0, @1, @2];
NSArray *section1Objects = @[@3, @4, @5];
IGTestReorderableSection *section0 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section0.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:section0Objects];
IGTestReorderableSection *section1 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:1];
section1.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:section1Objects];
section1.isReorderable = YES;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath, *toIndexPath;
// move an item from section 1 to section 0 and check that they are reverted
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:1];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[self.collectionView performBatchUpdates:^{
[self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.collectionView.dataSource collectionView:self.collectionView
moveItemAtIndexPath:fromIndexPath
toIndexPath:toIndexPath];
} completion:nil];
XCTAssertEqual(section0Objects[0], section0.sectionObject.objects[0]);
XCTAssertEqual(section0Objects[1], section0.sectionObject.objects[1]);
XCTAssertEqual(section0Objects[2], section0.sectionObject.objects[2]);
XCTAssertEqual(section1Objects[0], section1.sectionObject.objects[0]);
XCTAssertEqual(section1Objects[1], section1.sectionObject.objects[1]);
XCTAssertEqual(section1Objects[2], section1.sectionObject.objects[2]);
}
- (void)test_whenSingleItemsAreInteractivelyReorderedAcrossSections_thatIndexesRevertToOriginalState {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
NSArray *section0Objects = @[@0];
NSArray *section1Objects = @[@3];
IGTestReorderableSection *section0 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
section0.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:section0Objects];
IGTestReorderableSection *section1 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:1];
section1.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:section1Objects];
section1.isReorderable = YES;
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath, *toIndexPath;
// move an item from section 1 to section 0 and check that they are reverted
fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:1];
toIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
[self.collectionView performBatchUpdates:^{
[self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
[self.collectionView.dataSource collectionView:self.collectionView
moveItemAtIndexPath:fromIndexPath
toIndexPath:toIndexPath];
} completion:nil];
XCTAssertEqual(section0Objects[0], section0.sectionObject.objects[0]);
XCTAssertEqual(section1Objects[0], section1.sectionObject.objects[0]);
}
- (void)test_whenSingleItemInSectionIsInteractivelyReorderedThorughLastSpot_indexesUpdateCorrectly {
IGListTestAdapterReorderingDataSource *dataSource = [IGListTestAdapterReorderingDataSource new];
dataSource.objects = @[@0, @1, @2];
self.adapter.dataSource = dataSource;
self.adapter.moveDelegate = dataSource;
IGTestReorderableSection *section0 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:0];
IGTestReorderableSection *section1 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:1];
IGTestReorderableSection *section2 = (IGTestReorderableSection *)[self.adapter sectionControllerForSection:2];
section0.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
section0.isReorderable = YES;
section1.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
section2.sectionObject = [IGTestReorderableSectionObject sectionWithObjects:@[@0]];
[self.adapter performUpdatesAnimated:NO completion:nil];
NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:0 inSection:0];
NSIndexPath *lastSpotIndexPath = [NSIndexPath indexPathForItem:1 inSection:2];
NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:1 inSection:1];
// move the first section item to the middle while simulating dragging to the last spot and back.
NSIndexPath *interpretedPath = [self interpretedIndexPathFromIndexPath:fromIndexPath toIndexPath:lastSpotIndexPath];
interpretedPath = [self interpretedIndexPathFromIndexPath:interpretedPath toIndexPath:toIndexPath];
[self.adapter collectionView:self.collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:interpretedPath];
XCTAssertEqual(section0, [self.adapter sectionControllerForSection:1]);
XCTAssertEqual(section1, [self.adapter sectionControllerForSection:0]);
XCTAssertEqual(section2, [self.adapter sectionControllerForSection:2]);
}
- (void)test_whenReload_thatPerformanceDelegateEventsSent {
self.dataSource.objects = @[@1];
// Use IGListReloadDataUpdater to make sure updates happen synchronously
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListReloadDataUpdater new]
viewController:nil];
adapter.collectionView = self.collectionView;
adapter.dataSource = self.dataSource;
id mockDelegate = [OCMockObject niceMockForProtocol:@protocol(IGListAdapterPerformanceDelegate)];
adapter.performanceDelegate = mockDelegate;
[mockDelegate setExpectationOrderMatters:YES];
// Sizing
[[mockDelegate expect] listAdapterWillCallSize:adapter];
[[mockDelegate expect] listAdapter:adapter didCallSizeOnSectionController:OCMOCK_ANY atIndex:0];
// Dequeue
[[mockDelegate expect] listAdapterWillCallDequeueCell:adapter];
[[mockDelegate expect] listAdapter:adapter didCallDequeueCell:OCMOCK_ANY onSectionController:OCMOCK_ANY atIndex:0];
// Display
[[mockDelegate expect] listAdapterWillCallDisplayCell:adapter];
[[mockDelegate expect] listAdapter:adapter didCallDisplayCell:OCMOCK_ANY onSectionController:OCMOCK_ANY atIndex:0];
// End display
[[mockDelegate expect] listAdapterWillCallEndDisplayCell:adapter];
[[mockDelegate expect] listAdapter:adapter didCallEndDisplayCell:OCMOCK_ANY onSectionController:OCMOCK_ANY atIndex:0];
// Commit changes
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
// Remove cell
self.dataSource.objects = @[];
// Commit changes
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
[mockDelegate verify];
}
- (void)test_whenScroll_thatPerformanceDelegateEventsSent {
id mockDelegate = [OCMockObject mockForProtocol:@protocol(IGListAdapterPerformanceDelegate)];
self.adapter.performanceDelegate = mockDelegate;
[mockDelegate setExpectationOrderMatters:YES];
[[mockDelegate expect] listAdapterWillCallScroll:self.adapter];
[[mockDelegate expect] listAdapter:self.adapter didCallScroll:self.collectionView];
[self.adapter scrollViewDidScroll:self.collectionView];
[mockDelegate verify];
}
#pragma mark - Deleted Section Controllers
- (void)test_whenSectionControllerRemoved_thatCellForIndexPathIsNil {
self.dataSource.objects = @[@1];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1];
self.dataSource.objects = @[@2];
[self.adapter performUpdatesAnimated:NO completion:nil];
XCTAssertNil([sectionController.collectionContext cellForItemAtIndex:0 sectionController:sectionController]);
}
- (void)test_whenSectionControllerRemoved_thatFullyVisibleCellsIsEmpty {
self.dataSource.objects = @[@1];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1];
self.dataSource.objects = @[@2];
[self.adapter performUpdatesAnimated:NO completion:nil];
NSArray<UICollectionViewCell *> *cells = [sectionController.collectionContext fullyVisibleCellsForSectionController:sectionController];
XCTAssertEqual(cells.count, 0);
}
- (void)test_whenSectionControllerRemoved_thatVisibleCellsIsEmpty {
self.dataSource.objects = @[@1];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1];
self.dataSource.objects = @[@2];
[self.adapter performUpdatesAnimated:NO completion:nil];
NSArray<UICollectionViewCell *> *cells = [sectionController.collectionContext visibleCellsForSectionController:sectionController];
XCTAssertEqual(cells.count, 0);
}
- (void)test_whenSectionControllerRemoved_thatVisibleIndexPathIsEmpty {
self.dataSource.objects = @[@1];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1];
self.dataSource.objects = @[@2];
[self.adapter performUpdatesAnimated:NO completion:nil];
NSArray<NSIndexPath *> *cells = [sectionController.collectionContext visibleIndexPathsForSectionController:sectionController];
XCTAssertEqual(cells.count, 0);
}
- (void)test_whenSectionControllerRemoved_thatDoesNotCrashOnInvalidatingLayout {
self.dataSource.objects = @[@1];
[self.adapter performUpdatesAnimated:NO completion:nil];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:@1];
self.dataSource.objects = @[@2];
[self.adapter performUpdatesAnimated:NO completion:nil];
[sectionController.collectionContext invalidateLayoutForSectionController:sectionController completion:nil];
}
- (void)test_whenSettingSupplementaryView_thatViewForSupplementaryElementExists {
self.dataSource.objects = @[@0];
[self.adapter reloadDataWithCompletion:nil];
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.adapter viewForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0 sectionController:controller]);
XCTAssertNil([self.adapter viewForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:1 sectionController:controller]);
}
- (void)test_whenSettingSupplementaryView_withSkipViewSectionControllerMap_thatViewForSupplementaryElementExists {
self.adapter.experiments |= IGListExperimentSkipViewSectionControllerMap;
self.dataSource.objects = @[@0];
[self.adapter reloadDataWithCompletion:nil];
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.adapter viewForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:0 sectionController:controller]);
XCTAssertNil([self.adapter viewForSupplementaryElementOfKind:UICollectionElementKindSectionHeader atIndex:1 sectionController:controller]);
}
@end