/*
 * 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.graphics.Rect
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import com.facebook.rendercore.incrementalmount.IncrementalMountOutput
import com.facebook.rendercore.incrementalmount.IncrementalMountRenderCoreExtension
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaPositionType
import java.util.ArrayList
import java.util.Collections
import org.assertj.core.api.Assertions
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LayoutStateCalculateTopsAndBottomsTest {
  @JvmField @Rule val lithoViewRule: LithoViewRule = LithoViewRule()

  @Test
  fun testCalculateTopsAndBottoms() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    Column.create(c)
                        .child(SimpleMountSpecTester.create(c).wrapInView().heightPx(50)))
                .child(SimpleMountSpecTester.create(c).heightPx(20))
                .child(
                    SimpleMountSpecTester.create(c)
                        .positionType(YogaPositionType.ABSOLUTE)
                        .positionPx(YogaEdge.TOP, 10)
                        .positionPx(YogaEdge.BOTTOM, 30))
                .build()
          }
        }
    val testLithoView = lithoViewRule.render(widthPx = 100, heightPx = 50 + 20) { component }
    val layoutState = testLithoView.componentTree.mainThreadLayoutState!!
    Assertions.assertThat(layoutState.mountableOutputCount).isEqualTo(5)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[0].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[1].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[2].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[3].bounds.top).isEqualTo(10)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[4].bounds.top).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[0].bounds.bottom).isEqualTo(40)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[1].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[2].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[3].bounds.bottom).isEqualTo(70)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[4].bounds.bottom).isEqualTo(70)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[2].index).isEqualTo(2)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[3].index).isEqualTo(4)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[4].index).isEqualTo(3)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[0].index).isEqualTo(4)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[1].index).isEqualTo(2)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[2].index).isEqualTo(1)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[3].index).isEqualTo(3)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[4].index).isEqualTo(0)
  }

  @Test
  fun testCalculateTopsAndBottomsWhenEqual() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(SimpleMountSpecTester.create(c).heightPx(50))
                .child(
                    SimpleMountSpecTester.create(c)
                        .wrapInView()
                        .positionType(YogaPositionType.ABSOLUTE)
                        .positionPx(YogaEdge.TOP, 0)
                        .positionPx(YogaEdge.BOTTOM, 0))
                .build()
          }
        }
    val testLithoView = lithoViewRule.render(widthPx = 100, heightPx = 50) { component }
    val layoutState = testLithoView.componentTree.mainThreadLayoutState!!
    Assertions.assertThat(layoutState.mountableOutputCount).isEqualTo(4)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[0].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[1].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[2].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[3].bounds.top).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[0].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[1].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[2].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[3].bounds.bottom).isEqualTo(50)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[0].index).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[1].index).isEqualTo(1)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[2].index).isEqualTo(2)
    Assertions.assertThat(layoutState.outputsOrderedByTopBounds[3].index).isEqualTo(3)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[3].index).isEqualTo(0)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[2].index).isEqualTo(1)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[1].index).isEqualTo(2)
    Assertions.assertThat(layoutState.outputsOrderedByBottomBounds[0].index).isEqualTo(3)
  }

  @Test
  fun testTopsComparatorIsEquivalenceRelation() {
    val outputs: Array<IncrementalMountOutput?> = arrayOfNulls<IncrementalMountOutput>(4)
    outputs[0] = createIncrementMountOutput(0, 10, 0)
    outputs[1] = createIncrementMountOutput(0, 10, 1)
    outputs[2] = createIncrementMountOutput(0, 20, 2)
    outputs[3] = createIncrementMountOutput(0, 20, 3)

    // reflexive
    for (output in outputs) {
      Assertions.assertThat(
              IncrementalMountRenderCoreExtension.sTopsComparator.compare(output, output))
          .isEqualTo(0)
    }

    // symmetric
    for (i in 0..3) {
      for (j in i + 1..3) {
        Assertions.assertThat(
                -1 *
                    IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                        outputs[j], outputs[i]))
            .isEqualTo(
                IncrementalMountRenderCoreExtension.sTopsComparator.compare(outputs[i], outputs[j]))
      }
    }

    // transitivity
    for (i in 0..3) {
      for (j in 0..3) {
        for (k in 0..3) {
          if (i != j && j != k && i != k) {
            if (IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                outputs[i], outputs[j]) ==
                IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                    outputs[j], outputs[k])) {
              Assertions.assertThat(
                      IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                          outputs[j], outputs[k]))
                  .isEqualTo(
                      IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                          outputs[i], outputs[j]))
            }
          }
        }
      }
    }
  }

  @Test
  fun testBottomsComparatorIsEquivalenceRelation() {
    val outputs: Array<IncrementalMountOutput?> = arrayOfNulls<IncrementalMountOutput>(4)
    outputs[0] = createIncrementMountOutput(0, 10, 0)
    outputs[1] = createIncrementMountOutput(0, 10, 1)
    outputs[2] = createIncrementMountOutput(0, 20, 2)
    outputs[3] = createIncrementMountOutput(0, 20, 3)

    // reflexive
    for (output in outputs) {
      Assertions.assertThat(
              IncrementalMountRenderCoreExtension.sBottomsComparator.compare(output, output))
          .isEqualTo(0)
    }

    // symmetric
    for (i in 0..3) {
      for (j in i + 1..3) {
        Assertions.assertThat(
                -1 *
                    IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                        outputs[j], outputs[i]))
            .isEqualTo(
                IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                    outputs[i], outputs[j]))
      }
    }

    // transitivity
    for (i in 0..3) {
      for (j in 0..3) {
        for (k in 0..3) {
          if (i != j && j != k && i != k) {
            if (IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                outputs[i], outputs[j]) ==
                IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                    outputs[j], outputs[k])) {
              Assertions.assertThat(
                      IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                          outputs[j], outputs[k]))
                  .isEqualTo(
                      IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                          outputs[i], outputs[j]))
            }
          }
        }
      }
    }
  }

  @Test
  fun testTopsComparatorWithOverflow() {
    val maxIntTop: IncrementalMountOutput = createIncrementMountOutput(Int.MAX_VALUE, 20, 0)
    val largeNegativeTop: IncrementalMountOutput = createIncrementMountOutput(-2147483646, 20, 1)
    val minIntTop: IncrementalMountOutput = createIncrementMountOutput(Int.MIN_VALUE, 20, 2)
    val largePositiveTop: IncrementalMountOutput = createIncrementMountOutput(2147483646, 20, 3)
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                maxIntTop, largeNegativeTop))
        .isPositive()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                largeNegativeTop, maxIntTop))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                minIntTop, largePositiveTop))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(
                largePositiveTop, minIntTop))
        .isPositive()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(minIntTop, maxIntTop))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sTopsComparator.compare(maxIntTop, minIntTop))
        .isPositive()
  }

  @Test
  fun testBottomComparatorWithOverflow() {
    val maxIntBottom: IncrementalMountOutput = createIncrementMountOutput(20, Int.MAX_VALUE, 0)
    val largeNegativeBottom: IncrementalMountOutput = createIncrementMountOutput(20, -2147483646, 1)
    val minIntBottom: IncrementalMountOutput = createIncrementMountOutput(20, Int.MIN_VALUE, 2)
    val largePositiveBottom: IncrementalMountOutput = createIncrementMountOutput(20, 2147483646, 3)
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                maxIntBottom, largeNegativeBottom))
        .isPositive()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                largeNegativeBottom, maxIntBottom))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                minIntBottom, largePositiveBottom))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                largePositiveBottom, minIntBottom))
        .isPositive()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                minIntBottom, maxIntBottom))
        .isNegative()
    Assertions.assertThat(
            IncrementalMountRenderCoreExtension.sBottomsComparator.compare(
                maxIntBottom, minIntBottom))
        .isPositive()
  }

  @Test
  fun testTopsComparatorAdheresToContract() {
    val nodes: MutableList<IncrementalMountOutput> = ArrayList<IncrementalMountOutput>()
    var currentIndex = 0
    nodes.add(createIncrementMountOutput(0, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(0, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(21, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(47, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(21, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(21, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483628, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483617, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483617, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483617, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483617, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483568, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483557, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483557, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483646, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483477, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483278, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483612, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(-2147483611, 10, currentIndex++))
    nodes.add(createIncrementMountOutput(2147483647, 20, currentIndex++))
    Collections.sort(nodes, IncrementalMountRenderCoreExtension.sTopsComparator)
  }

  @Test
  fun testBottomsComparatorAdheresToContract() {
    val nodes: MutableList<IncrementalMountOutput> = ArrayList<IncrementalMountOutput>()
    var currentIndex = 0
    nodes.add(createIncrementMountOutput(20, 0, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 0, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 21, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 47, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 21, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 21, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483628, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483617, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483617, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483617, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483617, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483568, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483557, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483557, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483646, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483477, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483278, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483612, currentIndex++))
    nodes.add(createIncrementMountOutput(20, -2147483611, currentIndex++))
    nodes.add(createIncrementMountOutput(20, 2147483647, currentIndex++))
    Collections.sort(nodes, IncrementalMountRenderCoreExtension.sBottomsComparator)
  }

  companion object {
    private fun createIncrementMountOutput(
        top: Int,
        bottom: Int,
        index: Int,
        hostId: Long = 0
    ): IncrementalMountOutput {
      return IncrementalMountOutput((index + 1) * 1L, index, Rect(0, top, 10, bottom), false, null)
    }
  }
}


/*
 * 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.LithoViewRule
import com.facebook.litho.testing.TestLayoutComponent
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import com.facebook.litho.widget.TestNullLayoutComponent
import com.facebook.rendercore.MountItemsPool
import com.facebook.yoga.YogaEdge
import java.lang.Exception
import org.assertj.core.api.Assertions
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 LayoutStateCalculateVisibilityOutputsTest {
  @JvmField @Rule val lithoViewRule: LithoViewRule = LithoViewRule()

  @Before
  @Throws(Exception::class)
  fun setup() {
    MountItemsPool.clear()
  }

  @Test
  fun testNoUnnecessaryVisibilityOutputs() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    Column.create(c)
                        .child(
                            SimpleMountSpecTester.create(c)
                                .visibleHandler(
                                    c.newEventHandler(1) as EventHandler<VisibleEvent>)))
                .child(
                    SimpleMountSpecTester.create(c)
                        .invisibleHandler(c.newEventHandler(2) as EventHandler<InvisibleEvent>))
                .child(SimpleMountSpecTester.create(c))
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 100, 100)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(2)
  }

  @Test
  fun testNoUnnecessaryVisibilityOutputsWithFullImpression() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    Column.create(c)
                        .child(
                            SimpleMountSpecTester.create(c)
                                .visibleHandler(
                                    c.newEventHandler(1) as EventHandler<VisibleEvent>)))
                .child(
                    SimpleMountSpecTester.create(c)
                        .fullImpressionHandler(
                            c.newEventHandler(3) as EventHandler<FullImpressionVisibleEvent>))
                .child(SimpleMountSpecTester.create(c))
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 100, 100)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(2)
  }

  @Test
  fun testNoUnnecessaryVisibilityOutputsWithFocused() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    Column.create(c)
                        .child(
                            SimpleMountSpecTester.create(c)
                                .visibleHandler(
                                    c.newEventHandler(1) as EventHandler<VisibleEvent>)))
                .child(
                    SimpleMountSpecTester.create(c)
                        .focusedHandler(c.newEventHandler(4) as EventHandler<FocusedVisibleEvent>))
                .child(SimpleMountSpecTester.create(c))
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 100, 100)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(2)
  }

  @Test
  fun testVisibilityOutputsForDelegateComponents() {
    val isDelegate = true
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    TestLayoutComponent.create(c, 0, 0, true, false, isDelegate)
                        .visibleHandler(c.newEventHandler(1) as EventHandler<VisibleEvent>))
                .wrapInView()
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 100, 100)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(1)
  }

  @Test
  fun testLayoutOutputsForDeepLayoutSpecs() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    Column.create(c)
                        .child(
                            TestLayoutComponent.create(c)
                                .visibleHandler(c.newEventHandler(1) as EventHandler<VisibleEvent>))
                        .invisibleHandler(c.newEventHandler(2) as EventHandler<InvisibleEvent>))
                .child(
                    Column.create(c)
                        .child(
                            TestLayoutComponent.create(c)
                                .invisibleHandler(
                                    c.newEventHandler(1) as EventHandler<InvisibleEvent>))
                        .visibleHandler(c.newEventHandler(2) as EventHandler<VisibleEvent>))
                .wrapInView()
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 350, 200)

    // Check total layout outputs.
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(4)

    // Check number of Components with VisibleEvent handlers.
    var visibleHandlerCount = 0
    for (i in 0 until layoutState.visibilityOutputCount) {
      if (layoutState.getVisibilityOutputAt(i).visibleEventHandler != null) {
        visibleHandlerCount += 1
      }
    }
    Assertions.assertThat(visibleHandlerCount).isEqualTo(2)
  }

  @Test
  fun testLayoutOutputsForForceWrappedComponent() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .child(
                    SimpleMountSpecTester.create(c)
                        .visibleHandler(c.newEventHandler(1) as EventHandler<VisibleEvent>)
                        .wrapInView())
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 100, 100)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(1)
  }

  @Test
  fun testLayoutOutputForRootWithNullLayout() {
    val componentWithNullLayout: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component? {
            return null
          }
        }
    val layoutState = calculateLayoutState(componentWithNullLayout, 350, 200)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(0)
  }

  @Test
  fun testLayoutComponentForNestedTreeChildWithNullLayout() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .paddingPx(YogaEdge.ALL, 2)
                .child(TestNullLayoutComponent.create(c))
                .invisibleHandler(c.newEventHandler(2) as EventHandler<InvisibleEvent>)
                .build()
          }
        }
    val layoutState = calculateLayoutState(component, 350, 200)
    Assertions.assertThat(layoutState.visibilityOutputCount).isEqualTo(1)
  }

  private fun calculateLayoutState(component: Component, widthPx: Int, heightPx: Int): LayoutState {
    val testLithoView = lithoViewRule.render(widthPx = widthPx, heightPx = heightPx) { component }
    return testLithoView.componentTree.mainThreadLayoutState!!
  }
}


/*
 * 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 android.view.View
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.Text
import com.facebook.litho.widget.layoutstate.withoutdrawableoutput.RootComponent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LayoutStateCalculationWithoutDrawableOutputsTest {

  private lateinit var context: ComponentContext
  private lateinit var componentTree: ComponentTree
  private lateinit var lithoView: LithoView

  @Before
  fun before() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    lithoView = LithoView(context)
    componentTree = ComponentTree.create(context).build()
    lithoView.componentTree = componentTree
  }

  @Test
  fun whenDrawableOutputsEnabledAndChildrenNotWrappedInView_shouldHaveExplicitDrawableOutputsForBackgroundAndForeground() {
    var output: LithoRenderUnit? = null
    componentTree =
        ComponentTree.create(context)
            .componentsConfiguration(
                ComponentsConfiguration.create().shouldAddHostViewForRootComponent(false).build())
            .build()
    lithoView!!.componentTree = componentTree
    attach()
    componentTree.setRootSync(RootComponent.create(context).shouldWrapInView(false).build())
    val state: LayoutState =
        requireNotNull(lithoView.componentTree?.mainThreadLayoutState) { "empty layout state" }
    assertThat(state?.mountableOutputCount).isEqualTo(7)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(0)) // root host view
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(1)) // background 1
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(2)) // text 1
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(3)) // foreground 1
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(4)) // background 2
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(5)) // text 2
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(6)) // foreground 2
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
  }

  @Test
  fun whenDrawableOutputsEnabledAndChildrenWrappedInView_shouldHaveExplicitDrawableOutputsForBackgroundAndForeground() {
    var output: LithoRenderUnit? = null
    componentTree =
        ComponentTree.create(context)
            .componentsConfiguration(
                ComponentsConfiguration.create().shouldAddHostViewForRootComponent(false).build())
            .build()
    lithoView!!.componentTree = componentTree
    componentTree.setRootSync(RootComponent.create(context).shouldWrapInView(true).build())
    attach()
    val state: LayoutState =
        requireNotNull(lithoView.componentTree?.mainThreadLayoutState) { "empty layout state" }
    assertThat(state.mountableOutputCount).isEqualTo(9)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(0)) // root host view
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(1)) // host view 1
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(2)) // background 1
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(3)) // text 1
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(4)) // foreground 1
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(5)) // host view 2
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(6)) // background 2
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(7)) // text 2
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(8)) // foreground 2
    assertThat(output?.component).isOfAnyClassIn(DrawableComponent::class.java)
  }

  @Test
  fun whenDrawableOutputsDisabledAndChildrenNotWrappedInView_shouldNotHaveDrawableOutputsForBackgroundAndForeground() {
    var output: LithoRenderUnit? = null
    // disable layout outputs for drawables
    componentTree =
        ComponentTree.create(context)
            .componentsConfiguration(
                ComponentsConfiguration.create().shouldAddHostViewForRootComponent(true).build())
            .build()
    lithoView!!.componentTree = componentTree
    componentTree.setRootSync(RootComponent.create(context).shouldWrapInView(false).build())
    attach()
    val state: LayoutState =
        requireNotNull(lithoView.componentTree?.mainThreadLayoutState) { "empty layout state" }
    assertThat(state.mountableOutputCount).isEqualTo(5) // 2 bg and fg lesser.
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(1))
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(2))
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(3))
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(4))
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
  }

  @Test
  fun whenDrawableOutputsDisabledAndChildrenWrappedInView_shouldNotHaveDrawableOutputsForBackgroundAndForeground() {
    var output: LithoRenderUnit? = null
    // disable layout outputs for drawables
    componentTree =
        ComponentTree.create(context)
            .componentsConfiguration(
                ComponentsConfiguration.create().shouldAddHostViewForRootComponent(true).build())
            .build()
    lithoView!!.componentTree = componentTree
    componentTree.setRootSync(RootComponent.create(context).shouldWrapInView(true).build())
    attach()
    val state: LayoutState =
        requireNotNull(lithoView.componentTree?.mainThreadLayoutState) { "empty layout state" }
    assertThat(state.mountableOutputCount).isEqualTo(5) // 2 bg and fg lesser.
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(1))
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(2))
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(3))
    assertThat(output?.component).isOfAnyClassIn(HostComponent::class.java)
    output = LithoRenderUnit.getRenderUnit(state.getMountableOutputAt(4))
    assertThat(output?.component).isOfAnyClassIn(Text::class.java)
  }

  private fun attach() {
    lithoView.onAttachedToWindow()
    lithoView.measure(
        View.MeasureSpec.makeMeasureSpec(1_080, View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(1_920, View.MeasureSpec.UNSPECIFIED))
    lithoView.layout(0, 0, 1_080, 1_920)
    lithoView.notifyVisibleBoundsChanged()
  }
}


/*
 * 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.animation.StateListAnimator
import android.annotation.TargetApi
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.util.SparseArray
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.LayoutStateCreateTreeTest.TestDrawableComponentWithMockInternalNode
import com.facebook.litho.annotations.ImportantForAccessibility
import com.facebook.litho.drawable.ComparableColorDrawable
import com.facebook.litho.testing.TestComponent
import com.facebook.litho.testing.TestSizeDependentComponent
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaDirection
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaPositionType
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.annotation.Config

@RunWith(LithoTestRunner::class)
class LayoutStateCreateTreeTest {

  private lateinit var componentContext: ComponentContext
  private lateinit var resolveStateContext: ResolveStateContext

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

  @Test
  fun simpleLayoutCreatesExpectedInternalNodeTree() {
    val component: Component =
        object : InlineLayoutSpec(componentContext) {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(Column.create(c).child(SimpleMountSpecTester.create(c)))
                  .build()
        }
    var node = Resolver.resolve(resolveStateContext, componentContext, component)
    assertThat(node?.childCount).isEqualTo(1)
    assertThat(node?.headComponent).isEqualTo(component)
    assertThat(node?.tailComponent).isInstanceOf(Column::class.java)
    node = node?.getChildAt(0)
    assertThat(node?.childCount).isEqualTo(1)
    assertThat(node?.tailComponent).isInstanceOf(Column::class.java)
    node = node?.getChildAt(0)
    assertThat(node?.childCount).isEqualTo(0)
    assertThat(node?.tailComponent).isInstanceOf(SimpleMountSpecTester::class.java)
  }

  @Test
  fun testHandlersAreAppliedToCorrectInternalNodes() {
    val clickHandler1: EventHandler<ClickEvent> = mock()
    val clickHandler2: EventHandler<ClickEvent> = mock()
    val clickHandler3: EventHandler<ClickEvent> = mock()
    val longClickHandler1: EventHandler<LongClickEvent> = mock()
    val longClickHandler2: EventHandler<LongClickEvent> = mock()
    val longClickHandler3: EventHandler<LongClickEvent> = mock()
    val touchHandler1: EventHandler<TouchEvent> = mock()
    val touchHandler2: EventHandler<TouchEvent> = mock()
    val touchHandler3: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler1: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler2: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler3: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler1: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler2: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler3: EventHandler<FocusChangedEvent> = mock()
    val component: Component =
        object : InlineLayoutSpec(componentContext) {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(
                      Column.create(c)
                          .child(
                              SimpleMountSpecTester.create(c)
                                  .clickHandler(clickHandler1)
                                  .longClickHandler(longClickHandler1)
                                  .touchHandler(touchHandler1)
                                  .interceptTouchHandler(interceptTouchHandler1)
                                  .focusChangeHandler(focusChangedHandler1))
                          .clickHandler(clickHandler2)
                          .longClickHandler(longClickHandler2)
                          .touchHandler(touchHandler2)
                          .interceptTouchHandler(interceptTouchHandler2)
                          .focusChangeHandler(focusChangedHandler2))
                  .clickHandler(clickHandler3)
                  .longClickHandler(longClickHandler3)
                  .touchHandler(touchHandler3)
                  .interceptTouchHandler(interceptTouchHandler3)
                  .focusChangeHandler(focusChangedHandler3)
                  .build()
        }
    var node = Resolver.resolve(resolveStateContext, componentContext, component)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler3)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler3)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler3)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler3)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler3)
    node = node?.getChildAt(0)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler2)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler2)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler2)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler2)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler2)
    node = node?.getChildAt(0)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler1)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler1)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler1)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler1)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler1)
  }

  @Test
  fun testHandlersAreAppliedToCorrectInternalNodesForSizeDependentComponent() {
    val clickHandler1: EventHandler<ClickEvent> = mock()
    val clickHandler2: EventHandler<ClickEvent> = mock()
    val clickHandler3: EventHandler<ClickEvent> = mock()
    val longClickHandler1: EventHandler<LongClickEvent> = mock()
    val longClickHandler2: EventHandler<LongClickEvent> = mock()
    val longClickHandler3: EventHandler<LongClickEvent> = mock()
    val touchHandler1: EventHandler<TouchEvent> = mock()
    val touchHandler2: EventHandler<TouchEvent> = mock()
    val touchHandler3: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler1: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler2: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler3: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler1: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler2: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler3: EventHandler<FocusChangedEvent> = mock()
    val component: Component =
        object : InlineLayoutSpec(componentContext) {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(
                      Column.create(c)
                          .child(
                              TestSizeDependentComponent.create(c)
                                  .clickHandler(clickHandler1)
                                  .longClickHandler(longClickHandler1)
                                  .touchHandler(touchHandler1)
                                  .interceptTouchHandler(interceptTouchHandler1)
                                  .focusChangeHandler(focusChangedHandler1))
                          .clickHandler(clickHandler2)
                          .longClickHandler(longClickHandler2)
                          .touchHandler(touchHandler2)
                          .interceptTouchHandler(interceptTouchHandler2)
                          .focusChangeHandler(focusChangedHandler2))
                  .clickHandler(clickHandler3)
                  .longClickHandler(longClickHandler3)
                  .touchHandler(touchHandler3)
                  .interceptTouchHandler(interceptTouchHandler3)
                  .focusChangeHandler(focusChangedHandler3)
                  .build()
        }
    var node = Resolver.resolve(resolveStateContext, componentContext, component)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler3)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler3)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler3)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler3)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler3)
    node = node?.getChildAt(0)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler2)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler2)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler2)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler2)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler2)
    node = node?.getChildAt(0)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler1)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler1)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler1)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler1)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler1)
  }

  @Test
  fun testOverridingHandlers() {
    val clickHandler1: EventHandler<ClickEvent> = mock()
    val clickHandler2: EventHandler<ClickEvent> = mock()
    val longClickHandler1: EventHandler<LongClickEvent> = mock()
    val longClickHandler2: EventHandler<LongClickEvent> = mock()
    val touchHandler1: EventHandler<TouchEvent> = mock()
    val touchHandler2: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler1: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler2: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler1: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler2: EventHandler<FocusChangedEvent> = mock()
    val component: Component =
        object : InlineLayoutSpec(componentContext) {
          override fun onCreateLayout(c: ComponentContext): Component {
            return Wrapper.create(c)
                .delegate(
                    object : InlineLayoutSpec() {
                      override fun onCreateLayout(c: ComponentContext): Component =
                          SimpleMountSpecTester.create(c)
                              .clickHandler(clickHandler1)
                              .longClickHandler(longClickHandler1)
                              .touchHandler(touchHandler1)
                              .interceptTouchHandler(interceptTouchHandler1)
                              .focusChangeHandler(focusChangedHandler1)
                              .build()
                    })
                .clickHandler(clickHandler2)
                .longClickHandler(longClickHandler2)
                .touchHandler(touchHandler2)
                .interceptTouchHandler(interceptTouchHandler2)
                .focusChangeHandler(focusChangedHandler2)
                .build()
          }
        }
    val node = Resolver.resolve(resolveStateContext, componentContext, component)
    assertThat(node?.childCount).isEqualTo(0)
    assertThat(node?.tailComponent).isInstanceOf(SimpleMountSpecTester::class.java)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler2)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler2)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler2)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler2)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler2)
  }

  @Test
  fun testOverridingHandlersForSizeDependentComponent() {
    val clickHandler1: EventHandler<ClickEvent> = mock()
    val clickHandler2: EventHandler<ClickEvent> = mock()
    val longClickHandler1: EventHandler<LongClickEvent> = mock()
    val longClickHandler2: EventHandler<LongClickEvent> = mock()
    val touchHandler1: EventHandler<TouchEvent> = mock()
    val touchHandler2: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler1: EventHandler<InterceptTouchEvent> = mock()
    val interceptTouchHandler2: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler1: EventHandler<FocusChangedEvent> = mock()
    val focusChangedHandler2: EventHandler<FocusChangedEvent> = mock()
    val component: Component =
        object : InlineLayoutSpec(componentContext) {
          override fun onCreateLayout(c: ComponentContext): Component {
            return Wrapper.create(c)
                .delegate(
                    object : InlineLayoutSpec() {
                      override fun onCreateLayout(c: ComponentContext): Component =
                          TestSizeDependentComponent.create(c)
                              .clickHandler(clickHandler1)
                              .longClickHandler(longClickHandler1)
                              .touchHandler(touchHandler1)
                              .interceptTouchHandler(interceptTouchHandler1)
                              .focusChangeHandler(focusChangedHandler1)
                              .build()
                    })
                .clickHandler(clickHandler2)
                .longClickHandler(longClickHandler2)
                .touchHandler(touchHandler2)
                .interceptTouchHandler(interceptTouchHandler2)
                .focusChangeHandler(focusChangedHandler2)
                .build()
          }
        }
    val node = Resolver.resolve(resolveStateContext, componentContext, component)
    assertThat(node?.childCount).isEqualTo(0)
    assertThat(node?.tailComponent).isInstanceOf(TestSizeDependentComponent::class.java)
    assertThat(node?.nodeInfo?.clickHandler).isEqualTo(clickHandler2)
    assertThat(node?.nodeInfo?.longClickHandler).isEqualTo(longClickHandler2)
    assertThat(node?.nodeInfo?.touchHandler).isEqualTo(touchHandler2)
    assertThat(node?.nodeInfo?.interceptTouchHandler).isEqualTo(interceptTouchHandler2)
    assertThat(node?.nodeInfo?.focusChangeHandler).isEqualTo(focusChangedHandler2)
  }

  @Test
  @Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  fun testAddingAllAttributes() {
    val background = ComparableColorDrawable.create(Color.RED)
    val foreground = ComparableColorDrawable.create(Color.BLACK)
    val clickHandler: EventHandler<ClickEvent> = mock()
    val longClickHandler: EventHandler<LongClickEvent> = mock()
    val touchHandler: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler: EventHandler<FocusChangedEvent> = mock()
    val visibleHandler: EventHandler<VisibleEvent> = mock()
    val focusedHandler: EventHandler<FocusedVisibleEvent> = mock()
    val unfocusedHandler: EventHandler<UnfocusedVisibleEvent> = mock()
    val fullImpressionHandler: EventHandler<FullImpressionVisibleEvent> = mock()
    val invisibleHandler: EventHandler<InvisibleEvent> = mock()
    val visibleRectChangedHandler: EventHandler<VisibilityChangedEvent> = mock()
    val viewTag = Any()
    val viewTags = SparseArray<Any>()
    val dispatchPopulateAccessibilityEventHandler:
        EventHandler<DispatchPopulateAccessibilityEventEvent> =
        mock()
    val onInitializeAccessibilityEventHandler: EventHandler<OnInitializeAccessibilityEventEvent> =
        mock()
    val onInitializeAccessibilityNodeInfoHandler:
        EventHandler<OnInitializeAccessibilityNodeInfoEvent> =
        mock()
    val onPopulateAccessibilityEventHandler: EventHandler<OnPopulateAccessibilityEventEvent> =
        mock()
    val onRequestSendAccessibilityEventHandler: EventHandler<OnRequestSendAccessibilityEventEvent> =
        mock()
    val performAccessibilityActionHandler: EventHandler<PerformAccessibilityActionEvent> = mock()
    val sendAccessibilityEventHandler: EventHandler<SendAccessibilityEventEvent> = mock()
    val sendAccessibilityEventUncheckedHandler: EventHandler<SendAccessibilityEventUncheckedEvent> =
        mock()
    val stateListAnimator = mock<StateListAnimator>()
    val component: Component =
        TestDrawableComponentWithMockInternalNode.create(componentContext)
            .layoutDirection(YogaDirection.INHERIT)
            .alignSelf(YogaAlign.AUTO)
            .positionType(YogaPositionType.ABSOLUTE)
            .flex(2f)
            .flexGrow(3f)
            .flexShrink(4f)
            .flexBasisPx(5)
            .flexBasisPercent(6f)
            .importantForAccessibility(ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
            .duplicateParentState(false)
            .marginPx(YogaEdge.ALL, 5)
            .marginPx(YogaEdge.RIGHT, 6)
            .marginPx(YogaEdge.LEFT, 4)
            .marginPercent(YogaEdge.ALL, 10f)
            .marginPercent(YogaEdge.VERTICAL, 12f)
            .marginPercent(YogaEdge.RIGHT, 5f)
            .marginAuto(YogaEdge.LEFT)
            .marginAuto(YogaEdge.TOP)
            .marginAuto(YogaEdge.RIGHT)
            .marginAuto(YogaEdge.BOTTOM)
            .paddingPx(YogaEdge.ALL, 1)
            .paddingPx(YogaEdge.RIGHT, 2)
            .paddingPx(YogaEdge.LEFT, 3)
            .paddingPercent(YogaEdge.VERTICAL, 7f)
            .paddingPercent(YogaEdge.RIGHT, 6f)
            .paddingPercent(YogaEdge.ALL, 5f)
            .positionPx(YogaEdge.ALL, 11)
            .positionPx(YogaEdge.RIGHT, 12)
            .positionPx(YogaEdge.LEFT, 13)
            .positionPercent(YogaEdge.VERTICAL, 17f)
            .positionPercent(YogaEdge.RIGHT, 16f)
            .positionPercent(YogaEdge.ALL, 15f)
            .widthPx(5)
            .widthPercent(50f)
            .minWidthPx(15)
            .minWidthPercent(100f)
            .maxWidthPx(25)
            .maxWidthPercent(26f)
            .heightPx(30)
            .heightPercent(31f)
            .minHeightPx(32)
            .minHeightPercent(33f)
            .maxHeightPx(34)
            .maxHeightPercent(35f)
            .aspectRatio(20f)
            .touchExpansionPx(YogaEdge.RIGHT, 22)
            .touchExpansionPx(YogaEdge.LEFT, 23)
            .touchExpansionPx(YogaEdge.ALL, 21)
            .background(background)
            .foreground(foreground)
            .wrapInView()
            .clickHandler(clickHandler)
            .focusChangeHandler(focusChangedHandler)
            .longClickHandler(longClickHandler)
            .touchHandler(touchHandler)
            .interceptTouchHandler(interceptTouchHandler)
            .focusable(true)
            .selected(false)
            .enabled(false)
            .accessibilityHeading(false)
            .visibleHeightRatio(55f)
            .visibleWidthRatio(56f)
            .visibleHandler(visibleHandler)
            .focusedHandler(focusedHandler)
            .unfocusedHandler(unfocusedHandler)
            .fullImpressionHandler(fullImpressionHandler)
            .invisibleHandler(invisibleHandler)
            .visibilityChangedHandler(visibleRectChangedHandler)
            .contentDescription("test")
            .viewTag(viewTag)
            .viewTags(viewTags)
            .shadowElevationPx(60f)
            .clipToOutline(false)
            .transitionKey("transitionKey")
            .transitionKeyType(Transition.TransitionKeyType.GLOBAL)
            .testKey("testKey")
            .accessibilityRole(AccessibilityRole.BUTTON)
            .accessibilityRoleDescription("Test Role Description")
            .dispatchPopulateAccessibilityEventHandler(dispatchPopulateAccessibilityEventHandler)
            .onInitializeAccessibilityEventHandler(onInitializeAccessibilityEventHandler)
            .onInitializeAccessibilityNodeInfoHandler(onInitializeAccessibilityNodeInfoHandler)
            .onPopulateAccessibilityEventHandler(onPopulateAccessibilityEventHandler)
            .onRequestSendAccessibilityEventHandler(onRequestSendAccessibilityEventHandler)
            .performAccessibilityActionHandler(performAccessibilityActionHandler)
            .sendAccessibilityEventHandler(sendAccessibilityEventHandler)
            .sendAccessibilityEventUncheckedHandler(sendAccessibilityEventUncheckedHandler)
            .stateListAnimator(stateListAnimator)
            .build()
    val node = Resolver.resolve(resolveStateContext, componentContext, component)
    val output: LayoutProps = Mockito.spy(LayoutProps::class.java)
    component.commonProps?.copyLayoutProps(output)
    verify(output).layoutDirection(YogaDirection.INHERIT)
    verify(output).alignSelf(YogaAlign.AUTO)
    verify(output).positionType(YogaPositionType.ABSOLUTE)
    verify(output).flex(2f)
    verify(output).flexGrow(3f)
    verify(output).flexShrink(4f)
    verify(output).flexBasisPx(5)
    verify(output).flexBasisPercent(6f)
    verify(node)
        ?.importantForAccessibility(ImportantForAccessibility.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
    verify(node)?.duplicateParentState(false)
    verify(output).marginPx(YogaEdge.ALL, 5)
    verify(output).marginPx(YogaEdge.RIGHT, 6)
    verify(output).marginPx(YogaEdge.LEFT, 4)
    verify(output).marginPercent(YogaEdge.ALL, 10f)
    verify(output).marginPercent(YogaEdge.VERTICAL, 12f)
    verify(output).marginPercent(YogaEdge.RIGHT, 5f)
    verify(output).marginAuto(YogaEdge.LEFT)
    verify(output).marginAuto(YogaEdge.TOP)
    verify(output).marginAuto(YogaEdge.RIGHT)
    verify(output).marginAuto(YogaEdge.BOTTOM)
    verify(output).paddingPx(YogaEdge.ALL, 1)
    verify(output).paddingPx(YogaEdge.RIGHT, 2)
    verify(output).paddingPx(YogaEdge.LEFT, 3)
    verify(output).paddingPercent(YogaEdge.VERTICAL, 7f)
    verify(output).paddingPercent(YogaEdge.RIGHT, 6f)
    verify(output).paddingPercent(YogaEdge.ALL, 5f)
    verify(output).positionPx(YogaEdge.ALL, 11)
    verify(output).positionPx(YogaEdge.RIGHT, 12)
    verify(output).positionPx(YogaEdge.LEFT, 13)
    verify(output).positionPercent(YogaEdge.VERTICAL, 17f)
    verify(output).positionPercent(YogaEdge.RIGHT, 16f)
    verify(output).positionPercent(YogaEdge.ALL, 15f)
    verify(output).widthPx(5)
    verify(output).widthPercent(50f)
    verify(output).minWidthPx(15)
    verify(output).minWidthPercent(100f)
    verify(output).maxWidthPx(25)
    verify(output).maxWidthPercent(26f)
    verify(output).heightPx(30)
    verify(output).heightPercent(31f)
    verify(output).minHeightPx(32)
    verify(output).minHeightPercent(33f)
    verify(output).maxHeightPx(34)
    verify(output).maxHeightPercent(35f)
    verify(output).aspectRatio(20f)
    verify(node)?.touchExpansionPx(YogaEdge.RIGHT, 22)
    verify(node)?.touchExpansionPx(YogaEdge.LEFT, 23)
    verify(node)?.touchExpansionPx(YogaEdge.ALL, 21)
    verify(node)?.background(background)
    verify(node)?.foreground(foreground)
    verify(node)?.wrapInView()
    verify(node)?.applyNodeInfo(anyOrNull())
    verify(node)?.visibleHeightRatio(55f)
    verify(node)?.visibleWidthRatio(56f)
    verify(node)?.visibleHandler(visibleHandler)
    verify(node)?.focusedHandler(focusedHandler)
    verify(node)?.unfocusedHandler(unfocusedHandler)
    verify(node)?.fullImpressionHandler(fullImpressionHandler)
    verify(node)?.invisibleHandler(invisibleHandler)
    verify(node)?.visibilityChangedHandler(visibleRectChangedHandler)
    verify(node)?.transitionKey(eq("transitionKey"), anyOrNull<String>())
    verify(node)?.transitionKeyType(Transition.TransitionKeyType.GLOBAL)
    verify(node)?.testKey("testKey")
    verify(node)?.stateListAnimator(stateListAnimator)
  }

  private class TestDrawableComponentWithMockInternalNode : TestComponent() {
    protected fun canResolve(): Boolean = true

    override fun resolve(resolveStateContext: ResolveStateContext, c: ComponentContext): LithoNode {
      val result = mock<LithoLayoutResult>()
      val node = mock<LithoNode>()
      val nodeInfo = mock<NodeInfo>()
      whenever(node.mutableNodeInfo()).thenReturn(nodeInfo)
      whenever(
              node.calculateLayout(
                  anyOrNull(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt()))
          .thenReturn(result)
      whenever(result.node).thenReturn(node)
      return node
    }

    public override fun resolveWithResult(
        resolveStateContext: ResolveStateContext,
        c: ComponentContext
    ): ComponentResolveResult {
      val lithoNode = resolve(resolveStateContext, c)
      return ComponentResolveResult(lithoNode, null)
    }

    class Builder(
        c: ComponentContext,
        defStyleAttr: Int,
        defStyleRes: Int,
        private var _component: Component
    ) : Component.Builder<Builder?>(c, defStyleAttr, defStyleRes, _component) {
      override fun setComponent(component: Component) {
        this._component = component
      }

      override fun getThis(): Builder = this

      override fun build(): Component = _component
    }

    companion object {
      @JvmStatic
      fun create(c: ComponentContext): Builder {
        return Builder(c, 0, 0, TestDrawableComponentWithMockInternalNode())
      }
    }
  }
}


/*
 * 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.LithoViewRule
import com.facebook.litho.testing.TestLayoutComponent
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LayoutStateEventHandlerTest {

  @JvmField @Rule val lithoViewRule: LithoViewRule = LithoViewRule()

  private var rootComponent: Component? = null
  private var nestedComponent: Component? = null

  @Before
  fun setup() {
    rootComponent =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            assertCorrectEventHandler(c.newEventHandler(1), 1, rootComponent)
            Wrapper.create(c).delegate(nestedComponent).build()
            assertCorrectEventHandler(c.newEventHandler(2), 2, rootComponent)
            Wrapper.create(c).delegate(nestedComponent).build()
            assertCorrectEventHandler(c.newEventHandler(3), 3, rootComponent)
            return TestLayoutComponent.create(c).build()
          }
        }
    nestedComponent =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            assertCorrectEventHandler(c.newEventHandler(1), 1, nestedComponent)
            return TestLayoutComponent.create(c).build()
          }
        }
  }

  @Test
  fun testNestedEventHandlerInput() {
    lithoViewRule.render { rootComponent }
  }

  companion object {
    private fun assertCorrectEventHandler(
        eventHandler: EventHandler<*>,
        expectedId: Int,
        expectedInput: Component?
    ) {
      Assertions.assertThat(eventHandler.dispatchInfo.hasEventDispatcher).isEqualTo(expectedInput)
      Assertions.assertThat(eventHandler.id).isEqualTo(expectedId)
    }
  }
}


/*
 * 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 android.os.Looper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.SizeSpec.EXACTLY
import com.facebook.litho.SizeSpec.makeSizeSpec
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.ThreadTestingUtils
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.robolectric.Shadows
import org.robolectric.annotation.LooperMode
import org.robolectric.shadows.ShadowLooper

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

  private var widthSpec = 0
  private var heightSpec = 0
  private lateinit var context: ComponentContext
  private val config =
      ComponentsConfiguration.getDefaultComponentsConfiguration().useCancelableLayoutFutures
  private lateinit var resolveThreadShadowLooper: ShadowLooper
  private lateinit var layoutThreadShadowLooper: ShadowLooper

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    ComponentsConfiguration.setDefaultComponentsConfigurationBuilder(
        ComponentsConfiguration.getDefaultComponentsConfigurationBuilder()
            .useCancelableLayoutFutures(true))
    widthSpec = makeSizeSpec(40, EXACTLY)
    heightSpec = makeSizeSpec(40, EXACTLY)
    layoutThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultLayoutThreadLooper")
                as Looper)
    resolveThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultResolveThreadLooper")
                as Looper)
  }

  private fun runToEndOfTasks() {
    resolveThreadShadowLooper.runToEndOfTasks()
    layoutThreadShadowLooper.runToEndOfTasks()
  }

  @After
  fun tearDown() {
    ComponentsConfiguration.setDefaultComponentsConfigurationBuilder(
        ComponentsConfiguration.getDefaultComponentsConfigurationBuilder()
            .useCancelableLayoutFutures(config))
  }

  @Test
  fun testStopResolvingRowChildrenIfLsfReleased() {
    val layoutStateFutureMock: TreeFuture<*> = mock { on { isReleased } doReturn false }
    val c = ComponentContext(context)
    val resolveStateContext = c.setRenderStateContextForTests()
    resolveStateContext.setLayoutStateFuture(layoutStateFutureMock)
    val wait = CountDownLatch(1)
    val child1 =
        TestChildComponent(
            wait,
            null,
            object : WaitActions {
              override fun unblock(layoutStateFuture: TreeFuture<*>?) {
                doReturn(true).`when`(layoutStateFutureMock).isReleased
              }
            })
    val child2 = TestChildComponent()
    val row = Row.create(context).child(child1).child(child2).build()
    val result = row.resolve(resolveStateContext, c)
    Assert.assertTrue(child1.hasRunLayout)
    Assert.assertFalse(child2.hasRunLayout)
    Assert.assertNull(result)
  }

  @Test
  fun testStopResolvingColumnChildrenIfLsfReleased() {
    val layoutStateFutureMock: TreeFuture<*> = mock()
    val c = ComponentContext(context)
    val resolveStateContext = c.setRenderStateContextForTests()
    resolveStateContext.setLayoutStateFuture(layoutStateFutureMock)
    val wait = CountDownLatch(1)
    val child1 =
        TestChildComponent(
            wait,
            null,
            object : WaitActions {
              override fun unblock(layoutStateFuture: TreeFuture<*>?) {
                doReturn(true).`when`(layoutStateFutureMock).isReleased
              }
            })
    val child2 = TestChildComponent()
    val column = Column.create(context).child(child1).child(child2).build()
    val result = column.resolve(resolveStateContext, c)
    Assert.assertTrue(child1.hasRunLayout)
    Assert.assertFalse(child2.hasRunLayout)
    Assert.assertNull(result)
  }

  // This test is similar to testMainWaitingOnBgBeforeRelease, except that the bg thread
  // LayoutStateFuture gets released after the sync layout is triggered. In this case the UI thread
  // should not be blocked on the bg thread anymore, because the released Lsf will return a null
  // LayoutState.
  @Test
  fun testDontWaitOnReleasedLSF() {
    val layoutStateFutures = arrayOfNulls<TreeFuture<*>?>(2)
    val waitBeforeAsserts = CountDownLatch(1)
    val scheduleSyncLayout = CountDownLatch(1)
    val finishBgLayout = CountDownLatch(1)
    val componentTree: ComponentTree
    val child1 =
        TestChildComponent(
            // Testing scenario: we schedule a LSF on bg thread which gets released before compat UI
            // thread
            // layout is scheduled.
            waitActions =
                object : WaitActions {
                  override fun unblock(layoutStateFuture: TreeFuture<*>?) {
                    // Something happens here which releases the ongoing lsf, such as a state update
                    // triggered from onCreateLayout.
                    if (layoutStateFutures[0] == null) {
                      layoutStateFutures[0] = layoutStateFuture
                      layoutStateFuture?.release()
                      scheduleSyncLayout.countDown()
                      ThreadTestingUtils.failSilentlyIfInterrupted {
                        finishBgLayout.await(5000, TimeUnit.MILLISECONDS)
                      }
                      waitBeforeAsserts.countDown()
                    } else {
                      layoutStateFutures[1] = layoutStateFuture
                      finishBgLayout.countDown()
                    }
                  }
                })
    val column_0 = Column.create(context).child(TestChildComponent()).build()
    val column = Column.create(context).child(child1).build()
    val handler = ThreadPoolLayoutHandler.getNewInstance(LayoutThreadPoolConfigurationImpl(1, 1, 5))
    componentTree =
        ComponentTree.create(context, column_0)
            .componentsConfiguration(
                ComponentsConfiguration.create().useCancelableLayoutFutures(true).build())
            .layoutThreadHandler(handler)
            .build()
    componentTree.setLithoView(LithoView(context))

    componentTree.setRootAndSizeSpecAsync(column, widthSpec, heightSpec)
    runToEndOfTasks()
    ThreadTestingUtils.failSilentlyIfInterrupted {
      scheduleSyncLayout.await(5000, TimeUnit.MILLISECONDS)
    }
    componentTree.setRootAndSizeSpecSync(column, widthSpec, heightSpec, Size())
    ThreadTestingUtils.failSilentlyIfInterrupted {
      waitBeforeAsserts.await(5000, TimeUnit.MILLISECONDS)
    }
    Assert.assertNotNull(layoutStateFutures[0])
    Assert.assertNotNull(layoutStateFutures[1])
  }

  private fun createTestStateUpdate(): StateContainer.StateUpdate {
    return StateContainer.StateUpdate(0)
  }

  private interface WaitActions {
    fun unblock(layoutStateFuture: TreeFuture<*>?)
  }

  private class TestChildComponent
  @JvmOverloads
  constructor(
      private val wait: CountDownLatch? = null,
      private val unlockFinishedLayout: CountDownLatch? = null,
      val waitActions: WaitActions? = null
  ) : Component() {
    private val layoutStateFutureList: MutableList<TreeFuture<*>?>
    var hasRunLayout = false

    override fun render(
        resolveStateContext: ResolveStateContext,
        c: ComponentContext,
        widthSpec: Int,
        heightSpec: Int
    ): RenderResult {
      waitActions?.unblock(c.layoutStateFuture)
      if (wait != null) {
        ThreadTestingUtils.failSilentlyIfInterrupted { wait.await(5000, TimeUnit.MILLISECONDS) }
      }
      hasRunLayout = true
      layoutStateFutureList.add(c.layoutStateFuture)
      unlockFinishedLayout?.countDown()
      return RenderResult(Column.create(c).build())
    }

    init {
      layoutStateFutureList = ArrayList()
    }
  }
}


/*
 * 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.SizeSpec.makeSizeSpec
import com.facebook.litho.testing.TestLayoutComponent
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 LayoutStateSizeTest {

  private lateinit var layoutState: LayoutState
  private lateinit var component: Component
  private lateinit var context: ComponentContext

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    component = TestLayoutComponent.create(context).build()
    Whitebox.setInternalState(component, "mId", COMPONENT_ID)
    val result =
        ResolveTreeFuture.resolve(context, component, TreeState(), 1, 1, null, null, null, null)
    layoutState =
        LayoutTreeFuture.layout(
            result,
            makeSizeSpec(WIDTH, SizeSpec.EXACTLY),
            makeSizeSpec(HEIGHT, SizeSpec.EXACTLY),
            1,
            1,
            false,
            null,
            null,
            null,
            null)
  }

  @Test
  fun testCompatibleSize() {
    assertThat(layoutState.isCompatibleSize(WIDTH, HEIGHT)).isTrue
  }

  @Test
  fun testIncompatibleWidthSpec() {
    assertThat(layoutState.isCompatibleSize(WIDTH + 1_000, HEIGHT)).isFalse
  }

  @Test
  fun testIncompatibleHeightSpec() {
    assertThat(layoutState.isCompatibleSize(WIDTH, HEIGHT + 1_000)).isFalse
  }

  companion object {
    private const val COMPONENT_ID = 37
    private const val WIDTH = 49
    private const val HEIGHT = 51
  }
}


/*
 * 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.SizeSpec.makeSizeSpec
import com.facebook.litho.testing.TestLayoutComponent
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 LayoutStateSpecTest {

  private var widthSpec = 0
  private var heightSpec = 0
  private lateinit var layoutState: LayoutState
  private lateinit var component: Component
  private lateinit var context: ComponentContext

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    widthSpec = makeSizeSpec(39, SizeSpec.EXACTLY)
    heightSpec = makeSizeSpec(41, SizeSpec.EXACTLY)
    component = TestLayoutComponent.create(context).build()
    Whitebox.setInternalState(component, "mId", COMPONENT_ID)
    val result =
        ResolveTreeFuture.resolve(context, component, TreeState(), 1, 1, null, null, null, null)
    layoutState =
        LayoutTreeFuture.layout(result, widthSpec, heightSpec, 1, 1, false, null, null, null, null)
  }

  @Test
  fun testCompatibleInputAndSpec() {
    assertThat(layoutState.isCompatibleComponentAndSpec(COMPONENT_ID, widthSpec, heightSpec)).isTrue
  }

  @Test
  fun testIncompatibleInput() {
    assertThat(
            layoutState.isCompatibleComponentAndSpec(COMPONENT_ID + 1_000, widthSpec, heightSpec))
        .isFalse
  }

  @Test
  fun testIncompatibleWidthSpec() {
    assertThat(
            layoutState.isCompatibleComponentAndSpec(COMPONENT_ID, widthSpec + 1_000, heightSpec))
        .isFalse
  }

  @Test
  fun testIncompatibleHeightSpec() {
    assertThat(
            layoutState.isCompatibleComponentAndSpec(COMPONENT_ID, widthSpec, heightSpec + 1_000))
        .isFalse
  }

  companion object {
    private const val COMPONENT_ID = 37
  }
}


/*
 * 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.SizeSpec.makeSizeSpec
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.TestLayoutComponent
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LayoutStateTest {

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

  @Test
  fun toRenderTree_emptyMountableOutputs_hasPositionForRoot() {
    val context = legacyLithoViewRule.context
    val result =
        ResolveTreeFuture.resolve(
            context,
            TestLayoutComponent.create(context).build(),
            TreeState(),
            -1,
            -1,
            null,
            null,
            null,
            null)
    val layoutState =
        LayoutTreeFuture.layout(
            result,
            makeSizeSpec(20, SizeSpec.EXACTLY),
            makeSizeSpec(20, SizeSpec.EXACTLY),
            -1,
            -1,
            false,
            null,
            null,
            null,
            null)
    layoutState.toRenderTree()
    assertThat(layoutState.mountableOutputCount).isEqualTo(1)
    val renderTreeNode = layoutState.getMountableOutputAt(0)
    val id = renderTreeNode.renderUnit.id
    assertThat(layoutState.getPositionForId(id)).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 androidx.core.view.ViewCompat
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.LifecycleStep.getSteps
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.LayoutWithSizeSpecLifecycleTester
import com.facebook.litho.widget.MountSpecPureRenderLifecycleTester
import com.facebook.litho.widget.Text
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

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

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

  @Test
  fun onSetRootWithoutLayoutWithSizeSpec_shouldNotCallLifecycleMethods() {
    val info: List<StepInfo> = ArrayList()
    val component: Component =
        LayoutWithSizeSpecLifecycleTester.create(legacyLithoViewRule.context).steps(info).build()
    legacyLithoViewRule.setRoot(component).idle()
    assertThat(getSteps(info))
        .describedAs("No lifecycle methods should be called")
        .containsExactly(LifecycleStep.ON_CREATE_INITIAL_STATE)
  }

  @Test
  fun onSetRootWithLayoutWithSizeSpecAtRoot_shouldCallLifecycleMethods() {
    val info: List<StepInfo> = ArrayList()
    val component: Component =
        LayoutWithSizeSpecLifecycleTester.create(legacyLithoViewRule.context).steps(info).build()
    legacyLithoViewRule.setRoot(component)
    legacyLithoViewRule.attachToWindow().measure().layout()
    assertThat(getSteps(info))
        .describedAs("Should call the lifecycle methods in expected order")
        .containsExactly(
            LifecycleStep.ON_CREATE_INITIAL_STATE, LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
  }

  @Test
  fun onSetRootWithLayoutWithSizeSpecWhichDoesNotRemeasure_shouldCallLifecycleMethods() {
    val info: List<StepInfo> = ArrayList()
    val c = legacyLithoViewRule.context
    val component: Component =
        Column.create(c)
            .child(
                LayoutWithSizeSpecLifecycleTester.create(c)
                    .steps(info)
                    .importantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES))
            .build()
    legacyLithoViewRule.setRoot(component)
    legacyLithoViewRule.attachToWindow().measure().layout()
    assertThat(getSteps(info))
        .describedAs("Should call the lifecycle methods in expected order")
        .containsExactly(
            LifecycleStep.ON_CREATE_INITIAL_STATE, LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
  }

  @Test
  fun onSetRootWithLayoutWithSizeSpecWhichRemeasure_shouldCallLifecycleMethods() {
    val info: List<StepInfo> = ArrayList()
    val c = legacyLithoViewRule.context
    val tracker = LifecycleTracker()
    val mountable: Component =
        MountSpecPureRenderLifecycleTester.create(c).lifecycleTracker(tracker).build()
    val component: Component =
        Row.create(c)
            .heightPx(100) // Ensures that nested tree is resolved only twice
            .child(LayoutWithSizeSpecLifecycleTester.create(c).steps(info).body(mountable))
            .child(Text.create(c).text("Hello World"))
            .build()
    legacyLithoViewRule.setRoot(component).attachToWindow().measure().layout()
    assertThat(getSteps(info))
        .describedAs("Should call the lifecycle methods in expected order")
        .containsExactly(
            LifecycleStep.ON_CREATE_INITIAL_STATE,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC,
            LifecycleStep.ON_CREATE_LAYOUT_WITH_SIZE_SPEC)
  }
}


/*
 * 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.graphics.Color
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.drawable.ComparableColorDrawable
import com.facebook.litho.it.R
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.rendercore.testing.ViewAssertions
import com.facebook.rendercore.testing.match.MatchNode
import com.facebook.rendercore.testing.match.ViewMatchNode
import org.junit.After
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 LegacyApplyStylesTest {

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

  @Before
  fun setup() {
    TempComponentsConfigurations.setShouldAddHostViewForRootComponent(false)
  }

  @After
  fun restoreConfiguration() {
    TempComponentsConfigurations.restoreShouldAddHostViewForRootComponent()
  }

  @Test
  fun styles_withAccessibilityAndContentDescriptionStyle_appliesAccessibilityAndContentDescription() {
    legacyLithoViewRule
        .setSizeSpecs(exactly(100), exactly(100))
        .setRoot(
            Row.create(legacyLithoViewRule.context)
                .wrapInView()
                .child(
                    Column.create(
                            legacyLithoViewRule.context,
                            0,
                            R.style.ApplyStylesTest_AccessibilityAndContentDescription)
                        .flexGrow(1f)
                        .wrapInView())
                .build())
        .measure()
        .layout()
        .attachToWindow()
    ViewAssertions.assertThat(legacyLithoViewRule.lithoView)
        .matches(
            ViewMatchNode.forType(LithoView::class.java)
                .child(
                    ViewMatchNode.forType(ComponentHost::class.java)
                        .prop("contentDescription", "Test Content Description")
                        .prop("isImportantForAccessibility", true)))
  }

  @Test
  fun styles_withDuplicateParentStateStyle_appliesDuplicateParentState() {
    legacyLithoViewRule
        .setSizeSpecs(exactly(100), exactly(100))
        .setRoot(
            Row.create(legacyLithoViewRule.context)
                .wrapInView()
                .child(
                    Column.create(
                            legacyLithoViewRule.context,
                            0,
                            R.style.ApplyStylesTest_DuplicateParentState)
                        .flexGrow(1f)
                        .wrapInView())
                .build())
        .measure()
        .layout()
        .attachToWindow()
    ViewAssertions.assertThat(legacyLithoViewRule.lithoView)
        .matches(
            ViewMatchNode.forType(LithoView::class.java)
                .child(
                    ViewMatchNode.forType(ComponentHost::class.java)
                        .prop("isDuplicateParentStateEnabled", true)))
  }

  @Test
  fun styles_withBackgroundForegroundStyle_appliesBackgroundAndForeground() {
    legacyLithoViewRule
        .setSizeSpecs(exactly(100), exactly(100))
        .setRoot(
            Row.create(legacyLithoViewRule.context)
                .wrapInView()
                .child(
                    Column.create(
                            legacyLithoViewRule.context,
                            0,
                            R.style.ApplyStylesTest_BackgroundForeground)
                        .flexGrow(1f)
                        .wrapInView())
                .build())
        .measure()
        .layout()
        .attachToWindow()
    ViewAssertions.assertThat(legacyLithoViewRule.lithoView)
        .matches(
            ViewMatchNode.forType(LithoView::class.java)
                .child(
                    ViewMatchNode.forType(ComponentHost::class.java)
                        .prop(
                            "drawables",
                            MatchNode.list(
                                MatchNode.forType(MatrixDrawable::class.java)
                                    .prop(
                                        "mountedDrawable",
                                        MatchNode.forType(ComparableColorDrawable::class.java)
                                            .prop("color", Color.WHITE)),
                                MatchNode.forType(MatrixDrawable::class.java)
                                    .prop(
                                        "mountedDrawable",
                                        MatchNode.forType(ComparableColorDrawable::class.java)
                                            .prop("color", Color.BLACK))))))
  }
}


/*
 * 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.graphics.Color
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import java.lang.Exception
import junit.framework.Assert
import org.assertj.core.api.Assertions
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyDuplicateParentChildrenStateTest {
  @JvmField @Rule val lithoViewRule: LithoViewRule = LithoViewRule()

  @Before
  @Throws(Exception::class)
  fun setUp() {
    TempComponentsConfigurations.setShouldAddHostViewForRootComponent(false)
  }

  @Test
  fun duplicateParentState_avoidedIfRedundant() {
    val component: Component =
        object : InlineLayoutSpec() {
          protected override fun onCreateLayout(c: ComponentContext): Component {
            return Column.create(c)
                .duplicateParentState(true)
                .clickHandler(c.newEventHandler(1) as EventHandler<ClickEvent>)
                .child(
                    Column.create(c)
                        .duplicateParentState(false)
                        .child(SimpleMountSpecTester.create(c).duplicateParentState(true)))
                .child(
                    Column.create(c)
                        .duplicateParentState(true)
                        .child(SimpleMountSpecTester.create(c).duplicateParentState(true)))
                .child(
                    Column.create(c)
                        .clickHandler(c.newEventHandler(2) as EventHandler<ClickEvent>)
                        .child(SimpleMountSpecTester.create(c).duplicateParentState(true)))
                .child(
                    Column.create(c)
                        .clickHandler(c.newEventHandler(3) as EventHandler<ClickEvent>)
                        .child(SimpleMountSpecTester.create(c).duplicateParentState(false)))
                .child(
                    Column.create(c)
                        .clickHandler(c.newEventHandler(3) as EventHandler<ClickEvent>)
                        .backgroundColor(Color.RED)
                        .foregroundColor(Color.RED))
                .child(Column.create(c).backgroundColor(Color.BLUE).foregroundColor(Color.BLUE))
                .build()
          }
        }
    val testLithoView = lithoViewRule.render(widthPx = 100, heightPx = 100) { component }
    val layoutState = testLithoView.componentTree.mainThreadLayoutState!!
    Assertions.assertThat(layoutState.mountableOutputCount).isEqualTo(12)
    Assert.assertTrue(
        "Clickable root output has duplicate state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(0)).flags))
    Assert.assertFalse(
        "Parent doesn't duplicate host state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(1)).flags))
    Assert.assertTrue(
        "Parent does duplicate host state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(2)).flags))
    Assert.assertTrue(
        "Drawable duplicates clickable parent state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(4)).flags))
    Assert.assertFalse(
        "Drawable doesn't duplicate clickable parent state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(6)).flags))
    Assert.assertTrue(
        "Background should duplicate clickable node state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(8)).flags))
    Assert.assertTrue(
        "Foreground should duplicate clickable node state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(9)).flags))
    Assert.assertFalse(
        "Background should duplicate non-clickable node state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(10)).flags))
    Assert.assertFalse(
        "Foreground should duplicate non-clickable node state",
        LithoRenderUnit.isDuplicateParentState(
            LithoRenderUnit.getRenderUnit(layoutState.getMountableOutputAt(11)).flags))
  }

  @After
  fun restoreConfiguration() {
    TempComponentsConfigurations.restoreShouldAddHostViewForRootComponent()
  }
}