ComponentKitTests/CKComponentControllerTests.mm (458 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 <ComponentKit/CKComponent.h> #import <ComponentKit/CKComponentController.h> #import <ComponentKit/CKComponentControllerEvents.h> #import <ComponentKit/CKComponentControllerHelper.h> #import <ComponentKit/CKComponentScope.h> #import <ComponentKit/CKComponentSubclass.h> #import <ComponentKit/CKComponentHostingView.h> #import <ComponentKit/CKThreadLocalComponentScope.h> #import <ComponentKit/CKComponentControllerInternal.h> #import <ComponentKitTestHelpers/CKComponentLifecycleTestHelper.h> #import <ComponentKitTestHelpers/CKComponentTestRootScope.h> #import <ComponentKitTestHelpers/CKLifecycleTestComponent.h> #import "CKComponentTestCase.h" using namespace CKComponentControllerHelper; // Used for testing component controller that doesn't have lifecycle methods implemented. @interface CKEmptyComponentController: CKComponentController @end @interface CKComponentControllerTests : CKComponentTestCase @end @implementation CKComponentControllerTests static CKComponent *componentProvider(id<NSObject> model, id<NSObject>context) { return [CKLifecycleTestComponent new]; } - (void)testThatCreatingComponentCreatesAController { CKComponentTestRootScope scope; CKLifecycleTestComponent *fooComponent = [CKLifecycleTestComponent new]; XCTAssertNotNil(fooComponent.controller); } - (void)testThatAttachingManagerInstantiatesComponentController { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertNotNil(fooComponent.controller, @"Expected mounting a component to create controller"); } - (void)testThatRemountingUnchangedComponentDoesNotCallDidUpdateComponent { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; CKLifecycleTestComponentController *controller = fooComponent.controller; [componentLifecycleTestController detachFromView]; controller.calledDidUpdateComponent = NO; // Reset to NO [componentLifecycleTestController attachToView:view]; XCTAssertFalse(controller.calledDidUpdateComponent, @"Component did not update so should not call didUpdateComponent"); } - (void)testThatUpdatingManagerUpdatesComponentController { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; UIView *view = [UIView new]; const CKComponentLifecycleTestHelperState state1 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state1]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent1 = (CKLifecycleTestComponent *)state1.componentLayout.component; const CKComponentLifecycleTestHelperState state2 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state2]; CKLifecycleTestComponent *fooComponent2 = (CKLifecycleTestComponent *)state1.componentLayout.component; XCTAssertTrue(fooComponent1.controller == fooComponent2.controller, @"Expected controller %@ to match %@", fooComponent1.controller, fooComponent2.controller); } - (void)testThatAttachingManagerCallsDidAcquireView { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertTrue(fooComponent.controller.counts.didAcquireView > 0, @"Expected mounting to acquire view"); XCTAssertNotNil(fooComponent.controller.view, @"Expected mounting to acquire view"); } - (void)testThatDetachingManagerCallsDidRelinquishView { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertFalse(fooComponent.controller.counts.willRelinquishView > 0, @"Did not expect view to be released before detach"); [componentLifecycleTestController detachFromView]; XCTAssertTrue(fooComponent.controller.counts.willRelinquishView > 0, @"Expected detach to call release view"); XCTAssertNil(fooComponent.controller.view, @"Expected detach to release view"); } - (void)testThatUpdatingStateWhileAttachedRelinquishesOldViewAndAcquiresNewOne { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertTrue(fooComponent.controller.counts.didAcquireView > 0, @"Expected mounting to acquire view"); XCTAssertNotNil(fooComponent.controller.view, @"Expected mounting to acquire view"); UIView *originalView = fooComponent.controller.view; fooComponent.controller.counts = {}; // reset [fooComponent updateStateToIncludeNewAttribute]; XCTAssertTrue(fooComponent.controller.counts.willRelinquishView > 0, @"Expected state update to relinquish old view"); XCTAssertTrue(fooComponent.controller.counts.didAcquireView > 0, @"Expected state update to relinquish old view"); XCTAssertTrue(originalView != fooComponent.controller.view, @"Expected different view"); } - (void)testThatAttachingWithDifferentViewRelinquishesOldViewAndAcquiresNewOne { const auto componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const auto state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; const auto view1 = [UIView new]; [componentLifecycleTestController attachToView:view1]; const auto fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; // Attaching first view should only trigger `didAcquireView` because there is no previous view // to be relinquished. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 0); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 1); const auto view2 = [UIView new]; [componentLifecycleTestController attachToView:view2]; // Attaching second view should trigger both `willRelinquishView` and `didAcquireView` because // we need to relinquish the previous view it acquired. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 1); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 2); } - (void)testThatAttachingWithDifferentViewAfterUpdatingStateRelinquishesOldViewAndAcquiresNewOne { const auto componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const auto state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; const auto view1 = [UIView new]; [componentLifecycleTestController attachToView:view1]; const auto fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; // Attaching first view should only trigger `didAcquireView` because there is no previous view // to be relinquished. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 0); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 1); [fooComponent updateState:^id(id currentState) { return @NO; } mode:CKUpdateModeSynchronous]; const auto view2 = [UIView new]; [componentLifecycleTestController attachToView:view2]; // Attaching second view should trigger both `willRelinquishView` and `didAcquireView` because // we need to relinquish the previous view it acquired. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 1); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 2); } - (void)testThatAttachingWithSameViewAfterUpdatingStateDoesNotRelinquishesOldView { const auto componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const auto state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; const auto view = [UIView new]; [componentLifecycleTestController attachToView:view]; const auto fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; // Attaching first view should only trigger `didAcquireView` because there is no previous view // to be relinquished. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 0); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 1); [fooComponent updateState:^id(id currentState) { return @NO; } mode:CKUpdateModeSynchronous]; [componentLifecycleTestController attachToView:view]; // Attaching the same view should not trigger `willRelinquishView` or `didAcquireView` after // state of component is updated. XCTAssertEqual(fooComponent.controller.counts.willRelinquishView, 0); XCTAssertEqual(fooComponent.controller.counts.didAcquireView, 1); } - (void)testThatResponderChainIsInOrderComponentThenControllerThenRootView { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertEqualObjects([fooComponent nextResponder], fooComponent.controller, @"Component's nextResponder should be component controller"); XCTAssertEqualObjects([fooComponent.controller nextResponder], view, @"Root component's controller's nextResponder should be root view"); } - (void)testThatResponderChainTargetsCorrectResponder { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state]; UIView *view = [UIView new]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertEqualObjects([fooComponent targetForAction:nil withSender:fooComponent], fooComponent, @"Component should respond to this action"); XCTAssertEqualObjects([fooComponent targetForAction:nil withSender:nil], fooComponent.controller, @"Component's controller should respond to this action"); } - (void)testThatEarlyReturnNew_fromFirstComponent_allowsComponentCreation_whenNotEarlyReturning_onStateUpdate { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; UIView *view = [UIView new]; CKLifecycleTestComponentSetShouldEarlyReturnNew(YES); const CKComponentLifecycleTestHelperState state1 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state1]; [componentLifecycleTestController attachToView:view]; CKLifecycleTestComponentSetShouldEarlyReturnNew(NO); const CKComponentLifecycleTestHelperState state2 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController updateWithState:state2]; CKLifecycleTestComponent *fooComponent2 = (CKLifecycleTestComponent *)state2.componentLayout.component; XCTAssertTrue([fooComponent2.controller isKindOfClass:[CKLifecycleTestComponentController class]], @"Expected controller %@ to exist and be of type CKLifecycleTestComponentController", fooComponent2.controller); } - (void)testThatComponentControllerReceivesComponentTreeWillAppearEvent { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state]; CKComponentScopeRootAnnounceControllerAppearance([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)[componentLifecycleTestController state].componentLayout.component; XCTAssertTrue(fooComponent.controller.calledComponentTreeWillAppear, @"Expected controller %@ to have received component tree will appear event", fooComponent.controller); } - (void)testThatComponentControllerReceivesComponentTreeDidDisappearEvent { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state]; CKComponentScopeRootAnnounceControllerAppearance([componentLifecycleTestController state].scopeRoot); [componentLifecycleTestController detachFromView]; CKComponentScopeRootAnnounceControllerDisappearance([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)[componentLifecycleTestController state].componentLayout.component; XCTAssertTrue(fooComponent.controller.calledComponentTreeDidDisappear, @"Expected controller %@ to have received component tree did disappear event", fooComponent.controller); } - (void)testThatComponentControllerReceivesComponentTreeWillAppearEventAfterAdditionalStateUpdates { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state1 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; const CKComponentLifecycleTestHelperState state2 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state1]; [componentLifecycleTestController updateWithState:state2]; CKComponentScopeRootAnnounceControllerAppearance([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)[componentLifecycleTestController state].componentLayout.component; XCTAssertTrue(fooComponent.controller.calledComponentTreeWillAppear, @"Expected controller %@ to have received component tree will appear event", fooComponent.controller); } - (void)testThatComponentControllerReceivesComponentTreeDidDisappearEventAfterAdditionalStateUpdates { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state1 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; const CKComponentLifecycleTestHelperState state2 = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state1]; [componentLifecycleTestController updateWithState:state2]; CKComponentScopeRootAnnounceControllerAppearance([componentLifecycleTestController state].scopeRoot); [componentLifecycleTestController detachFromView]; CKComponentScopeRootAnnounceControllerDisappearance([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)[componentLifecycleTestController state].componentLayout.component; XCTAssertTrue(fooComponent.controller.calledComponentTreeDidDisappear, @"Expected controller %@ to have received component tree did disappear event", fooComponent.controller); } - (void)testComponentControllerReceivesDidInitEvent { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state]; CKComponentScopeRootAnnounceControllerInitialization([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertTrue(fooComponent.controller.calledDidInit, @"Expected component controller to get did init event"); } - (void)testComponentControllerReceivesInvalidateEvent { CKComponentLifecycleTestHelper *componentLifecycleTestController = [[CKComponentLifecycleTestHelper alloc] initWithComponentProvider:componentProvider sizeRangeProvider:nil]; const CKComponentLifecycleTestHelperState state = [componentLifecycleTestController prepareForUpdateWithModel:nil constrainedSize:{{0,0}, {100, 100}} context:nil]; [componentLifecycleTestController attachToView:[UIView new]]; [componentLifecycleTestController updateWithState:state]; [componentLifecycleTestController detachFromView]; CKComponentScopeRootAnnounceControllerInvalidation([componentLifecycleTestController state].scopeRoot); CKLifecycleTestComponent *fooComponent = (CKLifecycleTestComponent *)state.componentLayout.component; XCTAssertTrue(fooComponent.controller.calledInvalidateController, @"Expected component controller to get invalidation event"); } - (void)testRemovedComponentControllersFromPreviousScopeRootMatchingPredicate { // `CKLifecycleTestComponentController` has `invalidateController` implemented. const auto componentController1 = [[CKLifecycleTestComponentController alloc] initWithComponent:nil]; const auto componentController2 = [[CKLifecycleTestComponentController alloc] initWithComponent:nil]; const auto previousScopeRoot = [CKComponentScopeRoot rootWithListener:nil analyticsListener:nil componentPredicates:{} componentControllerPredicates:{&CKComponentControllerInvalidateEventPredicate}]; [previousScopeRoot registerComponentController:componentController1]; [previousScopeRoot registerComponentController:componentController2]; const auto newScopeRoot = [previousScopeRoot newRoot]; [newScopeRoot registerComponentController:componentController1]; // New scope root doesn't have `componentController2` registered. const auto removedComponentControllers = removedControllersFromPreviousScopeRootMatchingPredicate(newScopeRoot, previousScopeRoot, &CKComponentControllerInvalidateEventPredicate); XCTAssertEqual(removedComponentControllers[0], componentController2); } - (void)testRemovedComponentControllersAreEmptyFromPreviousScopeRootMatchingPredicate { // `CKLifecycleTestComponentController` has `invalidateController` implemented. const auto componentController1 = [[CKLifecycleTestComponentController alloc] initWithComponent:nil]; const auto componentController2 = [[CKLifecycleTestComponentController alloc] initWithComponent:nil]; const auto previousScopeRoot = [CKComponentScopeRoot rootWithListener:nil analyticsListener:nil componentPredicates:{} componentControllerPredicates:{&CKComponentControllerInvalidateEventPredicate}]; [previousScopeRoot registerComponentController:componentController1]; [previousScopeRoot registerComponentController:componentController2]; const auto newScopeRoot = [previousScopeRoot newRoot]; [newScopeRoot registerComponentController:componentController1]; [newScopeRoot registerComponentController:componentController2]; // Both component controllers are presented in new scope root. const auto removedComponentControllers = removedControllersFromPreviousScopeRootMatchingPredicate(newScopeRoot, previousScopeRoot, &CKComponentControllerInvalidateEventPredicate); XCTAssertTrue(removedComponentControllers.empty()); } - (void)testRemovedComponentControllersAreEmptyWhenPreviousScopeRootIsNil { const auto scopeRoot = [CKComponentScopeRoot rootWithListener:nil analyticsListener:nil componentPredicates:{} componentControllerPredicates:{&CKComponentControllerInvalidateEventPredicate}]; const auto removedComponentControllers = removedControllersFromPreviousScopeRootMatchingPredicate(scopeRoot, nil, &CKComponentControllerInvalidateEventPredicate); XCTAssertTrue(removedComponentControllers.empty()); } - (void)testRemovedComponentControllersFromPreviousScopeRootNotMatchingPredicate { const auto componentController = [[CKEmptyComponentController alloc] initWithComponent:nil]; const auto previousScopeRoot = [CKComponentScopeRoot rootWithListener:nil analyticsListener:nil componentPredicates:{} componentControllerPredicates:{&CKComponentControllerInvalidateEventPredicate}]; [previousScopeRoot registerComponentController:componentController]; const auto newScopeRoot = [previousScopeRoot newRoot]; // `componentController` doesn't match predicate. const auto removedComponentControllers = removedControllersFromPreviousScopeRootMatchingPredicate(newScopeRoot, previousScopeRoot, &CKComponentControllerInvalidateEventPredicate); XCTAssertTrue(removedComponentControllers.empty()); } - (void)testDidUpdateComponentIsCalledOnMountIfCommponentIsNotUpdatedWithSetter { const auto component1 = [CKLifecycleTestComponent new]; const auto component2 = [CKLifecycleTestComponent new]; const auto controller = [[CKLifecycleTestComponentController alloc] initWithComponent:component1]; XCTAssertEqual(controller.calledWillUpdateComponent, 0); XCTAssertEqual(controller.calledDidUpdateComponent, 0); [controller componentWillMount:component2]; XCTAssertEqual(controller.calledWillUpdateComponent, 1); XCTAssertEqual(controller.calledDidUpdateComponent, 0); [controller componentDidMount:component2]; XCTAssertEqual(controller.calledWillUpdateComponent, 1); XCTAssertEqual(controller.calledDidUpdateComponent, 1); } - (void)testDidUpdateComponentIsCalledOnMountIfCommponentIsUpdatedWithSetter { const auto component1 = [CKLifecycleTestComponent new]; const auto component2 = [CKLifecycleTestComponent new]; const auto controller = [[CKLifecycleTestComponentController alloc] initWithComponent:component1]; XCTAssertEqual(controller.calledWillUpdateComponent, 0); XCTAssertEqual(controller.calledDidUpdateComponent, 0); controller.latestComponent = component2; XCTAssertEqual(controller.calledWillUpdateComponent, 1); XCTAssertEqual(controller.calledDidUpdateComponent, 0); [controller componentWillMount:component2]; XCTAssertEqual(controller.calledWillUpdateComponent, 1); XCTAssertEqual(controller.calledDidUpdateComponent, 0); [controller componentDidMount:component2]; XCTAssertEqual(controller.calledWillUpdateComponent, 1); XCTAssertEqual(controller.calledDidUpdateComponent, 1); } @end @implementation CKEmptyComponentController @end