ComponentKitTests/TransactionalDataSource/CKDataSourceStateUpdateTests.mm (101 lines of code) (raw):

/* * Copyright (c) 2014-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 <ComponentKitTestHelpers/CKTestRunLoopRunning.h> #import <ComponentKit/CKComponentInternal.h> #import <ComponentKit/CKComponentSubclass.h> #import <ComponentKit/CKComponentLayout.h> #import <ComponentKit/CKComponentProvider.h> #import <ComponentKit/CKDataSourceInternal.h> #import <ComponentKit/CKDataSourceItem.h> #import <ComponentKit/CKDataSourceState.h> #import <ComponentKit/CKDataSourceListener.h> #import "CKStateExposingComponent.h" #import "CKDataSourceStateTestHelpers.h" @interface CKDataSourceStateUpdateTests : XCTestCase <CKDataSourceListener> @end @implementation CKDataSourceStateUpdateTests { CKDataSource *_dataSource; CKDataSourceState *_state; } static CKComponent *ComponentProvider(id<NSObject> model, id<NSObject> context) { return [CKStateExposingComponent new]; } - (void)testSynchronousStateUpdateResultsInUpdatedComponent { _dataSource = CKComponentTestDataSource(ComponentProvider, self); NSString *const newState = @"new state"; [self _updateStates:@[newState] mode:CKUpdateModeSynchronous]; // Even for synchronous updates, the update is deferred to the end of the run loop, so we must spin the runloop. XCTAssertTrue(CKRunRunLoopUntilBlockIsTrue(^BOOL{ return [self _isEqualState:newState]; })); } - (void)testMultipleSynchronousStateUpdatesAreCoalesced { _dataSource = CKComponentTestDataSource(ComponentProvider, self); NSArray<id> *const newStates = @[@1, @2, @3]; [self _updateStates:newStates mode:CKUpdateModeSynchronous]; XCTAssertTrue(CKRunRunLoopUntilBlockIsTrue(^BOOL{ return [self _isEqualState:newStates.lastObject]; })); } - (void)testAsynchronousStateUpdateResultsInUpdatedComponent { _dataSource = CKComponentTestDataSource(ComponentProvider, self); NSString *const newState = @"new state"; [self _updateStates:@[newState] mode:CKUpdateModeAsynchronous]; XCTAssertTrue(CKRunRunLoopUntilBlockIsTrue(^BOOL{ return [self _isEqualState:newState]; })); } - (void)testStateUpdatesAreProcessedInTheOrderTheyWereEnqueued { _dataSource = CKComponentTestDataSource(ComponentProvider, self); NSArray<id> *const newStates = @[@"NewState", @"NewStateUpdate1", @"NewStateUpdate1Update2"]; [self _updateStates:newStates mode:CKUpdateModeSynchronous]; XCTAssertTrue(CKRunRunLoopUntilBlockIsTrue(^BOOL{ return [self _isEqualState:newStates.lastObject]; })); } - (void)testStateUpdatesAreNotProcessedIfShouldPauseStateUpdatesIsYes { _dataSource = CKComponentTestDataSource(ComponentProvider, self); _dataSource.shouldPauseStateUpdates = YES; const auto state1 = _dataSource.state; [self _updateStates:@[@"Test"] mode:CKUpdateModeSynchronous]; XCTAssertEqual(_dataSource.state, state1); _dataSource.shouldPauseStateUpdates = NO; XCTAssertNotEqual(_dataSource.state, state1); } #pragma mark - CKDataSourceListener - (void)dataSource:(CKDataSource *)dataSource didModifyPreviousState:(CKDataSourceState *)previousState withState:(CKDataSourceState *)state byApplyingChanges:(CKDataSourceAppliedChanges *)changes { _state = state; } - (void)dataSource:(CKDataSource *)dataSource willApplyDeferredChangeset:(CKDataSourceChangeset *)deferredChangeset { } #pragma mark - Helpers - (void)_updateStates:(NSArray<id> *)states mode:(CKUpdateMode)mode { CKRunRunLoopUntilBlockIsTrue(^BOOL{ return _state != nil; }); CKDataSourceItem *const item = [_state objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; CKComponent *const component = (CKComponent *)[item rootLayout].component(); for (id state in states) { [component updateState:^(id oldState){return state;} mode:mode]; } } - (BOOL)_isEqualState:(id)state { CKDataSourceItem *updatedItem = [_state objectAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; return updatedItem ? [((CKStateExposingComponent *)[updatedItem rootLayout].component()).state isEqual:state] : NO; } @end