99#import < XCTest/XCTest.h>
1010
1111#import " ASTableView.h"
12+ #import " ASDisplayNode+Subclasses.h"
1213
1314#define NumberOfSections 10
1415#define NumberOfRowsPerSection 20
@@ -56,9 +57,24 @@ - (void)dealloc
5657
5758@end
5859
60+ @interface ASTestTextCellNode : ASTextCellNode
61+ /* * Calculated by counting how many times -layoutSpecThatFits: is called on the main thread. */
62+ @property (atomic ) int numberOfLayoutsOnMainThread;
63+ @end
64+
65+ @implementation ASTestTextCellNode
66+
67+ - (ASLayoutSpec *)layoutSpecThatFits : (ASSizeRange)constrainedSize
68+ {
69+ if ([NSThread isMainThread ]) {
70+ _numberOfLayoutsOnMainThread++;
71+ }
72+ return [super layoutSpecThatFits: constrainedSize];
73+ }
74+
75+ @end
76+
5977@interface ASTableViewFilledDataSource : NSObject <ASTableViewDataSource, ASTableViewDelegate>
60- /* * Calculated by counting how many times a constrained size is asked for the first node on main thread. */
61- @property (atomic ) int numberOfRelayouts;
6278@end
6379
6480@implementation ASTableViewFilledDataSource
@@ -75,22 +91,12 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger
7591
7692- (ASCellNode *)tableView : (ASTableView *)tableView nodeForRowAtIndexPath : (NSIndexPath *)indexPath
7793{
78- ASTextCellNode *textCellNode = [ASTextCellNode new ];
94+ ASTestTextCellNode *textCellNode = [ASTestTextCellNode new ];
7995 textCellNode.text = indexPath.description ;
8096
8197 return textCellNode;
8298}
8399
84- - (ASSizeRange)tableView : (ASTableView *)tableView constrainedSizeForNodeAtIndexPath : (NSIndexPath *)indexPath
85- {
86- if ([NSThread isMainThread ] && indexPath.section == 0 && indexPath.row == 0 ) {
87- _numberOfRelayouts++;
88- }
89- CGFloat maxWidth = tableView.bounds .size .width ;
90- return ASSizeRangeMake (CGSizeMake (maxWidth, 0 ),
91- CGSizeMake (maxWidth, FLT_MAX));
92- }
93-
94100@end
95101
96102@interface ASTableViewTests : XCTestCase
@@ -260,7 +266,7 @@ - (void)testRelayoutAllRowsWithZeroSizeInitially
260266
261267- (void )triggerSizeChangeAndAssertRelayoutAllRowsForTableView : (ASTableView *)tableView newSize : (CGSize)newSize
262268{
263- XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription: @" nodesMeasuredUsingNewConstrainedSizeExpectation " ];
269+ XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription: @" nodesMeasuredUsingNewConstrainedSize " ];
264270
265271 [tableView beginUpdates ];
266272
@@ -270,13 +276,11 @@ - (void)triggerSizeChangeAndAssertRelayoutAllRowsForTableView:(ASTableView *)tab
270276 [tableView layoutIfNeeded ];
271277
272278 [tableView endUpdatesAnimated: NO completion: ^(BOOL completed) {
273- int numberOfRelayouts = ((ASTableViewFilledDataSource *)(tableView.asyncDataSource )).numberOfRelayouts ;
274- XCTAssertEqual (numberOfRelayouts, 1 );
275-
276279 for (int section = 0 ; section < NumberOfSections; section++) {
277280 for (int row = 0 ; row < NumberOfRowsPerSection; row++) {
278281 NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
279- ASCellNode *node = [tableView nodeForRowAtIndexPath: indexPath];
282+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: indexPath];
283+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 1 );
280284 XCTAssertEqual (node.constrainedSizeForCalculatedLayout .max .width , newSize.width );
281285 }
282286 }
@@ -290,4 +294,138 @@ - (void)triggerSizeChangeAndAssertRelayoutAllRowsForTableView:(ASTableView *)tab
290294 }];
291295}
292296
297+ - (void )testRelayoutVisibleRowsWhenEditingModeIsChanged
298+ {
299+ CGSize tableViewSize = CGSizeMake (100 , 500 );
300+ ASTestTableView *tableView = [[ASTestTableView alloc ] initWithFrame: CGRectMake (0 , 0 , tableViewSize.width, tableViewSize.height)
301+ style: UITableViewStylePlain
302+ asyncDataFetching: YES ];
303+ ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new ];
304+
305+ tableView.asyncDelegate = dataSource;
306+ tableView.asyncDataSource = dataSource;
307+
308+ XCTestExpectation *reloadDataExpectation = [self expectationWithDescription: @" reloadData" ];
309+ [tableView reloadDataWithCompletion: ^{
310+ for (int section = 0 ; section < NumberOfSections; section++) {
311+ for (int row = 0 ; row < NumberOfRowsPerSection; row++) {
312+ NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
313+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: indexPath];
314+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 0 );
315+ XCTAssertEqual (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
316+ }
317+ }
318+ [reloadDataExpectation fulfill ];
319+ }];
320+ [self waitForExpectationsWithTimeout: 5 handler: ^(NSError *error) {
321+ if (error) {
322+ XCTFail (@" Expectation failed: %@ " , error);
323+ }
324+ }];
325+
326+ NSArray *visibleNodes = [tableView visibleNodes ];
327+ XCTAssertGreaterThan (visibleNodes.count , 0 );
328+
329+ // Cause table view to enter editing mode.
330+ // Visibile nodes should be re-measured on main thread with the new (smaller) content view width.
331+ // Other nodes are untouched.
332+ XCTestExpectation *relayoutAfterEnablingEditingExpectation = [self expectationWithDescription: @" relayoutAfterEnablingEditing" ];
333+ [tableView beginUpdates ];
334+ [tableView setEditing: YES ];
335+ [tableView endUpdatesAnimated: YES completion: ^(BOOL completed) {
336+ for (int section = 0 ; section < NumberOfSections; section++) {
337+ for (int row = 0 ; row < NumberOfRowsPerSection; row++) {
338+ NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
339+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: indexPath];
340+ if ([visibleNodes containsObject: node]) {
341+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 1 );
342+ XCTAssertLessThan (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
343+ } else {
344+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 0 );
345+ XCTAssertEqual (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
346+ }
347+ }
348+ }
349+ [relayoutAfterEnablingEditingExpectation fulfill ];
350+ }];
351+ [self waitForExpectationsWithTimeout: 5 handler: ^(NSError *error) {
352+ if (error) {
353+ XCTFail (@" Expectation failed: %@ " , error);
354+ }
355+ }];
356+
357+ // Cause table view to leave editing mode.
358+ // Visibile nodes should be re-measured again.
359+ // All nodes should have max constrained width equals to the table view width.
360+ XCTestExpectation *relayoutAfterDisablingEditingExpectation = [self expectationWithDescription: @" relayoutAfterDisablingEditing" ];
361+ [tableView beginUpdates ];
362+ [tableView setEditing: NO ];
363+ [tableView endUpdatesAnimated: YES completion: ^(BOOL completed) {
364+ for (int section = 0 ; section < NumberOfSections; section++) {
365+ for (int row = 0 ; row < NumberOfRowsPerSection; row++) {
366+ NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
367+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: indexPath];
368+ BOOL visible = [visibleNodes containsObject: node];
369+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , visible ? 2 : 0 );
370+ XCTAssertEqual (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
371+ }
372+ }
373+ [relayoutAfterDisablingEditingExpectation fulfill ];
374+ }];
375+ [self waitForExpectationsWithTimeout: 5 handler: ^(NSError *error) {
376+ if (error) {
377+ XCTFail (@" Expectation failed: %@ " , error);
378+ }
379+ }];
380+ }
381+
382+ - (void )testRelayoutRowsAfterEditingModeIsChangedAndTheyBecomeVisible
383+ {
384+ CGSize tableViewSize = CGSizeMake (100 , 500 );
385+ ASTestTableView *tableView = [[ASTestTableView alloc ] initWithFrame: CGRectMake (0 , 0 , tableViewSize.width, tableViewSize.height)
386+ style: UITableViewStylePlain
387+ asyncDataFetching: YES ];
388+ ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new ];
389+
390+ tableView.asyncDelegate = dataSource;
391+ tableView.asyncDataSource = dataSource;
392+
393+ XCTestExpectation *reloadDataExpectation = [self expectationWithDescription: @" reloadData" ];
394+ [tableView reloadDataWithCompletion: ^{
395+ for (int section = 0 ; section < NumberOfSections; section++) {
396+ for (int row = 0 ; row < NumberOfRowsPerSection; row++) {
397+ NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
398+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: indexPath];
399+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 0 );
400+ XCTAssertEqual (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
401+ }
402+ }
403+ [reloadDataExpectation fulfill ];
404+ }];
405+ [self waitForExpectationsWithTimeout: 5 handler: ^(NSError *error) {
406+ if (error) {
407+ XCTFail (@" Expectation failed: %@ " , error);
408+ }
409+ }];
410+
411+ // Cause table view to enter editing mode and then scroll to the bottom.
412+ // The last node should be re-measured on main thread with the new (smaller) content view width.
413+ NSIndexPath *lastRowIndexPath = [NSIndexPath indexPathForRow: (NumberOfRowsPerSection - 1 ) inSection: (NumberOfSections - 1 )];
414+ XCTestExpectation *relayoutExpectation = [self expectationWithDescription: @" relayout" ];
415+ [tableView beginUpdates ];
416+ [tableView setEditing: YES ];
417+ [tableView setContentOffset: CGPointMake (0 , CGFLOAT_MAX) animated: YES ];
418+ [tableView endUpdatesAnimated: YES completion: ^(BOOL completed) {
419+ ASTestTextCellNode *node = (ASTestTextCellNode *)[tableView nodeForRowAtIndexPath: lastRowIndexPath];
420+ XCTAssertEqual (node.numberOfLayoutsOnMainThread , 1 );
421+ XCTAssertLessThan (node.constrainedSizeForCalculatedLayout .max .width , tableViewSize.width );
422+ [relayoutExpectation fulfill ];
423+ }];
424+ [self waitForExpectationsWithTimeout: 5 handler: ^(NSError *error) {
425+ if (error) {
426+ XCTFail (@" Expectation failed: %@ " , error);
427+ }
428+ }];
429+ }
430+
293431@end
0 commit comments