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

package com.facebook.litho

import android.view.ContextThemeWrapper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.it.R
import com.facebook.litho.it.R.attr.testAttrLargePadding
import com.facebook.litho.it.R.attr.testAttrLargeText
import com.facebook.litho.it.R.style.PaddingStyle
import com.facebook.litho.it.R.style.TestTheme
import com.facebook.litho.it.R.style.TextSizeStyle
import com.facebook.litho.testing.LegacyLithoViewRule
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.Text
import com.facebook.yoga.YogaEdge
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
@Ignore("t16280359")
class ComponentStyleTest {

  private var dimen = 0
  private var largeDimen = 0
  private lateinit var context: ComponentContext

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

  @Before
  fun setup() {
    context =
        ComponentContext(
            ContextThemeWrapper(ApplicationProvider.getApplicationContext(), TestTheme))
    legacyLithoViewRule.useContext(context)
    dimen = context.resources.getDimensionPixelSize(R.dimen.test_dimen)
    largeDimen = context.resources.getDimensionPixelSize(R.dimen.test_large_dimen)
  }

  @Test
  fun testStyleProp() {
    val component = Text.create(context, 0, TextSizeStyle).text("text").build()
    assertThat(Whitebox.getInternalState<Any>(component, "textSize") as Int).isEqualTo(dimen)
  }

  @Test
  fun testOverrideStyleProp() {
    val component =
        Text.create(context, 0, TextSizeStyle).text("text").textSizePx(2 * dimen).build()
    assertThat(Whitebox.getInternalState<Any>(component, "textSize") as Int).isEqualTo(2 * dimen)
  }

  @Test
  fun testStyleLayout() {
    val component = Text.create(context, 0, PaddingStyle).text("text").build()
    val result =
        LegacyLithoViewRule.getRootLayout(
            legacyLithoViewRule, component, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
    assertThat(result?.yogaNode?.getPadding(YogaEdge.LEFT)).isEqualTo(dimen)
  }

  @Test
  fun testOverrideStyleLayout() {
    val component =
        Text.create(context, 0, PaddingStyle)
            .text("text")
            .paddingPx(YogaEdge.ALL, dimen * 2)
            .build()
    val result =
        LegacyLithoViewRule.getRootLayout(
            legacyLithoViewRule, component, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
    assertThat(result?.yogaNode?.getPadding(YogaEdge.LEFT)).isEqualTo(2 * dimen)
  }

  @Test
  fun testAttributeStyleProp() {
    val component = Text.create(context, testAttrLargeText, 0).text("text").build()
    assertThat(Whitebox.getInternalState<Any>(component, "textSize") as Int).isEqualTo(largeDimen)
  }

  @Test
  fun testOverrideAttributeStyleProp() {
    val component =
        Text.create(context, testAttrLargeText, 0).text("text").textSizePx(dimen).build()
    assertThat(Whitebox.getInternalState<Any>(component, "textSize") as Int).isEqualTo(dimen)
  }

  @Test
  fun testAttributeStyleLayout() {
    val component = Text.create(context, testAttrLargePadding, 0).text("text").build()
    val result =
        LegacyLithoViewRule.getRootLayout(
            legacyLithoViewRule, component, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
    assertThat(result?.yogaNode?.getPadding(YogaEdge.LEFT)).isEqualTo(largeDimen)
  }

  @Test
  fun testOverrideAttributeStyleLayout() {
    val component =
        Text.create(context, testAttrLargePadding, 0)
            .text("text")
            .paddingPx(YogaEdge.ALL, dimen * 2)
            .build()
    val result =
        LegacyLithoViewRule.getRootLayout(
            legacyLithoViewRule, component, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
    assertThat(result?.yogaNode?.getPadding(YogaEdge.LEFT)).isEqualTo(2 * dimen)
  }

  @Test
  fun testStyleResOverridenByAttrResForProp() {
    val component = Text.create(context, testAttrLargeText, TextSizeStyle).text("text").build()
    assertThat(Whitebox.getInternalState<Any>(component, "textSize") as Int).isEqualTo(largeDimen)
  }

  @Test
  fun testStyleResOverridenByAttrResForLayout() {
    val component = Text.create(context, testAttrLargePadding, PaddingStyle).text("text").build()
    val result =
        LegacyLithoViewRule.getRootLayout(
            legacyLithoViewRule, component, SizeSpec.UNSPECIFIED, SizeSpec.UNSPECIFIED)
    assertThat(result?.yogaNode?.getPadding(YogaEdge.LEFT)).isEqualTo(largeDimen)
  }
}


/*
 * 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.os.Handler
import android.os.Looper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.testing.TestLayoutComponent
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

/** Tests for [ComponentTree.Builder] */
@RunWith(LithoTestRunner::class)
class ComponentTreeBuilderTest {

  private lateinit var looper: Looper
  private lateinit var componentsLogger: ComponentsLogger
  private lateinit var context: ComponentContext
  private lateinit var root: Component
  private lateinit var componentTreeBuilder: ComponentTree.Builder

  @Before
  fun setup() {
    looper = mock()
    componentsLogger = mock()
    context =
        ComponentContext(ApplicationProvider.getApplicationContext(), LOG_TAG, componentsLogger)
    root = TestLayoutComponent.create(context).build()
    componentTreeBuilder = ComponentTree.create(context, root)
  }

  @Test
  fun testDefaultCreation() {
    val componentTree = componentTreeBuilder.build()
    assertSameAsInternalState(componentTree, root, "mRoot")
    assertDefaults(componentTree)
  }

  @Test
  fun testCreationWithInputs() {
    val componentTree = componentTreeBuilder.layoutThreadLooper(looper).build()
    assertSameAsInternalState(componentTree, root, "mRoot")
    assertEqualToInternalState(componentTree, true, "mIsLayoutDiffingEnabled")
    assertThat(componentTree.isIncrementalMountEnabled).isTrue
    assertThat(context.logger).isEqualTo(componentsLogger)
    assertThat(context.logTag).isEqualTo(LOG_TAG)
    val handler = Whitebox.getInternalState<Handler>(componentTree, "mLayoutThreadHandler")
    assertThat(looper).isSameAs(handler.looper)
  }

  companion object {
    private const val LOG_TAG = "logTag"

    @JvmStatic
    private fun assertSameAsInternalState(
        componentTree: ComponentTree,
        obj: Any?,
        internalName: String
    ) {
      assertThat(obj).isSameAs(Whitebox.getInternalState(componentTree, internalName))
    }

    @JvmStatic
    private fun assertEqualToInternalState(
        componentTree: ComponentTree,
        obj: Any,
        internalName: String
    ) {
      assertThat(Whitebox.getInternalState(componentTree, internalName) as Any).isEqualTo(obj)
    }

    @JvmStatic
    private fun assertDefaults(componentTree: ComponentTree) {
      assertEqualToInternalState(componentTree, true, "mIsLayoutDiffingEnabled")
      assertThat(componentTree.isIncrementalMountEnabled).isTrue
    }
  }
}


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

package com.facebook.litho

import android.content.Context
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import com.facebook.litho.debug.LithoDebugEvent.LayoutCommitted
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.testing.LithoViewRule
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.rendercore.debug.DebugEvent
import com.facebook.rendercore.debug.DebugEventDispatcher
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class ComponentTreeDebugEventListenerTest {

  @get:Rule val rule = LithoViewRule()

  @Test
  fun `should process events if associated with a ComponentTree`() {
    val listener = TestListener()

    val componentContext = ComponentContext(getApplicationContext() as Context)
    val componentTree =
        ComponentTree.create(componentContext, EmptyComponent())
            .withComponentTreeDebugEventListener(listener)
            .build()

    rule.render(componentTree = componentTree) { MyComponent() }
    rule.idle()

    assertThat(listener.observedEvents).hasSize(1)
    assertThat(listener.observedEvents.first().type).isEqualTo(LayoutCommitted)
  }

  @Test
  fun `should not process events if not associated with a ComponentTree`() {
    val listener = TestListener()

    val componentContext = ComponentContext(getApplicationContext() as Context)
    val componentTree =
        ComponentTree.create(componentContext, EmptyComponent())
            .withComponentTreeDebugEventListener(null)
            .build()

    rule.render(componentTree = componentTree) { MyComponent() }
    rule.idle()

    assertThat(listener.observedEvents).hasSize(0)
  }

  @Test
  fun `should not process any component tree event once released`() {
    val listener = TestListener()

    val componentContext = ComponentContext(getApplicationContext() as Context)
    val componentTree =
        ComponentTree.create(componentContext, EmptyComponent())
            .withComponentTreeDebugEventListener(listener)
            .build()

    rule.render(componentTree = componentTree) { MyComponent() }
    rule.idle()

    assertThat(listener.observedEvents).hasSize(1)
    assertThat(listener.observedEvents.last().type).isEqualTo(LayoutCommitted)

    /* if we dispatch the event we are looking for we will process it */
    DebugEventDispatcher.dispatch(
        type = LayoutCommitted, renderStateId = { componentTree.mId.toString() })
    assertThat(listener.observedEvents).hasSize(2)
    assertThat(listener.observedEvents.last().type).isEqualTo(LayoutCommitted)

    /* if we dispatch the event we are looking for after the Component Tree is released, it will not
    be processed */
    componentTree.release()
    DebugEventDispatcher.dispatch(
        type = LayoutCommitted, renderStateId = { componentTree.mId.toString() })
    assertThat(listener.observedEvents).hasSize(2)
  }

  private class TestListener : ComponentTreeDebugEventListener {

    val observedEvents: List<DebugEvent>
      get() = _observedEvents

    private val _observedEvents = mutableListOf<DebugEvent>()

    override fun onEvent(debugEvent: DebugEvent) {
      _observedEvents.add(debugEvent)
    }

    override val events: Set<String> = setOf(LayoutCommitted)
  }

  private class MyComponent : KComponent() {
    override fun ComponentScope.render(): Component = Text("Hello")
  }
}


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

package com.facebook.litho

import android.content.Context
import android.view.View
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.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.Assume
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(LithoTestRunner::class)
class ComponentTreeDumpingHelperTest {

  @Before
  fun skipIfRelease() {
    Assume.assumeTrue(
        "These tests cover debug functionality and can only be run for internal builds.",
        ComponentsConfiguration.IS_INTERNAL_BUILD)
  }

  @Test
  fun testBasicComponentTreeDumping() {
    val component: Component =
        object : InlineLayoutSpec() {
          override fun onCreateLayout(c: ComponentContext): Component? =
              SimpleMountSpecTester.create(c).widthPx(100).heightPx(100).build()
        }
    var componentContext: ComponentContext =
        ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    val componentTree = ComponentTree.create(componentContext, component).build()
    componentContext = ComponentContextUtils.withComponentTree(componentContext, componentTree)
    val lithoView = LithoView(ApplicationProvider.getApplicationContext<Context>())
    lithoView.componentTree = componentTree
    lithoView.measure(
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
        View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
    val string = ComponentTreeDumpingHelper.dumpContextTree(componentTree)
    assertThat(string)
        .containsPattern("InlineLayout\\{V}\n  SimpleMountSpecTester\\{V 100.0x100.0}")
  }
}


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

package com.facebook.litho

import android.content.Context
import android.util.Pair
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.testing.testrunner.LithoTestRunner
import java.util.ArrayList
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock

@RunWith(LithoTestRunner::class)
class ComponentTreeEventHandlerTest {

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

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

  @Test
  fun testAllEventHandlersForEventDispatchInfoAreUpdated() {
    val component = mock<SpecGeneratedComponent>()
    val componentGlobalKey = "component1"
    val scopedContext = ComponentContext.withComponentScope(context, component, componentGlobalKey)
    val eventHandlersController = componentTree.eventHandlersController
    val eventHandler1: EventHandler<*> =
        Component.newEventHandler<Any>(
            component.javaClass, "TestComponent", scopedContext, 1, emptyArray())
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(0)
    val eventHandler2: EventHandler<*> =
        Component.newEventHandler<Any>(
            component.javaClass, "TestComponent", scopedContext, 1, emptyArray())
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(0)
    assertThat(eventHandler1.dispatchInfo).isNotSameAs(eventHandler2.dispatchInfo)
    val eventHandlers = ArrayList<Pair<String, EventHandler<*>>>()
    eventHandlers.add(Pair(componentGlobalKey, eventHandler1))
    eventHandlers.add(Pair(componentGlobalKey, eventHandler2))
    eventHandlersController.canonicalizeEventDispatchInfos(eventHandlers)
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(1)
    assertThat(eventHandler1.dispatchInfo).isSameAs(eventHandler2.dispatchInfo)
    val newComponent = mock<SpecGeneratedComponent>()
    val newScopedContext =
        ComponentContext.withComponentScope(context, newComponent, componentGlobalKey)
    eventHandlersController.updateEventDispatchInfoForGlobalKey(
        newScopedContext, newComponent, componentGlobalKey)
    eventHandlersController.clearUnusedEventDispatchInfos()
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(1)
    assertThat(eventHandler1.dispatchInfo.componentContext).isSameAs(newScopedContext)
    assertThat(eventHandler2.dispatchInfo.componentContext).isSameAs(newScopedContext)
    assertThat(eventHandler1.dispatchInfo.hasEventDispatcher).isSameAs(newComponent)
    assertThat(eventHandler2.dispatchInfo.hasEventDispatcher).isSameAs(newComponent)
  }

  @Test
  fun testClearUnusedEntries() {
    val component = mock<SpecGeneratedComponent>()
    val component2 = mock<SpecGeneratedComponent>()
    val component2_2 = mock<SpecGeneratedComponent>()
    val componentGlobalKey1 = "component1"
    val componentGlobalKey2 = "component2"
    val scopedContext = ComponentContext.withComponentScope(context, component, componentGlobalKey1)
    val scopedContext2 =
        ComponentContext.withComponentScope(context, component2, componentGlobalKey2)
    val scopedContext2_2 =
        ComponentContext.withComponentScope(context, component2_2, componentGlobalKey2)
    val eventHandlersController = componentTree.eventHandlersController
    val eventHandler1: EventHandler<*> =
        Component.newEventHandler<Any>(
            component.javaClass, "TestComponent", scopedContext, 1, emptyArray())
    val eventHandler2: EventHandler<*> =
        Component.newEventHandler<Any>(
            component2.javaClass, "TestComponent2", scopedContext2, 1, emptyArray())
    val eventHandlers = ArrayList<Pair<String, EventHandler<*>>>()
    eventHandlers.add(Pair(componentGlobalKey1, eventHandler1))
    eventHandlers.add(Pair(componentGlobalKey2, eventHandler2))
    eventHandlersController.canonicalizeEventDispatchInfos(eventHandlers)
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(2)
    assertThat(eventHandler1.dispatchInfo.componentContext).isSameAs(scopedContext)
    assertThat(eventHandler2.dispatchInfo.componentContext).isSameAs(scopedContext2)
    assertThat(eventHandler1.dispatchInfo.hasEventDispatcher).isSameAs(component)
    assertThat(eventHandler2.dispatchInfo.hasEventDispatcher).isSameAs(component2)
    eventHandlersController.updateEventDispatchInfoForGlobalKey(
        scopedContext2_2, component2_2, componentGlobalKey2)
    eventHandlersController.clearUnusedEventDispatchInfos()
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(1)
    assertThat(eventHandlersController.dispatchInfos[componentGlobalKey2]).isNotNull
    eventHandlersController.clearUnusedEventDispatchInfos()
    assertThat(eventHandlersController.dispatchInfos.size).isEqualTo(0)
    assertThat(eventHandler1.dispatchInfo.componentContext).isSameAs(scopedContext)
    assertThat(eventHandler2.dispatchInfo.componentContext).isSameAs(scopedContext2_2)
    assertThat(eventHandler1.dispatchInfo.hasEventDispatcher).isSameAs(component)
    assertThat(eventHandler2.dispatchInfo.hasEventDispatcher).isSameAs(component2_2)
  }
}


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

package com.facebook.litho

import android.content.Context
import android.os.Looper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.SizeSpec.EXACTLY
import com.facebook.litho.SizeSpec.makeSizeSpec
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows
import org.robolectric.annotation.LooperMode
import org.robolectric.shadows.ShadowLooper

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

  private lateinit var context: ComponentContext
  private lateinit var component: Component
  private lateinit var componentTree: ComponentTree
  private lateinit var resolveThreadShadowLooper: ShadowLooper
  private lateinit var layoutThreadShadowLooper: ShadowLooper
  private var widthSpec: Int = 0
  private var widthSpec2: Int = 0
  private var heightSpec: Int = 0
  private var heightSpec2: Int = 0

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    component = SimpleMountSpecTester.create(context).build()
    componentTree = ComponentTree.create(context, component).build()
    resolveThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultResolveThreadLooper")
                as Looper)
    layoutThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultLayoutThreadLooper")
                as Looper)
    widthSpec = makeSizeSpec(39, EXACTLY)
    widthSpec2 = makeSizeSpec(40, EXACTLY)
    heightSpec = makeSizeSpec(41, EXACTLY)
    heightSpec2 = makeSizeSpec(42, EXACTLY)
  }

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

  @Test
  fun testNoLayoutComputed() {
    assertThat(componentTree.hasCompatibleLayout(widthSpec, heightSpec)).isFalse
  }

  @Test
  fun testMainThreadLayoutSet() {
    componentTree.setRootAndSizeSpecSync(component, widthSpec, heightSpec)
    assertThat(componentTree.hasCompatibleLayout(widthSpec, heightSpec)).isTrue
    assertThat(componentTree.hasCompatibleLayout(widthSpec2, heightSpec2)).isFalse
  }

  @Test
  fun testBackgroundLayoutSet() {
    componentTree.setSizeSpecAsync(widthSpec, heightSpec)
    assertThat(componentTree.hasCompatibleLayout(widthSpec, heightSpec)).isFalse

    // Now the background thread run the queued task.
    runToEndOfTasks()
    assertThat(componentTree.hasCompatibleLayout(widthSpec, heightSpec)).isTrue
  }
}


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

package com.facebook.litho

import android.content.Context
import android.graphics.Color
import android.graphics.Color.BLACK
import android.graphics.Color.YELLOW
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.testing.TestTransitionComponent
import com.facebook.litho.testing.eventhandler.EventHandlerTestHelper
import com.facebook.litho.testing.eventhandler.EventHandlerTestHelper.MockEventHandler
import com.facebook.litho.testing.helper.ComponentTestHelper
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import com.facebook.litho.widget.SolidColor
import com.facebook.litho.widget.Text
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows

@RunWith(LithoTestRunner::class)
class ComponentTreeMountTest {

  private lateinit var context: ComponentContext

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

  @Test
  fun testRemountsWithNewInputOnSameLayout() {
    val lithoView =
        ComponentTestHelper.mountComponent(
            context, SimpleMountSpecTester.create(context).color(Color.BLACK).build())
    Shadows.shadowOf(lithoView).callOnAttachedToWindow()
    assertThat(lithoView.drawables).hasSize(1)
    assertThat((lithoView.drawables[0] as ColorDrawable).color).isEqualTo(Color.BLACK)
    lithoView.componentTree?.root =
        SimpleMountSpecTester.create(context).color(Color.YELLOW).build()
    assertThat(lithoView.drawables).hasSize(1)
    assertThat((lithoView.drawables[0] as ColorDrawable).color).isEqualTo(Color.YELLOW)
  }

  @Test
  fun testReentrantMounts() {
    val lithoView = ComponentTestHelper.mountComponent(context, EmptyComponent(), true, true)
    val visibleEventHandler =
        EventHandlerTestHelper.createMockEventHandler(
            VisibleEvent::class.java,
            MockEventHandler<VisibleEvent?, Void?> {
              lithoView.setComponent(
                  TestTransitionComponent.create(
                          context,
                          Row.create(context)
                              .child(Text.create(context).text("text").textSizeDip(20f))
                              .child(
                                  SolidColor.create(context)
                                      .color(Color.BLUE)
                                      .widthDip(20f)
                                      .heightDip(20f))
                              .build())
                      .build())
              null
            })
    lithoView.setComponent(
        TestTransitionComponent.create(
                context,
                Column.create(context)
                    .child(
                        SolidColor.create(context).color(Color.YELLOW).widthDip(10f).heightDip(10f))
                    .child(
                        SolidColor.create(context).color(Color.GREEN).widthDip(10f).heightDip(10f))
                    .child(
                        SolidColor.create(context).color(Color.GRAY).widthDip(10f).heightDip(10f))
                    .build())
            .visibleHandler(visibleEventHandler)
            .build())
    lithoView.notifyVisibleBoundsChanged(Rect(0, 0, 100, 100), true)
  }
}


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

package com.facebook.litho

import android.content.Context
import android.os.Looper
import androidx.test.core.app.ApplicationProvider
import com.facebook.litho.SizeSpec.EXACTLY
import com.facebook.litho.SizeSpec.makeSizeSpec
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.SimpleMountSpecTester
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.reset
import org.mockito.kotlin.verify
import org.robolectric.Shadows
import org.robolectric.annotation.LooperMode
import org.robolectric.shadows.ShadowLooper

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

  private lateinit var context: ComponentContext
  private lateinit var component: Component
  private lateinit var componentTree: ComponentTree
  private lateinit var resolveThreadShadowLooper: ShadowLooper
  private lateinit var layoutThreadShadowLooper: ShadowLooper
  private lateinit var listener: ComponentTree.NewLayoutStateReadyListener
  private var widthSpec = 0
  private var widthSpec2 = 0
  private var heightSpec = 0
  private var heightSpec2 = 0

  @Before
  fun setup() {
    context = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    component = SimpleMountSpecTester.create(context).build()
    componentTree = ComponentTree.create(context, component).build()
    resolveThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultResolveThreadLooper")
                as Looper)
    layoutThreadShadowLooper =
        Shadows.shadowOf(
            Whitebox.invokeMethod<Any>(ComponentTree::class.java, "getDefaultLayoutThreadLooper")
                as Looper)
    listener = mock()
    widthSpec = makeSizeSpec(39, EXACTLY)
    widthSpec2 = makeSizeSpec(40, EXACTLY)
    heightSpec = makeSizeSpec(41, EXACTLY)
    heightSpec2 = makeSizeSpec(42, EXACTLY)
  }

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

  @Test
  fun testListenerInvokedForSetRoot() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setRootAndSizeSpecSync(component, widthSpec, heightSpec)
    verify(listener).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerInvokedForSetRootAsync() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setSizeSpecAsync(widthSpec, heightSpec)
    verify(listener, never()).onNewLayoutStateReady(anyOrNull<ComponentTree>())

    // Now the background thread run the queued task.
    runToEndOfTasks()
    verify(listener).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerInvokedOnlyOnceForMultipleSetRootAsync() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setSizeSpecAsync(widthSpec, heightSpec)
    componentTree.setSizeSpecAsync(widthSpec2, heightSpec2)
    verify(listener, never()).onNewLayoutStateReady(anyOrNull<ComponentTree>())

    // Now the background thread run the queued task.
    runToEndOfTasks()
    verify(listener).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerInvokedForSetRootAsyncWhenAttached() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setSizeSpecAsync(widthSpec, heightSpec)
    componentTree.setLithoView(LithoView(context))
    componentTree.attach()
    verify(listener, never()).onNewLayoutStateReady(anyOrNull<ComponentTree>())

    // Now the background thread run the queued task.
    runToEndOfTasks()
    verify(listener).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerInvokedForMeasure() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setLithoView(LithoView(context))
    componentTree.attach()
    componentTree.setSizeSpec(widthSpec, heightSpec)
    verify(listener).onNewLayoutStateReady(componentTree)
    reset(listener)
    componentTree.measure(widthSpec2, heightSpec2, intArrayOf(0, 0), false)
    verify(listener).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerNotInvokedWhenMeasureDoesntComputeALayout() {
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setLithoView(LithoView(context))
    componentTree.attach()
    componentTree.setSizeSpec(widthSpec, heightSpec)
    verify(listener).onNewLayoutStateReady(componentTree)
    reset(listener)
    componentTree.measure(widthSpec, heightSpec, intArrayOf(0, 0), false)
    verify(listener, never()).onNewLayoutStateReady(componentTree)
  }

  @Test
  fun testListenerNotInvokedWhenNewMeasureSpecsAreCompatible() {
    componentTree.setLithoView(LithoView(context))
    componentTree.attach()
    componentTree.setSizeSpec(widthSpec, heightSpec)
    componentTree.newLayoutStateReadyListener = listener
    componentTree.setSizeSpec(widthSpec, heightSpec)
    verify(listener, never()).onNewLayoutStateReady(componentTree)
  }
}


/*
 * 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.logging.TestComponentsReporter
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
import com.facebook.litho.widget.TreePropTestContainerComponent
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.LooperMode

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

  private lateinit var c: ComponentContext

  @Before
  fun setup() {
    val componentsReporter = TestComponentsReporter()
    c = ComponentContext(ApplicationProvider.getApplicationContext<Context>())
    ComponentsReporter.provide(componentsReporter)
  }

  @Test
  fun test() {
    val component = TreePropTestContainerComponent.create(c).build()
    getLithoView(component)
  }

  private fun getLithoView(component: Component): LithoView {
    val lithoView = LithoView(c)
    val componentTree = ComponentTree.create(c, component).isReconciliationEnabled(true).build()
    lithoView.componentTree = componentTree
    lithoView.measure(unspecified(640), unspecified(480))
    lithoView.layout(0, 0, lithoView.measuredWidth, lithoView.measuredHeight)
    return lithoView
  }
}