mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-11 06:55:23 +00:00
Add first-pass view model support to collection node. #trivial (#356)
* Add first-pass view model support for collection node. Much more to come! * Address issues * Update the gorram license header * Dear lord
This commit is contained in:
parent
c297060113
commit
83111de0cc
@ -117,6 +117,15 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
|
|||||||
*/
|
*/
|
||||||
@property (atomic, readonly, nullable) NSIndexPath *indexPath;
|
@property (atomic, readonly, nullable) NSIndexPath *indexPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BETA: API is under development. We will attempt to provide an easy migration pathway for any changes.
|
||||||
|
*
|
||||||
|
* The view-model currently assigned to this node, if any.
|
||||||
|
*
|
||||||
|
* This property may be set off the main thread, but this method will never be invoked concurrently on the
|
||||||
|
*/
|
||||||
|
@property (atomic, nullable) id viewModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller
|
* The backing view controller, or @c nil if the node wasn't initialized with backing view controller
|
||||||
* @note This property must be accessed on the main thread.
|
* @note This property must be accessed on the main thread.
|
||||||
|
@ -418,6 +418,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
*/
|
*/
|
||||||
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
- (nullable __kindof ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the view-model for the item at the given index path, if any.
|
||||||
|
*
|
||||||
|
* @param indexPath The index path of the requested item.
|
||||||
|
*
|
||||||
|
* @return The view-model for the given item, or @c nil if no item exists at the specified path or no view-model was provided.
|
||||||
|
*
|
||||||
|
* @warning This API is beta and subject to change. We'll try to provide an easy migration path.
|
||||||
|
*/
|
||||||
|
- (nullable id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath AS_WARN_UNUSED_RESULT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the index path for the item with the given node.
|
* Retrieve the index path for the item with the given node.
|
||||||
*
|
*
|
||||||
@ -503,6 +514,17 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
*/
|
*/
|
||||||
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode;
|
- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --BETA--
|
||||||
|
* Asks the data source for a view-model for the item at the given index path.
|
||||||
|
*
|
||||||
|
* @param collectionNode The sender.
|
||||||
|
* @param indexPath The index path of the item.
|
||||||
|
*
|
||||||
|
* @return An object that contains all the data for this item.
|
||||||
|
*/
|
||||||
|
- (nullable id)collectionNode:(ASCollectionNode *)collectionNode viewModelForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similar to -collectionNode:nodeForItemAtIndexPath:
|
* Similar to -collectionNode:nodeForItemAtIndexPath:
|
||||||
* This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented.
|
* This method takes precedence over collectionNode:nodeForItemAtIndexPath: if implemented.
|
||||||
|
@ -590,6 +590,12 @@
|
|||||||
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node;
|
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id)viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
[self reloadDataInitiallyIfNeeded];
|
||||||
|
return [self.dataController.pendingMap elementForItemAtIndexPath:indexPath].viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
|
||||||
{
|
{
|
||||||
return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement];
|
return [self.dataController.pendingMap indexPathForElement:cellNode.collectionElement];
|
||||||
|
@ -207,6 +207,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
|||||||
unsigned int collectionViewNumberOfItemsInSection:1;
|
unsigned int collectionViewNumberOfItemsInSection:1;
|
||||||
unsigned int collectionNodeNodeForItem:1;
|
unsigned int collectionNodeNodeForItem:1;
|
||||||
unsigned int collectionNodeNodeBlockForItem:1;
|
unsigned int collectionNodeNodeBlockForItem:1;
|
||||||
|
unsigned int viewModelForItem:1;
|
||||||
unsigned int collectionNodeNodeForSupplementaryElement:1;
|
unsigned int collectionNodeNodeForSupplementaryElement:1;
|
||||||
unsigned int collectionNodeNodeBlockForSupplementaryElement:1;
|
unsigned int collectionNodeNodeBlockForSupplementaryElement:1;
|
||||||
unsigned int collectionNodeSupplementaryElementKindsInSection:1;
|
unsigned int collectionNodeSupplementaryElementKindsInSection:1;
|
||||||
@ -450,6 +451,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
|||||||
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
|
_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeForSupplementaryElementOfKind:atIndexPath:)];
|
||||||
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
|
_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)];
|
||||||
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
|
_asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)];
|
||||||
|
_asyncDataSourceFlags.viewModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:viewModelForItemAtIndexPath:)];
|
||||||
|
|
||||||
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
|
_asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)];
|
||||||
if (_asyncDataSourceFlags.interop) {
|
if (_asyncDataSourceFlags.interop) {
|
||||||
@ -1611,6 +1613,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
|||||||
|
|
||||||
#pragma mark - ASDataControllerSource
|
#pragma mark - ASDataControllerSource
|
||||||
|
|
||||||
|
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
if (!_asyncDataSourceFlags.viewModelForItem) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
|
||||||
|
return [_asyncDataSource collectionNode:collectionNode viewModelForItemAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
|
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath
|
||||||
{
|
{
|
||||||
ASCellNodeBlock block = nil;
|
ASCellNodeBlock block = nil;
|
||||||
|
@ -1629,6 +1629,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
|
|||||||
|
|
||||||
#pragma mark - ASDataControllerSource
|
#pragma mark - ASDataControllerSource
|
||||||
|
|
||||||
|
- (id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
|
{
|
||||||
|
// Not currently supported for tables. Will be added when the collection API stabilizes.
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {
|
- (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAtIndexPath:(NSIndexPath *)indexPath {
|
||||||
ASCellNodeBlock block = nil;
|
ASCellNodeBlock block = nil;
|
||||||
|
|
||||||
|
@ -31,8 +31,10 @@ AS_SUBCLASSING_RESTRICTED
|
|||||||
@property (nonatomic, assign) ASSizeRange constrainedSize;
|
@property (nonatomic, assign) ASSizeRange constrainedSize;
|
||||||
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;
|
@property (nonatomic, readonly, weak) id<ASRangeManagingNode> owningNode;
|
||||||
@property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection;
|
@property (nonatomic, assign) ASPrimitiveTraitCollection traitCollection;
|
||||||
|
@property (nonatomic, readonly, nullable) id viewModel;
|
||||||
|
|
||||||
- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock
|
- (instancetype)initWithViewModel:(nullable id)viewModel
|
||||||
|
nodeBlock:(ASCellNodeBlock)nodeBlock
|
||||||
supplementaryElementKind:(nullable NSString *)supplementaryElementKind
|
supplementaryElementKind:(nullable NSString *)supplementaryElementKind
|
||||||
constrainedSize:(ASSizeRange)constrainedSize
|
constrainedSize:(ASSizeRange)constrainedSize
|
||||||
owningNode:(id<ASRangeManagingNode>)owningNode
|
owningNode:(id<ASRangeManagingNode>)owningNode
|
||||||
|
@ -31,7 +31,8 @@
|
|||||||
ASCellNode *_node;
|
ASCellNode *_node;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithNodeBlock:(ASCellNodeBlock)nodeBlock
|
- (instancetype)initWithViewModel:(id)viewModel
|
||||||
|
nodeBlock:(ASCellNodeBlock)nodeBlock
|
||||||
supplementaryElementKind:(NSString *)supplementaryElementKind
|
supplementaryElementKind:(NSString *)supplementaryElementKind
|
||||||
constrainedSize:(ASSizeRange)constrainedSize
|
constrainedSize:(ASSizeRange)constrainedSize
|
||||||
owningNode:(id<ASRangeManagingNode>)owningNode
|
owningNode:(id<ASRangeManagingNode>)owningNode
|
||||||
@ -40,6 +41,7 @@
|
|||||||
NSAssert(nodeBlock != nil, @"Node block must not be nil");
|
NSAssert(nodeBlock != nil, @"Node block must not be nil");
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
|
_viewModel = viewModel;
|
||||||
_nodeBlock = nodeBlock;
|
_nodeBlock = nodeBlock;
|
||||||
_supplementaryElementKind = [supplementaryElementKind copy];
|
_supplementaryElementKind = [supplementaryElementKind copy];
|
||||||
_constrainedSize = constrainedSize;
|
_constrainedSize = constrainedSize;
|
||||||
@ -62,6 +64,7 @@
|
|||||||
node.owningNode = _owningNode;
|
node.owningNode = _owningNode;
|
||||||
node.collectionElement = self;
|
node.collectionElement = self;
|
||||||
ASTraitCollectionPropagateDown(node, _traitCollection);
|
ASTraitCollectionPropagateDown(node, _traitCollection);
|
||||||
|
node.viewModel = _viewModel;
|
||||||
_node = node;
|
_node = node;
|
||||||
}
|
}
|
||||||
return _node;
|
return _node;
|
||||||
|
@ -76,6 +76,8 @@ extern NSString * const ASCollectionInvalidUpdateException;
|
|||||||
*/
|
*/
|
||||||
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size;
|
- (BOOL)dataController:(ASDataController *)dataController presentedSizeForElement:(ASCollectionElement *)element matchesSize:(CGSize)size;
|
||||||
|
|
||||||
|
- (nullable id)dataController:(ASDataController *)dataController viewModelForItemAtIndexPath:(NSIndexPath *)indexPath;
|
||||||
|
|
||||||
@optional
|
@optional
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -384,6 +384,8 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
|||||||
id<ASDataControllerSource> dataSource = self.dataSource;
|
id<ASDataControllerSource> dataSource = self.dataSource;
|
||||||
id<ASRangeManagingNode> node = self.node;
|
id<ASRangeManagingNode> node = self.node;
|
||||||
for (NSIndexPath *indexPath in indexPaths) {
|
for (NSIndexPath *indexPath in indexPaths) {
|
||||||
|
id viewModel = [dataSource dataController:self viewModelForItemAtIndexPath:indexPath];
|
||||||
|
|
||||||
ASCellNodeBlock nodeBlock;
|
ASCellNodeBlock nodeBlock;
|
||||||
if (isRowKind) {
|
if (isRowKind) {
|
||||||
nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath];
|
||||||
@ -396,7 +398,8 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
|||||||
constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
||||||
}
|
}
|
||||||
|
|
||||||
ASCollectionElement *element = [[ASCollectionElement alloc] initWithNodeBlock:nodeBlock
|
ASCollectionElement *element = [[ASCollectionElement alloc] initWithViewModel:viewModel
|
||||||
|
nodeBlock:nodeBlock
|
||||||
supplementaryElementKind:isRowKind ? nil : kind
|
supplementaryElementKind:isRowKind ? nil : kind
|
||||||
constrainedSize:constrainedSize
|
constrainedSize:constrainedSize
|
||||||
owningNode:node
|
owningNode:node
|
||||||
|
@ -27,10 +27,15 @@
|
|||||||
UIWindow *window;
|
UIWindow *window;
|
||||||
UIViewController *viewController;
|
UIViewController *viewController;
|
||||||
ASCollectionNode *collectionNode;
|
ASCollectionNode *collectionNode;
|
||||||
|
NSMutableArray<NSMutableArray *> *sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setUp {
|
- (void)setUp {
|
||||||
[super setUp];
|
[super setUp];
|
||||||
|
// Default is 2 sections: 2 items in first, 1 item in second.
|
||||||
|
sections = [NSMutableArray array];
|
||||||
|
[sections addObject:[NSMutableArray arrayWithObjects:[NSObject new], [NSObject new], nil]];
|
||||||
|
[sections addObject:[NSMutableArray arrayWithObjects:[NSObject new], nil]];
|
||||||
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
||||||
viewController = [[UIViewController alloc] init];
|
viewController = [[UIViewController alloc] init];
|
||||||
|
|
||||||
@ -46,6 +51,7 @@
|
|||||||
@selector(numberOfSectionsInCollectionNode:),
|
@selector(numberOfSectionsInCollectionNode:),
|
||||||
@selector(collectionNode:numberOfItemsInSection:),
|
@selector(collectionNode:numberOfItemsInSection:),
|
||||||
@selector(collectionNode:nodeBlockForItemAtIndexPath:),
|
@selector(collectionNode:nodeBlockForItemAtIndexPath:),
|
||||||
|
@selector(collectionNode:viewModelForItemAtIndexPath:),
|
||||||
nil];
|
nil];
|
||||||
[mockDataSource setExpectationOrderMatters:YES];
|
[mockDataSource setExpectationOrderMatters:YES];
|
||||||
|
|
||||||
@ -59,48 +65,138 @@
|
|||||||
[super tearDown];
|
[super tearDown];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testInitialDataLoadingCallPattern
|
#pragma mark - Test Methods
|
||||||
{
|
|
||||||
/// BUG: these methods are called twice in a row i.e. this for-loop shouldn't be here. https://github.com/TextureGroup/Texture/issues/351
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
NSArray *counts = @[ @2 ];
|
|
||||||
[self expectDataSourceMethodsWithCounts:counts];
|
|
||||||
}
|
|
||||||
|
|
||||||
[window layoutIfNeeded];
|
- (void)testInitialDataLoading
|
||||||
|
{
|
||||||
|
[self loadInitialData];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testReloadingAnItem
|
||||||
|
{
|
||||||
|
[self loadInitialData];
|
||||||
|
|
||||||
|
// Reload at (0, 0)
|
||||||
|
NSIndexPath *reloadedPath = [NSIndexPath indexPathForItem:0 inSection:0];
|
||||||
|
|
||||||
|
sections[reloadedPath.section][reloadedPath.item] = [NSObject new];
|
||||||
|
[self performUpdateInvalidatingItems:@[ reloadedPath ] block:^{
|
||||||
|
[collectionNode reloadItemsAtIndexPaths:@[ reloadedPath ]];
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)testInsertingAnItem
|
||||||
|
{
|
||||||
|
[self loadInitialData];
|
||||||
|
|
||||||
|
// Insert at (1, 0)
|
||||||
|
NSIndexPath *insertedPath = [NSIndexPath indexPathForItem:0 inSection:1];
|
||||||
|
|
||||||
|
[sections[insertedPath.section] insertObject:[NSObject new] atIndex:insertedPath.item];
|
||||||
|
[self performUpdateInvalidatingItems:@[ insertedPath ] block:^{
|
||||||
|
[collectionNode insertItemsAtIndexPaths:@[ insertedPath ]];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Helpers
|
#pragma mark - Helpers
|
||||||
|
|
||||||
|
- (void)loadInitialData
|
||||||
|
{
|
||||||
|
/// BUG: these methods are called twice in a row i.e. this for-loop shouldn't be here. https://github.com/TextureGroup/Texture/issues/351
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
// It reads all the counts
|
||||||
|
[self expectDataSourceCountMethods];
|
||||||
|
|
||||||
|
// It reads the contents for each item.
|
||||||
|
for (NSInteger section = 0; section < sections.count; section++) {
|
||||||
|
NSArray *items = sections[section];
|
||||||
|
|
||||||
|
// For each item:
|
||||||
|
for (NSInteger i = 0; i < items.count; i++) {
|
||||||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
||||||
|
[self expectContentMethodsForItemAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[window layoutIfNeeded];
|
||||||
|
|
||||||
|
// Assert item counts & content:
|
||||||
|
[self assertCollectionNodeContent];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds expectations for the sequence:
|
* Adds expectations for the sequence:
|
||||||
*
|
*
|
||||||
* numberOfSectionsInCollectionNode:
|
* numberOfSectionsInCollectionNode:
|
||||||
* for section in countsArray
|
* for section in countsArray
|
||||||
* numberOfItemsInSection:
|
* numberOfItemsInSection:
|
||||||
* for item < itemCount
|
|
||||||
* nodeBlockForItemAtIndexPath:
|
|
||||||
*/
|
*/
|
||||||
- (void)expectDataSourceMethodsWithCounts:(NSArray<NSNumber *> *)counts
|
- (void)expectDataSourceCountMethods
|
||||||
{
|
{
|
||||||
// -numberOfSectionsInCollectionNode
|
// -numberOfSectionsInCollectionNode
|
||||||
OCMExpect([mockDataSource numberOfSectionsInCollectionNode:collectionNode])
|
OCMExpect([mockDataSource numberOfSectionsInCollectionNode:collectionNode])
|
||||||
.andReturn(counts.count);
|
.andReturn(sections.count);
|
||||||
|
|
||||||
// For each section:
|
// For each section:
|
||||||
// Note: Skip fast enumeration for readability.
|
// Note: Skip fast enumeration for readability.
|
||||||
for (NSInteger section = 0; section < counts.count; section++) {
|
for (NSInteger section = 0; section < sections.count; section++) {
|
||||||
NSInteger itemCount = counts[section].integerValue;
|
NSInteger itemCount = sections[section].count;
|
||||||
OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section])
|
OCMExpect([mockDataSource collectionNode:collectionNode numberOfItemsInSection:section])
|
||||||
.andReturn(itemCount);
|
.andReturn(itemCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For each item:
|
// Expects viewModelForItemAtIndexPath: and nodeBlockForItemAtIndexPath:
|
||||||
for (NSInteger i = 0; i < itemCount; i++) {
|
- (void)expectContentMethodsForItemAtIndexPath:(NSIndexPath *)indexPath
|
||||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
|
{
|
||||||
|
id viewModel = sections[indexPath.section][indexPath.item];
|
||||||
|
OCMExpect([mockDataSource collectionNode:collectionNode viewModelForItemAtIndexPath:indexPath])
|
||||||
|
.andReturn(viewModel);
|
||||||
OCMExpect([mockDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath])
|
OCMExpect([mockDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath])
|
||||||
.andReturn((ASCellNodeBlock)^{ return [[ASCellNode alloc] init]; });
|
.andReturn((ASCellNodeBlock)^{ return [ASCellNode new]; });
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)assertCollectionNodeContent
|
||||||
|
{
|
||||||
|
// Assert section count
|
||||||
|
XCTAssertEqual(collectionNode.numberOfSections, sections.count);
|
||||||
|
|
||||||
|
for (NSInteger section = 0; section < sections.count; section++) {
|
||||||
|
NSArray *items = sections[section];
|
||||||
|
// Assert item count
|
||||||
|
XCTAssertEqual([collectionNode numberOfItemsInSection:section], items.count);
|
||||||
|
for (NSInteger item = 0; item < items.count; item++) {
|
||||||
|
// Assert view model
|
||||||
|
// Could use pointer equality but the error message is less readable.
|
||||||
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
|
||||||
|
id viewModel = sections[indexPath.section][indexPath.item];
|
||||||
|
XCTAssertEqualObjects(viewModel, [collectionNode viewModelForItemAtIndexPath:indexPath]);
|
||||||
|
ASCellNode *node = [collectionNode nodeForItemAtIndexPath:indexPath];
|
||||||
|
XCTAssertEqualObjects(node.viewModel, viewModel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the collection node, with expectations and assertions about the call-order and the correctness of the
|
||||||
|
* new data. You should update the data source _before_ calling this method.
|
||||||
|
*
|
||||||
|
* invalidatedIndexPaths are the items we expect to get refetched (reloaded/inserted).
|
||||||
|
*/
|
||||||
|
- (void)performUpdateInvalidatingItems:(NSArray<NSIndexPath *> *)invalidatedIndexPaths block:(void(^)())update
|
||||||
|
{
|
||||||
|
// When we do an edit, it'll read the new counts
|
||||||
|
[self expectDataSourceCountMethods];
|
||||||
|
|
||||||
|
// Then it'll load the contents for inserted/reloaded items.
|
||||||
|
for (NSIndexPath *indexPath in invalidatedIndexPaths) {
|
||||||
|
[self expectContentMethodsForItemAtIndexPath:indexPath];
|
||||||
|
}
|
||||||
|
|
||||||
|
[collectionNode performBatchUpdates:update completion:nil];
|
||||||
|
|
||||||
|
[self assertCollectionNodeContent];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
//
|
//
|
||||||
// ItemNode.h
|
// ItemNode.h
|
||||||
// Sample
|
// Texture
|
||||||
//
|
//
|
||||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||||
// This source code is licensed under the BSD-style license found in the
|
// 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
|
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||||
// of patent rights can be found in the PATENTS file in the same directory.
|
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||||
//
|
//
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
// you may not use this file except in compliance with the License.
|
||||||
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
// You may obtain a copy of the License at
|
||||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
//
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
//
|
//
|
||||||
// ItemNode.m
|
// ItemNode.m
|
||||||
// Sample
|
// Texture
|
||||||
//
|
//
|
||||||
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||||
// This source code is licensed under the BSD-style license found in the
|
// 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
|
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||||
// of patent rights can be found in the PATENTS file in the same directory.
|
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||||
//
|
//
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
// you may not use this file except in compliance with the License.
|
||||||
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
// You may obtain a copy of the License at
|
||||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
//
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "ItemNode.h"
|
#import "ItemNode.h"
|
||||||
@ -50,7 +50,7 @@ const CGFloat kSoldOutGBHeight = 50.0;
|
|||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
_viewModel = viewModel;
|
self.viewModel = viewModel;
|
||||||
[self setup];
|
[self setup];
|
||||||
[self updateLabels];
|
[self updateLabels];
|
||||||
[self updateBackgroundColor];
|
[self updateBackgroundColor];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user