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

package com.facebook.litho

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

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

  private lateinit var context: ComponentContext

  @JvmField @Rule val legacyLithoViewRule = LegacyLithoViewRule()

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

  @Test
  fun testDynamicAlphaApplied() {
    val startValue = 0.8f
    val alphaDV = DynamicValue(startValue)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context, Column.create(context).widthPx(80).heightPx(80).alpha(alphaDV).build())
    assertThat(lithoView.alpha).isEqualTo(startValue)
    alphaDV.set(0.5f)
    assertThat(lithoView.alpha).isEqualTo(0.5f)
    alphaDV.set(0f)
    assertThat(lithoView.alpha).isEqualTo(0f)
    alphaDV.set(1f)
    assertThat(lithoView.alpha).isEqualTo(1f)
  }

  @Test
  fun testDynamicTranslationApplied() {
    val startValueX = 100f
    val startValueY = -100f
    val translationXDV = DynamicValue(startValueX)
    val translationYDV = DynamicValue(startValueY)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            Column.create(context)
                .widthPx(80)
                .heightPx(80)
                .translationX(translationXDV)
                .translationY(translationYDV)
                .build())
    assertThat(lithoView.translationX).isEqualTo(startValueX)
    assertThat(lithoView.translationY).isEqualTo(startValueY)
    translationXDV.set(50f)
    translationYDV.set(20f)
    assertThat(lithoView.translationX).isEqualTo(50f)
    assertThat(lithoView.translationY).isEqualTo(20f)
    translationXDV.set(-50f)
    translationYDV.set(-20f)
    assertThat(lithoView.translationX).isEqualTo(-50f)
    assertThat(lithoView.translationY).isEqualTo(-20f)
    translationXDV.set(0f)
    translationYDV.set(0f)
    assertThat(lithoView.translationX).isEqualTo(0f)
    assertThat(lithoView.translationY).isEqualTo(0f)
  }

  @Test
  fun testDynamicBackgroundColorApplied() {
    val startValue = Color.RED
    val backgroundColorDV = DynamicValue(startValue)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            Column.create(context)
                .widthPx(80)
                .heightPx(80)
                .backgroundColor(backgroundColorDV)
                .build())
    assertThat(lithoView.background).isInstanceOf(ColorDrawable::class.java)
    assertThat((lithoView.background as ColorDrawable).color).isEqualTo(startValue)
    backgroundColorDV.set(Color.BLUE)
    assertThat((lithoView.background as ColorDrawable).color).isEqualTo(Color.BLUE)
    backgroundColorDV.set(-0x77777778)
    assertThat((lithoView.background as ColorDrawable).color).isEqualTo(-0x77777778)
    backgroundColorDV.set(Color.TRANSPARENT)
    assertThat((lithoView.background as ColorDrawable).color).isEqualTo(Color.TRANSPARENT)
  }

  @Test
  fun testDynamicRotationApplied() {
    val startValue = 0f
    val rotationDV = DynamicValue(startValue)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context, Column.create(context).widthPx(80).heightPx(80).rotation(rotationDV).build())
    assertThat(lithoView.rotation).isEqualTo(startValue)
    rotationDV.set(364f)
    assertThat(lithoView.rotation).isEqualTo(364f)
    rotationDV.set(520f)
    assertThat(lithoView.rotation).isEqualTo(520f)
    rotationDV.set(-1f)
    assertThat(lithoView.rotation).isEqualTo(-1f)
  }

  @Test
  fun testDynamicScaleApplied() {
    val startValueX = 1.5f
    val startValueY = -1.5f
    val scaleXDV = DynamicValue(startValueX)
    val scaleYDV = DynamicValue(startValueY)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            Column.create(context)
                .widthPx(80)
                .heightPx(80)
                .scaleX(scaleXDV)
                .scaleY(scaleYDV)
                .build())
    assertThat(lithoView.scaleX).isEqualTo(startValueX)
    assertThat(lithoView.scaleY).isEqualTo(startValueY)
    scaleXDV.set(0.5f)
    scaleYDV.set(2f)
    assertThat(lithoView.scaleX).isEqualTo(0.5f)
    assertThat(lithoView.scaleY).isEqualTo(2f)
    scaleXDV.set(2f)
    scaleYDV.set(0.5f)
    assertThat(lithoView.scaleX).isEqualTo(2f)
    assertThat(lithoView.scaleY).isEqualTo(.5f)
    scaleXDV.set(0f)
    scaleYDV.set(0f)
    assertThat(lithoView.scaleX).isEqualTo(0f)
    assertThat(lithoView.scaleY).isEqualTo(0f)
  }

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


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

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyMountStateFocusableTest {

  private lateinit var context: ComponentContext

  @Before
  fun setup() {
    TempComponentsConfigurations.setShouldAddHostViewForRootComponent(false)
    context =
        ComponentContext(
            androidx.test.core.app.ApplicationProvider.getApplicationContext<Context>())
  }

  @Test
  fun testRootHostFocusable() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c).focusable(true).child(SimpleMountSpecTester.create(c)).build()
            })
    assertThat(lithoView.childCount).isEqualTo(0)
    assertThat(lithoView.isFocusable).isTrue
  }

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


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

package com.facebook.litho

import android.graphics.Rect
import com.facebook.litho.SizeSpec.EXACTLY
import com.facebook.litho.SizeSpec.makeSizeSpec
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.Text
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

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

  private lateinit var context: ComponentContext

  @JvmField @Rule val legacyLithoViewRule = LegacyLithoViewRule()

  @Before
  fun setup() {
    TempComponentsConfigurations.setShouldAddHostViewForRootComponent(false)
    context = legacyLithoViewRule.context
    legacyLithoViewRule.useLithoView(LithoView(context))
  }

  @Test
  fun testRootViewAttributes_incrementalMountAfterUnmount_setViewAttributes() {
    val root = Text.create(context).text("Test").contentDescription("testcd").build()
    legacyLithoViewRule
        .setRoot(root)
        .attachToWindow()
        .setSizeSpecs(makeSizeSpec(1_000, EXACTLY), makeSizeSpec(1_000, EXACTLY))
        .measure()
        .layout()
    val lithoView = legacyLithoViewRule.lithoView
    assertThat(lithoView.contentDescription).isEqualTo("testcd")
    lithoView.unmountAllItems()
    assertThat(lithoView.contentDescription).isNull()
    lithoView.mountComponent(Rect(0, 5, 10, 15), true)
    assertThat(lithoView.contentDescription).isEqualTo("testcd")
  }

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


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

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyMountStateRemountEventHandlerTest {

  private lateinit var context: ComponentContext

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

  @Test
  fun testReuseLongClickListenerOnSameView() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .longClickHandler(c.newEventHandler(1) as? EventHandler<LongClickEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    val longClickListener = LithoViewAttributesExtension.getComponentLongClickListener(lithoView)
    assertThat(longClickListener).isNotNull
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .longClickHandler(c.newEventHandler(1) as? EventHandler<LongClickEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    assertThat(
            longClickListener ===
                LithoViewAttributesExtension.getComponentLongClickListener(lithoView))
        .isTrue
  }

  @Test
  fun testReuseFocusChangeListenerListenerOnSameView() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .focusChangeHandler(c.newEventHandler(1) as? EventHandler<FocusChangedEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    val focusChangeListener =
        LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView)
    assertThat(focusChangeListener).isNotNull
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .focusChangeHandler(c.newEventHandler(1) as? EventHandler<FocusChangedEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    assertThat(
            focusChangeListener ===
                LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView))
        .isTrue
  }

  @Test
  fun testReuseTouchListenerOnSameView() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .touchHandler(c.newEventHandler(1) as? EventHandler<TouchEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    val touchListener = LithoViewAttributesExtension.getComponentTouchListener(lithoView)
    assertThat(touchListener).isNotNull
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .touchHandler(c.newEventHandler(2) as? EventHandler<TouchEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    assertThat(LithoViewAttributesExtension.getComponentTouchListener(lithoView))
        .isEqualTo(touchListener)
  }

  @Test
  fun testUnsetClickHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .clickHandler(c.newEventHandler(1) as? EventHandler<ClickEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(lithoView.isClickable).isTrue
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    assertThat(lithoView.isClickable).isFalse
  }

  @Test
  fun testUnsetLongClickHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .longClickHandler(c.newEventHandler(1) as? EventHandler<LongClickEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(LithoViewAttributesExtension.getComponentLongClickListener(lithoView)).isNotNull
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentLongClickListener(lithoView)
    assertThat(listener).isNotNull
    assertThat(listener?.eventHandler).isNull()
  }

  @Test
  fun testUnsetFocusChangeHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .focusChangeHandler(c.newEventHandler(1) as? EventHandler<FocusChangedEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView)).isNotNull
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView)
    assertThat(listener).isNotNull
    assertThat(listener?.eventHandler).isNull()
  }

  @Test
  fun testUnsetTouchHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .touchHandler(c.newEventHandler(1) as? EventHandler<TouchEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentTouchListener(lithoView)
    assertThat(listener?.eventHandler).isNull()
  }

  @Test
  fun testSetLongClickHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(LithoViewAttributesExtension.getComponentLongClickListener(lithoView)).isNull()
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .longClickHandler(c.newEventHandler(1) as? EventHandler<LongClickEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentLongClickListener(lithoView)
    assertThat(listener).isNotNull
    assertThat(listener?.eventHandler).isNotNull
  }

  @Test
  fun testSetFocusChangeHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView)).isNull()
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .focusChangeHandler(c.newEventHandler(1) as? EventHandler<FocusChangedEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentFocusChangeListener(lithoView)
    assertThat(listener).isNotNull
    assertThat(listener?.eventHandler).isNotNull
  }

  @Test
  fun testSetTouchHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(LithoViewAttributesExtension.getComponentTouchListener(lithoView)).isNull()
    lithoView.componentTree?.root =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component =
              Column.create(c)
                  .touchHandler(c.newEventHandler(1) as? EventHandler<TouchEvent>)
                  .child(SimpleMountSpecTester.create(c))
                  .child(SimpleMountSpecTester.create(c))
                  .build()
        }
    val listener = LithoViewAttributesExtension.getComponentTouchListener(lithoView)
    assertThat(listener).isNotNull
    assertThat(listener?.eventHandler).isNotNull
  }

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


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

package com.facebook.litho

import android.content.Context
import android.graphics.Color
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.drawable.ComparableDrawable
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.EditText
import com.facebook.litho.widget.Text
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyMountStateRemountTest {

  private lateinit var context: ComponentContext

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

  @Test
  fun testRemountOnNodeInfoLayoutChanges() {
    val oldComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(Text.create(context).textSizeSp(12f).text("label:"))
            .child(
                EditText.create(context)
                    .text("Hello World")
                    .textSizeSp(12f)
                    .viewTag("Alpha")
                    .enabled(true))
            .build()
    val lithoView = LithoView(context)
    val componentTree =
        ComponentTree.create(context, oldComponent)
            .incrementalMount(false)
            .layoutDiffing(true)
            .build()
    ComponentTestHelper.mountComponent(lithoView, componentTree, exactly(400), exactly(400))
    val oldView = lithoView.getChildAt(0)
    val oldTag = oldView.tag
    val oldIsEnabled = oldView.isEnabled
    val newComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(Text.create(context).textSizeSp(12f).text("label:"))
            .child(
                EditText.create(context)
                    .text("Hello World")
                    .textSizeSp(12f)
                    .viewTag("Beta")
                    .enabled(false))
            .build()
    componentTree.setRootAndSizeSpecSync(newComponent, exactly(400), exactly(400))
    componentTree.setSizeSpec(exactly(400), exactly(400))
    val newView = lithoView.getChildAt(0)
    assertThat(newView).isSameAs(oldView)
    val newTag = newView.tag
    val newIsEnabled = newView.isEnabled
    assertThat(newTag).isNotEqualTo(oldTag)
    assertThat(newIsEnabled).isNotEqualTo(oldIsEnabled)
  }

  @Test
  fun testRemountOnNoLayoutChanges() {
    val oldComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(
                EditText.create(context)
                    .backgroundColor(Color.RED)
                    .foregroundColor(Color.CYAN)
                    .text("Hello World")
                    .viewTag("Alpha")
                    .contentDescription("some description"))
            .build()
    val lithoView = LithoView(context)
    val componentTree =
        ComponentTree.create(context, oldComponent)
            .incrementalMount(false)
            .layoutDiffing(true)
            .build()
    ComponentTestHelper.mountComponent(lithoView, componentTree, exactly(400), exactly(400))
    val oldView = lithoView.getChildAt(0)
    val oldTag = oldView.tag
    val oldContentDescription = oldView.contentDescription.toString()
    val oldBackground = oldView.background
    val newComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(
                EditText.create(context)
                    .backgroundColor(Color.RED)
                    .foregroundColor(Color.CYAN)
                    .text("Hello World")
                    .viewTag("Alpha")
                    .contentDescription("some description"))
            .build()
    componentTree.setRootAndSizeSpecSync(newComponent, exactly(400), exactly(400))
    componentTree.setSizeSpec(exactly(400), exactly(400))
    val newView = lithoView.getChildAt(0)
    assertThat(newView).isSameAs(oldView)
    val newTag = newView.tag
    val newContentDescription = newView.contentDescription.toString()
    val newBackground = newView.background

    // Check that props were not set again
    assertThat(newTag).isSameAs(oldTag)
    assertThat(newContentDescription).isSameAs(oldContentDescription)
    assertThat(oldBackground).isSameAs(newBackground)
  }

  @Test
  fun testRemountOnViewNodeInfoLayoutChanges() {
    val oldComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(Text.create(context).textSizeSp(12f).text("label:"))
            .child(
                EditText.create(context)
                    .text("Hello World")
                    .textSizeSp(12f)
                    .backgroundColor(Color.RED))
            .build()
    val lithoView = LithoView(context)
    val componentTree =
        ComponentTree.create(context, oldComponent)
            .incrementalMount(false)
            .layoutDiffing(true)
            .build()
    ComponentTestHelper.mountComponent(lithoView, componentTree, exactly(400), exactly(400))
    val oldView = lithoView.getChildAt(0)
    val oldDrawable = oldView.background as ComparableDrawable
    val newComponent =
        Column.create(context)
            .backgroundColor(Color.WHITE)
            .child(Text.create(context).textSizeSp(12f).text("label:"))
            .child(
                EditText.create(context)
                    .text("Hello World")
                    .textSizeSp(12f)
                    .backgroundColor(Color.CYAN))
            .build()
    componentTree.setRootAndSizeSpecSync(newComponent, exactly(400), exactly(400))
    componentTree.setSizeSpec(exactly(400), exactly(400))
    val newView = lithoView.getChildAt(0)
    assertThat(newView).isSameAs(oldView)
    val newDrawable = newView.background as ComparableDrawable
    assertThat(oldDrawable.isEquivalentTo(newDrawable)).isFalse
  }

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


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

package com.facebook.litho

import android.content.Context
import android.view.ViewGroup
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleLayoutSpecWithClickHandlersTester
import com.facebook.litho.widget.SimpleMountSpecTester
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyMountStateViewClickTest {

  private lateinit var context: ComponentContext

  @JvmField @Rule val legacyLithoViewRule = LegacyLithoViewRule()

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

  @Test
  fun testRootHostClickable() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .clickHandler(c.newEventHandler(1) as? EventHandler<ClickEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(lithoView.childCount).isEqualTo(0)
    assertThat(lithoView.isClickable).isTrue
  }

  @Test
  fun testRootHostClickableWithLongClickHandler() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .longClickHandler(c.newEventHandler(1) as? EventHandler<LongClickEvent>)
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(lithoView.childCount).isEqualTo(0)
    assertThat(lithoView.isLongClickable).isTrue
  }

  @Test
  fun testRootHostClickableUnmount() {
    val component =
        SimpleLayoutSpecWithClickHandlersTester.create(legacyLithoViewRule.context).build()
    legacyLithoViewRule.setRoot(component)
    setupLithoViewParentAndComponentTree(legacyLithoViewRule.lithoView, component)
    legacyLithoViewRule.attachToWindow().measure().layout()
    assertThat(legacyLithoViewRule.lithoView.isClickable).isTrue
    assertThat(legacyLithoViewRule.lithoView.isLongClickable).isTrue
    legacyLithoViewRule.lithoView.unmountAllItems()
    assertThat(legacyLithoViewRule.lithoView.isClickable).isFalse
    assertThat(legacyLithoViewRule.lithoView.isLongClickable).isFalse
  }

  // When testing a LithoView via a LegacyLithoViewRule - we must set the parent and component tree
  // prior to attach / measure / layout for that LithoView otherwise mounting will not behave
  // properly.
  private fun setupLithoViewParentAndComponentTree(lithoView: LithoView, component: Component) {
    val parent: ViewGroup =
        object : ViewGroup(lithoView.context) {
          override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) = Unit
        }
    parent.addView(lithoView)
    lithoView.componentTree =
        ComponentTree.create(context, component)
            .incrementalMount(false)
            .layoutDiffing(false)
            .visibilityProcessing(false)
            .build()
  }

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


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

package com.facebook.litho

import android.content.Context
import android.util.SparseArray
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyMountStateViewTagsTest {

  private lateinit var context: ComponentContext

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

  @Test
  fun testRootHostViewTags() {
    val tag = Any()
    val tags = SparseArray<Any>(1)
    tags.put(DUMMY_ID, tag)
    val lithoView =
        ComponentTestHelper.mountComponent(
            context,
            object : InlineLayoutSpec() {
              override fun onCreateLayout(c: ComponentContext): Component =
                  Column.create(c)
                      .viewTags(tags)
                      .child(SimpleMountSpecTester.create(c))
                      .child(SimpleMountSpecTester.create(c))
                      .build()
            })
    assertThat(lithoView.getTag(DUMMY_ID)).isEqualTo(tag)
  }

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

  companion object {
    private const val DUMMY_ID = 0x10000000
  }
}


/*
 * 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.R
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.MountSpecLithoRenderUnit.Companion.STATE_DIRTY
import com.facebook.litho.MountSpecLithoRenderUnit.Companion.STATE_UNKNOWN
import com.facebook.litho.MountSpecLithoRenderUnit.Companion.STATE_UPDATED
import com.facebook.litho.SizeSpec.makeSizeSpec
import com.facebook.litho.config.TempComponentsConfigurations
import com.facebook.litho.drawable.ComparableColorDrawable
import com.facebook.litho.testing.TestDrawableComponent
import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LegacyTreeDiffingTest {

  private lateinit var context: ComponentContext

  @Before
  fun setup() {
    TempComponentsConfigurations.setShouldAddHostViewForRootComponent(false)
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    redDrawable = ComparableColorDrawable.create(Color.RED)
    blackDrawable = ComparableColorDrawable.create(Color.BLACK)
    transparentDrawable = ComparableColorDrawable.create(Color.TRANSPARENT)
  }

  @Test
  fun testLayoutOutputUpdateStateWithBackground() {
    val component1 = TestLayoutSpecBgState(false)
    val component2 = TestLayoutSpecBgState(false)
    val component3 = TestLayoutSpecBgState(true)
    val lithoView = LithoView(context)
    val componentTree = ComponentTree.create(context, component1).build()
    lithoView.componentTree = componentTree
    lithoView.onAttachedToWindow()
    componentTree.setRootAndSizeSpecSync(
        component1, makeSizeSpec(10, SizeSpec.EXACTLY), makeSizeSpec(10, SizeSpec.EXACTLY))
    val state = requireNotNull(componentTree.mainThreadLayoutState)
    assertOutputsState(state, MountSpecLithoRenderUnit.STATE_UNKNOWN)
    componentTree.root = component2
    val secondState = requireNotNull(componentTree.mainThreadLayoutState)
    assertThat(5).isEqualTo(secondState.mountableOutputCount)
    assertOutputsState(secondState, MountSpecLithoRenderUnit.STATE_UPDATED)
    componentTree.root = component3
    val thirdState = requireNotNull(componentTree.mainThreadLayoutState)
    assertThat(5).isEqualTo(thirdState.mountableOutputCount)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(thirdState.getMountableOutputAt(1))))
        .isEqualTo(STATE_DIRTY)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(thirdState.getMountableOutputAt(2))))
        .isEqualTo(STATE_UPDATED)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(thirdState.getMountableOutputAt(3))))
        .isEqualTo(STATE_UPDATED)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(thirdState.getMountableOutputAt(4))))
        .isEqualTo(STATE_UPDATED)
  }

  // This test covers the same case with the foreground since the code path is the same!
  @Test
  fun testLayoutOutputUpdateStateWithBackgroundInWithLayout() {
    val component1 = TestLayoutSpecInnerState(false)
    val component2 = TestLayoutSpecInnerState(false)
    val component3 = TestLayoutSpecInnerState(true)
    val lithoView = LithoView(context)
    val componentTree = ComponentTree.create(context, component1).build()
    lithoView.componentTree = componentTree
    lithoView.onAttachedToWindow()
    componentTree.setRootAndSizeSpecSync(
        component1, makeSizeSpec(10, SizeSpec.EXACTLY), makeSizeSpec(10, SizeSpec.EXACTLY))
    val state = requireNotNull(componentTree.mainThreadLayoutState)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(requireNotNull(state.getMountableOutputAt(2))))
        .isEqualTo(STATE_UNKNOWN)
    componentTree.root = component2
    val secondState = requireNotNull(componentTree.mainThreadLayoutState)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(secondState.getMountableOutputAt(2))))
        .isEqualTo(STATE_UPDATED)
    componentTree.root = component3
    val thirdState = requireNotNull(componentTree.mainThreadLayoutState)
    assertThat(
            MountSpecLithoRenderUnit.getUpdateState(
                requireNotNull(thirdState.getMountableOutputAt(2))))
        .isEqualTo(STATE_DIRTY)
  }

  private class TestLayoutSpecBgState(private val changeBg: Boolean) : InlineLayoutSpec() {
    override fun onCreateLayout(c: ComponentContext): Component? =
        Column.create(c)
            .background(if (changeBg) blackDrawable else redDrawable)
            .foreground(transparentDrawable)
            .child(TestDrawableComponent.create(c))
            .child(Column.create(c).child(TestDrawableComponent.create(c)))
            .build()
  }

  private class TestLayoutSpecInnerState(private val changeChildDrawable: Boolean) :
      InlineLayoutSpec() {
    override fun onCreateLayout(c: ComponentContext): Component? =
        Column.create(c)
            .background(redDrawable)
            .foregroundRes(R.drawable.btn_default)
            .child(
                TestDrawableComponent.create(c)
                    .background(if (changeChildDrawable) redDrawable else blackDrawable))
            .child(Column.create(c).child(TestDrawableComponent.create(c)))
            .build()
  }

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

  companion object {
    private lateinit var redDrawable: Drawable
    private lateinit var blackDrawable: Drawable
    private lateinit var transparentDrawable: Drawable

    private fun assertOutputsState(
        layoutState: LayoutState,
        @MountSpecLithoRenderUnit.UpdateState state: Int
    ) {
      assertThat(STATE_DIRTY)
          .isEqualTo(MountSpecLithoRenderUnit.getUpdateState(layoutState.getMountableOutputAt(0)))
      for (i in 1 until layoutState.mountableOutputCount) {
        assertThat(state)
            .isEqualTo(MountSpecLithoRenderUnit.getUpdateState(layoutState.getMountableOutputAt(i)))
      }
    }
  }
}


/*
 * 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.rendercore.LayoutContextExtraData
import com.facebook.yoga.YogaDirection
import com.facebook.yoga.YogaNode
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever

@RunWith(JUnit4::class)
class LithoLayoutContextExtraDataTest {

  @Mock private lateinit var yogaNode: YogaNode

  private lateinit var layoutContextExtraData: LayoutContextExtraData<*>

  @Before
  fun setUp() {
    MockitoAnnotations.initMocks(this)
    layoutContextExtraData = LithoLayoutContextExtraData(yogaNode)
  }

  @Test
  fun testGetExtraDataReturnsExpectedType() {
    val extraData = layoutContextExtraData.extraLayoutData

    assertThat(extraData).isInstanceOf(LithoLayoutContextExtraData.LithoLayoutExtraData::class.java)
  }

  @Test
  fun testGetExtraDataReturnsExpectedValueForLayoutDirection() {
    whenever(yogaNode.layoutDirection).thenReturn(YogaDirection.RTL)

    val extraData =
        layoutContextExtraData.extraLayoutData as LithoLayoutContextExtraData.LithoLayoutExtraData

    assertThat(extraData.layoutDirection).isEqualTo(YogaDirection.RTL)
  }
}


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

package com.facebook.litho

import android.graphics.Rect
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.LayoutSpecLifecycleTester
import com.facebook.litho.widget.MountSpecLifecycleTester
import java.util.ArrayList
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

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

  @JvmField @Rule val legacyLithoViewRule: LegacyLithoViewRule = LegacyLithoViewRule()
  private lateinit var lithoView: LithoView
  private lateinit var component: LayoutSpecLifecycleTester
  private lateinit var mountableComponent: MountSpecLifecycleTester
  private lateinit var lithoLifecycleProviderDelegate: LithoLifecycleProviderDelegate
  private lateinit var steps: MutableList<LifecycleStep.StepInfo>
  private lateinit var lifecycleTracker: LifecycleTracker

  @Before
  fun setup() {
    val c = legacyLithoViewRule.context
    lithoLifecycleProviderDelegate = LithoLifecycleProviderDelegate()
    steps = ArrayList<LifecycleStep.StepInfo>()
    lifecycleTracker = LifecycleTracker()
    component = LayoutSpecLifecycleTester.create(c).widthPx(10).heightPx(5).steps(steps).build()
    mountableComponent =
        MountSpecLifecycleTester.create(c)
            .widthPx(10)
            .heightPx(5)
            .lifecycleTracker(lifecycleTracker)
            .build()
    lithoView = LithoView.create(c, Column.create(c).build(), lithoLifecycleProviderDelegate)
    legacyLithoViewRule.useLithoView(lithoView)
  }

  @After
  fun resetViews() {
    steps.clear()
    lithoLifecycleProviderDelegate.moveToLifecycle(LithoLifecycleProvider.LithoLifecycle.DESTROYED)
  }

  @Test
  fun lithoLifecycleProviderDelegateInvisibleToVisibleTest() {
    legacyLithoViewRule
        .setRoot(component)
        .attachToWindow()
        .setSizeSpecs(exactly(10), exactly(5))
        .measure()
        .layout()
    legacyLithoViewRule.lithoView.notifyVisibleBoundsChanged(Rect(0, 0, 10, 10), true)
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_INVISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Invisible event is expected to be dispatched")
        .contains(LifecycleStep.ON_EVENT_INVISIBLE)
    steps.clear()
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_VISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Visible event is expected to be dispatched")
        .contains(LifecycleStep.ON_EVENT_VISIBLE)
  }

  @Test
  fun lithoLifecycleProviderDelegateInvisibleToInvisibleTest() {
    legacyLithoViewRule.setRoot(component).setSizeSpecs(exactly(10), exactly(5))
    legacyLithoViewRule.attachToWindow().measure().layout().setSizeSpecs(10, 10)
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_INVISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Invisible event is expected to be dispatched")
        .contains(LifecycleStep.ON_EVENT_INVISIBLE)
    steps.clear()
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_INVISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Invisible event is expected to be dispatched")
        .isEmpty()
  }

  @Test
  fun lithoLifecycleProviderDelegateVisibleToVisibleTest() {
    legacyLithoViewRule.setRoot(component).setSizeSpecs(exactly(10), exactly(5))
    legacyLithoViewRule.attachToWindow().measure().layout().setSizeSpecs(10, 10)
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_VISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Visible event is expected to be dispatched")
        .contains(LifecycleStep.ON_EVENT_VISIBLE)
    steps.clear()
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_VISIBLE)
    assertThat(LifecycleStep.getSteps(steps))
        .describedAs("Visible event is expected to be dispatched")
        .isEmpty()
  }

  @Test
  fun lithoLifecycleProviderDelegateVisibleToDestroyedTest() {
    legacyLithoViewRule.setRoot(mountableComponent).setSizeSpecs(exactly(10), exactly(5))
    legacyLithoViewRule.attachToWindow().measure().layout().setSizeSpecs(10, 10)
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_VISIBLE)
    assertThat(lifecycleTracker.steps)
        .describedAs("Visible event is expected to be dispatched")
        .contains(LifecycleStep.ON_MOUNT)
    lifecycleTracker.reset()
    lithoLifecycleProviderDelegate.moveToLifecycle(LithoLifecycleProvider.LithoLifecycle.DESTROYED)
    assertThat(lifecycleTracker.steps)
        .describedAs("Visible event is expected to be dispatched")
        .contains(LifecycleStep.ON_UNMOUNT)
  }

  @Test
  fun lithoLifecycleProviderComponentTreeResetVisibilityFlags() {
    legacyLithoViewRule.setRoot(component).setSizeSpecs(exactly(10), exactly(5))
    legacyLithoViewRule.attachToWindow().measure().layout().setSizeSpecs(10, 10)
    lithoLifecycleProviderDelegate.moveToLifecycle(
        LithoLifecycleProvider.LithoLifecycle.HINT_INVISIBLE)
    var hasVisibilityHint: Boolean =
        Whitebox.getInternalState<Boolean>(legacyLithoViewRule.lithoView, "mHasVisibilityHint")
    var pauseMountingWhileVisibilityHintFalse: Boolean =
        Whitebox.getInternalState<Boolean>(
            legacyLithoViewRule.lithoView, "mPauseMountingWhileVisibilityHintFalse")
    assertThat(hasVisibilityHint).isTrue
    assertThat(pauseMountingWhileVisibilityHintFalse).isTrue
    legacyLithoViewRule.useComponentTree(ComponentTree.create(legacyLithoViewRule.context).build())
    hasVisibilityHint =
        Whitebox.getInternalState<Boolean>(legacyLithoViewRule.lithoView, "mHasVisibilityHint")
    pauseMountingWhileVisibilityHintFalse =
        Whitebox.getInternalState<Boolean>(
            legacyLithoViewRule.lithoView, "mPauseMountingWhileVisibilityHintFalse")
    assertThat(hasVisibilityHint).isFalse
    assertThat(pauseMountingWhileVisibilityHintFalse).isFalse
  }
}


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

package com.facebook.litho

import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.TouchExpansionDelegateTest.Companion.emulateClickEvent
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.error.TestCrasherOnCreateLayout
import com.facebook.litho.testing.error.TestHasDelegateThatCrashesOnCreateLayout
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.OnClickCallbackComponent
import com.facebook.litho.widget.OnErrorNotPresentChild
import com.facebook.litho.widget.OnErrorPassUpChildTester
import com.facebook.litho.widget.OnErrorPassUpParentTester
import com.facebook.litho.widget.TriggerCallbackComponent
import java.lang.RuntimeException
import java.util.ArrayList
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import org.hamcrest.core.Is
import org.junit.Assume
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

/** Tests to make sure we wrap exceptions from common apis with the right metadata. */
@LooperMode(LooperMode.Mode.LEGACY)
@RunWith(LithoTestRunner::class)
class LithoMetadataExceptionWrapperTest {

  @JvmField @Rule var legacyLithoViewRule = LegacyLithoViewRule()

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

  @Test
  fun onCreateLayout_deepComponentStack_exceptionShowsComponentStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage(
        object : BaseMatcher<String?>() {
          override fun matches(item: Any): Boolean {
            if (item is String) {
              return item.contains("Wrapper(Row)") &&
                  item.contains("Row") &&
                  item.contains("Column") &&
                  item.contains("TestHasDelegateThatCrashesOnCreateLayout") &&
                  item.contains("TestCrasherOnCreateLayout")
            }
            return false
          }

          override fun describeTo(description: Description) = Unit
        })
    val c = legacyLithoViewRule.context
    legacyLithoViewRule
        .setRoot(
            Wrapper.create(c)
                .delegate(
                    Row.create(c)
                        .child(
                            Column.create(c)
                                .child(TestHasDelegateThatCrashesOnCreateLayout.create(c)))
                        .build()))
        .measure()
        .layout()
        .attachToWindow()
  }

  @Test
  fun onCreateLayout_onlyRootComponent_exceptionShowsComponentStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage("layout_stack: TestCrasherOnCreateLayout")
    val c = legacyLithoViewRule.context
    legacyLithoViewRule
        .setRoot(TestCrasherOnCreateLayout.create(c))
        .measure()
        .layout()
        .attachToWindow()
  }

  @Test
  fun onCreateLayout_withReRaisedErrorFromErrorBoundary_showsRightComponentStack() {
    Assume.assumeThat(
        "Error boundary is enabled in debug builds.",
        ComponentsConfiguration.IS_INTERNAL_BUILD,
        Is.`is`(true))
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage(
        object : BaseMatcher<String?>() {
          override fun matches(item: Any): Boolean {
            if (item is String) {
              return item.contains("OnErrorPassUpParentTester") &&
                  item.contains("OnErrorPassUpChildTester") &&
                  item.contains("ThrowExceptionGrandChildTester")
            }
            return false
          }

          override fun describeTo(description: Description) = Unit
        })
    val info: List<String> = ArrayList()
    val context = legacyLithoViewRule.context
    val component: Component =
        OnErrorPassUpParentTester.create(context)
            .child(OnErrorPassUpChildTester.create(context).info(info).build())
            .info(info)
            .build()
    legacyLithoViewRule.setRoot(component)
    legacyLithoViewRule.attachToWindow().measure().layout()
  }

  @Test
  fun onCreateLayout_withIndirectReRaisedErrorFromErrorBoundary_showsRightComponentStack() {
    Assume.assumeThat(
        "Error boundary is enabled in debug builds.",
        ComponentsConfiguration.IS_INTERNAL_BUILD,
        Is.`is`(true))
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage(
        object : BaseMatcher<String?>() {
          override fun matches(item: Any): Boolean {
            if (item is String) {
              return item.contains("OnErrorPassUpParentTester") &&
                  item.contains("OnErrorNotPresentChild") &&
                  item.contains("ThrowExceptionGrandChildTester")
            }
            return false
          }

          override fun describeTo(description: Description) = Unit
        })
    val info: List<String> = ArrayList()
    val context = legacyLithoViewRule.context
    val component =
        OnErrorPassUpParentTester.create(context)
            .child(OnErrorNotPresentChild.create(context).build())
            .info(info)
            .build()
    legacyLithoViewRule.setRoot(component)
    legacyLithoViewRule.attachToWindow().measure().layout()
  }

  @Test
  fun onCreateLayout_withLogTag_showsLogTagInStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage("log_tag: myLogTag")
    val c = ComponentContext(ApplicationProvider.getApplicationContext(), "myLogTag", null)
    legacyLithoViewRule
        .useComponentTree(ComponentTree.create(c).build())
        .setRoot(TestCrasherOnCreateLayout.create(c))
        .measure()
        .layout()
        .attachToWindow()
  }

  @Test
  fun onMount_withLogTag_showsLogTagInStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage("log_tag: myLogTag")
    val c = ComponentContext(ApplicationProvider.getApplicationContext(), "myLogTag", null)
    legacyLithoViewRule
        .useComponentTree(ComponentTree.create(c).build())
        .setSizePx(100, 100)
        .setRoot(TestCrasherOnCreateLayout.create(c))
        .measure()
        .layout()
        .attachToWindow()
  }

  @Test
  fun onClickEvent_withLogTag_showsLogTagInStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage("log_tag: myLogTag")
    expectedException.expectMessage("<cls>com.facebook.litho.widget.OnClickCallbackComponent</cls>")
    val c = ComponentContext(ApplicationProvider.getApplicationContext(), "myLogTag", null)
    val component: Component =
        Column.create(c)
            .child(
                OnClickCallbackComponent.create(c).widthPx(10).heightPx(10).callback {
                  throw RuntimeException("Expected test exception")
                })
            .build()
    legacyLithoViewRule
        .useComponentTree(ComponentTree.create(c).build())
        .setRoot(component)
        .attachToWindow()
        .measure()
        .layout()
    legacyLithoViewRule.lithoView.emulateClickEvent(7, 7)
  }

  @Test
  fun onTrigger_withLogTag_showsLogTagInStack() {
    expectedException.expect(LithoMetadataExceptionWrapper::class.java)
    expectedException.expectMessage("log_tag: myLogTag")
    expectedException.expectMessage("<cls>com.facebook.litho.widget.TriggerCallbackComponent</cls>")
    val c = ComponentContext(ApplicationProvider.getApplicationContext(), "myLogTag", null)
    val handle = Handle()
    val component: Component =
        Column.create(c)
            .child(
                TriggerCallbackComponent.create(c)
                    .widthPx(10)
                    .heightPx(10)
                    .handle(handle)
                    .callback { throw RuntimeException("Expected test exception") })
            .build()
    legacyLithoViewRule
        .useComponentTree(ComponentTree.create(c).build())
        .setRoot(component)
        .attachToWindow()
        .measure()
        .layout()
    TriggerCallbackComponent.doTrigger(legacyLithoViewRule.componentTree.context, handle)
  }
}


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

package com.facebook.litho

import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.yoga.YogaDirection
import com.facebook.yoga.YogaEdge
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LithoNodeResolvedPaddingTest {

  @JvmField @Rule val legacyLithoViewRule = LegacyLithoViewRule()
  private lateinit var builder: Component.Builder<*>

  @Before
  fun setup() {
    val context = legacyLithoViewRule.context
    builder = Column.create(context)
  }

  private fun padding(edge: YogaEdge, padding: Int): LithoNodeResolvedPaddingTest {
    builder.paddingPx(edge, padding)
    return this
  }

  private fun direction(direction: YogaDirection): LithoNodeResolvedPaddingTest {
    builder.layoutDirection(direction)
    return this
  }

  private fun calculateLayout(): LithoLayoutResult? =
      legacyLithoViewRule
          .attachToWindow()
          .setRoot(builder.build())
          .measure()
          .layout()
          .currentRootNode

  @Test
  fun testPaddingLeftWithUndefinedStartEnd() {
    val result = padding(YogaEdge.LEFT, 10).direction(YogaDirection.LTR).calculateLayout()
    assertThat(result?.paddingLeft).isEqualTo(10)
  }

  @Test
  fun testPaddingLeftWithDefinedStart() {
    val result =
        padding(YogaEdge.START, 5)
            .padding(YogaEdge.LEFT, 10)
            .direction(YogaDirection.LTR)
            .calculateLayout()
    assertThat(result?.paddingLeft).isEqualTo(5)
  }

  @Test
  fun testPaddingLeftWithDefinedEnd() {
    val result =
        padding(YogaEdge.END, 5)
            .padding(YogaEdge.LEFT, 10)
            .direction(YogaDirection.LTR)
            .calculateLayout()
    assertThat(result?.paddingLeft).isEqualTo(10)
  }

  @Test
  fun testPaddingLeftWithDefinedStartInRtl() {
    val result =
        padding(YogaEdge.START, 5)
            .padding(YogaEdge.LEFT, 10)
            .direction(YogaDirection.RTL)
            .calculateLayout()
    assertThat(result?.paddingLeft).isEqualTo(10)
  }

  @Test
  fun testPaddingLeftWithDefinedEndInRtl() {
    val result =
        padding(YogaEdge.END, 5)
            .padding(YogaEdge.LEFT, 10)
            .direction(YogaDirection.RTL)
            .calculateLayout()
    assertThat(result?.paddingLeft).isEqualTo(5)
  }

  @Test
  fun testPaddingRightWithUndefinedStartEnd() {
    val result = padding(YogaEdge.RIGHT, 10).direction(YogaDirection.LTR).calculateLayout()
    assertThat(result?.paddingRight).isEqualTo(10)
  }

  @Test
  fun testPaddingRightWithDefinedStart() {
    padding(YogaEdge.START, 5).padding(YogaEdge.RIGHT, 10).direction(YogaDirection.LTR)
    val result = calculateLayout()
    assertThat(result?.paddingRight).isEqualTo(10)
  }

  @Test
  fun testPaddingRightWithDefinedEnd() {
    val result =
        padding(YogaEdge.END, 5)
            .padding(YogaEdge.RIGHT, 10)
            .direction(YogaDirection.LTR)
            .calculateLayout()
    assertThat(result?.paddingRight).isEqualTo(5)
  }

  @Test
  fun testPaddingRightWithDefinedStartInRtl() {
    val result =
        padding(YogaEdge.START, 5)
            .padding(YogaEdge.RIGHT, 10)
            .direction(YogaDirection.RTL)
            .calculateLayout()
    assertThat(result?.paddingRight).isEqualTo(5)
  }

  @Test
  fun testPaddingRightWithDefinedEndInRtl() {
    val result =
        padding(YogaEdge.END, 5)
            .padding(YogaEdge.RIGHT, 10)
            .direction(YogaDirection.RTL)
            .calculateLayout()
    assertThat(result?.paddingRight).isEqualTo(10)
  }
}


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

package com.facebook.litho

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.util.SparseArray
import androidx.core.view.ViewCompat
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.logging.TestComponentsReporter
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
import com.facebook.litho.widget.ComponentWithState
import com.facebook.litho.widget.Text
import com.facebook.yoga.YogaDirection
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@RunWith(LithoTestRunner::class)
class LithoNodeTest {

  @JvmField @Rule val legacyLithoViewRule = LegacyLithoViewRule()

  private fun acquireInternalNode(): LithoNode {
    val context = legacyLithoViewRule.context
    legacyLithoViewRule
        .attachToWindow()
        .setRootAndSizeSpecSync(Column.create(context).build(), unspecified(0), unspecified(0))
        .measure()
        .layout()
    val root = requireNotNull(legacyLithoViewRule.currentRootNode)
    return root.node
  }

  private val componentsReporter = TestComponentsReporter()

  @Before
  fun setup() {
    ComponentsReporter.provide(componentsReporter)
  }

  @Test
  fun testLayoutDirectionFlag() {
    val node = acquireInternalNode()
    node.layoutDirection(YogaDirection.INHERIT)
    assertThat(isFlagSet(node, "PFLAG_LAYOUT_DIRECTION_IS_SET")).isTrue
    clearFlag(node, "PFLAG_LAYOUT_DIRECTION_IS_SET")
    assertEmptyFlags(node)
  }

  @Test
  fun testAddingAllAttributes() {
    val node = spy(acquireInternalNode())
    val nodeInfo: NodeInfo = mock()
    whenever(node.mutableNodeInfo()).thenReturn(nodeInfo)
    val clickHandler: EventHandler<ClickEvent> = mock()
    val longClickHandler: EventHandler<LongClickEvent> = mock()
    val touchHandler: EventHandler<TouchEvent> = mock()
    val interceptTouchHandler: EventHandler<InterceptTouchEvent> = mock()
    val focusChangedHandler: EventHandler<FocusChangedEvent> = mock()
    val viewTag = Any()
    val viewTags = SparseArray<Any>()
    val dispatchPopulateAccessibilityEventHandler:
        EventHandler<DispatchPopulateAccessibilityEventEvent> =
        mock()
    val onInitializeAccessibilityEventHandler: EventHandler<OnInitializeAccessibilityEventEvent> =
        mock()
    val onInitializeAccessibilityNodeInfoHandler:
        EventHandler<OnInitializeAccessibilityNodeInfoEvent> =
        mock()
    val onPopulateAccessibilityEventHandler: EventHandler<OnPopulateAccessibilityEventEvent> =
        mock()
    val onRequestSendAccessibilityEventHandler: EventHandler<OnRequestSendAccessibilityEventEvent> =
        mock()
    val performAccessibilityActionHandler: EventHandler<PerformAccessibilityActionEvent> = mock()
    val sendAccessibilityEventHandler: EventHandler<SendAccessibilityEventEvent> = mock()
    val sendAccessibilityEventUncheckedHandler: EventHandler<SendAccessibilityEventUncheckedEvent> =
        mock()
    val toBeAppliedInfo = NodeInfo()
    toBeAppliedInfo.clickHandler = clickHandler
    toBeAppliedInfo.focusChangeHandler = focusChangedHandler
    toBeAppliedInfo.longClickHandler = longClickHandler
    toBeAppliedInfo.touchHandler = touchHandler
    toBeAppliedInfo.interceptTouchHandler = interceptTouchHandler
    toBeAppliedInfo.setFocusable(true)
    toBeAppliedInfo.setSelected(false)
    toBeAppliedInfo.setEnabled(false)
    toBeAppliedInfo.setAccessibilityHeading(false)
    toBeAppliedInfo.contentDescription = "test"
    toBeAppliedInfo.viewTag = viewTag
    toBeAppliedInfo.viewTags = viewTags
    toBeAppliedInfo.shadowElevation = 60f
    toBeAppliedInfo.ambientShadowColor = Color.RED
    toBeAppliedInfo.spotShadowColor = Color.BLUE
    toBeAppliedInfo.clipToOutline = false
    toBeAppliedInfo.accessibilityRole = AccessibilityRole.BUTTON
    toBeAppliedInfo.accessibilityRoleDescription = "Test Role Description"
    toBeAppliedInfo.dispatchPopulateAccessibilityEventHandler =
        dispatchPopulateAccessibilityEventHandler
    toBeAppliedInfo.onInitializeAccessibilityEventHandler = onInitializeAccessibilityEventHandler
    toBeAppliedInfo.onInitializeAccessibilityNodeInfoHandler =
        onInitializeAccessibilityNodeInfoHandler
    toBeAppliedInfo.onPopulateAccessibilityEventHandler = onPopulateAccessibilityEventHandler
    toBeAppliedInfo.onRequestSendAccessibilityEventHandler = onRequestSendAccessibilityEventHandler
    toBeAppliedInfo.performAccessibilityActionHandler = performAccessibilityActionHandler
    toBeAppliedInfo.sendAccessibilityEventHandler = sendAccessibilityEventHandler
    toBeAppliedInfo.sendAccessibilityEventUncheckedHandler = sendAccessibilityEventUncheckedHandler
    node.applyNodeInfo(toBeAppliedInfo)
    verify(nodeInfo).clickHandler = clickHandler
    verify(nodeInfo).focusChangeHandler = focusChangedHandler
    verify(nodeInfo).longClickHandler = longClickHandler
    verify(nodeInfo).touchHandler = touchHandler
    verify(nodeInfo).interceptTouchHandler = interceptTouchHandler
    verify(nodeInfo).setFocusable(true)
    verify(nodeInfo).setSelected(false)
    verify(nodeInfo).setEnabled(false)
    verify(nodeInfo).setAccessibilityHeading(false)
    verify(nodeInfo).contentDescription = "test"
    verify(nodeInfo).viewTag = viewTag
    verify(nodeInfo).viewTags = viewTags
    verify(nodeInfo).shadowElevation = 60f
    verify(nodeInfo).ambientShadowColor = Color.RED
    verify(nodeInfo).spotShadowColor = Color.BLUE
    verify(nodeInfo).clipToOutline = false
    verify(nodeInfo).accessibilityRole = AccessibilityRole.BUTTON
    verify(nodeInfo).accessibilityRoleDescription = "Test Role Description"
    verify(nodeInfo).dispatchPopulateAccessibilityEventHandler =
        dispatchPopulateAccessibilityEventHandler
    verify(nodeInfo).onInitializeAccessibilityEventHandler = onInitializeAccessibilityEventHandler
    verify(nodeInfo).onInitializeAccessibilityNodeInfoHandler =
        onInitializeAccessibilityNodeInfoHandler
    verify(nodeInfo).onPopulateAccessibilityEventHandler = onPopulateAccessibilityEventHandler
    verify(nodeInfo).onRequestSendAccessibilityEventHandler = onRequestSendAccessibilityEventHandler
    verify(nodeInfo).performAccessibilityActionHandler = performAccessibilityActionHandler
    verify(nodeInfo).sendAccessibilityEventHandler = sendAccessibilityEventHandler
    verify(nodeInfo).sendAccessibilityEventUncheckedHandler = sendAccessibilityEventUncheckedHandler
  }

  @Test
  fun testApplyNodeInfo() {
    val node = acquireInternalNode()
    assertThat(node.nodeInfo == null).describedAs("Should be NULL at the beginning").isTrue
    val toBeAppliedInfo = NodeInfo()
    toBeAppliedInfo.alpha = 0.5f
    toBeAppliedInfo.setEnabled(true)
    toBeAppliedInfo.setClickable(true)
    node.applyNodeInfo(toBeAppliedInfo)
    assertThat(node.nodeInfo === toBeAppliedInfo)
        .describedAs("Should be the same instance as passing")
        .isTrue
    assertThat(node.nodeInfo?.alpha == 0.5f)
        .describedAs("Properties should be copied into the NodeInfo of the LithoNode")
        .isTrue
    assertThat(node.nodeInfo?.enabledState == NodeInfo.ENABLED_SET_TRUE)
        .describedAs("Properties should be copied into the NodeInfo of the LithoNode")
        .isTrue
    assertThat(node.nodeInfo?.clickableState == NodeInfo.CLICKABLE_SET_TRUE)
        .describedAs("Properties should be copied into the NodeInfo of the LithoNode")
        .isTrue
    val toBeAppliedInfo2 = NodeInfo()
    toBeAppliedInfo2.scale = 1.0f
    toBeAppliedInfo2.setSelected(true)
    toBeAppliedInfo2.setClickable(false)
    node.applyNodeInfo(toBeAppliedInfo2)
    assertThat(node.nodeInfo?.alpha == 0.5f)
        .describedAs("Properties should not be overridden")
        .isTrue
    assertThat(node.nodeInfo?.enabledState == NodeInfo.ENABLED_SET_TRUE)
        .describedAs("Properties should not be overridden")
        .isTrue
    assertThat(node.nodeInfo?.clickableState == NodeInfo.CLICKABLE_SET_FALSE)
        .describedAs("Properties should be overridden")
        .isTrue
    assertThat(node.nodeInfo?.scale == 1.0f).describedAs("Properties should be merged").isTrue
    assertThat(node.nodeInfo?.selectedState == NodeInfo.SELECTED_SET_TRUE)
        .describedAs("Properties should be merged")
        .isTrue
    node.applyNodeInfo(null)
    assertThat(node.nodeInfo != null)
        .describedAs("Should be the same after merging a NULL object")
        .isTrue
  }

  @Test
  fun testMutableNodeInfo() {
    val node = acquireInternalNode()
    val nodeInfo1 = node.mutableNodeInfo()
    assertThat(nodeInfo1 != null).describedAs("Should never return NULL").isTrue
    assertThat(node.nodeInfo === nodeInfo1)
        .describedAs("Should be the same instance as getNodeInfo returned")
        .isTrue
    val nodeInfo2 = node.mutableNodeInfo()
    assertThat(nodeInfo2 === nodeInfo1)
        .describedAs("Should return the same instance no matter how many times it is called")
        .isTrue
    val initNodeIfo = NodeInfo()
    initNodeIfo.alpha = 0.5f
    initNodeIfo.setEnabled(true)
    initNodeIfo.setClickable(true)
    node.applyNodeInfo(initNodeIfo)
    val mutableNodeInfo = node.mutableNodeInfo()
    assertThat(initNodeIfo !== mutableNodeInfo)
        .describedAs("Should return a copy of the initNodeInfo")
        .isTrue
    assertThat(mutableNodeInfo.alpha == 0.5f)
        .describedAs("Properties should be transferred into a copy of the initNodeInfo")
        .isTrue
    assertThat(mutableNodeInfo.enabledState == NodeInfo.ENABLED_SET_TRUE)
        .describedAs("Properties should be transferred into a copy of the initNodeInfo")
        .isTrue
    assertThat(mutableNodeInfo.clickableState == NodeInfo.CLICKABLE_SET_TRUE)
        .describedAs("Properties should be transferred into a copy of the initNodeInfo")
        .isTrue
    mutableNodeInfo.scale = 1.0f
    assertThat(node.nodeInfo != null)
        .describedAs("Should be the same state as mutableNodeInfo")
        .isTrue
    assertThat(node.nodeInfo?.scale == 1.0f)
        .describedAs("Property of the nodeInfo should also be updated")
        .isTrue
  }

  @Test
  fun testImportantForAccessibilityFlag() {
    val node = acquireInternalNode()
    node.importantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
    assertThat(isFlagSet(node, "PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET")).isTrue
    clearFlag(node, "PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET")
    assertEmptyFlags(node)
  }

  @Test
  fun testBackgroundFlag() {
    val node = acquireInternalNode()
    node.backgroundColor(0xFFFF0000.toInt())
    assertThat(isFlagSet(node, "PFLAG_BACKGROUND_IS_SET")).isTrue
    clearFlag(node, "PFLAG_BACKGROUND_IS_SET")
    assertEmptyFlags(node)
  }

  @Test
  fun testForegroundFlag() {
    val node = acquireInternalNode()
    node.foregroundColor(0xFFFF0000.toInt())
    assertThat(isFlagSet(node, "PFLAG_FOREGROUND_IS_SET")).isTrue
    clearFlag(node, "PFLAG_FOREGROUND_IS_SET")
    assertEmptyFlags(node)
  }

  @Test
  fun testTransitionKeyFlag() {
    val node = acquireInternalNode()
    node.transitionKey("key", "")
    assertThat(isFlagSet(node, "PFLAG_TRANSITION_KEY_IS_SET")).isTrue
    clearFlag(node, "PFLAG_TRANSITION_KEY_IS_SET")
    assertEmptyFlags(node)
  }

  @Test
  fun testCopyIntoNodeSetFlags() {
    val orig = acquireNestedTreeHolder()
    val dest = acquireInternalNode()
    orig.importantForAccessibility(0)
    orig.duplicateParentState(true)
    orig.background(ColorDrawable())
    orig.foreground(null)
    orig.visibleHandler(null)
    orig.focusedHandler(null)
    orig.fullImpressionHandler(null)
    orig.invisibleHandler(null)
    orig.unfocusedHandler(null)
    orig.visibilityChangedHandler(null)
    orig.transferInto(dest)
    assertThat(isFlagSet(dest, "PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_BACKGROUND_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_FOREGROUND_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_VISIBLE_HANDLER_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_FOCUSED_HANDLER_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_FULL_IMPRESSION_HANDLER_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_INVISIBLE_HANDLER_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_UNFOCUSED_HANDLER_IS_SET")).isTrue
    assertThat(isFlagSet(dest, "PFLAG_VISIBLE_RECT_CHANGED_HANDLER_IS_SET")).isTrue
  }

  @Test
  fun testComponentCreateAndRetrieveCachedLayoutLS_measure() {
    val baseContext = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    val c =
        ComponentContextUtils.withComponentTree(
            baseContext, ComponentTree.create(baseContext).build())
    val resolveStateContext = c.setRenderStateContextForTests()
    val resultCache = resolveStateContext.cache
    val unspecifiedSizeSpec = unspecified(0)
    val exactSizeSpec = exactly(50)
    val textComponent = Text.create(c).textSizePx(16).text("test").build()
    val textSize = Size()
    textComponent.measure(c, exactSizeSpec, unspecifiedSizeSpec, textSize)
    assertThat(resultCache.getCachedResult(textComponent)).isNotNull
    val cachedLayout = resultCache.getCachedResult(textComponent)
    assertThat(cachedLayout).isNotNull
    assertThat(cachedLayout?.lastWidthSpec).isEqualTo(exactSizeSpec)
    assertThat(cachedLayout?.lastHeightSpec).isEqualTo(unspecifiedSizeSpec)
    resultCache.clearCache(textComponent)
    assertThat(resultCache.getCachedResult(textComponent)).isNull()
  }

  @Test
  fun testComponentCreateAndRetrieveCachedLayoutLS_measureMightNotCacheInternalNode() {
    val baseContext = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    val c =
        ComponentContextUtils.withComponentTree(
            baseContext, ComponentTree.create(baseContext).build())
    val resolveStateContext = c.setRenderStateContextForTests()
    val resultCache = resolveStateContext.cache
    val unspecifiedSizeSpec = unspecified(0)
    val exactSizeSpec = exactly(50)
    val textComponent = Text.create(c).textSizePx(16).text("test").build()
    val textSize = Size()
    textComponent.measureMightNotCacheInternalNode(c, exactSizeSpec, unspecifiedSizeSpec, textSize)
    assertThat(resultCache.getCachedResult(textComponent)).isNotNull
    val cachedLayout = resultCache.getCachedResult(textComponent)
    assertThat(cachedLayout).isNotNull
    assertThat(cachedLayout?.lastWidthSpec).isEqualTo(exactSizeSpec)
    assertThat(cachedLayout?.lastHeightSpec).isEqualTo(unspecifiedSizeSpec)
    resultCache.clearCache(textComponent)
    assertThat(resultCache.getCachedResult(textComponent)).isNull()
  }

  @Test
  fun testMeasureMightNotCacheInternalNode_ContextWithoutStateHandler_returnsMeasurement() {
    if (ComponentsConfiguration.enableLayoutCaching) {
      return
    }
    val c = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    c.setRenderStateContextForTests()
    val component =
        Column.create(c)
            .child(Text.create(c).textSizePx(16).text("test"))
            .child(ComponentWithState.create(c))
            .build()
    val textSize = Size()
    component.measureMightNotCacheInternalNode(c, exactly(50), unspecified(0), textSize)
    assertThat(textSize.width).isEqualTo(50)
    assertThat(textSize.height).isGreaterThan(0)
  }

  @Test
  fun testMeasureMightNotCacheInternalNode_ContextWithoutLayoutStateContextOrStateHandler_returnsMeasurement() {
    if (ComponentsConfiguration.enableLayoutCaching) {
      return
    }
    val c = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    c.setRenderStateContextForTests()
    val component =
        Column.create(c)
            .child(Text.create(c).textSizePx(16).text("test"))
            .child(ComponentWithState.create(c))
            .build()
    val textSize = Size()
    component.measureMightNotCacheInternalNode(c, exactly(50), unspecified(0), textSize)
    assertThat(textSize.width).isEqualTo(50)
    assertThat(textSize.height).isGreaterThan(0)
  }

  companion object {
    private fun acquireNestedTreeHolder(): NestedTreeHolder = NestedTreeHolder(null)

    private fun isFlagSet(node: LithoNode, flagName: String): Boolean {
      val flagPosition = Whitebox.getInternalState<Long>(LithoNode::class.java, flagName)
      val flags = Whitebox.getInternalState<Long>(node, "mPrivateFlags")
      return flags and flagPosition != 0L
    }

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

    private fun assertEmptyFlags(node: LithoNode) {
      assertThat(Whitebox.getInternalState<Any>(node, "mPrivateFlags") as Long).isEqualTo(0L)
    }
  }
}


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

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.rendercore.LayoutCache
import com.facebook.rendercore.LayoutContext
import com.facebook.yoga.YogaDirection
import com.facebook.yoga.YogaEdge
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class LithoNodeTouchExpansionTest {

  private lateinit var node: LithoNode
  lateinit var context: ComponentContext
  private lateinit var layoutStateContext: LayoutStateContext

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    val resolveStateContext = context.setRenderStateContextForTests()
    node =
        requireNotNull(
            Resolver.resolve(resolveStateContext, context, Column.create(context).build()))
    node.mutableNodeInfo().touchHandler = EventHandler(null, 1)
    layoutStateContext =
        LayoutStateContext(
            resolveStateContext.treeId,
            resolveStateContext.cache,
            context,
            resolveStateContext.treeState,
            resolveStateContext.layoutVersion,
            resolveStateContext.rootComponentId,
            resolveStateContext.isAccessibilityEnabled,
            null,
            null)
  }

  private fun setDirection(direction: YogaDirection): LithoNodeTouchExpansionTest {
    node.layoutDirection(direction)
    return this
  }

  private fun touchExpansionPx(edge: YogaEdge, value: Int): LithoNodeTouchExpansionTest {
    node.touchExpansionPx(edge, value)
    return this
  }

  private fun calculateLayout(): LithoLayoutResult? {
    val context =
        LayoutContext(
            context.androidContext, LithoRenderContext(layoutStateContext), 0, LayoutCache(), null)
    return node.calculateLayout(context, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
  }

  @Test
  fun testTouchExpansionLeftWithoutTouchHandling() {
    node.mutableNodeInfo().touchHandler = null
    val result = touchExpansionPx(YogaEdge.LEFT, 10).calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(0)
  }

  @Test
  fun testTouchExpansionTopWithoutTouchHandling() {
    node.mutableNodeInfo().touchHandler = null
    val result = touchExpansionPx(YogaEdge.TOP, 10).calculateLayout()
    assertThat(result?.touchExpansionTop).isEqualTo(0)
  }

  @Test
  fun testTouchExpansionRightWithoutTouchHandling() {
    node.mutableNodeInfo().touchHandler = null
    val result = touchExpansionPx(YogaEdge.RIGHT, 10).calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(0)
  }

  @Test
  fun testTouchExpansionBottomWithoutTouchHandling() {
    node.mutableNodeInfo().touchHandler = null
    val result = touchExpansionPx(YogaEdge.BOTTOM, 10).calculateLayout()
    assertThat(result?.touchExpansionBottom).isEqualTo(0)
  }

  @Test
  fun testTouchExpansionLeftWithUndefinedStartEnd() {
    val result = touchExpansionPx(YogaEdge.LEFT, 10).calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(10)
  }

  @Test
  fun testTouchExpansionLeftWithDefinedStart() {
    val result =
        touchExpansionPx(YogaEdge.START, 5).touchExpansionPx(YogaEdge.LEFT, 10).calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(5)
  }

  @Test
  fun testTouchExpansionLeftWithDefinedEnd() {
    val result =
        touchExpansionPx(YogaEdge.END, 5).touchExpansionPx(YogaEdge.LEFT, 10).calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(10)
  }

  @Test
  fun testTouchExpansionLeftWithDefinedStartInRtl() {
    val result =
        setDirection(YogaDirection.RTL)
            .touchExpansionPx(YogaEdge.START, 5)
            .touchExpansionPx(YogaEdge.LEFT, 10)
            .calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(10)
  }

  @Test
  fun testTouchExpansionLeftWithDefinedEndInRtl() {
    val result =
        setDirection(YogaDirection.RTL)
            .touchExpansionPx(YogaEdge.END, 5)
            .touchExpansionPx(YogaEdge.LEFT, 10)
            .calculateLayout()
    assertThat(result?.touchExpansionLeft).isEqualTo(5)
  }

  @Test
  fun testTouchExpansionRightWithUndefinedStartEnd() {
    val result = touchExpansionPx(YogaEdge.RIGHT, 10).calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(10)
  }

  @Test
  fun testTouchExpansionRightWithDefinedStart() {
    val result =
        touchExpansionPx(YogaEdge.START, 5).touchExpansionPx(YogaEdge.RIGHT, 10).calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(10)
  }

  @Test
  fun testTouchExpansionRightWithDefinedEnd() {
    val result =
        touchExpansionPx(YogaEdge.END, 5).touchExpansionPx(YogaEdge.RIGHT, 10).calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(5)
  }

  @Test
  fun testTouchExpansionRightWithDefinedStartInRtl() {
    val result =
        setDirection(YogaDirection.RTL)
            .touchExpansionPx(YogaEdge.START, 5)
            .touchExpansionPx(YogaEdge.RIGHT, 10)
            .calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(5)
  }

  @Test
  fun testTouchExpansionRightWithDefinedEndInRtl() {
    val result =
        setDirection(YogaDirection.RTL)
            .touchExpansionPx(YogaEdge.END, 5)
            .touchExpansionPx(YogaEdge.RIGHT, 10)
            .calculateLayout()
    assertThat(result?.touchExpansionRight).isEqualTo(10)
  }
}


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

package com.facebook.litho

import android.content.Context
import android.os.Looper
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.LithoLifecycleProvider.LithoLifecycle
import com.facebook.litho.testing.BackgroundLayoutLooperRule
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.ComponentRenderInfo
import com.facebook.litho.widget.LayoutSpecLifecycleTester
import com.facebook.litho.widget.RecyclerBinder
import com.facebook.litho.widget.RenderInfo
import java.util.ArrayList
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.robolectric.Shadows
import org.robolectric.annotation.LooperMode

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

  private lateinit var lithoLifecycleProviderDelegate: LithoLifecycleProviderDelegate
  private lateinit var recyclerBinder: RecyclerBinder
  private lateinit var recyclerView: RecyclerView

  @JvmField @Rule var backgroundLayoutLooperRule = BackgroundLayoutLooperRule()

  @Before
  fun setup() {
    lithoLifecycleProviderDelegate = LithoLifecycleProviderDelegate()
    val c = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    recyclerView = RecyclerView(ApplicationProvider.getApplicationContext())
    recyclerView.layoutParams = ViewGroup.LayoutParams(10, 100)
    recyclerBinder =
        RecyclerBinder.Builder().lithoLifecycleProvider(lithoLifecycleProviderDelegate).build(c)
    recyclerBinder.mount(recyclerView)
    val components: MutableList<RenderInfo> = ArrayList()
    val stepsList: MutableList<List<StepInfo>> = ArrayList()
    for (i in 0..19) {
      val steps: List<StepInfo> = ArrayList()
      val component =
          LayoutSpecLifecycleTester.create(c).widthPx(10).heightPx(5).steps(steps).build()
      components.add(ComponentRenderInfo.create().component(component).build())
      stepsList.add(steps)
    }
    recyclerBinder.insertRangeAt(0, components)
    recyclerBinder.notifyChangeSetComplete(true, null)
    recyclerBinder.measure(Size(), exactly(10), exactly(100), mock())
    backgroundLayoutLooperRule.runToEndOfTasksSync()
    Shadows.shadowOf(Looper.getMainLooper()).idle()
  }

  @Test
  fun lithoLifecycleProviderDelegateRecyclerBinderVisibleTest() {
    lithoLifecycleProviderDelegate.moveToLifecycle(LithoLifecycle.HINT_VISIBLE)
    for (j in 0..19) {
      assertThat(recyclerBinder.getComponentAt(j)?.lifecycleProvider?.lifecycleStatus)
          .describedAs("Visible event is expected to be dispatched")
          .isEqualTo(LithoLifecycle.HINT_VISIBLE)
    }
  }

  @Test
  fun lithoLifecycleProviderDelegateRecyclerBinderInvisibleTest() {
    lithoLifecycleProviderDelegate.moveToLifecycle(LithoLifecycle.HINT_INVISIBLE)
    for (j in 0..19) {
      assertThat(recyclerBinder.getComponentAt(j)?.lifecycleProvider?.lifecycleStatus)
          .describedAs("Invisible event is expected to be dispatched")
          .isEqualTo(LithoLifecycle.HINT_INVISIBLE)
    }
  }
}