Files
IGListKit/Tests/IGListAdapterE2ETests.m
Ryan Nystrom 0a416914d5 Rename sectionIndex API to just section
Summary:
Quick rename before releasing 3.0 to get this API in parity w/ `isFirstSection` and `isLastSection`. My b I should have caught this in review.

Closes #720

Reviewed By: jessesquires, amonshiz

Differential Revision: D5031184

fbshipit-source-id: 1cd7b00c8b32084fecd861e78808eea0d0056337
2017-05-11 14:49:59 -07:00

1484 lines
67 KiB
Objective-C

/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import <IGListKit/IGListKit.h>
#import "IGListAdapterInternal.h"
#import "IGListTestOffsettingLayout.h"
#import "IGTestCell.h"
#import "IGTestDelegateController.h"
#import "IGTestDelegateDataSource.h"
#import "IGTestObject.h"
#import "IGListTestCase.h"
@interface IGListAdapterE2ETests : IGListTestCase
@end
@implementation IGListAdapterE2ETests
- (void)setUp {
self.workingRangeSize = 2;
self.dataSource = [IGTestDelegateDataSource new];
[super setUp];
}
- (void)test_whenSettingUpTest_thenCollectionViewIsLoaded {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @3)
]];
XCTAssertEqual(self.collectionView.numberOfSections, 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3);
}
- (void)test_whenUsingStringValue_thenCellLabelsAreConfigured {
[self setupWithObjects:@[
genTestObject(@0, @"Foo"),
genTestObject(@1, @"Bar")
]];
IGTestCell *cell = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0, 0)];
XCTAssertEqualObjects(cell.label.text, @"Foo");
XCTAssertEqual(cell.delegate, [self.adapter sectionControllerForObject:self.dataSource.objects[0]]);
}
- (void)test_whenUpdating_withEqualObjects_thatCellConfigurationDoesntChange {
[self setupWithObjects:@[
genTestObject(@0, @"Foo"),
genTestObject(@1, @"Bar")
]];
// Get the section controller before we change the data source or perform updates
id c0 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
// Set equal but new-instance objects on the data source
self.dataSource.objects = @[
genTestObject(@0, @"Foo"),
genTestObject(@1, @"Bar")
];
// Perform updates on the adapter and check that the cell config uses the same section controller as before the updates
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
IGTestCell *cell = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0, 0)];
XCTAssertEqualObjects(cell.label.text, @"Foo");
XCTAssertNotNil(cell.delegate);
XCTAssertEqual(cell.delegate, c0);
XCTAssertEqual(cell.delegate, [self.adapter sectionControllerForObject:self.dataSource.objects[0]]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingItem_cellConfigurationChanges {
[self setupWithObjects:@[
genTestObject(@0, @"Foo"),
genTestObject(@1, @"Bar")
]];
// make sure our cells are propertly configured
IGTestCell *cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0, 0)];
IGTestCell *cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(1, 0)];
XCTAssertEqualObjects(cell1.label.text, @"Foo");
XCTAssertEqualObjects(cell2.label.text, @"Bar");
// Change the string value of both instances in the data source
IGTestObject *item1 = self.dataSource.objects[0];
item1.value = @"Baz";
IGTestObject *item2 = self.dataSource.objects[1];
item2.value = @"Quz";
// Only reload the first item, not the second
[self.adapter reloadObjects:@[item1]];
// The collection view will likely create new cells
cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(0, 0)];
cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:genIndexPath(1, 0)];
// Make sure that the cell in the first section was reloaded
XCTAssertEqualObjects(cell1.label.text, @"Baz");
// The cell in the second section should not be reloaded and should equal the string value from setup
XCTAssertEqualObjects(cell2.label.text, @"Bar");
}
- (void)test_whenObjectEqualityChanges_thatSectionCountChanges {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
self.dataSource.objects = @[
genTestObject(@1, @2),
genTestObject(@2, @3), // updated to 3 items (from 2)
genTestObject(@3, @2), // insert new object
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual(self.collectionView.numberOfSections, 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenUpdatesComplete_thatCellsExist {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
]];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadDataCompletes_thatCellsExist {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
XCTestExpectation *expectation = genExpectation;
[self.adapter reloadDataWithCompletion:^(BOOL finished) {
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1]]);
XCTAssertNotNil([self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:1]]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerInsertsIndexes_thatCountsAreUpdated {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @3;
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerDeletesIndexes_thatCountsAreUpdated {
// 2 sections each with 2 objects
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @1;
[batchContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerReloadsIndexes_thatCellConfigurationUpdates {
[self setupWithObjects:@[
genTestObject(@1, @"a"),
genTestObject(@2, @"b")
]];
XCTAssertEqual([self.collectionView numberOfSections], 2);
IGTestCell *cell = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertEqualObjects(cell.label.text, @"a");
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @"c";
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
IGTestCell *updatedCell = (IGTestCell *)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
XCTAssertEqualObjects(updatedCell.label.text, @"c");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerReloads_thatCountsAreUpdated {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @3;
[batchContext reloadSectionController:sectionController];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withSectionControllerMutations_thatCollectionCountsAreUpdated {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
IGTestObject *object1 = self.dataSource.objects[0];
IGTestObject *object2 = self.dataSource.objects[1];
// insert a new object in front of the one we are doing an item-level insert on
self.dataSource.objects = @[
genTestObject(@3, @1), // new
object1,
object2,
];
IGListSectionController *sectionController1 = [self.adapter sectionControllerForObject:object1];
IGListSectionController *sectionController2 = [self.adapter sectionControllerForObject:object2];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTestExpectation *expectation = genExpectation;
[sectionController1.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object1.value = @1;
object2.value = @3;
[batchContext deleteInSectionController:sectionController1 atIndexes:[NSIndexSet indexSetWithIndex:0]];
[batchContext insertInSectionController:sectionController2 atIndexes:[NSIndexSet indexSetWithIndex:2]];
[batchContext reloadInSectionController:sectionController2 atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
// 3 sections now b/c of the insert
XCTAssertEqual([self.collectionView numberOfSections], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerMoves_withSectionControllerMutations_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
IGTestObject *object = self.dataSource.objects[0];
self.dataSource.objects = @[
genTestObject(@2, @2),
object, // moved from 0 to 1
];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
// queue the update that performs the section move
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTestExpectation *expectation = genExpectation;
// queue an item update that gets batched with the section move
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @3;
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
// the object we are tracking should now be in section 1 and have 3 items
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenItemIsRemoved_withSectionControllerMutations_thatCollectionViewWorks {
// 2 sections each with 2 objects
[self setupWithObjects:@[
genTestObject(@2, @2),
genTestObject(@1, @2)
]];
IGTestObject *object = self.dataSource.objects[1];
// object at index 1 deleted
self.dataSource.objects = @[
genTestObject(@2, @2),
];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @1;
[batchContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withUnequalItem_withItemMoving_thatCollectionViewCountsUpdate {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
]];
self.dataSource.objects = @[
genTestObject(@3, @2),
genTestObject(@1, @3), // moved from index 0 to 1, value changed from 2 to 3
genTestObject(@2, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withItemMoving_withSectionControllerReloadIndexes_thatCollectionViewCountsUpdate {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @3),
]];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
self.dataSource.objects = @[
genTestObject(@2, @3),
genTestObject(@1, @2), // moved from index 0 to 1
];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withSectionControllerReloadIndexes_withItemDeleted_thatCollectionViewCountsUpdate {
[self setupWithObjects:@[
genTestObject(@1, @2), // item that will be deleted
genTestObject(@2, @3),
]];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
self.dataSource.objects = @[
genTestObject(@2, @3),
];
[self.adapter performUpdatesAnimated:YES completion:nil];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withNewItemInstances_thatSectionControllersEqual {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2)
]];
// grab section controllers before updating the objects
NSArray *beforeupdateObjects = self.dataSource.objects;
IGListSectionController *sectionController1 = [self.adapter sectionControllerForObject:beforeupdateObjects.firstObject];
IGListSectionController *sectionController2 = [self.adapter sectionControllerForObject:beforeupdateObjects.lastObject];
self.dataSource.objects = @[
genTestObject(@1, @3), // new instance, value changed from 2 to 3
genTestObject(@2, @2), // new instance but unchanged
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 2);
NSArray *afterupdateObjects = [self.adapter objects];
// pointer equality
XCTAssertEqual([self.adapter sectionControllerForObject:afterupdateObjects.firstObject], sectionController1);
XCTAssertEqual([self.adapter sectionControllerForObject:afterupdateObjects.lastObject], sectionController2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingMultipleUpdates_withNewItemInstances_thatSectionControllersReceiveNewInstances {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
]];
id object = self.dataSource.objects[0];
IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object];
// test delegate controller counts the number of times it receives -didUpdateToItem:
XCTAssertEqual(sectionController.updateCount, 1);
self.dataSource.objects = @[
object, // same object instance
genTestObject(@3, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual(sectionController, [self.adapter sectionControllerForObject:[self.adapter objects][0]]);
// should not have received -didUpdateToItem: since the instance did not change
XCTAssertEqual(sectionController.updateCount, 1);
self.dataSource.objects = @[
genTestObject(@1, @2), // new instance but equal
genTestObject(@3, @2),
];
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished3) {
XCTAssertEqual(sectionController, [self.adapter sectionControllerForObject:[self.adapter objects][0]]);
// a new instance was used, make sure the section controller was updated
XCTAssertEqual(sectionController.updateCount, 2);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenQueryingCollectionContext_withNewItemInstances_thatSectionMatchesCurrentIndex {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
]];
IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
self.dataSource.objects = @[
genTestObject(@2, @2),
genTestObject(@1, @2), // new instance but equal
genTestObject(@3, @2),
];
__block BOOL executedUpdateBlock = NO;
__weak __typeof__(sectionController) weakSectionController = sectionController;
sectionController.itemUpdateBlock = ^{
executedUpdateBlock = YES;
XCTAssertEqual(weakSectionController.section, 1);
};
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished3) {
XCTAssertTrue(executedUpdateBlock);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerMutates_withReloadData_thatSectionControllerMutationIsApplied {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @3;
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:2]];
} completion:nil];
XCTestExpectation *expectation = genExpectation;
[self.adapter reloadDataWithCompletion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
// check that the count of items in section 0 was updated from the previous batch update block
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenContentOffsetChanges_withPerformUpdates_thatCollectionViewWorks {
// this test layout changes the offset in -prepareLayout which occurs somewhere between the update block being
// applied and the completion block
self.collectionView.collectionViewLayout = [IGListTestOffsettingLayout new];
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
genTestObject(@3, @2),
]];
// remove the last object to check that we don't access OOB section controller when the layout changes the offset
self.dataSource.objects = @[
genTestObject(@1, @2),
genTestObject(@2, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingItems_withNewItemInstances_thatSectionControllersReceiveNewInstances {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
genTestObject(@3, @2),
]];
IGTestDelegateController *sectionController1 = [self.adapter sectionControllerForObject:genTestObject(@1, @2)];
IGTestDelegateController *sectionController2 = [self.adapter sectionControllerForObject:genTestObject(@2, @2)];
NSArray *newObjects = @[
genTestObject(@1, @3),
genTestObject(@2, @3),
];
[self.adapter reloadObjects:newObjects];
XCTAssertEqual(sectionController1.item, newObjects[0]);
XCTAssertEqual(sectionController2.item, newObjects[1]);
XCTAssertTrue([[self.adapter.sectionMap objects] indexOfObjectIdenticalTo:newObjects[0]] != NSNotFound);
XCTAssertTrue([[self.adapter.sectionMap objects] indexOfObjectIdenticalTo:newObjects[1]] != NSNotFound);
}
- (void)test_whenReloadingItems_withPerformUpdates_thatReloadIsApplied {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
genTestObject(@3, @3),
]];
IGTestObject *object = self.dataSource.objects[0];
IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object];
// using performBatchAnimated: to mimic re-entrant item reload
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @4; // from @1
[self.adapter reloadObjects:@[object]];
} completion:nil];
// object is moved from position 0 to 1
// it is also mutated in the previous update block AND queued for a reload
self.dataSource.objects = @[
genTestObject(@3, @3),
object,
genTestObject(@2, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 3);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 4); // reloaded section
XCTAssertEqual([self.collectionView numberOfItemsInSection:2], 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenSectionControllerMutates_whenThereIsNoWindow_thatCollectionViewCountsAreUpdated {
// remove the collection view from self.window so that we use reloadData
[self.collectionView removeFromSuperview];
[self setupWithObjects:@[
genTestObject(@1, @8)
]];
IGTestObject *object = self.dataSource.objects[0];
IGTestDelegateController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
// using performBatchAnimated: to mimic re-entrant item reload
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @6; // from @1
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
[batchContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(5, 3)]];
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 6);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withoutSettingDataSource_thatCompletionBlockExecutes {
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]];
[self.window addSubview:collectionView];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
adapter.collectionView = collectionView;
self.dataSource.objects = @[
genTestObject(@1, @1)
];
XCTestExpectation *expectation = genExpectation;
// call -performUpdatesAnimated: before we have set the data source
[adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
// since the data source isnt set, we complete syncronously. dispatch_async simulates setting the data source
// in a different runloop from the completion block so it should be set by the time we make our subsequent
// -performUpdatesAnimated: call
dispatch_async(dispatch_get_main_queue(), ^{
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @2)
];
[adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
XCTAssertEqual([collectionView numberOfSections], 2);
[expectation fulfill];
}];
});
}];
// setting the data source immediately queries it, since the collection view is also set
adapter.dataSource = self.dataSource;
// simulate display reloading data on the collection view
[collectionView layoutIfNeeded];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withItemsMovingInBlocks_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @0),
genTestObject(@2, @7),
genTestObject(@3, @8),
genTestObject(@4, @8),
genTestObject(@5, @8),
genTestObject(@6, @5),
genTestObject(@7, @8),
genTestObject(@8, @8),
genTestObject(@9, @8),
]];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.window.frame collectionViewLayout:[UICollectionViewFlowLayout new]];
[self.window addSubview:collectionView];
IGListAdapterUpdater *updater = [IGListAdapterUpdater new];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil];
adapter.dataSource = self.dataSource;
adapter.collectionView = collectionView;
[collectionView layoutSubviews];
XCTAssertEqual([collectionView numberOfSections], 9);
self.dataSource.objects = @[
genTestObject(@1, @0),
genTestObject(@10, @5),
genTestObject(@11, @7),
genTestObject(@2, @7),
genTestObject(@3, @8),
genTestObject(@6, @5), // "moves" in front of 4, 5 but doesn't change index in array
genTestObject(@4, @8),
genTestObject(@5, @8),
genTestObject(@7, @8),
genTestObject(@8, @8),
];
XCTestExpectation *expectation = genExpectation;
[adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual([collectionView numberOfSections], 10);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReleasingObjects_thatAssertDoesntFire {
[self setupWithObjects:@[
genTestObject(@1, @1)
]];
// if the adapter keeps a strong ref to self and uses an async method, this will hit asserts that a list item
// controller is nil. the adapter should be released and the completion block never called.
@autoreleasepool {
IGListAdapterUpdater *updater = [[IGListAdapterUpdater alloc] init];
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:updater viewController:nil workingRangeSize:2];
adapter.collectionView = self.collectionView;
adapter.dataSource = self.dataSource;
[adapter performUpdatesAnimated:NO completion:^(BOOL finished) {
XCTAssertTrue(NO, @"Should not reach completion block for adapter");
}];
}
self.collectionView = nil;
self.dataSource = nil;
// queued after perform updates
XCTestExpectation *expectation = genExpectation;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenItemDeleted_withDisplayDelegate_thatDelegateReceivesDeletedItem {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
]];
IGTestObject *object = self.dataSource.objects[0];
self.dataSource.objects = @[
genTestObject(@2, @2),
];
id mockDisplayHandler = [OCMockObject mockForProtocol:@protocol(IGListAdapterDelegate)];
self.adapter.delegate = mockDisplayHandler;
[[mockDisplayHandler expect] listAdapter:self.adapter didEndDisplayingObject:object atIndex:0];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished2) {
[mockDisplayHandler verify];
XCTAssertTrue(finished2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenItemReloaded_withDisplacingMutations_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @1),
genTestObject(@3, @1),
genTestObject(@4, @1),
genTestObject(@5, @1),
]];
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @2), // reloaded
genTestObject(@5, @2), // reloaded
genTestObject(@4, @2), // reloaded
genTestObject(@3, @1),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertTrue(finished);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenCollectionViewAppears_thatWillDisplayEventsAreSent {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
]];
IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
XCTAssertEqual(ic1.willDisplayCount, 1);
XCTAssertEqual(ic1.didEndDisplayCount, 0);
XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0);
IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
XCTAssertEqual(ic2.willDisplayCount, 1);
XCTAssertEqual(ic2.didEndDisplayCount, 0);
XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 0);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 0);
}
- (void)test_whenAdapterUpdates_withItemUpdated_thatdidEndDisplayEventsAreSent {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
]];
IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @1), // reloaded w/ 1 cell removed
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual(ic1.willDisplayCount, 1);
XCTAssertEqual(ic1.didEndDisplayCount, 0);
XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0);
XCTAssertEqual(ic2.willDisplayCount, 1);
XCTAssertEqual(ic2.didEndDisplayCount, 0);
XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenAdapterUpdates_withItemRemoved_thatdidEndDisplayEventsAreSent {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
]];
IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
self.dataSource.objects = @[
genTestObject(@1, @1)
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual(ic1.willDisplayCount, 1);
XCTAssertEqual(ic1.didEndDisplayCount, 0);
XCTAssertEqual([ic1.willDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 0);
XCTAssertEqual(ic2.willDisplayCount, 1);
XCTAssertEqual(ic2.didEndDisplayCount, 1);
XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic2.willDisplayCellIndexes countForObject:@1], 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenAdapterUpdates_withEmptyItems_thatdidEndDisplayEventsAreSent {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @2),
]];
IGTestDelegateController *ic1 = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
IGTestDelegateController *ic2 = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
self.dataSource.objects = @[];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual(ic1.didEndDisplayCount, 1);
XCTAssertEqual([ic1.didEndDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual(ic2.didEndDisplayCount, 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@0], 1);
XCTAssertEqual([ic2.didEndDisplayCellIndexes countForObject:@1], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenBatchUpdating_withCellQuery_thatCellIsNil {
__block BOOL executed = NO;
__weak __typeof__(self) weakSelf = self;
void (^block)(IGTestDelegateController *) = ^(IGTestDelegateController *ic) {
executed = YES;
XCTAssertNil([weakSelf.adapter cellForItemAtIndex:0 sectionController:ic]);
};
((IGTestDelegateDataSource *)self.dataSource).cellConfigureBlock = block;
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @1),
genTestObject(@3, @1),
]];
// delete the last object from the original array
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @1),
genTestObject(@4, @1),
genTestObject(@5, @1),
genTestObject(@6, @1),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenBatchUpdating_withDuplicateIdentifiers_thatHaveDifferentValues_thatCollectionViewWorks {
[self setupWithObjects:@[
// using string values IGTestDelegateController always returns 1 cell
genTestObject(@1, @"a"),
genTestObject(@2, @"a"),
genTestObject(@3, @"a"),
genTestObject(@4, @"b"), // problem item w/ key 4, value "b"
]];
self.dataSource.objects = @[
genTestObject(@1, @"a"),
genTestObject(@5, @"a"),
genTestObject(@6, @"a"),
genTestObject(@7, @"a"),
genTestObject(@4, @"a"), // key 4 but value "a", so this needs reloaded
genTestObject(@8, @"a"),
genTestObject(@4, @"b"), // key 4 but value didn't change
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenPerformingUpdates_withWorkingRange_thatAccessingCellDoesntCrash {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @1),
genTestObject(@3, @1),
]];
// section controller try to access a cell in -listAdapter:sectionControllerWillEnterWorkingRange:
// add items beyond the 100x100 frame so they access unavailable cells
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @1),
genTestObject(@3, @1),
genTestObject(@4, @1),
genTestObject(@5, @1),
genTestObject(@6, @1),
genTestObject(@7, @1),
genTestObject(@8, @1),
genTestObject(@9, @1),
genTestObject(@10, @1),
genTestObject(@11, @1),
];
XCTestExpectation *expectation = genExpectation;
// this will call -collectionView:performBatchUpdates:, trigger collectionView:willDisplayCell:forItemAtIndexPath:,
// which kicks off the working range logic
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingItems_withDeleteAndInsertCollision_thatUpdateCanBeApplied {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @5),
genTestObject(@3, @1),
]];
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
XCTestExpectation *expectation = genExpectation;
[section.collectionContext performBatchAnimated:NO updates:^(id<IGListBatchContext> batchContext) {
[batchContext deleteInSectionController:section atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]];
[batchContext insertInSectionController:section atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)]];
[batchContext reloadInSectionController:section atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 4)]];
} completion:^(BOOL finished) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingItems_withSectionInsertedInFront_thatUpdateCanBeApplied {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @5),
genTestObject(@3, @1),
]];
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
XCTestExpectation *expectation1 = genExpectation;
[section.collectionContext performBatchAnimated:NO updates:^(id<IGListBatchContext> batchContext) {
[batchContext reloadInSectionController:section atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 4)]];
} completion:^(BOOL finished) {
[expectation1 fulfill];
}];
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@4, @1), // insert to shift object @2
genTestObject(@2, @5),
genTestObject(@3, @1),
];
XCTestExpectation *expectation2 = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
[expectation2 fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingItems_withSectionDeletedInFront_thatUpdateCanBeApplied {
[self setupWithObjects:@[
genTestObject(@1, @1),
genTestObject(@2, @5),
genTestObject(@3, @1),
]];
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects[1]];
XCTestExpectation *expectation1 = genExpectation;
[section.collectionContext performBatchAnimated:NO updates:^(id<IGListBatchContext> batchContext) {
[batchContext reloadInSectionController:section atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 4)]];
} completion:^(BOOL finished) {
[expectation1 fulfill];
}];
self.dataSource.objects = @[
genTestObject(@2, @5),
genTestObject(@3, @1),
];
XCTestExpectation *expectation2 = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
[expectation2 fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenDataSourceDeallocatedAfterUpdateQueued_thatUpdateSuccesfullyCompletes {
IGTestDelegateDataSource *dataSource = [IGTestDelegateDataSource new];
dataSource.objects = @[genTestObject(@1, @1)];
self.adapter.collectionView = self.collectionView;
self.adapter.dataSource = dataSource;
[self.collectionView layoutIfNeeded];
dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
[expectation fulfill];
}];
dataSource = nil;
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenQueuingUpdate_withSectionControllerBatchUpdate_thatSectionControllerNotRetained {
__weak id weakSectionController = nil;
@autoreleasepool {
IGListAdapter *adapter = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
IGTestDelegateDataSource *dataSource = [IGTestDelegateDataSource new];
IGTestObject *object = genTestObject(@1, @2);
dataSource.objects = @[object];
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:[UICollectionViewFlowLayout new]];
adapter.collectionView = collectionView;
adapter.dataSource = dataSource;
[collectionView layoutIfNeeded];
XCTAssertEqual([collectionView numberOfSections], 1);
XCTAssertEqual([collectionView numberOfItemsInSection:0], 2);
IGListSectionController *section = [adapter sectionControllerForObject:object];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @3;
[batchContext insertInSectionController:section atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished) {}];
dataSource.objects = @[object, genTestObject(@2, @2)];
[adapter performUpdatesAnimated:YES completion:^(BOOL finished) {}];
weakSectionController = section;
XCTAssertNotNil(weakSectionController);
}
XCTAssertNil(weakSectionController);
}
- (void)test_whenMovingItems_withObjectMoving_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
genTestObject(@2, @2),
genTestObject(@3, @2),
]];
__block BOOL executed = NO;
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext moveInSectionController:section fromIndex:0 toIndex:1];
executed = YES;
} completion:nil];
self.dataSource.objects = @[
genTestObject(@3, @2),
genTestObject(@1, @2),
genTestObject(@2, @2),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertTrue(executed);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenMovingItems_withObjectReloaded_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
__block BOOL executed = NO;
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext moveInSectionController:section fromIndex:0 toIndex:1];
executed = YES;
} completion:nil];
self.dataSource.objects = @[
genTestObject(@1, @3),
];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertTrue(executed);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenMovingItems_withObjectDeleted_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
__block BOOL executed = NO;
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext moveInSectionController:section fromIndex:0 toIndex:1];
executed = YES;
} completion:nil];
self.dataSource.objects = @[];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertTrue(executed);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenMovingItems_withObjectInsertedBefore_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
__block BOOL executed = NO;
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext moveInSectionController:section fromIndex:0 toIndex:1];
executed = YES;
} completion:nil];
[self setupWithObjects:@[
genTestObject(@2, @2),
genTestObject(@1, @2),
]];
XCTestExpectation *expectation = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertTrue(executed);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenMovingItems_thatCollectionViewWorks {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
IGTestCell *cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
IGTestCell *cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]];
cell1.label.text = @"foo";
cell2.label.text = @"bar";
XCTestExpectation *expectation = genExpectation;
IGListSectionController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
[section.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext moveInSectionController:section fromIndex:0 toIndex:1];
} completion:^(BOOL finished) {
IGTestCell *movedCell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
IGTestCell *movedCell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:1 inSection:0]];
XCTAssertEqualObjects(movedCell1.label.text, @"bar");
XCTAssertEqualObjects(movedCell2.label.text, @"foo");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenInvalidatingSectionController_withSizeChange_thatCellsAreSameInstance_thatCellsFrameChanged {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
NSIndexPath *path1 = [NSIndexPath indexPathForItem:0 inSection:0];
NSIndexPath *path2 = [NSIndexPath indexPathForItem:1 inSection:0];
IGTestCell *cell1 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:path1];
IGTestCell *cell2 = (IGTestCell*)[self.collectionView cellForItemAtIndexPath:path2];
XCTAssertEqual(cell1.frame.size.height, 10);
XCTAssertEqual(cell2.frame.size.height, 10);
IGTestDelegateController *section = [self.adapter sectionControllerForObject:self.dataSource.objects.lastObject];
section.height = 20.0;
XCTestExpectation *expectation = genExpectation;
[section.collectionContext invalidateLayoutForSectionController:section completion:^(BOOL finished) {
XCTAssertEqual(cell1, [self.collectionView cellForItemAtIndexPath:path1]);
XCTAssertEqual(cell2, [self.collectionView cellForItemAtIndexPath:path2]);
XCTAssertEqual(cell1.frame.size.height, 20);
XCTAssertEqual(cell2.frame.size.height, 20);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenAdaptersSwapCollectionViews_thatOldAdapterDoesntUpdateOldCollectionView {
IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
IGTestDelegateDataSource *dataSource1 = [IGTestDelegateDataSource new];
dataSource1.objects = @[genTestObject(@1, @2)];
adapter1.dataSource = dataSource1;
adapter1.collectionView = self.collectionView;
[self.collectionView layoutIfNeeded];
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
IGTestDelegateDataSource *dataSource2 = [IGTestDelegateDataSource new];
dataSource2.objects = @[genTestObject(@1, @1), genTestObject(@2, @1)];
adapter2.dataSource = dataSource2;
adapter2.collectionView = self.collectionView;
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
dataSource1.objects = @[genTestObject(@1, @2), genTestObject(@2, @2), genTestObject(@3, @2), genTestObject(@4, @2)];
XCTestExpectation *expectation = genExpectation;
[adapter1 performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenAdaptersSwapCollectionViews_ {
IGListAdapter *adapter1 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
IGTestDelegateDataSource *dataSource1 = [IGTestDelegateDataSource new];
dataSource1.objects = @[genTestObject(@1, @2)];
adapter1.dataSource = dataSource1;
adapter1.collectionView = self.collectionView;
[self.collectionView layoutIfNeeded];
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
IGListAdapter *adapter2 = [[IGListAdapter alloc] initWithUpdater:[IGListAdapterUpdater new] viewController:nil];
IGTestDelegateDataSource *dataSource2 = [IGTestDelegateDataSource new];
dataSource2.objects = @[genTestObject(@1, @1), genTestObject(@2, @1)];
adapter2.dataSource = dataSource2;
adapter2.collectionView = self.collectionView;
[self.collectionView layoutIfNeeded];
XCTAssertEqual([self.collectionView numberOfSections], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
dataSource2.objects = @[genTestObject(@1, @2), genTestObject(@2, @1), genTestObject(@3, @1), genTestObject(@4, @1)];
XCTestExpectation *expectation = genExpectation;
[adapter2 performUpdatesAnimated:YES completion:^(BOOL finished) {
XCTAssertEqual([self.collectionView numberOfSections], 4);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:1], 1);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenDidUpdateAsyncReloads_withBatchUpdatesInProgress_thatReloadIsExecuted {
[self setupWithObjects:@[
genTestObject(@1, @1)
]];
IGTestDelegateController *section = [self.adapter sectionControllerForObject:self.dataSource.objects[0]];
XCTestExpectation *expectation1 = genExpectation;
__weak __typeof__(section) weakSection = section;
section.itemUpdateBlock = ^{
// currently inside -[IGListSectionController didUpdateToObject:], change the item (note: NEVER do this) manually
// so that the data powering numberOfItems changes (1 to 2). dispatch_async the update to skip outside of the
// -[UICollectionView performBatchUpdates:completion:] block execution
[weakSection.collectionContext performBatchAnimated:NO updates:^(id<IGListBatchContext> batchContext) {
weakSection.item = genTestObject(@1, @2);
[batchContext reloadSectionController:weakSection];
} completion:^(BOOL finished) {
[expectation1 fulfill];
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
}];
};
// add an object so that a batch update is triggered (diff result has changes)
self.dataSource.objects = @[
genTestObject(@1, @1),
genTestObject(@2, @1)
];
XCTestExpectation *expectation2 = genExpectation;
[self.adapter performUpdatesAnimated:YES completion:^(BOOL finished) {
// verify that the section still has 2 items since this completion executes AFTER the reload block above
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
[expectation2 fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)_test_whenInsertingItemsTwice_withDataUpdatedTwice_thatAllUpdatesAppliedWithoutException {
[self setupWithObjects:@[
genTestObject(@1, @2),
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @4;
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
[batchContext insertInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 4);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)FIXME_test_whenDeletingItemsTwice_withDataUpdatedTwice_thatAllUpdatesAppliedWithoutException {
[self setupWithObjects:@[
genTestObject(@1, @4),
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
object.value = @2;
[batchContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
[batchContext deleteInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 2);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
- (void)test_whenReloadingSameItemTwice_thatDeletesAndInsertsAreBalanced {
[self setupWithObjects:@[
genTestObject(@1, @4),
]];
IGTestObject *object = self.dataSource.objects[0];
IGListSectionController *sectionController = [self.adapter sectionControllerForObject:object];
XCTestExpectation *expectation = genExpectation;
[sectionController.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
[batchContext reloadInSectionController:sectionController atIndexes:[NSIndexSet indexSetWithIndex:0]];
} completion:^(BOOL finished2) {
XCTAssertEqual([self.collectionView numberOfSections], 1);
XCTAssertEqual([self.collectionView numberOfItemsInSection:0], 4);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:30 handler:nil];
}
@end