/*
 * 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.testing.LegacyLithoViewRule
import com.facebook.litho.testing.assertMatches
import com.facebook.litho.testing.child
import com.facebook.litho.testing.match
import com.facebook.litho.testing.setRoot
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
import com.facebook.yoga.YogaAlign
import com.facebook.yoga.YogaEdge
import com.facebook.yoga.YogaPositionType
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [StyleCompat] */
@RunWith(LithoTestRunner::class)
class StyleCompatTest {

  @Rule @JvmField val lithoViewRule = LegacyLithoViewRule()

  @Test
  fun widthAndHeight_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot { Row(style = StyleCompat.widthDip(100f).heightDip(200f).build()) }
        .assertMatches(match<LithoView> { bounds(0, 0, 100, 200) })
  }

  @Test
  fun widthPercentAndHeightPercent_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).heightPx(100).build()) {
            child(
                Row(style = StyleCompat.heightPercent(50f).widthPercent(50f).wrapInView().build()))
          }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(0, 0, 50, 50) }
            })
  }

  @Test
  fun maxWidth_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.heightPx(100).maxWidthPx(200).build()) {
            child(Row(style = StyleCompat.widthPx(500).build()))
          }
        }
        .assertMatches(match<LithoView> { bounds(0, 0, 200, 100) })
  }

  @Test
  fun minWidth_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot { Row(style = StyleCompat.heightPx(100).minWidthPx(200).build()) }
        .assertMatches(match<LithoView> { bounds(0, 0, 200, 100) })
  }

  @Test
  fun maxHeight_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).maxHeightPx(200).build()) {
            child(Row(style = StyleCompat.heightPx(500).build()))
          }
        }
        .assertMatches(match<LithoView> { bounds(0, 0, 100, 200) })
  }

  @Test
  fun minHeight_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot { Row(style = StyleCompat.widthPx(100).minHeightPx(200).build()) }
        .assertMatches(match<LithoView> { bounds(0, 0, 100, 200) })
  }

  @Test
  fun flexBasis_whenSet_becomesChildWidth() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).heightPx(100).build()) {
            child(Row(style = StyleCompat.flexBasisPx(50).wrapInView().build()))
          }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(0, 0, 50, 100) }
            })
  }

  @Test
  fun flexGrow_whenSet_childTakesWholeSpace() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).heightPx(100).build()) {
            child(Row(style = StyleCompat.flexGrow(1f).wrapInView().build()))
          }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(0, 0, 100, 100) }
            })
  }

  @Test
  fun flexShrink_whenSet_makesChildAsSmallAsPossible() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style = StyleCompat.widthPx(100).heightPx(100).build()) {
                child(
                    Row(
                        style =
                            StyleCompat.heightPx(100)
                                .minWidthPx(50)
                                .flexShrink(1f)
                                .wrapInView()
                                .build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(0, 0, 50, 100) }
            })
  }

  @Test
  fun alignSelf_whenSet_isRespected() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).heightPx(100).build()) {
            child(
                Row(
                    style =
                        StyleCompat.widthPx(100).alignSelf(YogaAlign.STRETCH).wrapInView().build()))
          }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(0, 0, 100, 100) }
            })
  }

  @Test
  fun padding_whenGranularPaddingSet_isRespected() {
    val left = 10
    val top = 20
    val right = 30
    val bottom = 40

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style =
                  StyleCompat.widthPx(100)
                      .heightPx(100)
                      .paddingPx(YogaEdge.START, left)
                      .paddingPx(YogaEdge.TOP, top)
                      .paddingPx(YogaEdge.END, right)
                      .paddingPx(YogaEdge.BOTTOM, bottom)
                      .build()) {
                child(Row(style = StyleCompat.flexGrow(1f).wrapInView().build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(left, top, 100 - left - right, 100 - top - bottom) }
            })
  }

  @Test
  fun padding_whenHorizontalVerticalPaddingSet_isRespected() {
    val horizontal = 10
    val vertical = 20

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style =
                  StyleCompat.widthPx(100)
                      .heightPx(100)
                      .paddingPx(YogaEdge.HORIZONTAL, horizontal)
                      .paddingPx(YogaEdge.VERTICAL, vertical)
                      .build()) {
                child(Row(style = StyleCompat.flexGrow(1f).wrapInView().build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> {
                bounds(horizontal, vertical, 100 - 2 * horizontal, 100 - 2 * vertical)
              }
            })
  }

  @Test
  fun padding_whenAllPaddingSet_isRespected() {
    val padding = 32

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style =
                  StyleCompat.widthPx(100).heightPx(100).paddingPx(YogaEdge.ALL, padding).build()) {
                child(Row(style = StyleCompat.flexGrow(1f).wrapInView().build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> {
                bounds(padding, padding, 100 - 2 * padding, 100 - 2 * padding)
              }
            })
  }

  @Test
  fun margin_whenGranularMarginSet_isRespected() {
    val left = 10
    val top = 20
    val right = 30
    val bottom = 40

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style = StyleCompat.widthPx(100).heightPx(100).build()) {
                child(
                    Row(
                        style =
                            StyleCompat.marginPx(YogaEdge.START, left)
                                .marginPx(YogaEdge.TOP, top)
                                .marginPx(YogaEdge.END, right)
                                .marginPx(YogaEdge.BOTTOM, bottom)
                                .flexGrow(1f)
                                .wrapInView()
                                .build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(left, top, 100 - left - right, 100 - top - bottom) }
            })
  }

  @Test
  fun margin_whenHorizontalVerticalMarginSet_isRespected() {
    val horizontal = 10
    val vertical = 20

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style = StyleCompat.widthPx(100).heightPx(100).build()) {
                child(
                    Row(
                        style =
                            StyleCompat.marginPx(YogaEdge.HORIZONTAL, horizontal)
                                .marginPx(YogaEdge.VERTICAL, vertical)
                                .flexGrow(1f)
                                .wrapInView()
                                .build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> {
                bounds(horizontal, vertical, 100 - 2 * horizontal, 100 - 2 * vertical)
              }
            })
  }

  @Test
  fun margin_whenAllMarginSet_isRespected() {
    val margin = 32

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(
              alignItems = YogaAlign.STRETCH,
              style = StyleCompat.widthPx(100).heightPx(100).build()) {
                child(
                    Row(
                        style =
                            StyleCompat.marginPx(YogaEdge.ALL, margin)
                                .flexGrow(1f)
                                .wrapInView()
                                .build()))
              }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(margin, margin, 100 - 2 * margin, 100 - 2 * margin) }
            })
  }

  @Test
  fun position_whenSet_isRespected() {
    val left = 10
    val top = 20
    val right = 30
    val bottom = 40

    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row(style = StyleCompat.widthPx(100).heightPx(100).build()) {
            child(
                Row(
                    style =
                        StyleCompat.positionType(YogaPositionType.ABSOLUTE)
                            .positionPx(YogaEdge.START, left)
                            .positionPx(YogaEdge.TOP, top)
                            .positionPx(YogaEdge.END, right)
                            .positionPx(YogaEdge.BOTTOM, bottom)
                            .wrapInView()
                            .build()))
          }
        }
        .assertMatches(
            match<LithoView> {
              bounds(0, 0, 100, 100)
              child<ComponentHost> { bounds(left, top, 100 - left - right, 100 - top - bottom) }
            })
  }

  /**
   * Test is using [Layout] and [NodeInfo] classes as a workaround for the issue with 'libyoga.so
   * already loaded in another classloader exception' caused by multiple ClassLoaders trying to load
   * Yoga when using @Config to specify a different target sdk. See:
   * https://www.internalfb.com/intern/staticdocs/litho/docs/testing/unit-testing/
   */
  @Test
  fun border_whenSet_isRespected() {
    class ComponentWithBorder : KComponent() {
      override fun ComponentScope.render(): Component {
        return Row(
            style =
                StyleCompat.border(
                        Border.create(context)
                            .color(YogaEdge.LEFT, Color.BLUE)
                            .color(YogaEdge.TOP, Color.RED)
                            .color(YogaEdge.RIGHT, Color.BLACK)
                            .color(YogaEdge.BOTTOM, Color.WHITE)
                            .radiusDip(Border.Corner.TOP_LEFT, 5f)
                            .radiusDip(Border.Corner.TOP_RIGHT, 6f)
                            .radiusDip(Border.Corner.BOTTOM_RIGHT, 7f)
                            .radiusDip(Border.Corner.BOTTOM_LEFT, 8f)
                            .build())
                    .build())
      }
    }

    val node = LegacyLithoViewRule.getRootLayout(lithoViewRule, ComponentWithBorder())?.node
    assertThat(node?.borderColors)
        .isEqualTo(intArrayOf(Color.BLUE, Color.RED, Color.BLACK, Color.WHITE))
    assertThat(node?.borderRadius).isEqualTo(floatArrayOf(5f, 6f, 7f, 8f))
  }

  /** See comment on [border_whenSet_isRespected] above. */
  @Test
  fun transition_whenSet_isRespected() {
    class ComponentWithTransition : KComponent() {
      override fun ComponentScope.render(): Component {
        return Row(
            style =
                StyleCompat.transitionKey(context, "test", Transition.TransitionKeyType.GLOBAL)
                    .build())
      }
    }

    val node = LegacyLithoViewRule.getRootLayout(lithoViewRule, ComponentWithTransition())?.node
    assertThat(node?.transitionKeyType).isEqualTo(Transition.TransitionKeyType.GLOBAL)
    assertThat(node?.transitionKey).isEqualTo("test")
  }
}


/*
 * 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.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate
import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.Mode
import org.assertj.core.api.Java6Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/** Unit tests [Style]. */
@RunWith(JUnit4::class)
class StyleTest {

  private class TestItemField : StyleItemField {
    companion object {
      val instance = TestItemField()
    }
  }

  @DataClassGenerate(toString = Mode.OMIT, equalsHashCode = Mode.KEEP)
  private data class TestStyleItem(override val value: String) : StyleItem<String> {

    override val field = TestItemField.instance

    override fun applyCommonProps(context: ComponentContext, commonProps: CommonProps) = Unit
  }

  @Test
  fun style_styleItemAdded_getNewInstanceWithOldAndNewItems() {
    val style1 = Style + TestStyleItem("first")
    val style2 = style1 + TestStyleItem("second")

    assertThat(style1).isNotEqualTo(style2)
    assertThat(style1.toStringList()).containsExactly("first")
    assertThat(style2.toStringList()).containsExactly("first", "second")
  }

  @Test
  fun style_stylesAdded_getNewInstanceInRightOrder() {
    val style1 = Style + TestStyleItem("first1") + TestStyleItem("first2")
    val style2 = Style + TestStyleItem("second1") + TestStyleItem("second2")
    val combined = style1 + style2

    assertThat(style1.toStringList()).containsExactly("first1", "first2")
    assertThat(style2.toStringList()).containsExactly("second1", "second2")
    assertThat(combined.toStringList()).containsExactly("first1", "first2", "second1", "second2")
  }

  @Test
  fun style_stylesAndStyleItemsAdded_getNewStyleInRightOrder() {
    val style1 = Style + TestStyleItem("first")
    val style2 = Style + TestStyleItem("second")
    val combined = style1 + style2 + TestStyleItem("third")

    assertThat(combined.toStringList()).containsExactly("first", "second", "third")
  }

  fun Style.testStyleItem(name: String): Style = this + TestStyleItem(name)

  @Test
  fun style_equals_itemEquality() {
    assertThat(Style.testStyleItem("A")).isEqualTo(Style.testStyleItem("A"))
    assertThat(Style.testStyleItem("A")).isNotEqualTo(Style.testStyleItem("B"))

    assertThat(Style.testStyleItem("A").testStyleItem("1"))
        .isEqualTo(Style.testStyleItem("A").testStyleItem("1"))
    assertThat(Style.testStyleItem("A").testStyleItem("1"))
        .isNotEqualTo(Style.testStyleItem("B").testStyleItem("1"))
    assertThat(Style.testStyleItem("A").testStyleItem("1"))
        .isNotEqualTo(Style.testStyleItem("A").testStyleItem("2"))
  }

  private fun Style.toStringList(): List<String> {
    val list = mutableListOf<String>()
    forEach { list.add((it as TestStyleItem).value) }
    return list
  }
}


/*
 * 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.core.height
import com.facebook.litho.core.width
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.view.onClick
import com.facebook.litho.view.viewTag
import com.facebook.litho.widget.collection.LazyList
import java.util.concurrent.atomic.AtomicInteger
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

/** Tests for [useCallback] and capturing of props/state within collections lambdas. */
@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class UseCallbackTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun useCallbackWithCollection_whenUseCallbackCapturesState_stateInCallbackIsUpToDate() {
    val renderCount = AtomicInteger(0)
    class CollectionWithSelectedRows(
        private val data: List<Int>,
        private val onSelectedChanged: (List<Int>) -> Unit,
        private val renderCount: AtomicInteger,
    ) : KComponent() {
      override fun ComponentScope.render(): Component? {
        val selected = useState { listOf<Int>() }

        useEffect(selected) {
          onSelectedChanged(selected.value)
          null
        }

        val clickCallback = useCallback { selectedItem: Int ->
          selected.updateSync(selected.value + listOf(selectedItem))
        }

        return LazyList {
          data.forEach { item ->
            child(
                id = item,
                component =
                    CollectionRow(
                        style = Style.viewTag("item_$item"),
                        item = item,
                        onRowClick = clickCallback,
                        renderCount = renderCount))
          }
        }
      }
    }

    val selectionEvents = mutableListOf<List<Int>>()
    val lithoView =
        lithoViewRule.render(widthPx = 1000, heightPx = 1000) {
          CollectionWithSelectedRows(
              data = listOf(1, 2, 3, 4, 5),
              onSelectedChanged = { list -> selectionEvents.add(list) },
              renderCount = renderCount)
        }

    assertThat(renderCount.get()).isEqualTo(5)

    lithoViewRule.act(lithoView) { clickOnTag("item_1") }

    assertThat(renderCount.get()).isEqualTo(5)

    lithoViewRule.act(lithoView) { clickOnTag("item_2") }

    assertThat(selectionEvents).containsExactly(listOf(), listOf(1), listOf(1, 2))
    assertThat(renderCount.get()).isEqualTo(5)
  }

  @Test
  fun useCallbackWithCollection_whenUseCallbackCapturesProps_propsAreUpToDate() {
    val renderCount = AtomicInteger(0)
    class CollectionWithUseCallback(
        private val data: List<Int>,
        private val rowClickedTag: String,
        private val onRowClick: (String) -> Unit,
        private val renderCount: AtomicInteger,
    ) : KComponent() {
      override fun ComponentScope.render(): Component? {
        val clickCallback = useCallback { _: Int -> onRowClick(rowClickedTag) }
        return LazyList {
          data.forEach { item ->
            child(
                id = item,
                component =
                    CollectionRow(
                        style = Style.viewTag("item_$item"),
                        item = item,
                        onRowClick = clickCallback,
                        renderCount = renderCount))
          }
        }
      }
    }

    val rowClickedEvents = mutableListOf<String>()
    val lithoView =
        lithoViewRule.render(widthPx = 1000, heightPx = 1000) {
          CollectionWithUseCallback(
              data = listOf(1, 2, 3, 4, 5),
              rowClickedTag = "setRoot1",
              onRowClick = { data -> rowClickedEvents.add(data) },
              renderCount = renderCount)
        }

    assertThat(renderCount.get()).isEqualTo(5)

    lithoViewRule.act(lithoView) { clickOnTag("item_1") }
    lithoViewRule.act(lithoView) { clickOnTag("item_2") }

    assertThat(renderCount.get()).isEqualTo(5)

    lithoView.setRoot(
        CollectionWithUseCallback(
            data = listOf(1, 2, 3, 4, 5),
            rowClickedTag = "setRoot2",
            onRowClick = { data -> rowClickedEvents.add(data) },
            renderCount = renderCount))
    lithoViewRule.idle()

    assertThat(renderCount.get()).isEqualTo(5)

    lithoViewRule.act(lithoView) { clickOnTag("item_1") }
    lithoViewRule.act(lithoView) { clickOnTag("item_2") }

    assertThat(renderCount.get()).isEqualTo(5)
    assertThat(rowClickedEvents).containsExactly("setRoot1", "setRoot1", "setRoot2", "setRoot2")
  }

  /**
   * This ends up showing correct behavior since the updateSync lambda will receive the latest state
   * value at the time its invoked (i.e. it does not use captured state).
   */
  @Test
  fun collection_whenCallbackUsesLambdaStateUpdate_stateInCallbackIsUpToDate() {
    val renderCount = AtomicInteger(0)
    class CollectionWithSelectedRows(
        private val data: List<Int>,
        private val onSelectedChanged: (List<Int>) -> Unit,
        private val renderCount: AtomicInteger,
    ) : KComponent() {
      override fun ComponentScope.render(): Component? {
        val selected = useState { listOf<Int>() }

        useEffect(selected) {
          onSelectedChanged(selected.value)
          null
        }

        val onClick = useCallback { item: Int ->
          selected.updateSync { oldList -> oldList + listOf(item) }
        }

        return LazyList {
          data.forEach { item ->
            child(
                id = item,
                component =
                    CollectionRow(
                        style = Style.viewTag("item_$item"),
                        item = item,
                        onRowClick = onClick,
                        renderCount = renderCount))
          }
        }
      }
    }

    val selectionEvents = mutableListOf<List<Int>>()
    val lithoView =
        lithoViewRule.render(widthPx = 1000, heightPx = 1000) {
          CollectionWithSelectedRows(
              data = listOf(1, 2, 3, 4, 5),
              onSelectedChanged = { list -> selectionEvents.add(list) },
              renderCount = renderCount)
        }

    assertThat(renderCount.get()).isEqualTo(5)

    lithoViewRule.act(lithoView) { clickOnTag("item_1") }
    lithoViewRule.act(lithoView) { clickOnTag("item_2") }

    assertThat(renderCount.get()).isEqualTo(5)
    assertThat(selectionEvents).containsExactly(listOf(), listOf(1), listOf(1, 2))
  }
}

private class CollectionRow(
    val style: Style,
    val item: Int,
    val onRowClick: ((Int) -> Unit)? = null,
    val renderCount: AtomicInteger
) : KComponent() {
  override fun ComponentScope.render(): Component? {
    renderCount.incrementAndGet()
    return Row(
        style = Style.onClick { onRowClick?.invoke(item) }.width(100.px).height(100.px) + style)
  }
}


/*
 * 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.kotlin.widget.Text
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.setRoot
import com.facebook.litho.testing.testrunner.LithoTestRunner
import java.util.concurrent.atomic.AtomicInteger
import org.assertj.core.api.Java6Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Tests for [useRef]. */
@RunWith(LithoTestRunner::class)
class UseRefTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun useRef_onMutation_doesNotTriggerLayout() {
    val logCount = AtomicInteger()
    val renderCount = AtomicInteger()

    class UseRefTestComponent(
        private val logCount: AtomicInteger,
        private val renderCount: AtomicInteger,
        private val seq: Int
    ) : KComponent() {
      override fun ComponentScope.render(): Component? {
        val hasLogged = useRef { false }
        if (!hasLogged.value) {
          logCount.incrementAndGet()
          hasLogged.value = true
        }

        renderCount.incrementAndGet()

        return Text(text = "Test $seq")
      }
    }

    val testLithoView =
        lithoViewRule.render {
          UseRefTestComponent(logCount = logCount, renderCount = renderCount, seq = 0)
        }

    assertThat(logCount.get()).isEqualTo(1)
    assertThat(renderCount.get()).isEqualTo(1)

    testLithoView.setRoot(
        UseRefTestComponent(logCount = logCount, renderCount = renderCount, seq = 0))

    assertThat(logCount.get()).isEqualTo(1)
    assertThat(renderCount.get()).isEqualTo(2)
  }
}


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

package com.facebook.litho.accessibility

import android.view.View
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.EventHandler
import com.facebook.litho.KComponent
import com.facebook.litho.OnInitializeAccessibilityEventEvent
import com.facebook.litho.OnInitializeAccessibilityNodeInfoEvent
import com.facebook.litho.OnPopulateAccessibilityEventEvent
import com.facebook.litho.OnPopulateAccessibilityNodeEvent
import com.facebook.litho.OnRequestSendAccessibilityEventEvent
import com.facebook.litho.PerformAccessibilityActionEvent
import com.facebook.litho.Row
import com.facebook.litho.SendAccessibilityEventEvent
import com.facebook.litho.SendAccessibilityEventUncheckedEvent
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
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
import org.mockito.kotlin.mock

/** Unit tests for accessibility styles. */
@RunWith(LithoTestRunner::class)
class AccessibilityStylesTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun contentDescription_whenSet_isSetOnView() {
    val testLithoView =
        lithoViewRule.render {
          Row(style = Style.width(200.px).height(200.px).contentDescription("Accessibility Test"))
        }

    assertThat(testLithoView.lithoView.contentDescription).isEqualTo("Accessibility Test")
  }

  @Test
  fun contentDescription_whenNull_isNotSet() {
    val testLithoView =
        lithoViewRule.render {
          Row(style = Style.width(200.px).height(200.px).contentDescription(null))
        }

    assertThat(testLithoView.lithoView.contentDescription).isNull()
  }

  @Test
  fun importantForAccessibility_whenSet_isSetOnView() {
    val testLithoView =
        lithoViewRule.render {
          Row(
              style =
                  Style.width(200.px)
                      .height(200.px)
                      .importantForAccessibility(ImportantForAccessibility.YES))
        }

    assertThat(testLithoView.lithoView.importantForAccessibility)
        .isEqualTo(View.IMPORTANT_FOR_ACCESSIBILITY_YES)
  }

  /**
   * Test is using [Layout] and [NodeInfo] classes as a workaround to be able to get the
   * onInitializeAccessibilityNodeInfoHandler value from the Node
   */
  @Test
  fun onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onInitializeAccessibilityNodeInfoHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onInitializeAccessibilityNodeInfo_whenSet_isSetOnView() {
    val eventHandler: EventHandler<OnInitializeAccessibilityNodeInfoEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onInitializeAccessibilityNodeInfo { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onInitializeAccessibilityNodeInfoHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onInitializeAccessibilityEvent_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onInitializeAccessibilityEventHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onInitializeAccessibilityEvent_whenSet_isSetOnView() {
    val eventHandler: EventHandler<OnInitializeAccessibilityEventEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onInitializeAccessibilityEvent { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onInitializeAccessibilityEventHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onPopulateAccessibilityEvent_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onPopulateAccessibilityEventHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onPopulateAccessibilityEvent_whenSet_isSetOnView() {
    val eventHandler: EventHandler<OnPopulateAccessibilityEventEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onPopulateAccessibilityEvent { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onPopulateAccessibilityEventHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onPopulateAccessibilityNode_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onPopulateAccessibilityNodeHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onPopulateAccessibilityNode_whenSet_isSetOnView() {
    val eventHandler: EventHandler<OnPopulateAccessibilityNodeEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onPopulateAccessibilityNode { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onPopulateAccessibilityNodeHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onRequestSendAccessibilityEvent_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onRequestSendAccessibilityEventHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun onRequestSendAccessibilityEvent_whenSet_isSetOnView() {
    val eventHandler: EventHandler<OnRequestSendAccessibilityEventEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onRequestSendAccessibilityEvent { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.onRequestSendAccessibilityEventHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun performAccessibilityAction_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.performAccessibilityActionHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun performAccessibilityAction_whenSet_isSetOnView() {
    val eventHandler: EventHandler<PerformAccessibilityActionEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.performAccessibilityAction { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.performAccessibilityActionHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun sendAccessibilityEvent_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.sendAccessibilityEventHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun sendAccessibilityEvent_whenSet_isSetOnView() {
    val eventHandler: EventHandler<SendAccessibilityEventEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.sendAccessibilityEvent { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.sendAccessibilityEventHandler).isNotNull
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun sendAccessibilityEventUnchecked_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.sendAccessibilityEventUncheckedHandler).isNull()
  }

  /** See comment on [onInitializeAccessibilityNodeInfo_whenNotSet_isNotSetOnView] above. */
  @Test
  fun sendAccessibilityEventUnchecked_whenSet_isSetOnView() {
    val eventHandler: EventHandler<SendAccessibilityEventUncheckedEvent> = mock()

    class TestComponentWithHandler : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.sendAccessibilityEventUnchecked { eventHandler })
      }
    }
    val testLithoView = lithoViewRule.render { TestComponentWithHandler() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.sendAccessibilityEventUncheckedHandler).isNotNull
  }

  @Test
  fun accessibilityRole_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px).accessibilityRole(null))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.accessibilityRole).isNull()
  }

  @Test
  fun accessibilityRoleDescription_whenNotSet_isNotSetOnView() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.width(200.px).accessibilityRoleDescription(null))
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    val node = testLithoView.currentRootNode?.node
    val nodeInfo = node?.nodeInfo
    assertThat(nodeInfo?.accessibilityRoleDescription).isNull()
  }
}


/*
 * 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.animated

import android.os.Looper.getMainLooper
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.DynamicValue
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
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
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.LooperMode

@LooperMode(LooperMode.Mode.PAUSED)
@RunWith(LithoTestRunner::class)
class AnimatedTest {

  // TODO(t112256774): Re-enable AnimatedTest tests. See https://fburl.com/h50b38s9 for more details

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  private lateinit var listener: AnimationFinishListener
  private lateinit var listener2: AnimationFinishListener
  private lateinit var listener3: AnimationFinishListener

  @Test(expected = IllegalStateException::class)
  fun startAnimation_called_twice_expect_IllegalStateException() {
    listener = mock()
    listener2 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    val animation2 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener2)
    val sequence = Animated.sequence(animation, animation2)
    sequence.start()
    sequence.start()
    shadowOf(getMainLooper()).idle()
  }

  @Test
  fun cancelAnimation_called_twice_expect_one_cancel_call_behaviour() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    val animation2 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener2)
    val sequence = Animated.sequence(animation, animation2)
    sequence.addListener(listener3)
    sequence.start()
    sequence.cancel()
    sequence.cancel()
    shadowOf(getMainLooper()).idle()
    verify(
            listener,
            times(1).description("timing animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(
            listener,
            never()
                .description(
                    "timing animation listener didn't call onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description("main sequence animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(listener2, never().description("timing2 animation listener not invoked")).onFinish(any())
  }

  @Test
  fun timingAnimation_whenAnimationFinish_alphaValueChange() {
    val alphaProgress = DynamicValue(0f)
    val animation = Animated.timing(target = alphaProgress, to = 1f, duration = 1000)
    val testLithoview = lithoViewRule.render { TestComponent(alphaProgress = alphaProgress) }

    val view = testLithoview.lithoView
    assertThat(view.alpha).isEqualTo(0f).describedAs("initial value")
    animation.start()
    shadowOf(getMainLooper()).idle()
    assertThat(view.alpha).isEqualTo(1f).describedAs("value after animation")
  }

  @Test
  fun timingAnimation_whenAnimationFinish_onFinishCallbackCalled() {
    listener = mock()
    listener2 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 1000, animationFinishListener = listener)
    animation.addListener(listener2)
    animation.start()
    shadowOf(getMainLooper()).idle()
    verify(listener, times(1).description("timing animation listener should finish"))
        .onFinish(false)
    verify(
            listener,
            never()
                .description("timing animation listener shouldnt call onFinish(cancelled = true)"))
        .onFinish(true)
    verify(listener2, times(1).description("main animation listener should finish")).onFinish(false)
    verify(
            listener2,
            never().description("main animation listener shouldnt call onFinish(cancelled = true)"))
        .onFinish(true)
  }

  @Test
  fun springAnimation_whenAnimationFinish_onFinishCallbackCalled() {
    listener = mock()
    listener2 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.spring(
            target = alphaProgress,
            to = 1f,
            SpringConfig(stiffness = 50f, dampingRatio = 0.5f),
            animationFinishListener = listener)
    animation.addListener(listener2)
    animation.start()
    shadowOf(getMainLooper()).idle()
    verify(listener, times(1).description("timing animation listener should finish"))
        .onFinish(false)
    verify(
            listener,
            never()
                .description("timing animation listener shouldnt call onFinish(cancelled = true)"))
        .onFinish(true)
    verify(listener2, times(1).description("main animation listener should finish")).onFinish(false)
    verify(
            listener2,
            never().description("main animation listener shouldnt call onFinish(cancelled = true)"))
        .onFinish(true)
  }

  @Test
  fun sequenceAnimation_whenMainAnimationCancelledImmediately_onCancelCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    val animation2 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener2)
    val sequence = Animated.sequence(animation, animation2)
    sequence.addListener(listener3)
    sequence.start()
    sequence.cancel()
    shadowOf(getMainLooper()).idle()
    // verify that if we cancel main animation straight away the first animation and the main
    // animation onCancel listeners will be invoked
    verify(
            listener,
            times(1).description("timing animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(
            listener,
            never()
                .description(
                    "timing animation listener didn't call onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description("main sequence animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(listener2, never().description("timing2 animation listener not invoked")).onFinish(any())
  }

  @Test
  fun sequenceAnimation_whenAnimationCancelledImmediately_onCancelCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    val animation2 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener2)
    val sequence = Animated.sequence(animation, animation2)
    sequence.addListener(listener3)
    sequence.start()
    animation.cancel()
    shadowOf(getMainLooper()).idle()
    // verify that if we cancel first animation straight away the first animation and the main
    // animation onCancel listeners will be invoked
    verify(
            listener,
            times(1).description("timing animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(
            listener,
            never()
                .description(
                    "timing animation listener didn't onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description("main sequence animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(listener2, never().description("timing2 animation listener not invoked")).onFinish(any())
  }

  @Test
  fun sequenceAnimation_whenSpringAnimationCancelledImmediately_onCancelCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    val animation =
        Animated.spring(
            target = alphaProgress,
            to = 1f,
            SpringConfig(stiffness = 50f, dampingRatio = 0.5f),
            animationFinishListener = listener)
    val animation2 =
        Animated.spring(
            target = alphaProgress,
            to = 1f,
            SpringConfig(stiffness = 50f, dampingRatio = 0.5f),
            animationFinishListener = listener2)
    val sequence = Animated.sequence(animation, animation2)
    sequence.addListener(listener3)
    sequence.start()
    animation.cancel()
    shadowOf(getMainLooper()).idle()
    // verify that if we cancel first animation straight away the first animation and the main
    // animation onCancel listeners will be invoked
    verify(
            listener,
            times(1).description("spring animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(
            listener,
            never()
                .description(
                    "spring animation listener didnt called onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description("main sequence animation listener called onFinish(cancelled = true)"))
        .onFinish(true)
    verify(
            listener3,
            never()
                .description(
                    "main sequence animation listener didnt called onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(listener2, never().description("spring2 animation listener not invoked")).onFinish(any())
  }

  @Test
  fun sequenceAnimation_whenAnimationCancelledFromSecondAnimation_onCancelCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    var animation2: AnimatedAnimation? = null
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    animation2 =
        Animated.timing(
            target = alphaProgress,
            to = 1f,
            duration = 2000,
            animationFinishListener = listener2,
            onUpdate = { animation2?.cancel() })
    val sequence = Animated.sequence(animation, animation2)
    sequence.addListener(listener3)
    sequence.start()
    shadowOf(getMainLooper()).idle()
    // verify that if we cancel second animation after it started the first animation onFinish
    // listener will be invoked, the second animation OnCancel listener will be
    // invoked and the main animation onCancel listener will be invoked
    verify(
            listener,
            times(1)
                .description("timing animation listener called onFinish(false) as it was finished"))
        .onFinish(false)
    verify(
            listener2,
            times(1)
                .description(
                    "timing 2 animation listener called onFinish(true) as it was cancelled"))
        .onFinish(true)
    verify(
            listener2,
            never()
                .description(
                    "timing 2 animation listener didnt called onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description(
                    "main sequence animation listener called onFinish(true) as it was cancelled"))
        .onFinish(true)
  }

  @Test
  fun parralelAnimation_whenAnimationCancelledFromSecondAnimation_onCancelCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    var animation2: AnimatedAnimation? = null
    val animation =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 2000, animationFinishListener = listener)
    animation2 =
        Animated.timing(
            target = alphaProgress,
            to = 1f,
            duration = 2000,
            animationFinishListener = listener2,
            onUpdate = { animation2?.cancel() })
    val parallel = Animated.parallel(animation, animation2)
    parallel.addListener(listener3)
    parallel.start()
    shadowOf(getMainLooper()).idle()
    // verify that if we cancel second animation after it started the first animation onFinish
    // listener will be invoked, the second animation OnCancel and OnFInish listeners will be
    // invoked and the main animation onCancel listener will be invoked
    verify(
            listener,
            times(1)
                .description("timing animation listener called onFinish(true) as it was cancelled"))
        .onFinish(true)
    verify(
            listener,
            never()
                .description(
                    "timing animation listener didnt called onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener2,
            times(1)
                .description(
                    "timing 2 animation listener called onFinish(true) as it was cancelled"))
        .onFinish(true)
    verify(
            listener2,
            never()
                .description(
                    "timing 2 animation listenerdidnt called onFinish(cancelled = false) as it was cancelled"))
        .onFinish(false)
    verify(
            listener3,
            times(1)
                .description(
                    "main sequence animation listener called onFinish(true) as it was cancelled"))
        .onFinish(true)
  }

  @Test
  fun sequenceAnimation_whenAnimationFinish_alphaAndXValueChange() {
    val alphaProgress = DynamicValue(0f)
    val xProgress = DynamicValue(100f)
    val animation1 = Animated.timing(target = alphaProgress, to = 1f, duration = 1000)
    val animation2 = Animated.timing(target = xProgress, to = 200f, duration = 1000)
    val testLithoview =
        lithoViewRule.render { TestComponent(alphaProgress = alphaProgress, xProgress = xProgress) }

    val view = testLithoview.lithoView
    assertThat(view.alpha).isEqualTo(0f).describedAs("alpha initial value")
    assertThat(view.translationX).isEqualTo(100f).describedAs("translationX initial value")
    val sequence = Animated.sequence(animation1, animation2)
    sequence.start()
    shadowOf(getMainLooper()).idle()
    assertThat(view.alpha).isEqualTo(1f).describedAs("alpha value after animation")
    assertThat(view.translationX).isEqualTo(200f).describedAs("translationX value after animation")
  }

  @Test
  fun sequenceAnimation_whenAnimationFinish_onFinishCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()

    val alphaProgress = DynamicValue(0f)
    val xProgress = DynamicValue(100f)
    val animation1 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 1000, animationFinishListener = listener2)
    val animation2 =
        Animated.timing(
            target = xProgress, to = 200f, duration = 1000, animationFinishListener = listener3)

    val sequence = Animated.sequence(animation1, animation2)
    sequence.addListener(listener)
    sequence.start()
    shadowOf(getMainLooper()).idle()

    verify(listener, times(1).description("sequence animation listener should finish"))
        .onFinish(false)
    verify(listener2, times(1).description("first timing animation listener should finish"))
        .onFinish(false)
    verify(listener3, times(1).description("second timing animation listener should finish"))
        .onFinish(false)
  }

  @Test
  fun loopAnimation_whenAnimationFinish_alphaAndXValueChange() {
    val alphaProgress = DynamicValue(0f)
    val xProgress = DynamicValue(100f)
    val animation1 = Animated.timing(target = alphaProgress, to = 1f, duration = 1000)
    val animation2 = Animated.timing(target = xProgress, to = 200f, duration = 1000)
    val testLithoview =
        lithoViewRule.render { TestComponent(alphaProgress = alphaProgress, xProgress = xProgress) }

    val view = testLithoview.lithoView
    assertThat(view.alpha).isEqualTo(0f).describedAs("alpha initial value")
    assertThat(view.translationX).isEqualTo(100f).describedAs("translationX initial value")
    val loop = Animated.loop(Animated.sequence(animation1, animation2), 2)
    loop.start()
    shadowOf(getMainLooper()).idle()
    assertThat(view.alpha).isEqualTo(1f).describedAs("alpha value after animation")
    assertThat(view.translationX).isEqualTo(200f).describedAs("translationX value after animation")
  }

  @Test
  fun loopAnimation_whenAnimationFinish_onFinishCallbackCalled() {
    listener = mock()
    listener2 = mock()
    listener3 = mock()
    val alphaProgress = DynamicValue(0f)
    val xProgress = DynamicValue(100f)
    val animation1 =
        Animated.timing(
            target = alphaProgress, to = 1f, duration = 1000, animationFinishListener = listener2)
    val animation2 =
        Animated.timing(
            target = xProgress, to = 200f, duration = 1000, animationFinishListener = listener3)

    val loop = Animated.loop(Animated.sequence(animation1, animation2), 2)
    loop.addListener(listener)
    loop.start()
    shadowOf(getMainLooper()).idle()
    verify(listener, times(1).description("loop animation listener should finish once"))
        .onFinish(false)
    verify(listener2, times(2).description("first timing animation listener should finish twice"))
        .onFinish(false)
    verify(listener3, times(2).description("second timing animation listener should finish twice"))
        .onFinish(false)
  }

  private class TestComponent(
      private val alphaProgress: DynamicValue<Float> = DynamicValue<Float>(0f),
      private val xProgress: DynamicValue<Float> = DynamicValue<Float>(100f)
  ) : KComponent() {
    override fun ComponentScope.render(): Component? {
      return Row(
          style = Style.width(100.px).height(100.px).alpha(alphaProgress).translationX(xProgress))
    }
  }
}


/*
 * 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.animated

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import com.facebook.litho.DynamicValue
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.px
import com.facebook.litho.testing.LithoViewRule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.LooperMode

/** Unit tests for common styles defined in [DynamicValueStyles]. */
@LooperMode(LooperMode.Mode.PAUSED)
@RunWith(RobolectricTestRunner::class)
class DynamicValueStylesTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun dynamic_backgroundColor_whenSet_isRespected() {
    val startValue: Int = Color.RED
    val backgroundColorDV: DynamicValue<Int> = DynamicValue<Int>(startValue)

    val testLithoView =
        lithoViewRule.render {
          Row(style = Style.width(100.px).height(100.px).backgroundColor(backgroundColorDV))
        }

    assertThat(testLithoView.lithoView.background).isInstanceOf(ColorDrawable::class.java)
    assertThat((testLithoView.lithoView.background as ColorDrawable).color).isEqualTo(Color.RED)

    backgroundColorDV.set(Color.WHITE)
    assertThat((testLithoView.lithoView.background as ColorDrawable).color).isEqualTo(Color.WHITE)

    backgroundColorDV.set(Color.TRANSPARENT)
    assertThat((testLithoView.lithoView.background as ColorDrawable).color)
        .isEqualTo(Color.TRANSPARENT)
  }

  @Test
  fun dynamic_alpha_whenSet_isRespected() {
    val alpha = 0.5f
    val alphaDV: DynamicValue<Float> = DynamicValue<Float>(alpha)

    val testLithoView =
        lithoViewRule.render { Row(style = Style.width(100.px).height(100.px).alpha(alphaDV)) }

    assertThat(testLithoView.lithoView.alpha).isEqualTo(alpha)

    alphaDV.set(1f)
    assertThat(testLithoView.lithoView.alpha).isEqualTo(1f)
    alphaDV.set(0.7f)
    assertThat(testLithoView.lithoView.alpha).isEqualTo(0.7f)
  }

  @Test
  fun dynamic_translation_whenSet_isRespected() {
    val translationXDV: DynamicValue<Float> = DynamicValue<Float>(10f)
    val translationYDV: DynamicValue<Float> = DynamicValue<Float>(10f)

    val testLithoView =
        lithoViewRule.render {
          Row(
              style =
                  Style.width(100.px)
                      .height(100.px)
                      .translationX(translationXDV)
                      .translationY(translationYDV))
        }

    assertThat(testLithoView.lithoView.translationX).isEqualTo(10f)
    assertThat(testLithoView.lithoView.translationY).isEqualTo(10f)

    translationXDV.set(-50f)
    translationYDV.set(20f)
    assertThat(testLithoView.lithoView.translationX).isEqualTo(-50f)
    assertThat(testLithoView.lithoView.translationY).isEqualTo(20f)

    translationXDV.set(35f)
    translationYDV.set(-75f)
    assertThat(testLithoView.lithoView.translationX).isEqualTo(35f)
    assertThat(testLithoView.lithoView.translationY).isEqualTo(-75f)
  }

  @Test
  fun dynamic_rotation_whenSet_isRespected() {
    val rotationDV: DynamicValue<Float> = DynamicValue<Float>(10f)

    val testLithoView =
        lithoViewRule.render {
          Row(style = Style.width(100.px).height(100.px).rotation(rotationDV))
        }

    assertThat(testLithoView.lithoView.rotation).isEqualTo(10f)

    rotationDV.set(-350f)
    assertThat(testLithoView.lithoView.rotation).isEqualTo(-350f)

    rotationDV.set(0.5f)
    assertThat(testLithoView.lithoView.rotation).isEqualTo(0.5f)
  }

  @Test
  fun dynamic_scale_whenSet_isRespected() {
    val scaleXDV: DynamicValue<Float> = DynamicValue<Float>(10f)
    val scaleYDV: DynamicValue<Float> = DynamicValue<Float>(10f)

    val testLithoView =
        lithoViewRule.render {
          Row(style = Style.width(100.px).height(100.px).scaleX(scaleXDV).scaleY(scaleYDV))
        }

    assertThat(testLithoView.lithoView.scaleX).isEqualTo(10f)
    assertThat(testLithoView.lithoView.scaleY).isEqualTo(10f)

    scaleXDV.set(-50f)
    scaleYDV.set(20f)
    assertThat(testLithoView.lithoView.scaleX).isEqualTo(-50f)
    assertThat(testLithoView.lithoView.scaleY).isEqualTo(20f)

    scaleXDV.set(0.5f)
    scaleYDV.set(0.7f)
    assertThat(testLithoView.lithoView.scaleX).isEqualTo(0.5f)
    assertThat(testLithoView.lithoView.scaleY).isEqualTo(0.7f)
  }
}


// (c) Facebook, Inc. and its affiliates. Confidential and proprietary.

/*
 * Copyright (c) Facebook, Inc. and its 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.examples

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.facebook.litho.Column
import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.accessibility.contentDescription
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.dp
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.px
import com.facebook.litho.stats.LithoStats
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.ComponentConditions.typeIs
import com.facebook.litho.testing.assertj.LithoAssertions.assertThat
import com.facebook.litho.testing.assertj.LithoViewAssert
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.useState
import com.facebook.litho.view.onClick
import com.facebook.litho.view.viewTag
import com.facebook.litho.visibility.onVisible
import com.facebook.litho.widget.Text
import java.lang.AssertionError
import java.util.concurrent.atomic.AtomicReference
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.hamcrest.core.IsInstanceOf
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.robolectric.annotation.LooperMode

/** Examples of LithoViewRule usage */
@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class LithoViewRuleExampleTest {
  // start_example
  @Rule @JvmField val lithoViewRule = LithoViewRule()
  // end_example

  @Test
  fun `verify InnerComponent appears when TestComponent is clicked`() {
    class InnerComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.height(100.dp).width(100.dp)) { child(Text(text = "some_text")) }
      }
    }

    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        val showChild = useState { false }

        return Row(
            style =
                Style.width(100.px).height(100.px).viewTag("test_view").onClick {
                  showChild.update(true)
                }) {
              child(Text("some_text2"))
              if (showChild.value) {
                child(InnerComponent())
              }
            }
      }
    }

    val testLithoView = lithoViewRule.render { TestComponent() }
    /** can use all of the assertions from: [LithoViewAssert] class */
    assertThat(testLithoView).willRenderContent().doesNotContainComponents(InnerComponent::class)
    // act_example_start
    lithoViewRule.act(testLithoView) { clickOnTag("test_view") }
    // act_example_end

    assertThat(testLithoView).containsExactlyOne(InnerComponent::class).hasVisibleText("some_text")
  }

  @Test
  fun `verify InnerComponent appears when TestComponent is visible`() {
    class InnerComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.height(100.dp).width(100.dp)) { child(Text(text = "some_text")) }
      }
    }

    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        val showChild = useState { false }

        return Row(
            style = Style.width(100.px).height(100.px).onVisible { showChild.update(true) }) {
              child(Text("some_other_text"))
              if (showChild.value) {
                child(InnerComponent())
              }
            }
      }
    }

    // visibility_test_start
    val testLithoView =
        lithoViewRule.createTestLithoView { TestComponent() }.attachToWindow().measure()
    /** Before the onVisible is called */
    assertThat(testLithoView.findComponent(InnerComponent::class)).isNull()
    /** Layout component and idle, triggering visibility event and any async updates */
    testLithoView.layout()
    lithoViewRule.idle()
    /** After the onVisible is called */
    assertThat(testLithoView).containsExactlyOne(InnerComponent::class)
    // visibility_test_end
    assertThat(testLithoView).hasVisibleText("some_text")
  }

  @Test
  fun `verify Components existence with ListAssert`() {
    class DeepComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return Row(style = Style.height(100.dp).width(100.dp))
      }
    }
    class InnerComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return DeepComponent()
      }
    }
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return Column {
          child(InnerComponent())
          child(InnerComponent())
          child(DeepComponent())
        }
      }
    }
    val testLithoView = lithoViewRule.render { TestComponent() }
    with(testLithoView) {
      assertThat(findAllComponents(InnerComponent::class, DeepComponent::class))
          .isNotEmpty
          .haveExactly(2, typeIs(InnerComponent::class))
          .haveExactly(3, typeIs(DeepComponent::class))
    }
  }

  @Test
  fun `verify subcomponents existence with contains`() {
    class DeepComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return Row(style = Style.height(100.dp).width(100.dp))
      }
    }
    class InnerComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return DeepComponent()
      }
    }
    class InnerSecondComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return DeepComponent()
      }
    }
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return Column {
          child(InnerComponent())
          child(InnerComponent())
          child(DeepComponent())
          child(InnerSecondComponent())
        }
      }
    }
    // contains_components_start
    val testLithoView = lithoViewRule.render { TestComponent() }
    assertThat(testLithoView)
        .containsExactly(2, InnerComponent::class)
        .containsComponents(DeepComponent::class, InnerSecondComponent::class)
    // contains_components_end
  }

  @Test
  fun `verify InnerComponent has given props`() {
    // has_props_start
    val testLithoView = lithoViewRule.render { TestComponent() }
    assertThat(testLithoView).willRenderContent()
    val component = testLithoView.findComponent(InnerComponent::class)

    assertThat(component)
        .hasProps(
            InnerComponent::value to "some_value",
            InnerComponent::style to Style.height(100.dp).width(100.dp))
        .hasPropsMatching(InnerComponent::value to IsInstanceOf.instanceOf(String::class.java))
    // has_props_end
  }

  @Test
  fun `verify InnerComponent has given props asserting on LithoView`() {
    val testLithoView = lithoViewRule.render { TestComponent() }
    assertThat(testLithoView)
        .willRenderContent()
        .hasDirectMatchingComponent(
            InnerComponent::class,
            InnerComponent::value to "some_value",
            InnerComponent::style to Style.height(100.dp).width(100.dp))
        .hasDirectMatchingComponentWithMatcher(
            InnerComponent::class,
            InnerComponent::value to IsInstanceOf.instanceOf(String::class.java))
  }

  @Test
  fun `verify InnerComponent has given props not on a direct child asserting on LithoView`() {
    class WrapperComponent(val delegate: Component, val tag: String = "") : KComponent() {
      override fun ComponentScope.render(): Component = delegate
    }
    class ParentTestComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return WrapperComponent(
            delegate =
                WrapperComponent(tag = "TAGGED", delegate = Column { child(TestComponent()) }))
      }
    }

    val testLithoView = lithoViewRule.render { ParentTestComponent() }
    assertThat(testLithoView).willRenderContent()
    // We want to catch that the component does not have props in direct component
    assertThatThrownBy {
          assertThat(testLithoView)
              .hasDirectMatchingComponent(
                  InnerComponent::class,
                  InnerComponent::value to "some_value",
                  InnerComponent::style to Style.height(100.dp).width(100.dp))
        }
        .isInstanceOf(AssertionError::class.java)

    // We want to catch that the component does not have matching props to th matcher in direct
    // component
    assertThatThrownBy {
          assertThat(testLithoView)
              .hasDirectMatchingComponentWithMatcher(
                  InnerComponent::class,
                  InnerComponent::value to IsInstanceOf.instanceOf(String::class.java))
        }
        .isInstanceOf(AssertionError::class.java)

    assertThat(testLithoView)
        .hasDirectMatchingComponent(WrapperComponent::class, WrapperComponent::tag to "TAGGED")
        .hasAnyMatchingComponent(
            InnerComponent::class,
            InnerComponent::value to "some_value",
            InnerComponent::style to Style.height(100.dp).width(100.dp))
        .hasAnyMatchingComponentWithMatcher(
            InnerComponent::class,
            InnerComponent::value to IsInstanceOf.instanceOf(String::class.java))
  }

  @Test
  fun `verify lithoView is not null`() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row { child(Text(text = "some_text")) }
      }
    }
    // start_render_example
    val testLithoView = lithoViewRule.render { TestComponent() }
    // end_render_example
    assertThat(testLithoView).willRenderContent()
    /** can use all of the assertions from: [LithoViewAssert] class */
  }

  @Test
  fun `verify children of the component`() {
    // find_direct_component_start
    val testLithoView = lithoViewRule.render { TestComponent() }
    val innerComponent = testLithoView.findDirectComponent(InnerComponent::class)
    assertThat(innerComponent).isNotNull()
    // find_direct_component_end
    // find_non_direct_component_start
    val textComponent = testLithoView.findComponent(Text::class)
    assertThat(textComponent).isNotNull()
    // find_non_direct_component_end
  }

  @Test
  fun `verify order for finding components`() {
    class HeadComponent(val childComponent: Component) : KComponent() {
      override fun ComponentScope.render(): Component = childComponent
    }
    class DelegateComponent(val delegate: Component) : KComponent() {
      override fun ComponentScope.render(): Component = delegate
    }
    class RowWithChildren : KComponent() {
      override fun ComponentScope.render(): Component = Row {
        child(Text(text = "Hello, I'm not a direct component"))
        child(InnerComponent())
      }
    }

    val rowWithChildren = RowWithChildren()
    val secondLayerDelegate = DelegateComponent(rowWithChildren)
    val firstLayerDelegate = DelegateComponent(secondLayerDelegate)
    lateinit var rootComponent: Component

    val testLithoView =
        lithoViewRule.render { HeadComponent(firstLayerDelegate).also { rootComponent = it } }

    // Ensure that we find direct components from the head component to the tail component
    assertThat(testLithoView.rootComponent).isSameAs(rootComponent)
    assertThat(testLithoView)
        .containsDirectComponents(HeadComponent::class, DelegateComponent::class)
    assertThat(testLithoView.findDirectComponent(DelegateComponent::class))
        .isNotNull
        .isSameAs(firstLayerDelegate)
    assertThat(testLithoView.findAllComponents(DelegateComponent::class))
        .containsExactly(firstLayerDelegate, secondLayerDelegate)

    // Ensure that direct component includes and stops at first flexbox
    assertThat(testLithoView)
        .containsDirectComponents(RowWithChildren::class, Row::class)
        .doesNotContainDirectComponents(Text::class, InnerComponent::class)
    assertThat(testLithoView.findDirectComponent(RowWithChildren::class))
        .isNotNull
        .isSameAs(rowWithChildren)
    assertThat(testLithoView).hasVisibleText("Hello, I'm not a direct component")

    // Ensure that we can find non-direct components
    assertThat(testLithoView.findComponent(Text::class)).isNotNull
    assertThat(testLithoView.findComponent(InnerComponent::class)).isNotNull
  }

  @Test
  fun `verify assertions on lithoview `() {
    // lithoview_assertion_start
    val testLithoView = lithoViewRule.render { TestComponent() }
    assertThat(testLithoView).hasVisibleText("some_value")
    /** can use all of the assertions from: [LithoViewAssert] class */
    // lithoview_assertion_end
  }

  @Test
  fun `verify click on component`() {
    class TestClickComponent(private val clickHandler: TestClickHandler) : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Row(style = Style.onClick { clickHandler.onClick(androidContext) }) {
          child(Text(text = "Text"))
        }
      }
    }

    val mockClickHandler: TestClickHandler = mock()
    val testLithoView = lithoViewRule.render { TestClickComponent(mockClickHandler) }
    lithoViewRule.act(testLithoView) { clickOnRootView() }

    verify(mockClickHandler).onClick(any())
  }

  @Test(expected = IllegalStateException::class)
  fun `verify runtime exception thrown when clicking on a non-clickable view`() {
    val testLithoView = lithoViewRule.render { Row() }
    lithoViewRule.act(testLithoView) { clickOnRootView() }
  }

  @Test
  fun `verify interactions on lithoView`() {
    // component_for_action_start
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        val showText = useState { false }
        return Row(
            style =
                Style.width(100.px)
                    .height(100.px)
                    .onClick { showText.update { isTextShowing -> !isTextShowing } }
                    .contentDescription("row")) {
              if (showText.value) {
                child(Text(text = "Text"))
              }
            }
      }
    }
    // component_for_action_end

    // test_interaction_start
    val testLithoView = lithoViewRule.render { TestComponent() }
    LithoStats.resetAllCounters()
    /** Find [Component] based on the text or [Component] class */
    assertThat(testLithoView.findViewWithTextOrNull("Text")).isNull()
    assertThat(testLithoView.findComponent(Text::class)).isNull()

    /** perform interaction defined in [LithoViewRule] */
    lithoViewRule.act(testLithoView) { clickOnContentDescription("row") }

    /** check number of state updates */
    assertThat(LithoStats.getComponentTriggeredAsyncStateUpdateCount()).isEqualTo(1)

    /** Find [Component] based on the text or [Component] class */
    assertThat(testLithoView.findViewWithTextOrNull("Text")).isNotNull()
    assertThat(testLithoView.findComponent(Text::class)).isNotNull()
    // test_interaction_end
  }

  @Test
  fun `verify the visibility event changed the state`() {
    lateinit var stateRef: AtomicReference<Boolean>

    class TestComponent : KComponent() {
      // idle_component_start
      override fun ComponentScope.render(): Component? {
        val visibilityEventCalled = useState { false }
        stateRef = AtomicReference(visibilityEventCalled.value)
        return Column(
            style =
                Style.width(10.dp).height(10.dp).onVisible { visibilityEventCalled.update(true) })
      }
    }

    lithoViewRule.render { TestComponent() }
    assertThat(stateRef.get()).isEqualTo(false)
    lithoViewRule.idle()
    assertThat(stateRef.get()).isEqualTo(true)
    // idle_component_end
  }

  // test_component_start
  class TestComponent() : KComponent() {
    override fun ComponentScope.render(): Component {
      return InnerComponent()
    }
  }
}

class InnerComponent(
    @VisibleForTesting val style: Style = Style.height(100.dp).width(100.dp),
    @VisibleForTesting val value: String = "some_value"
) : KComponent() {
  override fun ComponentScope.render(): Component {
    return Row(style = style) { child(Text(text = value)) }
  }
}
// test_component_end

private interface TestClickHandler {
  fun onClick(context: Context)
}


/*
 * 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.examples

import com.facebook.litho.Component
import com.facebook.litho.ComponentScope
import com.facebook.litho.KComponent
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.stringRes
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.assertj.LithoAssertions
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.Text
import com.facebook.litho.widget.collection.LazyList
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

/** Examples of LithoViewRuleList usage */
@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class LithoViewRuleListExampleTest {

  @Rule @JvmField val lithoViewRule = LithoViewRule()

  @Test
  fun `test child is present in LazyList`() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component {
        return LazyList {
          for (i in 1..10) {
            child(id = i, component = Text("child$i"))
          }
        }
      }
    }

    val testView =
        lithoViewRule.render(widthPx = exactly(100), heightPx = exactly(100)) { TestComponent() }

    assertThat(testView).isNotNull()

    val listComponent = testView.findCollectionComponent()
    assertThat(listComponent).isNotNull()

    assertThat(listComponent?.itemCount ?: return).isEqualTo(10)
    assertThat(listComponent.getItemAtIndex(0).component)
        .hasFieldOrPropertyWithValue("text", "child1")

    assertThat(listComponent.getItemAtIndex(listComponent.lastVisibleIndex).component)
        .hasFieldOrPropertyWithValue("text", "child3")

    assertThat(
            listComponent.items.slice(
                listComponent.firstVisibleIndex..listComponent.lastVisibleIndex))
        .hasSize(3)

    val component = listComponent.findFirstItem(Text::class)?.component
    assertThat(component).isNotNull()
    assertThat(component).hasFieldOrPropertyWithValue("text", "child1")
  }

  @Test
  fun `test child is present in LazyList with multiple component types`() {

    data class Item(val id: Int, val name: String, val quantity: Int)

    val items =
        listOf(
            Item(1, "Flour", 2),
            Item(2, "Cinnamon", 3),
            Item(3, "Salt", 100),
            Item(4, "Butter", 1),
        )

    class ItemComponent(val model: Item) : KComponent() {
      override fun ComponentScope.render(): Component? {
        return Text("${model.name}: ${model.quantity}")
      }
    }

    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component? {
        return LazyList() {
          child(Text("Item List"))
          for (item in items) {
            child(id = item.id, component = ItemComponent(item))
          }
        }
      }
    }

    val testView =
        lithoViewRule.render(widthPx = exactly(100), heightPx = exactly(100)) { TestComponent() }

    assertThat(testView.findViewWithTextOrNull("Flour: 2")).isNotNull()
    assertThat(testView.findAllComponents(Text::class))
        // this should return empty for now.
        .isEmpty()

    val listComponent = testView.findCollectionComponent()
    assertThat(listComponent).isNotNull()

    val component = listComponent?.findFirstItem(ItemComponent::class)?.component ?: return
    assertThat(component).hasFieldOrPropertyWithValue("model", items[0])

    val foundComponents = listComponent.findItems(ItemComponent::class)
    assertThat(foundComponents).hasSize(4)
  }

  @Test
  fun `test chained assertions`() {

    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component = LazyList {
        children(items = (0..4), id = { it }) { Text("$it") }
      }
    }

    val testView =
        lithoViewRule.render(widthPx = exactly(100), heightPx = exactly(100)) { TestComponent() }

    val listComponent = testView.findCollectionComponent()
    assertThat(listComponent).isNotNull
    listComponent ?: return

    val testComponentScope = ComponentScope(context = lithoViewRule.context)
    val zeroTextComponent = with(testComponentScope) { Text("0") }
    val fourTextComponent = with(testComponentScope) { Text("4") }
    val helloTextComponent = with(testComponentScope) { Text("Hello") }
    val fullComponentList =
        with(testComponentScope) { arrayOf(Text("0"), Text("1"), Text("2"), Text("3")) }

    LithoAssertions.assertThat(listComponent)
        .onFirstChild { it.isVisible }
        .onLastChild { it.component.isEquivalentTo(fourTextComponent) }
        .onChild(withIndex = 2) { it.isVisible }
        .onChild(withId = 3) { !it.isFullyVisible }
        .hasSize(5)
        .onChildren(matching = { it.isSticky }) { it.isEmpty() }
        .containsComponents(zeroTextComponent)
        .doesNotContainComponents(helloTextComponent)
        .containsExactlyComponents(*fullComponentList)
        .hasVisibleText("0")
        .doesNotHaveVisibleText("cheeseburger")

    LithoAssertions.assertThat(listComponent.firstItem).isVisible().isFullyVisible()

    LithoAssertions.assertThat(listComponent.firstVisibleItem)
        .isVisible()
        .isFullyVisible()
        .hasIndex(0)

    val secondChild = listComponent.getItemAtIndex(2)
    LithoAssertions.assertThat(secondChild)
        .isVisible()
        .isNotFullyVisible()
        .isEqualTo(listComponent.lastVisibleItem)

    LithoAssertions.assertThat(listComponent.lastItem).isNotVisible().isNotFullyVisible()
  }

  @Test
  fun `test res visible and non-visible text`() {
    class TestComponent : KComponent() {
      override fun ComponentScope.render(): Component = LazyList {
        child(id = "resTextChild", deps = emptyArray()) {
          Text(text = stringRes(android.R.string.selectAll))
        }
      }
    }

    val testView =
        lithoViewRule.render(widthPx = exactly(100), heightPx = exactly(100)) { TestComponent() }

    val listComponent = testView.findCollectionComponent()
    assertThat(listComponent).isNotNull

    LithoAssertions.assertThat(listComponent)
        .onFirstChild { it.isVisible }
        .hasVisibleText(android.R.string.selectAll)
        .doesNotHaveVisibleText(android.R.string.cancel)
  }
}


// (c) Facebook, Inc. and its affiliates. Confidential and proprietary.

/*
 * Copyright (c) Facebook, Inc. and its 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.flexbox

import com.facebook.litho.LithoView
import com.facebook.litho.Row
import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.kotlinStyle
import com.facebook.litho.px
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.assertMatches
import com.facebook.litho.testing.match
import com.facebook.litho.testing.setRoot
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/** Unit tests interop between Spec Components and flexbox [Style] properties. */
@RunWith(LithoTestRunner::class)
class FlexboxStyleCompatibilityTest {

  @Rule @JvmField val lithoViewRule = LegacyLithoViewRule()

  @Test
  fun specComponent_whenKotlinStyleSetOnBuilder_commonPropsAreApplied() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row.create(lithoViewRule.context)
              .wrapInView()
              .kotlinStyle(Style.width(100.px).height(100.px))
              .build()
        }
        .assertMatches(match<LithoView> { bounds(0, 0, 100, 100) })
  }

  @Test
  fun specComponent_whenKotlinStyleSetOnBuilderThenOverriden_overriddenValuesApply() {
    lithoViewRule
        .setSizeSpecs(unspecified(), unspecified())
        .setRoot {
          Row.create(lithoViewRule.context)
              .wrapInView()
              .kotlinStyle(Style.width(100.px).height(100.px))
              .widthPx(50)
              .heightPx(50)
              .build()
        }
        .assertMatches(match<LithoView> { bounds(0, 0, 50, 50) })
  }
}