/*
 *  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/CKBuildComponent.h>
#import <ComponentKit/CKComponentInternal.h>
#import <ComponentKit/CKComponentSubclass.h>
#import <ComponentKit/CKFlexboxComponent.h>
#import <ComponentKit/CKThreadLocalComponentScope.h>
#import <ComponentKit/CKTreeNode.h>
#import <ComponentKit/CKComponentScopeRootFactory.h>
#import <ComponentKit/CKComponentScopeRoot.h>
#import <ComponentKit/CKRenderHelpers.h>

#import <ComponentKitTestHelpers/CKRenderComponentTestHelpers.h>

#import "CKComponentTestCase.h"

@interface CKRenderComponentTests : CKComponentTestCase
@end

@interface CKRenderComponentAndScopeTreeTests : CKComponentTestCase
@end

@implementation CKRenderComponentTests
{
  CKTestRenderComponent *_c;
  CKComponentScopeRoot *_scopeRoot;
  @package
}

- (void)setUpForFasterStateUpdates
{
  [self setUpForFasterStateUpdates:^{
    return [CKTestRenderComponent new];
  }];
}

- (void)setUpForFasterStateUpdates:(CKComponent *(^)(void))componentFactory
{
  // New Tree Creation.
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  _c = (CKTestRenderComponent *)buildResults.component;
  _scopeRoot = buildResults.scopeRoot;
  XCTAssertEqual(_c.renderCalledCounter, 1);
}

- (void)setUpForFasterPropsUpdates:(CKComponent *(^)(void))componentFactory
{
  // New Tree Creation.
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);
  _c = (CKTestRenderComponent *)buildResults.component;
  _scopeRoot = buildResults.scopeRoot;
  XCTAssertEqual(_c.renderCalledCounter, 1);
}

#pragma mark - State updates

- (void)test_fasterStateUpdate_componentIsBeingReused_onStateUpdateOnADifferentComponentBranch
{
  [self setUpForFasterStateUpdates];

  // Simulate a state update on a different component branch:
  // 1. treeNodeDirtyIds with fake components ids.
  // 2. parentHasStateUpdate = NO
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  auto const c2 = [CKTestRenderComponent new];
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = {},
    .treeNodeDirtyIds = {100, 101}, // Use a random id that represents a fake state update on a different branch.
    .buildTrigger = CKBuildTriggerStateUpdate,
  });

  // As the state update doesn't affect the c2, we should reuse c instead.
  [self verifyComponentIsBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
}

- (void)test_fasterStateUpdate_componentIsNotBeingReused_onStateUpdateOnAParent
{
  __block CKCompositeComponentWithScopeAndState *root;
  __block CKTestRenderComponent *c;
  auto const componentFactory = ^{
    c = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    root = [CKCompositeComponentWithScopeAndState newWithComponent:c];
    return root;
  };
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate a state update on a parent component:
  // 1. parentHasStateUpdate = YES
  // 2. treeNodeDirtyIds with a fake parent id.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[root.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  __block CKCompositeComponentWithScopeAndState *root2;
  __block CKTestRenderComponent *c2;
  auto const componentFactory2 = ^{
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
    root2 = [CKCompositeComponentWithScopeAndState newWithComponent:c2];
    return root2;
  };

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);

  // c has dirty parent, however, the components are equal, so we reuse the previous component.
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 1);
  XCTAssertFalse(c2.didReuseComponent);
  XCTAssertNotEqual(c.childComponent, c2.childComponent);
  // Make sure the dirtyParent is pssed correctly to the child component.
  XCTAssertTrue(c2.childComponent.parentHasStateUpdate);
}

- (void)test_fasterStateUpdate_componentIsNotBeingReused_onStateUpdateOnTheComponent
{
  [self setUpForFasterStateUpdates];

  // Simulate a state update on the component itself:
  // 1. treeNodeDirtyIds, contains the component tree node id.
  // 2. stateUpdates, contains a state update block for the component.
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[_c.treeNode.scopeHandle] = {^id(id s) { return s; }};

  auto const c2 = [CKTestRenderComponent new];
  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = stateUpdates,
    .treeNodeDirtyIds = {
      _c.treeNode.nodeIdentifier
    },
    .buildTrigger = CKBuildTriggerStateUpdate,
  });

  // As the state update affect c2, we should recreate its children.
  [self verifyComponentIsNotBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
  // Make sure the dirtyParent is pssed correctly to the child component.
  XCTAssertTrue(c2.childComponent.parentHasStateUpdate);
}

- (void)test_fasterStateUpdate_componentIsNotBeingReused_onStateUpdateOnItsChild
{
  [self setUpForFasterStateUpdates];

  // Simulate a state update on the component child:
  // 1. treeNodeDirtyIds, contains the component tree node id.
  // 2. stateUpdates, contains a state update block for the component.
  // 3. hasDirtyParent = NO
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[_c.treeNode.scopeHandle] = {^id(id s) { return s; }};

  auto const c2 = [CKTestRenderComponent new];

  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = stateUpdates,
    .treeNodeDirtyIds = {
      _c.treeNode.nodeIdentifier,
    },
    .buildTrigger = CKBuildTriggerStateUpdate,
  });

  // As the state update affect c2, we should recreate its children.
  [self verifyComponentIsNotBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
  // Make sure the dirtyParent is pssed correctly to the child component.
  XCTAssertTrue(c2.childComponent.parentHasStateUpdate);
}

#pragma mark - Faster Props Update

- (void)test_fasterPropsUpdate_componentIsNotBeingReused_whenPropsAreNotEqual
{
  [self setUpForFasterPropsUpdates:^(){
    return [CKTestRenderComponent newWithProps:{.identifier = 1}];
  }];

  // Simulate props update.
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  auto const c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = {},
    .treeNodeDirtyIds = {},
    .buildTrigger = CKBuildTriggerPropsUpdate,
  });

  // Props are not equal, we cannot reuse the component in this case.
  [self verifyComponentIsNotBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
  // Make sure the dirtyParent is pssed correctly to the child component.
  XCTAssertFalse(c2.childComponent.parentHasStateUpdate);
}

- (void)test_fasterPropsUpdate_componentIsBeingReusedWhenPropsAreEqual
{
  // Use the same componentIdentifier for both components.
  // shouldComponentUpdate: will return NO in this case and we can reuse the component.
  NSUInteger componentIdentifier = 1;
  [self setUpForFasterPropsUpdates:^{
    return [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
  }];

  // Simulate props update.
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  auto const c2 = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = {},
    .treeNodeDirtyIds = {},
    .buildTrigger = CKBuildTriggerPropsUpdate,
  });

  // The components are equal, we can reuse the previous component.
  [self verifyComponentIsBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
  // Make sure the dirtyParent is pssed correctly to the child component.
  XCTAssertFalse(c2.childComponent.parentHasStateUpdate);
}

- (void)test_fasterPropsUpdate_componentIsBeingReused_onStateUpdateWithDirtyParentAndEqualComponents
{
  __block CKCompositeComponentWithScopeAndState *root;
  __block CKTestRenderComponent *c;
  auto const componentIdentifier = 1;
  auto const componentFactory = ^{
    c = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
    root = [CKCompositeComponentWithScopeAndState newWithComponent:c];
    return root;
  };
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate a state update on a parent component:
  // 1. parentHasStateUpdate = YES
  // 2. treeNodeDirtyIds with a fake parent id.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[root.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  __block CKCompositeComponentWithScopeAndState *root2;
  __block CKTestRenderComponent *c2;
  auto const componentFactory2 = ^{
    c2 = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
    root2 = [CKCompositeComponentWithScopeAndState newWithComponent:c2];
    return root2;
  };

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);

  // c has dirty parent, however, the components are equal, so we reuse the previous component.
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 0);
  XCTAssertTrue(c2.didReuseComponent);
  XCTAssertEqual(c.childComponent, c2.childComponent);
}

- (void)test_fasterPropsUpdate_componentIsBeingReused_onStateUpdateWithNonDirtyComponentAndEqualComponents
{
  auto const componentIdentifier = 1;
  [self setUpForFasterPropsUpdates:^{
    return [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
  }];

  // Simulate a state update on a parent component:
  // 1. parentHasStateUpdate = NO
  // 2. treeNodeDirtyIds is empty.
  CKThreadLocalComponentScope threadScope(_scopeRoot, {});
  auto const c2 = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
  CKComponentScopeRoot *scopeRoot2 = threadScope.newScopeRoot;

  CKRender::ComponentTree::Root::build(c2, {
    .scopeRoot = scopeRoot2,
    .previousScopeRoot = _scopeRoot,
    .stateUpdates = {},
    .treeNodeDirtyIds = {},
    .buildTrigger = CKBuildTriggerStateUpdate,
  });

  // c is not dirty, the components are equal, so we reuse the previous component.
  [self verifyComponentIsBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:scopeRoot2];
}

- (void)test_fasterPropsUpdate_componentIsNotBeingReusedWhenPropsAreEqualButNodeIdIsDirty
{
  // Use the same componentIdentifier for both components.
  // shouldComponentUpdate: will return NO in this case.
  // However, we simulate a case when the node id is dirty, hence cannot be reused.
  NSUInteger componentIdentifier = 1;
  [self setUpForFasterPropsUpdates:^{
    return [CKTestRenderComponent newWithProps:{
      .identifier = componentIdentifier,
      .shouldUseComponentContext = YES,
    }];
  }];

  // Simulate props update.
  __block CKTestRenderComponent *c2;
  auto const componentFactory = ^{
    c2 = [CKTestRenderComponent newWithProps:{
      .identifier = componentIdentifier,
      .shouldUseComponentContext = YES,
    }];
    return c2;
  };
  auto const scopeRoot = CK::makeNonNull(_scopeRoot);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // The components are equal, but the node id is dirty - hence, we cannot reuse the previous component.
  [self verifyComponentIsNotBeingReused:_c c2:c2 scopeRoot:_scopeRoot scopeRoot2:buildResults.scopeRoot];

}

#pragma mark - Props and State Update

- (void)test_fasterStateUpdate_componentIsNotBeingReused_onPropsAndStateUpdate
{
  __block CKCompositeComponentWithScopeAndState *root;
  __block CKTestRenderComponent *c;
  auto const componentIdentifier = 1;
  auto const componentFactory = ^{
    c = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
    root = [CKCompositeComponentWithScopeAndState newWithComponent:c];
    return root;
  };
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate props and a state update on a component
  // 1. parentHasStateUpdate = YES
  // 2. treeNodeDirtyIds with a fake parent id.
  // 3. forcePropsUpdates = YES
  // 4. Props (componentIdentifier) are equal = reuse
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[root.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  __block CKCompositeComponentWithScopeAndState *root2;
  __block CKTestRenderComponent *c2;
  auto const componentFactory2 = ^{
    c2 = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
    root2 = [CKCompositeComponentWithScopeAndState newWithComponent:c2];
    return root2;
  };

  auto const forcePropsUpdates = YES;

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, forcePropsUpdates);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);

  // forcePropsUpdates + state update should end up with PropsAndStateUpdate trigger - which behaves the same as PropsUpdates
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 0);
  XCTAssertFalse(c.didReuseComponent);
  XCTAssertTrue(c2.didReuseComponent);
  XCTAssertEqual(c.childComponent, c2.childComponent);
}

- (void)test_fasterStateUpdate_componentIsBeingReused_onPropsAndStateUpdate
{
  __block CKCompositeComponentWithScopeAndState *root;
  __block CKTestRenderComponent *c;
  auto const componentIdentifier = 1;
  auto const componentFactory = ^{
    c = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier}];
    root = [CKCompositeComponentWithScopeAndState newWithComponent:c];
    return root;
  };
  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate props and a state update on a component
  // 1. parentHasStateUpdate = YES
  // 2. treeNodeDirtyIds with a fake parent id.
  // 3. forcePropsUpdates = YES
  // 4. Props (componentIdentifier) are different = no reuse
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[root.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  __block CKCompositeComponentWithScopeAndState *root2;
  __block CKTestRenderComponent *c2;
  // Simulate props update
  auto const componentIdentifier2 = 2;
  auto const componentFactory2 = ^{
    c2 = [CKTestRenderComponent newWithProps:{.identifier = componentIdentifier2}];
    root2 = [CKCompositeComponentWithScopeAndState newWithComponent:c2];
    return root2;
  };

  auto const forcePropsUpdates = YES;

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, forcePropsUpdates);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);

  // forcePropsUpdates + state update should end up with PropsAndStateUpdate trigger - which behaves the same as PropsUpdates
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 1);
  XCTAssertFalse(c.didReuseComponent);
  XCTAssertFalse(c2.didReuseComponent);
  XCTAssertNotEqual(c.childComponent, c2.childComponent);
}

#pragma mark - parentHasStateUpdate

- (void)test_parentHasStateUpdatePropagatedCorrectly
{
  // Build new tree
  __block CKTestRenderComponent *c;
  __block CKCompositeComponentWithScopeAndState *rootComponent;

  auto const componentFactory = ^{
    c = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    rootComponent = generateComponentHierarchyWithComponent(c);
    return rootComponent;
  };

  auto const scopeRoot = CKComponentScopeRootWithDefaultPredicates(nil, nil);
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);
  XCTAssertFalse(c.childComponent.parentHasStateUpdate);

  // Simulate a state update on the root.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[rootComponent.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  __block CKTestRenderComponent *c2;
  __block CKCompositeComponentWithScopeAndState *rootComponent2;

  auto const componentFactory2 = ^{
    // Use different identifier for c2 to make sure we don't reuse it (otherwise the buildComponentTree won't be called on the child component).
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
    rootComponent2 = generateComponentHierarchyWithComponent(c2);
    return rootComponent2;
  };

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);
  XCTAssertTrue(c2.childComponent.parentHasStateUpdate);
}

- (void)test_registerComponentsAndControllersInScopeRootAfterReuse
{
  // Build new tree with siblings `CKTestRenderComponent` components.
  __block CKTestRenderComponent *c1;
  __block CKTestRenderComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build scope root with predicates.
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil,
                                                            {&CKComponentRenderTestsPredicate},
                                                            {&CKComponentControllerRenderTestsPredicate});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);
  // Verify components and controllers registration in the scope root.
  [self verifyComponentsAndControllersAreRegisteredInScopeRoot:buildResults.scopeRoot
                                                    components:{c1.childComponent, c2.childComponent}
                                            componentPredicate:&CKComponentRenderTestsPredicate
                                  componentControllerPredicate:&CKComponentControllerRenderTestsPredicate];

  // Simulate a state update on c2.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c2.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, CKReflowTriggerNone);
  // Verify c1 has been reused
  XCTAssertTrue(c1.didReuseComponent);
  // Verify components and controllers registration in the scope root.
  [self verifyComponentsAndControllersAreRegisteredInScopeRoot:buildResults2.scopeRoot
                                                    components:{c1.childComponent,c2.childComponent}
                                            componentPredicate:&CKComponentRenderTestsPredicate
                                  componentControllerPredicate:&CKComponentControllerRenderTestsPredicate];
}

- (void)test_registerComponentsAndControllersInScopeRootAfterReuseWithNonRenderComponents
{
  // Build new tree with siblings `CKTestRenderComponent` components.
  __block CKTestRenderComponent *c1;
  __block CKTestRenderComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2, .shouldUseNonRenderChild = YES}];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build scope root with predicates.
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil,
  {&CKAllNonNilComponentsTestsPredicate},
  {&CKAllNonNilComponentsControllersTestsPredicate});

  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);
  // Verify components and controllers registration in the scope root.
  [self verifyComponentsAndControllersAreRegisteredInScopeRoot:buildResults.scopeRoot
                                                    components:{
    c1, c1.childComponent,
    c2, c2.nonRenderChildComponent, c2.nonRenderChildComponent.childComponent,
  }
                                            componentPredicate:&CKAllNonNilComponentsTestsPredicate
                                  componentControllerPredicate:&CKAllNonNilComponentsControllersTestsPredicate];

  // Simulate a state update on c1.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c1.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, CKReflowTriggerNone);
  // Verify c1 has been reused
  XCTAssertTrue(c2.didReuseComponent);

  // Verify components and controllers registration in the scope root.
  [self verifyComponentsAndControllersAreRegisteredInScopeRoot:buildResults2.scopeRoot
                                                    components:{
    c1, c1.childComponent,
    c2, c2.nonRenderChildComponent, c2.nonRenderChildComponent.childComponent,
  }
                                            componentPredicate:&CKAllNonNilComponentsTestsPredicate
                                  componentControllerPredicate:&CKAllNonNilComponentsControllersTestsPredicate];
}

- (void)test_componentIsNotBeingReusedOnAStateUpdate_WhenEnableComponentReuseOptimizationsIsOff
{
  // Build new tree with siblings `CKTestRenderComponent` components.
  __block CKTestRenderComponent *c1;
  __block CKTestRenderComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build scope root with predicates.
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate a state update on c2.
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c2.treeNode.scopeHandle].push_back(^(id){
    return @2;
  });

  auto ignoreComponentReuseOptimizations = YES;
  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, ignoreComponentReuseOptimizations, NO);
  auto const reflowTrigger2 = !ignoreComponentReuseOptimizations ? CKReflowTriggerNone : CKReflowTriggerReload;
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, reflowTrigger2);
  // Verify no component have been reused.
  XCTAssertFalse(c1.didReuseComponent);
  XCTAssertFalse(c2.didReuseComponent);
}

- (void)test_componentIsNotBeingReusedOnAPropsUpdate_WhenEnableComponentReuseOptimizationsIsOff
{
  // Build new tree with siblings `CKTestRenderComponent` components.
  __block CKTestRenderComponent *c1;
  __block CKTestRenderComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderComponent newWithProps:{.identifier = 1}];
    c2 = [CKTestRenderComponent newWithProps:{.identifier = 2}];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build scope root with predicates.
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  auto ignoreComponentReuseOptimizations = YES;
  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, {}, ignoreComponentReuseOptimizations, NO);
  auto const reflowTrigger2 = !ignoreComponentReuseOptimizations ? CKReflowTriggerNone : CKReflowTriggerReload;
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, {}, componentFactory, buildTrigger2, reflowTrigger2);
  // Verify no component have been reused.
  XCTAssertFalse(c1.didReuseComponent);
  XCTAssertFalse(c2.didReuseComponent);
}

#pragma mark - Helpers

// Filters `CKTestChildRenderComponent` components.
static BOOL CKComponentRenderTestsPredicate(id<CKComponentProtocol> component) {
  return [component class] == [CKTestChildRenderComponent class];
}

// Filters `CKTestChildRenderComponentController` controllers.
static BOOL CKComponentControllerRenderTestsPredicate(id<CKComponentControllerProtocol> controller) {
  return [controller class] == [CKTestChildRenderComponentController class];
}

// Filters all non-nil components.
static BOOL CKAllNonNilComponentsTestsPredicate(id<CKComponentProtocol> component) {
  return component != nil;
}

// Filters all non-nil controllers.
static BOOL CKAllNonNilComponentsControllersTestsPredicate(id<CKComponentControllerProtocol> controller) {
  return controller != nil;
}

- (void)verifyComponentIsNotBeingReused:(CKTestRenderComponent *)c
                                     c2:(CKTestRenderComponent *)c2
                              scopeRoot:(CKComponentScopeRoot *)scopeRoot
                             scopeRoot2:(CKComponentScopeRoot *)scopeRoot2
{
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 1);
  XCTAssertFalse(c2.didReuseComponent);
  XCTAssertNotEqual(c.childComponent, c2.childComponent);
}

- (void)verifyComponentIsBeingReused:(CKTestRenderComponent *)c
                                  c2:(CKTestRenderComponent *)c2
                           scopeRoot:(CKComponentScopeRoot *)scopeRoot
                          scopeRoot2:(CKComponentScopeRoot *)scopeRoot2
{
  XCTAssertEqual(c.renderCalledCounter, 1);
  XCTAssertEqual(c2.renderCalledCounter, 0);
  XCTAssertTrue(c2.didReuseComponent);
  XCTAssertEqual(c.childComponent, c2.childComponent);
}

- (void)verifyComponentsAndControllersAreRegisteredInScopeRoot:(CKComponentScopeRoot *)scopeRoot
                                                    components:(std::vector<CKComponent *>)components
                                            componentPredicate:(CKComponentPredicate)componentPredicate
                                  componentControllerPredicate:(CKComponentControllerPredicate)componentControllerPredicate
{
  auto const registeredComponents = [scopeRoot componentsMatchingPredicate:componentPredicate];
  auto const registeredControllers = [scopeRoot componentControllersMatchingPredicate:componentControllerPredicate];
  for (auto const &c: components) {
    XCTAssertNotNil(c, @"component shoudn't be nil here");
    XCTAssertTrue(CK::Collection::contains(registeredComponents, c));
    if (c.controller) {
      XCTAssertTrue(CK::Collection::contains(registeredControllers, c.controller));
    }
  }
}

static CKCompositeComponentWithScopeAndState* generateComponentHierarchyWithComponent(CKComponent *c) {
  return
  [CKCompositeComponentWithScopeAndState
   newWithComponent:
   CK::FlexboxComponentBuilder()
       .child(c)
       .build()];
}

@end

@implementation CKRenderComponentAndScopeTreeTests

- (void)test_scopeFramePreserveStateDuringComponentReuse
{
  // Build new tree with siblings `CKTestRenderWithNonRenderWithStateChildComponent` components.
  // Each `CKTestRenderWithNonRenderWithStateChildComponent` has non-render component with state.
  __block CKTestRenderWithNonRenderWithStateChildComponent *c1;
  __block CKTestRenderWithNonRenderWithStateChildComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    c2 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build first component generation:
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate state update on c1.childComponent
  NSNumber *newState1 = @10;
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c1.childComponent.treeNode.scopeHandle].push_back(^(id){ return newState1; });
  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, CKReflowTriggerNone);
  // Verify `c2.childComponent` gets the correct new state
  XCTAssertEqual(newState1, c1.childComponent.state);
  // Verify c2 was reused
  XCTAssertTrue(c2.didReuseComponent);
  // Verify c1 wasn't reused
  XCTAssertFalse(c1.didReuseComponent);

  // Simulate state update on c2.childComponent
  NSNumber *newState2 = @20;
  CKComponentStateUpdateMap stateUpdates2;
  stateUpdates2[c2.childComponent.treeNode.scopeHandle].push_back(^(id){ return newState2;});

  auto const buildTrigger3 = CKBuildComponentTrigger(buildResults2.scopeRoot, stateUpdates2, NO, NO);
  auto const buildResults3 = CKBuildComponent(buildResults2.scopeRoot, stateUpdates2, componentFactory, buildTrigger3, CKReflowTriggerNone);
  // Verify `c2.childComponent` gets the correct new state
  XCTAssertEqual(newState2, c2.childComponent.state);
  // Verify c1 was reused
  XCTAssertTrue(c1.didReuseComponent);
  // Verify c2 wasn't reused
  XCTAssertFalse(c2.didReuseComponent);
  // Verify `c1.childComponent` preserves its state during component reuse
  XCTAssertEqual(newState1, c1.childComponent.state);

  // Simulate state update on c1.childComponent
  NSNumber *newState3 = @30;
  CKComponentStateUpdateMap stateUpdates3;
  stateUpdates3[c1.childComponent.treeNode.scopeHandle].push_back(^(id){ return newState3;});

  auto const buildTrigger4 = CKBuildComponentTrigger(buildResults3.scopeRoot, stateUpdates3, NO, NO);
  auto const buildResults4 = CKBuildComponent(buildResults3.scopeRoot, stateUpdates3, componentFactory, buildTrigger4, CKReflowTriggerNone);
  // Verify `c1.childComponent` gets the correct new state
  XCTAssertEqual(newState3, c1.childComponent.state);
  // Verify c2 was reused
  XCTAssertTrue(c2.didReuseComponent);
  // Verify c1 wasn't reused
  XCTAssertFalse(c1.didReuseComponent);
  // Verify `c2.childComponent` preserves its state during component reuse
  XCTAssertEqual(newState2, c2.childComponent.state);
}

- (void)test_renderComponentPreserveStateDuringComponentReuse
{
  // Build new tree with siblings `CKTestRenderWithNonRenderWithStateChildComponent` components.
  // Each `CKTestRenderWithNonRenderWithStateChildComponent` has non-render component with state.
  __block CKTestRenderWithNonRenderWithStateChildComponent *c1;
  __block CKTestRenderWithNonRenderWithStateChildComponent *c2;
  auto const componentFactory = ^{
    c1 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    c2 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build first component generation:
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate state update on c1
  NSNumber *newState1 = @10;
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c1.treeNode.scopeHandle].push_back(^(id){ return newState1; });

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, CKReflowTriggerNone);
  // Verify `c2.childComponent` gets the correct new state
  XCTAssertEqual(newState1, c1.state);
  // Verify c2 was reused
  XCTAssertTrue(c2.didReuseComponent);
  // Verify c1 wasn't reused
  XCTAssertFalse(c1.didReuseComponent);

  // Simulate state update on c2
  NSNumber *newState2 = @20;
  CKComponentStateUpdateMap stateUpdates2;
  stateUpdates2[c2.treeNode.scopeHandle].push_back(^(id){ return newState2;});

  auto const buildTrigger3 = CKBuildComponentTrigger(buildResults2.scopeRoot, stateUpdates2, NO, NO);
  auto const buildResults3 = CKBuildComponent(buildResults2.scopeRoot, stateUpdates2, componentFactory, buildTrigger3, CKReflowTriggerNone);
  // Verify `c2.childComponent` gets the correct new state
  XCTAssertEqual(newState2, c2.state);
  // Verify c1 was reused
  XCTAssertTrue(c1.didReuseComponent);
  // Verify c2 wasn't reused
  XCTAssertFalse(c2.didReuseComponent);
  // Verify `c1.childComponent` preserves its state during component reuse
  XCTAssertEqual(newState1, c1.state);

  // Simulate state update on c1
  NSNumber *newState3 = @30;
  CKComponentStateUpdateMap stateUpdates3;
  stateUpdates3[c1.treeNode.scopeHandle].push_back(^(id){ return newState3;});

  auto const buildTrigger4 = CKBuildComponentTrigger(buildResults3.scopeRoot, stateUpdates3, NO, NO);
  auto const buildResults4 = CKBuildComponent(buildResults3.scopeRoot, stateUpdates3, componentFactory, buildTrigger4, CKReflowTriggerNone);
  // Verify `c1.childComponent` gets the correct new state
  XCTAssertEqual(newState3, c1.state);
  // Verify c2 was reused
  XCTAssertTrue(c2.didReuseComponent);
  // Verify c1 wasn't reused
  XCTAssertFalse(c1.didReuseComponent);
  // Verify `c2.childComponent` preserves its state during component reuse
  XCTAssertEqual(newState2, c2.state);
}

- (void)test_nodeToParentLinksAfterReuse
{
  // Build new tree with siblings `CKTestRenderWithNonRenderWithStateChildComponent` components.
  // Each `CKTestRenderWithNonRenderWithStateChildComponent` has non-render component with state.
  __block CKTestRenderWithNonRenderWithStateChildComponent *c1;
  __block CKTestRenderWithNonRenderWithStateChildComponent *c2;
  __block CKTestLayoutComponent *root;
  auto const componentFactory = ^{
    c1 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    c2 = [CKTestRenderWithNonRenderWithStateChildComponent new];
    root = [CKTestLayoutComponent newWithChildren:{c1, c2}];
    return root;
  };

  // Build first component generation:
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Simulate state update on c1
  NSNumber *newState1 = @10;
  CKComponentStateUpdateMap stateUpdates;
  stateUpdates[c1.treeNode.scopeHandle].push_back(^(id){ return newState1; });

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory, buildTrigger2, CKReflowTriggerNone);
  // Verify `c2.childComponent` gets the correct new state
  XCTAssertEqual(newState1, c1.state);
  // Verify c2 was reused
  XCTAssertTrue(c2.didReuseComponent);
  // Verify c1 wasn't reused
  XCTAssertFalse(c1.didReuseComponent);

  NSMutableSet *components = [NSMutableSet setWithArray:@[c1, c2, c1.childComponent, c2.childComponent]];
  NSMutableSet *componentsFromParentNodeMap = [NSMutableSet set];

  // Add all the components from c1.childComponent to the root
  id<CKComponentProtocol> component = c1.childComponent;
  while (component) {
    [componentsFromParentNodeMap addObject:component];
    auto const parent = [buildResults2.scopeRoot rootNode].parentForNodeIdentifier(component.treeNode.nodeIdentifier);
    component = parent.component;
  }

  // Add all the components from c2.childComponent to the root
  id<CKComponentProtocol> component2 = c2.childComponent;
  while (component2) {
    [componentsFromParentNodeMap addObject:component2];
    auto const parent = [buildResults2.scopeRoot rootNode].parentForNodeIdentifier(component2.treeNode.nodeIdentifier);
    component2 = parent.component;
  }

  // Verify we got the same components from the node -> parent map as expected.
  XCTAssertTrue([components isEqualToSet:componentsFromParentNodeMap]);
}

- (void)test_componentReuseWhenReorderScopedComponents
{
  // Build new tree with siblings `CKCompositeComponentWithScope components
  // tha thas `CKTestRenderWithNonRenderWithStateChildComponent` children.
  // Each `CKTestRenderWithNonRenderWithStateChildComponent` has non-render component with state.
  __block CKCompositeComponentWithScope *c1;
  __block CKCompositeComponentWithScope *c2;
  __block CKCompositeComponentWithScope *c3;
  auto const componentFactory = ^{
    c1 = [CKCompositeComponentWithScope newWithComponentProvider:^CKComponent *{
      return [CKTestRenderWithNonRenderWithStateChildComponent new];
    } scopeIdentifier:@1];
    c2 = [CKCompositeComponentWithScope newWithComponentProvider:^CKComponent *{
      return [CKTestRenderWithNonRenderWithStateChildComponent new];
    } scopeIdentifier:@2];
    return [CKTestLayoutComponent newWithChildren:{c1, c2}];
  };

  // Build first component generation:
  auto const scopeRoot = CKComponentScopeRootWithPredicates(nil, nil, {}, {});
  auto const buildTrigger = CKBuildComponentTrigger(scopeRoot, {}, NO, NO);
  auto const buildResults = CKBuildComponent(scopeRoot, {}, componentFactory, buildTrigger, CKReflowTriggerNone);

  // Insert new child c3 before c1
  auto const componentFactory2 = ^{
    c3 = [CKCompositeComponentWithScope newWithComponentProvider:^CKComponent *{
      return [CKTestRenderWithNonRenderWithStateChildComponent new];
    } scopeIdentifier:@3];
    c1 = [CKCompositeComponentWithScope newWithComponentProvider:^CKComponent *{
      return [CKTestRenderWithNonRenderWithStateChildComponent new];
    } scopeIdentifier:@1];
    c2 = [CKCompositeComponentWithScope newWithComponentProvider:^CKComponent *{
      return [CKTestRenderWithNonRenderWithStateChildComponent new];
    } scopeIdentifier:@2];
    return [CKTestLayoutComponent newWithChildren:{c3, c1, c2}];
  };

  // Simulate state update on c1.child.childComponent
  NSNumber *newState1 = @10;
  CKComponentStateUpdateMap stateUpdates;
  auto c1Child = (CKTestRenderWithNonRenderWithStateChildComponent *)c1.childComponent;
  stateUpdates[c1Child.childComponent.treeNode.scopeHandle].push_back(^(id){ return newState1; });

  auto const buildTrigger2 = CKBuildComponentTrigger(buildResults.scopeRoot, stateUpdates, NO, NO);
  auto const buildResults2 = CKBuildComponent(buildResults.scopeRoot, stateUpdates, componentFactory2, buildTrigger2, CKReflowTriggerNone);

  // Accessing the updated children
  c1Child = (CKTestRenderWithNonRenderWithStateChildComponent *)c1.childComponent;
  auto c2Child = (CKTestRenderWithNonRenderWithStateChildComponent *)c2.childComponent;

  // Verify `c2.child.childComponent` gets the correct new state
  XCTAssertEqual(newState1, c1Child.childComponent.state);
  // Verify c2.child was reused
  XCTAssertTrue(c2Child.didReuseComponent);
  // Verify c1.child wasn't reused
  XCTAssertFalse(c1Child.didReuseComponent);
}

@end
