/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.stateupdates.BaseIncrementStateCaller
import com.facebook.litho.stateupdates.ComponentWithMeasureCallAndState
import com.facebook.litho.stateupdates.ComponentWithMeasureCallAndStateSpec
import com.facebook.litho.stateupdates.ComponentWithSizeAndMeasureCallAndState
import com.facebook.litho.stateupdates.ComponentWithSizeAndMeasureCallAndStateSpec
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.MountSpecPureRenderLifecycleTester
import java.util.ArrayList
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

/**
 * IMPORTANT INFORMATION! PLEASE READ!
 *
 * In this test class, all tests will test for complex component hierarchies of the following form:
 * Root --> Mid --> Bot Where Root, Mid and Bot can either be an OnCreateLayout (OCL) component, or
 * an OnCreateLayoutWithSizeSpec (OCLWSS) component.
 *
 * All 3 components hold an int state value, initialized as zero. Each component will render a Text
 * displaying a prefix ("root", "mid", "bot") + the value of the state value as their 1st child.
 * Root will accept Mid as a @Prop component, will call Component.measure on it, and use it as it's
 * 2nd child. Mid will accept Bot as a @Prop component, will call Component.measure on it, and use
 * it as it's 2nd child. Bot will accept a MountSpec component and use it as it's 2nd child.
 *
 * All tests will follow the same flow:
 * 1. Build such a hierarchy, each test will have a different variation of OCL / OCLWSS comps
 * 2. Ensure lifecycle steps for each comp + the MountSpec are as expected
 * 3. Ensure all texts are displaying the correct state
 * 4. Update state on the root comp, and repeat #2 and #3
 * 5. Update state on the mid comp, and repeat #2 and #3
 * 6. Update state on the bot comp, and repeat #2 and #3
 *
 * All tests use a common main test method ("testSpecificSetup") that is accessed via a Builder
 * class. Since all tests use the same flow, they all funnel into this method with different
 * component hierarchy setup and different expected LifecycleSteps for each component / phase.
 *
 * The name of each test will indicate the hierarchy being tested. For example, a test named
 * "test_OCLWSS_OCL_OCL" will hold the hierarchy of: Root as OCLWSS, Mid as OCL, Bot as OCL
 */
@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class NestedTreeResolutionWithStateTest {

  @JvmField @Rule val legacyLithoViewRule: LegacyLithoViewRule = LegacyLithoViewRule()

  /**
   * Tests OCL_OCL_OCL hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCL_OCL_OCL() {
    // Mid and bot steps will be the same on preUpdate
    val expectedStepsForMidAndBotPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_ATTACHED)

    // Mid and bot steps will be the same on update 1
    val expectedStepsForMidAndBotUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)

    // Testing OCL -> OCL -> OCL
    TestHierarchyBuilder.create(this, true, true, true)
        .setRootStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_ATTACHED))
        .setMidStepsPreUpdate(expectedStepsForMidAndBotPreUpdate)
        .setBotStepsPreUpdate(expectedStepsForMidAndBotPreUpdate)
        .setMountSpecStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_CREATE_MOUNT_CONTENT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .setRootStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT))
        .setMidStepsUpdate1(expectedStepsForMidAndBotUpdate1)
        .setBotStepsUpdate1(expectedStepsForMidAndBotUpdate1)
        .setMountSpecStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.SHOULD_UPDATE,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND)) // TODO (T133075661) expected empty, no OCTP
        .setRootStepsUpdate2(arrayOf(LifecycleStep.ON_CREATE_TREE_PROP))
        .setMidStepsUpdate2(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED))
        .setBotStepsUpdate2(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED))
        .setMountSpecStepsUpdate2(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .setRootStepsUpdate3(arrayOf(LifecycleStep.ON_CREATE_TREE_PROP))
        .setMidStepsUpdate3(arrayOf()) // empty
        .setBotStepsUpdate3(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED))
        .setMountSpecStepsUpdate3(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.SHOULD_UPDATE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .test()
  }

  /**
   * Tests OCLWSS_OCL_OCL hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCLWSS_OCL_OCL() {
    val rootStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)

    // Mid and bot steps will be the same pre update
    val expectedStepsForMidAndBotPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_ATTACHED)

    // Mid and bot steps will be the same for all updates
    val expectedStepsForMidAndBotAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)

    // Root steps will be the same for all updates
    val expectedStepsForRootAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // MountSpec steps will be the same for all updates
    val expectedStepsForMountSpecAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCLWSS -> OCL -> OCL
    TestHierarchyBuilder.create(this, false, true, true)
        .setRootStepsPreUpdate(rootStepsPreUpdate)
        .setMidStepsPreUpdate(expectedStepsForMidAndBotPreUpdate)
        .setBotStepsPreUpdate(expectedStepsForMidAndBotPreUpdate)
        .setMountSpecStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_CREATE_MOUNT_CONTENT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .setRootStepsUpdate1(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate1(expectedStepsForMidAndBotAllUpdates)
        .setBotStepsUpdate1(expectedStepsForMidAndBotAllUpdates)
        .setMountSpecStepsUpdate1(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate2(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate2(expectedStepsForMidAndBotAllUpdates)
        .setBotStepsUpdate2(expectedStepsForMidAndBotAllUpdates)
        .setMountSpecStepsUpdate2(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate3(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate3(expectedStepsForMidAndBotAllUpdates)
        .setBotStepsUpdate3(expectedStepsForMidAndBotAllUpdates)
        .setMountSpecStepsUpdate3(expectedStepsForMountSpecAllUpdates)
        .test()
  }

  /**
   * Tests OCL_OCLWSS_OCL hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCL_OCLWSS_OCL() {
    val midStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_ATTACHED)
    val mountSpecStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.ON_ATTACHED,
            LifecycleStep.ON_CREATE_MOUNT_CONTENT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)
    val midStepsForUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val botStepsForUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)
    val mountSpecStepsForUpdate1 =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)
    val rootStepsForUpdate2And3 = arrayOf<LifecycleStep?>(LifecycleStep.ON_CREATE_TREE_PROP)
    val midStepsForUpdate2And3 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val botStepsForUpdate2And3 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)
    val mountSpecStepsForUpdate2And3 =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCL -> OCLWSS -> OCL
    TestHierarchyBuilder.create(this, true, false, true)
        .setRootStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_ATTACHED))
        .setMidStepsPreUpdate(midStepsPreUpdate)
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(mountSpecStepsPreUpdate)
        .setRootStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT))
        .setMidStepsUpdate1(midStepsForUpdate1)
        .setBotStepsUpdate1(botStepsForUpdate1)
        .setMountSpecStepsUpdate1(mountSpecStepsForUpdate1)
        .setRootStepsUpdate2(rootStepsForUpdate2And3)
        .setMidStepsUpdate2(midStepsForUpdate2And3)
        .setBotStepsUpdate2(botStepsForUpdate2And3)
        .setMountSpecStepsUpdate2(mountSpecStepsForUpdate2And3)
        .setRootStepsUpdate3(rootStepsForUpdate2And3)
        .setMidStepsUpdate3(midStepsForUpdate2And3)
        .setBotStepsUpdate3(botStepsForUpdate2And3)
        .setMountSpecStepsUpdate3(mountSpecStepsForUpdate2And3)
        .test()
  }

  /**
   * Tests OCL_OCL_OCLWSS hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCL_OCL_OCLWSS() {
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsForUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val botStepsForUpdate2 =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_DETACHED,
            LifecycleStep.ON_ATTACHED)

    // Testing OCL -> OCL -> OCLWSS
    TestHierarchyBuilder.create(this, true, true, false)
        .setRootStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_ATTACHED))
        .setMidStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_ATTACHED))
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_CREATE_MOUNT_CONTENT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .setRootStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT))
        .setMidStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_CREATE_TREE_PROP))
        .setBotStepsUpdate1(botStepsForUpdate1)
        .setMountSpecStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.SHOULD_UPDATE,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND)) // TODO (T133075661) expected empty, no OCTP
        .setRootStepsUpdate2(arrayOf(LifecycleStep.ON_CREATE_TREE_PROP))
        .setMidStepsUpdate2(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED))
        .setBotStepsUpdate2(botStepsForUpdate2)
        .setMountSpecStepsUpdate2(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .setRootStepsUpdate3(arrayOf(LifecycleStep.ON_CREATE_TREE_PROP))
        .setMidStepsUpdate3(arrayOf()) // empty
        .setBotStepsUpdate3(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED))
        .setMountSpecStepsUpdate3(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_PREPARE,
                LifecycleStep.SHOULD_UPDATE,
                LifecycleStep.ON_MEASURE,
                LifecycleStep.ON_BOUNDS_DEFINED,
                LifecycleStep.ON_DETACHED,
                LifecycleStep.ON_ATTACHED,
                LifecycleStep.ON_UNBIND,
                LifecycleStep.ON_UNMOUNT,
                LifecycleStep.ON_MOUNT,
                LifecycleStep.ON_BIND))
        .test()
  }

  /**
   * Tests OCLWSS_OCLWSS_OCL hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCLWSS_OCLWSS_OCL() {
    val rootStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val midStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_ATTACHED)
    val mountSpecStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.ON_ATTACHED,
            LifecycleStep.ON_CREATE_MOUNT_CONTENT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Root steps will be the same for all updates
    val expectedStepsForRootAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // Mid steps will be the same for all updates
    val expectedStepsForMidAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // Bot steps will be the same for all updates
    val expectedStepsForBotAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)

    // MountSpec steps will be the same for all updates
    val expectedStepsForMountSpecAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCLWSS -> OCLWSS -> OCL
    TestHierarchyBuilder.create(this, false, false, true)
        .setRootStepsPreUpdate(rootStepsPreUpdate)
        .setMidStepsPreUpdate(midStepsPreUpdate)
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(mountSpecStepsPreUpdate)
        .setRootStepsUpdate1(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate1(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate1(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate1(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate2(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate2(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate2(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate2(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate3(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate3(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate3(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate3(expectedStepsForMountSpecAllUpdates)
        .test()
  }

  /**
   * Tests OCLWSS_OCL_OCLWSS hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCLWSS_OCL_OCLWSS() {
    val rootStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val mountSpecStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.ON_ATTACHED,
            LifecycleStep.ON_CREATE_MOUNT_CONTENT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Root steps will be the same for all updates
    val expectedStepsForRootAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // Mid steps will be the same for all updates
    val expectedStepsForMidAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT,
            LifecycleStep.ON_CREATE_TREE_PROP)

    // Bot steps will be the same for all updates
    val expectedStepsForBotAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // MountSpec steps will be the same for all updates
    val expectedStepsForMountSpecAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCLWSS -> OCL -> OCLWSS
    TestHierarchyBuilder.create(this, false, true, false)
        .setRootStepsPreUpdate(rootStepsPreUpdate)
        .setMidStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_ATTACHED))
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(mountSpecStepsPreUpdate)
        .setRootStepsUpdate1(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate1(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate1(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate1(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate2(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate2(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate2(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate2(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate3(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate3(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate3(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate3(expectedStepsForMountSpecAllUpdates)
        .test()
  }

  /**
   * Tests OCL_OCLWSS_OCLWSS hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCL_OCLWSS_OCLWSS() {
    val midStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val mountSpecStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.ON_ATTACHED,
            LifecycleStep.ON_CREATE_MOUNT_CONTENT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)
    val midStepsForUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val botStepsForUpdate1 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val mountSpecStepsForUpdate1 =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)
    val rootStepsForUpdate2And3 = arrayOf<LifecycleStep?>(LifecycleStep.ON_CREATE_TREE_PROP)
    val midStepsForUpdate2And3 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val botStepsForUpdate2And3 =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
    val mountSpecStepsForUpdate2And3 =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCL -> OCLWSS -> OCLWSS
    TestHierarchyBuilder.create(this, true, false, false)
        .setRootStepsPreUpdate(
            arrayOf(
                LifecycleStep.ON_CREATE_INITIAL_STATE,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT,
                LifecycleStep.ON_ATTACHED))
        .setMidStepsPreUpdate(midStepsPreUpdate)
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(mountSpecStepsPreUpdate)
        .setRootStepsUpdate1(
            arrayOf(
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CREATE_TREE_PROP,
                LifecycleStep.ON_CALCULATE_CACHED_VALUE,
                LifecycleStep.ON_CREATE_LAYOUT))
        .setMidStepsUpdate1(midStepsForUpdate1)
        .setBotStepsUpdate1(botStepsForUpdate1)
        .setMountSpecStepsUpdate1(mountSpecStepsForUpdate1)
        .setRootStepsUpdate2(rootStepsForUpdate2And3)
        .setMidStepsUpdate2(midStepsForUpdate2And3)
        .setBotStepsUpdate2(botStepsForUpdate2And3)
        .setMountSpecStepsUpdate2(mountSpecStepsForUpdate2And3)
        .setRootStepsUpdate3(rootStepsForUpdate2And3)
        .setMidStepsUpdate3(midStepsForUpdate2And3)
        .setBotStepsUpdate3(botStepsForUpdate2And3)
        .setMountSpecStepsUpdate3(mountSpecStepsForUpdate2And3)
        .test()
  }

  /**
   * Tests OCLWSS_OCLWSS_OCLWSS hierarchy.
   *
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  @Test
  fun test_OCLWSS_OCLWSS_OCLWSS() {
    val rootStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val midStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val botStepsPreUpdate =
        arrayOf(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_ATTACHED)
    val mountSpecStepsPreUpdate =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.ON_ATTACHED,
            LifecycleStep.ON_CREATE_MOUNT_CONTENT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Root steps will be the same for all updates
    val expectedStepsForRootAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // Mid steps will be the same for all updates
    val expectedStepsForMidAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // Bot steps will be the same for all updates
    val expectedStepsForBotAllUpdates =
        arrayOf(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_CALCULATE_CACHED_VALUE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)

    // MountSpec steps will be the same for all updates
    val expectedStepsForMountSpecAllUpdates =
        arrayOf<LifecycleStep?>(
            LifecycleStep.ON_CREATE_TREE_PROP,
            LifecycleStep.ON_PREPARE,
            LifecycleStep.ON_MEASURE,
            LifecycleStep.ON_BOUNDS_DEFINED,
            LifecycleStep.SHOULD_UPDATE,
            LifecycleStep.ON_UNBIND,
            LifecycleStep.ON_UNMOUNT,
            LifecycleStep.ON_MOUNT,
            LifecycleStep.ON_BIND)

    // Testing OCLWSS -> OCLWSS -> OCLWSS
    TestHierarchyBuilder.create(this, false, false, false)
        .setRootStepsPreUpdate(rootStepsPreUpdate)
        .setMidStepsPreUpdate(midStepsPreUpdate)
        .setBotStepsPreUpdate(botStepsPreUpdate)
        .setMountSpecStepsPreUpdate(mountSpecStepsPreUpdate)
        .setRootStepsUpdate1(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate1(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate1(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate1(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate2(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate2(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate2(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate2(expectedStepsForMountSpecAllUpdates)
        .setRootStepsUpdate3(expectedStepsForRootAllUpdates)
        .setMidStepsUpdate3(expectedStepsForMidAllUpdates)
        .setBotStepsUpdate3(expectedStepsForBotAllUpdates)
        .setMountSpecStepsUpdate3(expectedStepsForMountSpecAllUpdates)
        .test()
  }

  /**
   * Consolidated method to test all cases in this test file. Called from the Builder class.
   *
   * @param isRootOCL True if the Root is OnCreateLayout. False for OnCreateLayoutWithSizeSpec
   * @param isMidOCL True if the Mid is OnCreateLayout. False for OnCreateLayoutWithSizeSpec
   * @param isBotOCL True if the Bot is OnCreateLayout. False for OnCreateLayoutWithSizeSpec
   * @param rootStepsPreStateUpdate Expected LifecycleSteps for root, pre-update
   * @param midStepsPreStateUpdate Expected LifecycleSteps for mid, pre-update
   * @param botStepsPreStateUpdate Expected LifecycleSteps for bot, pre-update
   * @param mountSpecStepsPreStateUpdate Expected LifecycleSteps for mount-spec, pre-update
   * @param rootStepsStateUpdate1 Expected LifecycleSteps for root, post-update-1
   * @param midStepsStateUpdate1 Expected LifecycleSteps for mid, post-update-1
   * @param botStepsStateUpdate1 Expected LifecycleSteps for bot, post-update-1
   * @param mountSpecStateUpdate1 Expected LifecycleSteps for mount-spec, post-update-1
   * @param rootStepsStateUpdate2 Expected LifecycleSteps for root, post-update-2
   * @param midStepsStateUpdate2 Expected LifecycleSteps for mid, post-update-2
   * @param botStepsStateUpdate2 Expected LifecycleSteps for bot, post-update-2
   * @param mountSpecStateUpdate2 Expected LifecycleSteps for mount-spec, post-update-2
   * @param rootStepsStateUpdate3 Expected LifecycleSteps for root, post-update-3
   * @param midStepsStateUpdate3 Expected LifecycleSteps for mid, post-update-3
   * @param botStepsStateUpdate3 Expected LifecycleSteps for bot, post-update-3
   * @param mountSpecStateUpdate3 Expected LifecycleSteps for mount-spec, post-update-3
   * @see NestedTreeResolutionWithStateTest.TestHierarchyBuilder
   * @see NestedTreeResolutionWithStateTest class declaration documentation for additional details.
   */
  private fun testSpecificSetup(
      isRootOCL: Boolean,
      isMidOCL: Boolean,
      isBotOCL: Boolean,
      rootStepsPreStateUpdate: Array<LifecycleStep?>,
      midStepsPreStateUpdate: Array<LifecycleStep>,
      botStepsPreStateUpdate: Array<LifecycleStep>,
      mountSpecStepsPreStateUpdate: Array<LifecycleStep?>,
      rootStepsStateUpdate1: Array<LifecycleStep?>,
      midStepsStateUpdate1: Array<LifecycleStep>,
      botStepsStateUpdate1: Array<LifecycleStep>,
      mountSpecStateUpdate1: Array<LifecycleStep?>,
      rootStepsStateUpdate2: Array<LifecycleStep?>,
      midStepsStateUpdate2: Array<LifecycleStep>,
      botStepsStateUpdate2: Array<LifecycleStep>,
      mountSpecStateUpdate2: Array<LifecycleStep?>,
      rootStepsStateUpdate3: Array<LifecycleStep?>,
      midStepsStateUpdate3: Array<LifecycleStep>,
      botStepsStateUpdate3: Array<LifecycleStep>,
      mountSpecStateUpdate3: Array<LifecycleStep?>
  ) {
    val c = legacyLithoViewRule.context
    val widthSpec = exactly(500)
    val heightSpec = exactly(500)

    // Generate a component + state-update-callers for requested setup.
    val holder =
        createComponentHierarchySetup(c, widthSpec, heightSpec, isRootOCL, isMidOCL, isBotOCL)

    // Set the root and layout
    legacyLithoViewRule.setRoot(holder.component)
    legacyLithoViewRule.setSizeSpecs(widthSpec, heightSpec)
    legacyLithoViewRule.attachToWindow().measure().layout()

    // Ensure the root node is not null. We'll need this to extract scoped contexts to simulate
    // state updates.
    assertThat(legacyLithoViewRule.currentRootNode).isNotNull

    // Ensure the lifecycle steps are as expected before any state updates.
    var rootSteps = LifecycleStep.getSteps(holder.rootLayoutSpecSteps)
    var midSteps = LifecycleStep.getSteps(holder.midLayoutSpecSteps)
    var botSteps = LifecycleStep.getSteps(holder.botLayoutSpecSteps)
    var mountableSteps = holder.mountableLifecycleTracker.steps
    assertThat(rootSteps)
        .describedAs("Root steps pre update")
        .containsExactly(*rootStepsPreStateUpdate)
    assertThat(midSteps)
        .describedAs("Mid steps pre update")
        .containsExactly(*midStepsPreStateUpdate)
    assertThat(botSteps)
        .describedAs("Bot steps pre update")
        .containsExactly(*botStepsPreStateUpdate)
    assertThat(mountableSteps)
        .describedAs("MountSpec steps pre update")
        .containsExactly(*mountSpecStepsPreStateUpdate)

    // Ensure all texts showing initial states are as expected
    assertThat(legacyLithoViewRule.findViewWithText("root 0")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("mid 0")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("bot 0")).isNotNull

    // Test state update 1/3
    // Reset the lifecycle steps
    holder.clearAllSteps()

    // Extract the root component's context to simulate state updates
    val rootScopedContext = rootComponentContext

    // Simulate the state update for the root.
    holder.rootStateCaller.increment(rootScopedContext)

    // Ensure lifecycle steps are as expected after the state update
    rootSteps = LifecycleStep.getSteps(holder.rootLayoutSpecSteps)
    midSteps = LifecycleStep.getSteps(holder.midLayoutSpecSteps)
    botSteps = LifecycleStep.getSteps(holder.botLayoutSpecSteps)
    mountableSteps = holder.mountableLifecycleTracker.steps
    assertThat(rootSteps)
        .describedAs("Root steps after update 1")
        .containsExactly(*rootStepsStateUpdate1)
    assertThat(midSteps)
        .describedAs("Mid steps after update 1")
        .containsExactly(*midStepsStateUpdate1)
    assertThat(botSteps)
        .describedAs("Bot steps after update 1")
        .containsExactly(*botStepsStateUpdate1)
    assertThat(mountableSteps)
        .describedAs("MountSpec steps after update 1")
        .containsExactly(*mountSpecStateUpdate1)

    // Ensure the texts properly reflect the correct values after the state update
    assertThat(legacyLithoViewRule.findViewWithText("root 1")).isNotNull // Updated!
    assertThat(legacyLithoViewRule.findViewWithText("mid 0")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("bot 0")).isNotNull

    // Test state update 2/3
    // Reset the lifecycle steps
    holder.clearAllSteps()

    // Extract the mid component's context to simulate state updates
    val midScopedContext = midComponentContext

    // Simulate the state update for the mid component.
    holder.midStateCaller.increment(midScopedContext)

    // Ensure lifecycle steps are as expected after the state update
    rootSteps = LifecycleStep.getSteps(holder.rootLayoutSpecSteps)
    midSteps = LifecycleStep.getSteps(holder.midLayoutSpecSteps)
    botSteps = LifecycleStep.getSteps(holder.botLayoutSpecSteps)
    mountableSteps = holder.mountableLifecycleTracker.steps
    assertThat(rootSteps)
        .describedAs("Root steps after update 2")
        .containsExactly(*rootStepsStateUpdate2)
    assertThat(midSteps)
        .describedAs("Mid steps after update 2")
        .containsExactly(*midStepsStateUpdate2)
    assertThat(botSteps)
        .describedAs("Bot steps after update 2")
        .containsExactly(*botStepsStateUpdate2)
    assertThat(mountableSteps)
        .describedAs("MountSpec steps after update 2")
        .containsExactly(*mountSpecStateUpdate2)

    // Ensure the texts properly reflect the correct values after the state update
    assertThat(legacyLithoViewRule.findViewWithText("root 1")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("mid 1")).isNotNull // Updated!
    assertThat(legacyLithoViewRule.findViewWithText("bot 0")).isNotNull

    // Test state update 3/3
    // Reset the lifecycle steps
    holder.clearAllSteps()

    // Extract the bottom component's context to simulate state updates
    val botScopedContext = botComponentContext

    // Simulate the state update for the bottom component.
    holder.botStateCaller.increment(botScopedContext)

    // Ensure lifecycle steps are as expected after the state update
    rootSteps = LifecycleStep.getSteps(holder.rootLayoutSpecSteps)
    midSteps = LifecycleStep.getSteps(holder.midLayoutSpecSteps)
    botSteps = LifecycleStep.getSteps(holder.botLayoutSpecSteps)
    mountableSteps = holder.mountableLifecycleTracker.steps
    assertThat(rootSteps)
        .describedAs("Root steps after update 3")
        .containsExactly(*rootStepsStateUpdate3)
    assertThat(midSteps)
        .describedAs("Mid steps after update 3")
        .containsExactly(*midStepsStateUpdate3)
    assertThat(botSteps)
        .describedAs("Bot steps after update 3")
        .containsExactly(*botStepsStateUpdate3)
    assertThat(mountableSteps)
        .describedAs("MountSpec steps after update 3")
        .containsExactly(*mountSpecStateUpdate3)

    // Ensure the texts properly reflect the correct values after the state update
    assertThat(legacyLithoViewRule.findViewWithText("root 1")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("mid 1")).isNotNull
    assertThat(legacyLithoViewRule.findViewWithText("bot 1")).isNotNull // Updated!
  }

  /** Returns the ComponentContext to be used to trigger state-updates on the Root. */
  private val rootComponentContext: ComponentContext
    get() {
      assertThat(legacyLithoViewRule.currentRootNode).isNotNull
      return requireNotNull(
          getCorrectLayoutResult(legacyLithoViewRule.currentRootNode)
              ?.mNode
              ?.getComponentContextAt(1))
    }

  /** Returns the ComponentContext to be used to trigger state-updates on the Mid */
  private val midComponentContext: ComponentContext
    get() {
      val rootLayoutResult = legacyLithoViewRule.currentRootNode
      assertThat(rootLayoutResult).isNotNull
      return requireNotNull(
          if (rootLayoutResult is NestedTreeHolderResult) {
            val nestedResult = getCorrectLayoutResult(rootLayoutResult)
            val nestedMidResult = getCorrectLayoutResult(nestedResult?.getChildAt(1))
            assertThat(nestedMidResult).isNotNull
            nestedMidResult?.mNode?.getComponentContextAt(1)
          } else {
            rootLayoutResult?.mNode?.getChildAt(1)?.getComponentContextAt(0)
          })
    }

  /** Returns the ComponentContext to be used to trigger state-updates on the Bot */
  private val botComponentContext: ComponentContext
    get() {
      val rootLayoutResult = legacyLithoViewRule.currentRootNode
      assertThat(rootLayoutResult).isNotNull
      return requireNotNull(
          if (rootLayoutResult is NestedTreeHolderResult) {
            val nestedResult = getCorrectLayoutResult(rootLayoutResult)
            val nestedMidResult = getCorrectLayoutResult(nestedResult?.getChildAt(1))
            assertThat(nestedMidResult).isNotNull
            val nestedBotResult = getCorrectLayoutResult(nestedMidResult?.getChildAt(1))
            getCorrectLayoutResult(nestedBotResult)?.mNode?.getComponentContextAt(1)
          } else {
            val midResult = rootLayoutResult?.getChildAt(1)
            if (midResult is NestedTreeHolderResult) {
              val nestedResult = getCorrectLayoutResult(midResult)
              val nestedBotResult = getCorrectLayoutResult(nestedResult?.getChildAt(1))
              getCorrectLayoutResult(nestedBotResult)?.mNode?.getComponentContextAt(1)
            } else {
              rootLayoutResult?.mNode?.getChildAt(1)?.getChildAt(1)?.getComponentContextAt(0)
            }
          })
    }

  /**
   * Holder class for a component hierarchy described above. Holds the root component, state update
   * callers for each 3 components, and step info arrays for each component including the leaf
   * MountSpec.
   */
  class StateUpdateComponentHolder(
      val component: Component,
      rootStateCaller: BaseIncrementStateCaller,
      midStateCaller: BaseIncrementStateCaller,
      botStateCaller: BaseIncrementStateCaller,
      rootLayoutSpecSteps: MutableList<StepInfo>,
      midLayoutSpecSteps: MutableList<StepInfo>,
      botLayoutSpecSteps: MutableList<StepInfo>,
      mountableLifecycleTracker: LifecycleTracker
  ) {
    val rootStateCaller: BaseIncrementStateCaller
    val midStateCaller: BaseIncrementStateCaller
    val botStateCaller: BaseIncrementStateCaller
    val rootLayoutSpecSteps: MutableList<StepInfo>
    val midLayoutSpecSteps: MutableList<StepInfo>
    val botLayoutSpecSteps: MutableList<StepInfo>
    val mountableLifecycleTracker: LifecycleTracker

    /** Resets all lifecycle steps for all components. */
    fun clearAllSteps() {
      rootLayoutSpecSteps.clear()
      midLayoutSpecSteps.clear()
      botLayoutSpecSteps.clear()
      mountableLifecycleTracker.reset()
    }

    init {
      this.rootStateCaller = rootStateCaller
      this.midStateCaller = midStateCaller
      this.botStateCaller = botStateCaller
      this.rootLayoutSpecSteps = rootLayoutSpecSteps
      this.midLayoutSpecSteps = midLayoutSpecSteps
      this.botLayoutSpecSteps = botLayoutSpecSteps
      this.mountableLifecycleTracker = mountableLifecycleTracker
    }
  }

  /** Builder class used to prepare and trigger each individual test in this file. */
  private class TestHierarchyBuilder {
    private var testRunner: NestedTreeResolutionWithStateTest? = null
    private var isRootOCL = false
    private var isMidOCL = false
    private var isBotOCL = false
    private lateinit var rootStepsPreStateUpdate: Array<LifecycleStep?>
    private lateinit var midStepsPreStateUpdate: Array<LifecycleStep>
    private lateinit var botStepsPreStateUpdate: Array<LifecycleStep>
    private lateinit var mountSpecStepsPreStateUpdate: Array<LifecycleStep?>
    private lateinit var rootStepsStateUpdate1: Array<LifecycleStep?>
    private lateinit var midStepsStateUpdate1: Array<LifecycleStep>
    private lateinit var botStepsStateUpdate1: Array<LifecycleStep>
    private lateinit var mountSpecStateUpdate1: Array<LifecycleStep?>
    private lateinit var rootStepsStateUpdate2: Array<LifecycleStep?>
    private lateinit var midStepsStateUpdate2: Array<LifecycleStep>
    private lateinit var botStepsStateUpdate2: Array<LifecycleStep>
    private lateinit var mountSpecStateUpdate2: Array<LifecycleStep?>
    private lateinit var rootStepsStateUpdate3: Array<LifecycleStep?>
    private lateinit var midStepsStateUpdate3: Array<LifecycleStep>
    private lateinit var botStepsStateUpdate3: Array<LifecycleStep>
    private lateinit var mountSpecStateUpdate3: Array<LifecycleStep?>

    fun setRootStepsPreUpdate(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      rootStepsPreStateUpdate = steps
      return this
    }

    fun setMidStepsPreUpdate(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      midStepsPreStateUpdate = steps
      return this
    }

    fun setBotStepsPreUpdate(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      botStepsPreStateUpdate = steps
      return this
    }

    fun setMountSpecStepsPreUpdate(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      mountSpecStepsPreStateUpdate = steps
      return this
    }

    fun setRootStepsUpdate1(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      rootStepsStateUpdate1 = steps
      return this
    }

    fun setMidStepsUpdate1(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      midStepsStateUpdate1 = steps
      return this
    }

    fun setBotStepsUpdate1(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      botStepsStateUpdate1 = steps
      return this
    }

    fun setMountSpecStepsUpdate1(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      mountSpecStateUpdate1 = steps
      return this
    }

    fun setRootStepsUpdate2(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      rootStepsStateUpdate2 = steps
      return this
    }

    fun setMidStepsUpdate2(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      midStepsStateUpdate2 = steps
      return this
    }

    fun setBotStepsUpdate2(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      botStepsStateUpdate2 = steps
      return this
    }

    fun setMountSpecStepsUpdate2(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      mountSpecStateUpdate2 = steps
      return this
    }

    fun setRootStepsUpdate3(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      rootStepsStateUpdate3 = steps
      return this
    }

    fun setMidStepsUpdate3(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      midStepsStateUpdate3 = steps
      return this
    }

    fun setBotStepsUpdate3(steps: Array<LifecycleStep>): TestHierarchyBuilder {
      botStepsStateUpdate3 = steps
      return this
    }

    fun setMountSpecStepsUpdate3(steps: Array<LifecycleStep?>): TestHierarchyBuilder {
      mountSpecStateUpdate3 = steps
      return this
    }

    fun test() {
      testRunner?.testSpecificSetup(
          isRootOCL,
          isMidOCL,
          isBotOCL,
          rootStepsPreStateUpdate,
          midStepsPreStateUpdate,
          botStepsPreStateUpdate,
          mountSpecStepsPreStateUpdate,
          rootStepsStateUpdate1,
          midStepsStateUpdate1,
          botStepsStateUpdate1,
          mountSpecStateUpdate1,
          rootStepsStateUpdate2,
          midStepsStateUpdate2,
          botStepsStateUpdate2,
          mountSpecStateUpdate2,
          rootStepsStateUpdate3,
          midStepsStateUpdate3,
          botStepsStateUpdate3,
          mountSpecStateUpdate3)
    }

    companion object {
      @JvmStatic
      fun create(
          testRunner: NestedTreeResolutionWithStateTest?,
          isRootOCL: Boolean,
          isMidOCL: Boolean,
          isBotOCL: Boolean
      ): TestHierarchyBuilder {
        val builder = TestHierarchyBuilder()
        builder.isRootOCL = isRootOCL
        builder.isMidOCL = isMidOCL
        builder.isBotOCL = isBotOCL
        builder.testRunner = testRunner
        return builder
      }
    }
  }

  companion object {
    /**
     * Generate a component hierarchy as described above.
     *
     * @param c the component context
     * @param widthSpec the width-spec to use in Component.measure calls
     * @param heightSpec the height-spec to use in Component.measure calls
     * @param isRootOCL true if the root should be OCL (OCLWSS otherwise)
     * @param isMidOCL true if the mid should be OCL (OCLWSS otherwise)
     * @param isBottomOCL true if the bot should be OCL (OCLWSS otherise)
     * @return a holder class containing the root component, state update callers for each comp, and
     *   lifecycle step arrays for each component including the MountSpec held by the bot comp.
     */
    private fun createComponentHierarchySetup(
        c: ComponentContext,
        widthSpec: Int,
        heightSpec: Int,
        isRootOCL: Boolean,
        isMidOCL: Boolean,
        isBottomOCL: Boolean
    ): StateUpdateComponentHolder {
      val rootStepsInfo: MutableList<StepInfo> = ArrayList<StepInfo>()
      val midStepsInfo: MutableList<StepInfo> = ArrayList<StepInfo>()
      val botStepsInfo: MutableList<StepInfo> = ArrayList<StepInfo>()
      val mountableLifecycleTracker = LifecycleTracker()
      val mountable =
          MountSpecPureRenderLifecycleTester.create(c)
              .lifecycleTracker(mountableLifecycleTracker)
              .build()
      val rootCaller: BaseIncrementStateCaller
      val midCaller: BaseIncrementStateCaller
      val bottomCaller: BaseIncrementStateCaller
      val rootComponent: Component
      val midComponent: Component
      val bottomComponent: Component

      // bottom comp will set half height to make room for text
      if (isBottomOCL) {
        bottomCaller = ComponentWithMeasureCallAndStateSpec.Caller()
        bottomComponent =
            ComponentWithMeasureCallAndState.create(c)
                .steps(botStepsInfo)
                .shouldCacheResult(true)
                .widthPx(SizeSpec.getSize(widthSpec) / 2)
                .heightPx(SizeSpec.getSize(heightSpec) / 2)
                .prefix("bot")
                .mountSpec(mountable)
                .build()
      } else {
        bottomCaller = ComponentWithSizeAndMeasureCallAndStateSpec.Caller()
        bottomComponent =
            ComponentWithSizeAndMeasureCallAndState.create(c)
                .steps(botStepsInfo)
                .shouldCacheResult(true)
                .widthPx(SizeSpec.getSize(widthSpec) / 2)
                .heightPx(SizeSpec.getSize(heightSpec) / 2)
                .prefix("bot")
                .mountSpec(mountable)
                .build()
      }
      if (isMidOCL) {
        midCaller = ComponentWithMeasureCallAndStateSpec.Caller()
        midComponent =
            ComponentWithMeasureCallAndState.create(c)
                .steps(midStepsInfo)
                .shouldCacheResult(true)
                .component(bottomComponent)
                .widthSpec(widthSpec)
                .heightSpec(heightSpec)
                .prefix("mid")
                .build()
      } else {
        midCaller = ComponentWithSizeAndMeasureCallAndStateSpec.Caller()
        midComponent =
            ComponentWithSizeAndMeasureCallAndState.create(c)
                .steps(midStepsInfo)
                .shouldCacheResult(true)
                .component(bottomComponent)
                .prefix("mid")
                .build()
      }
      if (isRootOCL) {
        rootCaller = ComponentWithMeasureCallAndStateSpec.Caller()
        rootComponent =
            ComponentWithMeasureCallAndState.create(c)
                .steps(rootStepsInfo)
                .shouldCacheResult(true)
                .component(midComponent)
                .widthSpec(widthSpec)
                .heightSpec(heightSpec)
                .prefix("root")
                .build()
      } else {
        rootCaller = ComponentWithSizeAndMeasureCallAndStateSpec.Caller()
        rootComponent =
            ComponentWithSizeAndMeasureCallAndState.create(c)
                .steps(rootStepsInfo)
                .shouldCacheResult(true)
                .component(midComponent)
                .prefix("root")
                .build()
      }
      return StateUpdateComponentHolder(
          rootComponent,
          rootCaller,
          midCaller,
          bottomCaller,
          rootStepsInfo,
          midStepsInfo,
          botStepsInfo,
          mountableLifecycleTracker)
    }

    /**
     * Extracts the Nested-Result from a NestedTreeHolderResult if the provided LithoLayoutResult is
     * of that type, otherwise simply returns the provided LithoLayoutResult.
     *
     * Used as a helper method to extract the correct ComponentContext for state-updates for Root,
     * Mid or Bot.
     */
    private fun getCorrectLayoutResult(from: LithoLayoutResult?): LithoLayoutResult? =
        if (from is NestedTreeHolderResult) {
          val nestedResult = from.nestedResult
          assertThat(nestedResult).isNotNull
          nestedResult
        } else {
          from
        }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.util.SparseArray
import com.facebook.litho.AccessibilityRole.AccessibilityRoleType
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class NodeInfoTest {

  private lateinit var nodeInfo: NodeInfo
  private lateinit var updatedNodeInfo: NodeInfo

  @Before
  fun setup() {
    nodeInfo = NodeInfo()
    updatedNodeInfo = NodeInfo()
  }

  @Test
  fun testClickHandler() {
    val clickHandler = EventHandler<ClickEvent>(null, 1)
    nodeInfo.clickHandler = clickHandler
    assertThat(clickHandler).isSameAs(nodeInfo.clickHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(clickHandler).isSameAs(updatedNodeInfo.clickHandler)
  }

  @Test
  fun testTouchHandler() {
    val touchHandler = EventHandler<TouchEvent>(null, 1)
    nodeInfo.touchHandler = touchHandler
    assertThat(touchHandler).isSameAs(nodeInfo.touchHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(touchHandler).isSameAs(updatedNodeInfo.touchHandler)
  }

  @Test
  fun testFocusChangeHandler() {
    val focusChangeHandler = EventHandler<FocusChangedEvent>(null, 1)
    nodeInfo.focusChangeHandler = focusChangeHandler
    assertThat(focusChangeHandler).isSameAs(nodeInfo.focusChangeHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(focusChangeHandler).isSameAs(updatedNodeInfo.focusChangeHandler)
  }

  @Test
  fun testInterceptTouchHandler() {
    val interceptTouchHandler = EventHandler<InterceptTouchEvent>(null, 1)
    nodeInfo.interceptTouchHandler = interceptTouchHandler
    assertThat(interceptTouchHandler).isSameAs(nodeInfo.interceptTouchHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(interceptTouchHandler).isSameAs(updatedNodeInfo.interceptTouchHandler)
  }

  @Test
  fun testAccessibilityHeadingTrue() {
    assertThat(nodeInfo.accessibilityHeadingState).isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_UNSET)
    nodeInfo.setAccessibilityHeading(true)
    assertThat(nodeInfo.accessibilityHeadingState)
        .isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_SET_TRUE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.accessibilityHeadingState)
        .isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_SET_TRUE)
  }

  @Test
  fun testAccessibilityHeadingFalse() {
    assertThat(nodeInfo.accessibilityHeadingState).isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_UNSET)
    nodeInfo.setAccessibilityHeading(false)
    assertThat(nodeInfo.accessibilityHeadingState)
        .isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_SET_FALSE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.accessibilityHeadingState)
        .isEqualTo(NodeInfo.ACCESSIBILITY_HEADING_SET_FALSE)
  }

  @Test
  fun testAccessibilityRole() {
    @AccessibilityRoleType val role = AccessibilityRole.BUTTON
    nodeInfo.accessibilityRole = role
    assertThat(role).isSameAs(nodeInfo.accessibilityRole)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(role).isSameAs(updatedNodeInfo.accessibilityRole)
  }

  @Test
  fun testAccessibilityRoleDescription() {
    val roleDescription = "Test Role Description"
    nodeInfo.accessibilityRoleDescription = roleDescription
    assertThat(roleDescription).isSameAs(nodeInfo.accessibilityRoleDescription)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(roleDescription).isSameAs(updatedNodeInfo.accessibilityRoleDescription)
  }

  @Test
  fun testDispatchPopulateAccessibilityEventHandler() {
    val handler = EventHandler<DispatchPopulateAccessibilityEventEvent>(null, 1)
    nodeInfo.dispatchPopulateAccessibilityEventHandler = handler
    assertThat(handler).isSameAs(nodeInfo.dispatchPopulateAccessibilityEventHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.dispatchPopulateAccessibilityEventHandler)
  }

  @Test
  fun testOnInitializeAccessibilityEventHandler() {
    val handler = EventHandler<OnInitializeAccessibilityEventEvent>(null, 1)
    nodeInfo.onInitializeAccessibilityEventHandler = handler
    assertThat(handler).isSameAs(nodeInfo.onInitializeAccessibilityEventHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.onInitializeAccessibilityEventHandler)
  }

  @Test
  fun testOnPopulateAccessibilityEventHandler() {
    val handler = EventHandler<OnPopulateAccessibilityEventEvent>(null, 1)
    nodeInfo.onPopulateAccessibilityEventHandler = handler
    assertThat(handler).isSameAs(nodeInfo.onPopulateAccessibilityEventHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.onPopulateAccessibilityEventHandler)
  }

  @Test
  fun testOnPopulateAccessibilityNodeHandler() {
    val handler = EventHandler<OnPopulateAccessibilityNodeEvent>(null, 1)
    nodeInfo.onPopulateAccessibilityNodeHandler = handler
    assertThat(handler).isSameAs(nodeInfo.onPopulateAccessibilityNodeHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.onPopulateAccessibilityNodeHandler)
  }

  @Test
  fun testOnInitializeAccessibilityNodeInfoHandler() {
    val handler = EventHandler<OnInitializeAccessibilityNodeInfoEvent>(null, 1)
    nodeInfo.onInitializeAccessibilityNodeInfoHandler = handler
    assertThat(handler).isSameAs(nodeInfo.onInitializeAccessibilityNodeInfoHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.onInitializeAccessibilityNodeInfoHandler)
  }

  @Test
  fun testOnRequestSendAccessibilityEventHandler() {
    val handler = EventHandler<OnRequestSendAccessibilityEventEvent>(null, 1)
    nodeInfo.onRequestSendAccessibilityEventHandler = handler
    assertThat(handler).isSameAs(nodeInfo.onRequestSendAccessibilityEventHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.onRequestSendAccessibilityEventHandler)
  }

  @Test
  fun testPerformAccessibilityActionHandler() {
    val handler = EventHandler<PerformAccessibilityActionEvent>(null, 1)
    nodeInfo.performAccessibilityActionHandler = handler
    assertThat(handler).isSameAs(nodeInfo.performAccessibilityActionHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.performAccessibilityActionHandler)
  }

  @Test
  fun testSendAccessibilityEventHandler() {
    val handler = EventHandler<SendAccessibilityEventEvent>(null, 1)
    nodeInfo.sendAccessibilityEventHandler = handler
    assertThat(handler).isSameAs(nodeInfo.sendAccessibilityEventHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.sendAccessibilityEventHandler)
  }

  @Test
  fun testSendAccessibilityEventUncheckedHandler() {
    val handler = EventHandler<SendAccessibilityEventUncheckedEvent>(null, 1)
    nodeInfo.sendAccessibilityEventUncheckedHandler = handler
    assertThat(handler).isSameAs(nodeInfo.sendAccessibilityEventUncheckedHandler)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(handler).isSameAs(updatedNodeInfo.sendAccessibilityEventUncheckedHandler)
  }

  @Test
  fun testClickHandlerFlag() {
    nodeInfo.clickHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_CLICK_HANDLER_IS_SET")
  }

  @Test
  fun testLongClickHandlerFlag() {
    nodeInfo.longClickHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_LONG_CLICK_HANDLER_IS_SET")
  }

  @Test
  fun testFocusChangeHandlerFlag() {
    nodeInfo.focusChangeHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_FOCUS_CHANGE_HANDLER_IS_SET")
  }

  @Test
  fun testContentDescriptionFlag() {
    nodeInfo.contentDescription = "test"
    testFlagIsSetThenClear(nodeInfo, "PFLAG_CONTENT_DESCRIPTION_IS_SET")
  }

  @Test
  fun testAccessibilityRoleFlag() {
    nodeInfo.accessibilityRole = AccessibilityRole.BUTTON
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ACCESSIBILITY_ROLE_IS_SET")
  }

  @Test
  fun testAccessibilityRoleDescriptionFlag() {
    nodeInfo.accessibilityRoleDescription = "Test Role Description"
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ACCESSIBILITY_ROLE_DESCRIPTION_IS_SET")
  }

  @Test
  fun testDispatchPopulateAccessibilityEventHandlerFlag() {
    nodeInfo.dispatchPopulateAccessibilityEventHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_DISPATCH_POPULATE_ACCESSIBILITY_EVENT_HANDLER_IS_SET")
  }

  @Test
  fun testOnInitializeAccessibilityEventHandlerFlag() {
    nodeInfo.onInitializeAccessibilityEventHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ON_INITIALIZE_ACCESSIBILITY_EVENT_HANDLER_IS_SET")
  }

  @Test
  fun testOnPopulateAccessibilityEventHandlerFlag() {
    nodeInfo.onPopulateAccessibilityEventHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ON_POPULATE_ACCESSIBILITY_EVENT_HANDLER_IS_SET")
  }

  @Test
  fun testOnPopulateAccessibilityNodeHandlerFlag() {
    nodeInfo.onPopulateAccessibilityNodeHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ON_POPULATE_ACCESSIBILITY_NODE_HANDLER_IS_SET")
  }

  @Test
  fun testOnInitializeAccessibilityNodeInfoHandlerFlag() {
    nodeInfo.onInitializeAccessibilityNodeInfoHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ON_INITIALIZE_ACCESSIBILITY_NODE_INFO_HANDLER_IS_SET")
  }

  @Test
  fun testOnRequestSendAccessibilityEventHandlerFlag() {
    nodeInfo.onRequestSendAccessibilityEventHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_ON_REQUEST_SEND_ACCESSIBILITY_EVENT_HANDLER_IS_SET")
  }

  @Test
  fun testPerformAccessibilityActionHandlerFlag() {
    nodeInfo.performAccessibilityActionHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_PERFORM_ACCESSIBILITY_ACTION_HANDLER_IS_SET")
  }

  @Test
  fun testSendAccessibilityEventHandlerFlag() {
    nodeInfo.sendAccessibilityEventHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_SEND_ACCESSIBILITY_EVENT_HANDLER_IS_SET")
  }

  @Test
  fun testSendAccessibilityEventUncheckedHandlerFlag() {
    nodeInfo.sendAccessibilityEventUncheckedHandler = EventHandler(null, 1)
    testFlagIsSetThenClear(nodeInfo, "PFLAG_SEND_ACCESSIBILITY_EVENT_UNCHECKED_HANDLER_IS_SET")
  }

  @Test
  fun testViewTagsFlag() {
    nodeInfo.viewTags = SparseArray()
    testFlagIsSetThenClear(nodeInfo, "PFLAG_VIEW_TAGS_IS_SET")
  }

  @Test
  fun testFocusableTrue() {
    assertThat(nodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_UNSET)
    nodeInfo.setFocusable(true)
    assertThat(nodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_SET_TRUE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_SET_TRUE)
  }

  @Test
  fun testFocusableFalse() {
    assertThat(nodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_UNSET)
    nodeInfo.setFocusable(false)
    assertThat(nodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_SET_FALSE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.focusState).isEqualTo(NodeInfo.FOCUS_SET_FALSE)
  }

  @Test
  fun testSelectedTrue() {
    assertThat(nodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_UNSET)
    nodeInfo.setSelected(true)
    assertThat(nodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_SET_TRUE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_SET_TRUE)
  }

  @Test
  fun testSelectedFalse() {
    assertThat(nodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_UNSET)
    nodeInfo.setSelected(false)
    assertThat(nodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_SET_FALSE)
    nodeInfo.copyInto(updatedNodeInfo)
    assertThat(updatedNodeInfo.selectedState).isEqualTo(NodeInfo.SELECTED_SET_FALSE)
  }

  @Test
  fun testEnabledTrue() {
    assertThat(nodeInfo.enabledState).isEqualTo(NodeInfo.ENABLED_UNSET)
    nodeInfo.setEnabled(true)
    assertThat(nodeInfo.enabledState).isEqualTo(NodeInfo.ENABLED_SET_TRUE)
  }

  @Test
  fun testEnabledFalse() {
    assertThat(nodeInfo.enabledState).isEqualTo(NodeInfo.ENABLED_UNSET)
    nodeInfo.setEnabled(false)
    assertThat(nodeInfo.enabledState).isEqualTo(NodeInfo.ENABLED_SET_FALSE)
  }

  companion object {
    private fun testFlagIsSetThenClear(nodeInfo: NodeInfo?, flagName: String) {
      assertThat(isFlagSet(nodeInfo, flagName)).isTrue
      clearFlag(nodeInfo, flagName)
      assertEmptyFlags(nodeInfo)
    }

    private fun isFlagSet(nodeInfo: NodeInfo?, flagName: String): Boolean {
      val flagPosition = Whitebox.getInternalState<Int>(NodeInfo::class.java, flagName)
      val flags = Whitebox.getInternalState<Int>(nodeInfo, "mPrivateFlags")
      return flags and flagPosition != 0
    }

    private fun clearFlag(nodeInfo: NodeInfo?, flagName: String) {
      val flagPosition = Whitebox.getInternalState<Int>(NodeInfo::class.java, flagName)
      var flags = Whitebox.getInternalState<Int>(nodeInfo, "mPrivateFlags")
      flags = flags and flagPosition.inv()
      Whitebox.setInternalState(nodeInfo, "mPrivateFlags", flags)
    }

    private fun assertEmptyFlags(nodeInfo: NodeInfo?) {
      assertThat(Whitebox.getInternalState<Any>(nodeInfo, "mPrivateFlags") as Int == 0).isTrue
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.NoOpEventHandler.Companion.getNoOpEventHandler
import com.facebook.litho.testing.testrunner.LithoTestRunner
import java.lang.RuntimeException
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class NoOpEventHandlerTest {

  @Test
  fun testGetNoOpEventHandler() {
    val eventHandler: EventHandler<*> = getNoOpEventHandler<Any>()
    assertThat(eventHandler.isEquivalentTo(null)).isFalse
    val eventHandler1: EventHandler<*> = getNoOpEventHandler<Any>()
    assertThat(eventHandler.isEquivalentTo(eventHandler1)).isTrue
  }

  @Test
  fun testHasEventDispatcherNotNull() {
    val eventHandler: NoOpEventHandler<*> = getNoOpEventHandler<Any>()
    assertThat(eventHandler.isEquivalentTo(null)).isFalse
    assertThat(getNoOpEventHandler<Any>().dispatchInfo.hasEventDispatcher != null).isTrue
  }

  @Test(expected = RuntimeException::class)
  fun testComponentContextThrowsExceptionWithoutComponentScope() {
    val componentContext = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    assertThat(
            componentContext
                .newEventHandler<Any>(1, arrayOfNulls(1))
                .isEquivalentTo(getNoOpEventHandler<Any>()))
        .isTrue
  }

  @Test(expected = RuntimeException::class)
  fun testComponentLifeCycleThrowsExceptionWithoutComponentScope() {
    val componentContext = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    val component: Component? = null
    assertThat(
            Component.newEventHandler<Any>(
                    component!!.javaClass, "Component", componentContext, 1, arrayOfNulls(1))
                .isEquivalentTo(getNoOpEventHandler<Any>()))
        .isTrue
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import java.lang.IllegalArgumentException
import java.lang.IndexOutOfBoundsException
import java.lang.RuntimeException
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class OutputUnitsAffinityGroupTest {

  @JvmField @Rule var expectedException = ExpectedException.none()

  @Test
  fun testEmpty() {
    val group = OutputUnitsAffinityGroup<Any>()
    assertThat(group.size()).isZero
    assertThat(group[OutputUnitType.CONTENT]).isNull()
    assertThat(group[OutputUnitType.BACKGROUND]).isNull()
    assertThat(group[OutputUnitType.FOREGROUND]).isNull()
    assertThat(group[OutputUnitType.BORDER]).isNull()
    assertThat(group[OutputUnitType.HOST]).isNull()
  }

  @Test
  fun testAddingValid() {
    val group = OutputUnitsAffinityGroup<Any>()
    val content = Any()
    group.add(OutputUnitType.CONTENT, content)
    assertThat(group.size()).isEqualTo(1)
    assertThat(group[OutputUnitType.CONTENT]).isSameAs(content)
    assertThat(group.typeAt(0)).isEqualTo(OutputUnitType.CONTENT)
    assertThat(group.getAt(0)).isSameAs(content)
    val background = Any()
    group.add(OutputUnitType.BACKGROUND, background)
    assertThat(group.size()).isEqualTo(2)
    assertThat(group[OutputUnitType.CONTENT]).isSameAs(content)
    assertThat(group[OutputUnitType.BACKGROUND]).isSameAs(background)
    val type0 = group.typeAt(0)
    val type1 = group.typeAt(1)
    assertThat(type0).isIn(OutputUnitType.CONTENT, OutputUnitType.BACKGROUND)
    assertThat(type1).isIn(OutputUnitType.CONTENT, OutputUnitType.BACKGROUND)
    assertThat(type0).isNotEqualTo(type1)
    val value0 = group.getAt(0)
    val value1 = group.getAt(1)
    if (type0 == OutputUnitType.CONTENT) {
      assertThat(value0).isSameAs(content)
      assertThat(value1).isSameAs(background)
    } else {
      assertThat(value0).isSameAs(background)
      assertThat(value1).isSameAs(content)
    }
    val border = Any()
    group.add(OutputUnitType.BORDER, border)
    assertThat(group.size()).isEqualTo(3)
    assertThat(group[OutputUnitType.CONTENT]).isSameAs(content)
    assertThat(group[OutputUnitType.BACKGROUND]).isSameAs(background)
    assertThat(group[OutputUnitType.BORDER]).isSameAs(border)
  }

  @Test
  fun testAddingMultipleForSameType() {
    expectedException.expect(RuntimeException::class.java)
    expectedException.expectMessage("Already contains unit for type CONTENT")
    val group = OutputUnitsAffinityGroup<Any>()
    group.add(OutputUnitType.CONTENT, Any())
    group.add(OutputUnitType.CONTENT, Any())
  }

  @Test
  fun testAddingNull() {
    expectedException.expect(IllegalArgumentException::class.java)
    expectedException.expectMessage("value should not be null")
    val group = OutputUnitsAffinityGroup<Any?>()
    group.add(OutputUnitType.FOREGROUND, null)
  }

  @Test
  fun testAddingHostToNotEmptyGroup() {
    expectedException.expect(RuntimeException::class.java)
    expectedException.expectMessage(
        "OutputUnitType.HOST unit should be the only member of an OutputUnitsAffinityGroup")
    val group = OutputUnitsAffinityGroup<Any>()
    group.add(OutputUnitType.CONTENT, Any())
    group.add(OutputUnitType.HOST, Any())
  }

  @Test
  fun testAddingToGroupThatContainsHost() {
    expectedException.expect(RuntimeException::class.java)
    expectedException.expectMessage(
        "OutputUnitType.HOST unit should be the only member of an OutputUnitsAffinityGroup")
    val group = OutputUnitsAffinityGroup<Any>()
    group.add(OutputUnitType.HOST, Any())
    group.add(OutputUnitType.BACKGROUND, Any())
  }

  @Test
  fun testIllegalRange() {
    expectedException.expect(IndexOutOfBoundsException::class.java)
    expectedException.expectMessage("index=2, size=2")
    val group = OutputUnitsAffinityGroup<Any>()
    group.add(OutputUnitType.BACKGROUND, Any())
    group.add(OutputUnitType.FOREGROUND, Any())
    group.getAt(2)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class RecyclePoolTest {
  @Test
  fun testClear() {
    val pool = RecyclePool<Any>("test", 10, false)
    val elemNum = 7
    for (i in 0 until elemNum) {
      pool.release(Any())
    }
    assertThat(pool.currentSize).isEqualTo(elemNum)
    pool.clear()
    assertThat(pool.currentSize).isEqualTo(0)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.Text
import java.lang.IllegalStateException
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class RequiredPropsTest {

  private lateinit var context: ComponentContext

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
  }

  @Test
  fun testWithoutRequiredProps() {
    var error = ""
    try {
      Text.create(context).build()
    } catch (e: IllegalStateException) {
      error = e.message ?: ""
    }
    assertTrue("Error message did not mention the missing required prop", error.contains("text"))
  }

  @Test
  fun testWithRequiredProps() {
    Text.create(context).text("text").build()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.view.ContextThemeWrapper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.it.R
import com.facebook.litho.it.R.attr.testAttrDimen
import com.facebook.litho.it.R.attr.testAttrDrawable
import com.facebook.litho.it.R.attr.undefinedAttrDimen
import com.facebook.litho.it.R.attr.undefinedAttrDrawable
import com.facebook.litho.it.R.dimen.default_dimen
import com.facebook.litho.it.R.dimen.test_dimen
import com.facebook.litho.it.R.dimen.test_dimen_float
import com.facebook.litho.it.R.drawable.test_bg
import com.facebook.litho.it.R.style.TestTheme
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.rendercore.utils.MeasureSpecUtils
import com.facebook.yoga.YogaEdge
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows

@RunWith(LithoTestRunner::class)
class ResolveAttributeTest {

  @JvmField @Rule val legacyLithoViewRule: LegacyLithoViewRule = LegacyLithoViewRule()

  @Before
  fun setup() {
    legacyLithoViewRule.useContext(
        ComponentContext(
            ContextThemeWrapper(ApplicationProvider.getApplicationContext(), TestTheme)))
  }

  @Test
  fun testResolveDrawableAttribute() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).backgroundAttr(testAttrDrawable, 0).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val d = c.resources.getDrawable(test_bg)
    val drawable = legacyLithoViewRule.currentRootNode?.background
    assertThat(Shadows.shadowOf(drawable).createdFromResId)
        .isEqualTo(Shadows.shadowOf(d).createdFromResId)
  }

  @Test
  fun testResolveDimenAttribute() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).widthAttr(testAttrDimen, default_dimen).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val node = legacyLithoViewRule.currentRootNode
    val dimen = c.resources.getDimensionPixelSize(R.dimen.test_dimen)
    assertThat(node?.width).isEqualTo(dimen)
  }

  @Test
  fun testDefaultDrawableAttribute() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).backgroundAttr(undefinedAttrDrawable, test_bg).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val node = legacyLithoViewRule.currentRootNode
    val d = c.resources.getDrawable(test_bg)
    val drawable = node?.background
    assertThat(Shadows.shadowOf(drawable).createdFromResId)
        .isEqualTo(Shadows.shadowOf(d).createdFromResId)
  }

  @Test
  fun testDefaultDimenAttribute() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).widthAttr(undefinedAttrDimen, test_dimen).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val node = legacyLithoViewRule.currentRootNode
    val dimen = c.resources.getDimensionPixelSize(R.dimen.test_dimen)
    assertThat(node?.width).isEqualTo(dimen)
  }

  @Test
  fun testFloatDimenWidthAttribute() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).widthAttr(undefinedAttrDimen, test_dimen_float).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val node = legacyLithoViewRule.currentRootNode
    val dimen = c.resources.getDimensionPixelSize(test_dimen_float)
    assertThat(node?.width).isEqualTo(dimen)
  }

  @Test
  fun testFloatDimenPaddingAttribute() {
    val c = legacyLithoViewRule.context
    val column =
        Column.create(c).paddingAttr(YogaEdge.LEFT, undefinedAttrDimen, test_dimen_float).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(
            column, MeasureSpecUtils.unspecified(), MeasureSpecUtils.unspecified())
        .measure()
        .layout()
    val node = legacyLithoViewRule.currentRootNode
    val dimen = c.resources.getDimensionPixelSize(test_dimen_float)
    assertThat(node?.paddingLeft).isEqualTo(dimen)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho

import android.view.ContextThemeWrapper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.it.R.dimen.test_dimen
import com.facebook.litho.it.R.dimen.test_dimen_float
import com.facebook.litho.it.R.style.TestTheme
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.TextInput
import com.facebook.rendercore.utils.MeasureSpecUtils.unspecified
import com.facebook.yoga.YogaEdge
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class ResolveResTest {

  @JvmField @Rule val legacyLithoViewRule: LegacyLithoViewRule = LegacyLithoViewRule()

  @Before
  fun setup() {
    legacyLithoViewRule.useContext(
        ComponentContext(
            ContextThemeWrapper(ApplicationProvider.getApplicationContext(), TestTheme)))
  }

  @Test
  fun testDefaultDimenWidthRes() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).widthRes(test_dimen).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(column, unspecified(), unspecified())
        .measure()
        .layout()
    val dimen = c.resources.getDimensionPixelSize(test_dimen)
    assertThat(legacyLithoViewRule.lithoView.width).isEqualTo(dimen)
  }

  @Test
  fun testDefaultDimenPaddingRes() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).paddingRes(YogaEdge.LEFT, test_dimen).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(column, unspecified(), unspecified())
        .measure()
        .layout()
    val dimen = c.resources.getDimensionPixelSize(test_dimen)
    assertThat(legacyLithoViewRule.lithoView.width).isEqualTo(dimen)
  }

  @Test
  fun testFloatDimenWidthRes() {
    val c = legacyLithoViewRule.context
    val column = Column.create(c).widthRes(test_dimen_float).build()
    legacyLithoViewRule
        .setRootAndSizeSpecSync(column, unspecified(), unspecified())
        .measure()
        .layout()
    val dimen = c.resources.getDimensionPixelSize(test_dimen_float)
    assertThat(legacyLithoViewRule.lithoView.width).isEqualTo(dimen)
  }

  @Test
  fun testFloatDimenPaddingRes() {
    val c = legacyLithoViewRule.context
    val row =
        Row.create(c).child(TextInput.create(c).paddingRes(YogaEdge.LEFT, test_dimen_float)).build()
    legacyLithoViewRule.attachToWindow().setSizePx(100, 100).setRoot(row).measure().layout()
    val dimen = c.resources.getDimensionPixelSize(test_dimen_float)
    assertThat(legacyLithoViewRule.lithoView.getChildAt(0).paddingLeft).isEqualTo(dimen)
  }
}