Skip to content

Commit

Permalink
Supporting size-mode enum in LithoTestRule to enable auto-resize
Browse files Browse the repository at this point in the history
Summary:
# Problem
As explained in T187041319, using LithoTestRule and you update the size of the root LithoView (e.g. due to a root change or state update), those changes won't be reflected until you call .measure().layout(), since that's when mount() is called in cases we need to resize the LithoView.

# Solution
Added `LithoViewResizeMode` enum and using in `LithoTestRule` to determine auto-resizing.

Reviewed By: adityasharat

Differential Revision: D67935972

fbshipit-source-id: a1fdcf118d6a12d8e87d0a61f4c4e1149b4b51a2
  • Loading branch information
aashay-gaikwad authored and facebook-github-bot committed Jan 14, 2025
1 parent bde19d4 commit 8a99f1d
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.facebook.litho

import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.testrunner.LithoTestRunner
import java.lang.Exception
import kotlinx.coroutines.CoroutineExceptionHandler
Expand All @@ -42,6 +43,10 @@ class UseCoroutineScopeTest {

@Rule @JvmField val lithoViewRule = LithoTestRule()

@Rule
@JvmField
val manualResizeLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

private val testDispatcher = StandardTestDispatcher()

@Before
Expand Down Expand Up @@ -243,7 +248,7 @@ class UseCoroutineScopeTest {
}
}

val lithoView = lithoViewRule.createTestLithoView()
val lithoView = manualResizeLithoTestRule.createTestLithoView()
try {
lithoView.setRoot(UseCoroutineScopeComponent())
} catch (e: Exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.facebook.litho

import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.widget.ClickEventTrackingImage
import com.facebook.litho.widget.ClickEventTrackingRow
Expand All @@ -31,6 +32,9 @@ import org.robolectric.annotation.LooperMode
@RunWith(LithoTestRunner::class)
class ClickEventHandlerUpdateTest {
@JvmField @Rule val mLithoTestRule = LithoTestRule()
@JvmField
@Rule
val manualResizeLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Test
fun `click event handler on host should update`() {
Expand All @@ -40,7 +44,7 @@ class ClickEventHandlerUpdateTest {

// render with tag 0
val testView =
mLithoTestRule.render(widthPx = 1000, heightPx = 1000) {
manualResizeLithoTestRule.render(widthPx = 1000, heightPx = 1000) {
Row.create(context)
.wrapInView()
.child(ClickEventTrackingRow.create(context).id("0").clickObserver(clickObserver))
Expand Down
125 changes: 125 additions & 0 deletions litho-it/src/test/com/facebook/litho/LithoTestRuleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.kotlin.widget.Text
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.view.alpha
import com.facebook.litho.view.onClick
import com.facebook.litho.view.viewTag
import com.facebook.litho.view.wrapInView
import com.facebook.litho.widget.ComponentWithTreeProp
import com.facebook.litho.widget.TextDrawable
import com.facebook.litho.widget.treeprops.SimpleTreeProp
import com.facebook.rendercore.Dimen
import com.facebook.rendercore.px
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
Expand All @@ -38,6 +43,9 @@ import org.robolectric.annotation.LooperMode
class LithoTestRuleTest {

@JvmField @Rule val mLithoTestRule = LithoTestRule()
@JvmField
@Rule
val manualResizeLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Test
fun onLithoTestRuleWithTreeProp_shouldPropagateTreeProp() {
Expand Down Expand Up @@ -82,4 +90,121 @@ class LithoTestRuleTest {
assertThat((thrown.stackTraceToString()).contains("Timed out!")).isFalse
assertThat(thrown).hasStackTraceContaining("Hi There!")
}

@Test
fun `given dynamic size component when size is updated then view has updated size on resetting root`() {
val testLithoView =
mLithoTestRule.render {
ComponentWithDynamicSize(width = 100.px, height = 100.px, alpha = 1f)
}

testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG).run {
assertThat(width).isEqualTo(100)
assertThat(height).isEqualTo(100)
assertThat(alpha).isEqualTo(1f)
}

val updatedWidth = 200
val updatedHeight = 300
val updatedAlpha = 0.5f
testLithoView.setRoot(
ComponentWithDynamicSize(
width = updatedWidth.px, height = updatedHeight.px, alpha = updatedAlpha))

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(width).isEqualTo(updatedWidth)
assertThat(height).isEqualTo(updatedHeight)
assertThat(alpha).isEqualTo(updatedAlpha)
}
}

@Test
fun `given rule with manual resizing and dynamic size component when size is updated then view size not updated on resetting root`() {
val width = 100
val height = 200
val alpha = 1f
val testLithoView =
manualResizeLithoTestRule.render { ComponentWithDynamicSize(width.px, height.px, alpha) }

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(this.width).isEqualTo(width)
assertThat(this.height).isEqualTo(height)
assertThat(this.alpha).isEqualTo(alpha)
}

testLithoView.setRoot(ComponentWithDynamicSize(width = 200.px, height = 300.px, alpha = .5f))

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(this.width).isEqualTo(width)
assertThat(this.height).isEqualTo(height)
assertThat(this.alpha).isEqualTo(alpha)
}
}

@Test
fun `given dynamic size component when size is updated then view has updated size on resetting root async`() {
val testLithoView =
mLithoTestRule.render {
ComponentWithDynamicSize(width = 100.px, height = 100.px, alpha = 1f)
}

testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG).run {
assertThat(width).isEqualTo(100)
assertThat(height).isEqualTo(100)
assertThat(alpha).isEqualTo(1f)
}

val updatedWidth = 200
val updatedHeight = 300
val updatedAlpha = 0.5f
testLithoView.setRootAsync(
ComponentWithDynamicSize(
width = updatedWidth.px, height = updatedHeight.px, alpha = updatedAlpha))

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(width).isEqualTo(updatedWidth)
assertThat(height).isEqualTo(updatedHeight)
assertThat(alpha).isEqualTo(updatedAlpha)
}
}

@Test
fun `given rule with manual resizing and dynamic size component when size is updated then view size not updated on resetting root async`() {
val width = 100
val height = 200
val alpha = 1f
val testLithoView =
manualResizeLithoTestRule.render { ComponentWithDynamicSize(width.px, height.px, alpha) }

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(this.width).isEqualTo(width)
assertThat(this.height).isEqualTo(height)
assertThat(this.alpha).isEqualTo(alpha)
}

testLithoView.setRootAsync(
ComponentWithDynamicSize(width = 200.px, height = 300.px, alpha = .5f))

with(testLithoView.findViewWithTag(ComponentWithDynamicSize.VIEW_TAG)) {
assertThat(this.width).isEqualTo(width)
assertThat(this.height).isEqualTo(height)
assertThat(this.alpha).isEqualTo(alpha)
}
}
}

private class ComponentWithDynamicSize(
private val width: Dimen,
private val height: Dimen,
private val alpha: Float,
) : KComponent() {
override fun ComponentScope.render(): Component {
val style = Style.width(width).height(height).alpha(alpha).viewTag(VIEW_TAG)

return Row(style = Style.wrapInView()) { child(Row(style = style) { child(Text("test")) }) }
}

companion object {
const val VIEW_TAG = "test_view"
}
}
69 changes: 38 additions & 31 deletions litho-it/src/test/com/facebook/litho/MountSpecLifecycleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.config.PreAllocationHandler
import com.facebook.litho.testing.LithoStatsRule
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.exactly
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
Expand Down Expand Up @@ -48,19 +49,23 @@ class MountSpecLifecycleTest {

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

@JvmField
@Rule
val manualResizeLithoViewRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@JvmField @Rule val lithoStatsRule: LithoStatsRule = LithoStatsRule()

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

@Test
fun lifecycle_onSetComponentWithoutLayout_shouldNotCallLifecycleMethods() {
val lifecycleTracker = LifecycleTracker()
lithoViewRule.createTestLithoView {
MountSpecLifecycleTester.create(lithoViewRule.context)
manualResizeLithoViewRule.createTestLithoView {
MountSpecLifecycleTester.create(manualResizeLithoViewRule.context)
.lifecycleTracker(lifecycleTracker)
.build()
}
lithoViewRule.idle()
manualResizeLithoViewRule.idle()
assertThat(lifecycleTracker.steps)
.describedAs("Only render lifecycle methods should be called")
.containsExactly(
Expand Down Expand Up @@ -384,54 +389,56 @@ class MountSpecLifecycleTest {
fun onSetRootWithPreallocatedMountContent_shouldCallLifecycleMethods() {
val looper = ShadowLooper.getLooperForThread(Thread.currentThread())
val info: List<StepInfo> = ArrayList()
val testLithoView =
lithoViewRule.createTestLithoView(
componentTree =
ComponentTree.create(lithoViewRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
preAllocationHandler =
PreAllocationHandler.Custom(
RunnableHandler.DefaultHandler(looper))))
.build()) {
PreallocatedMountSpecLifecycleTester.create(lithoViewRule.context).steps(info).build()
}
testLithoView.measure()
lithoViewRule.createTestLithoView(
componentTree =
ComponentTree.create(lithoViewRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
preAllocationHandler =
PreAllocationHandler.Custom(RunnableHandler.DefaultHandler(looper))))
.build()) {
PreallocatedMountSpecLifecycleTester.create(lithoViewRule.context).steps(info).build()
}

ShadowLooper.runUiThreadTasks()
assertThat(LifecycleStep.getSteps(info))
.describedAs("Should call the lifecycle methods on new instance in expected order")
.containsExactly(
LifecycleStep.ON_PREPARE,
LifecycleStep.ON_MEASURE,
LifecycleStep.ON_BOUNDS_DEFINED,
LifecycleStep.ON_ATTACHED)
LifecycleStep.ON_ATTACHED,
LifecycleStep.ON_CREATE_MOUNT_CONTENT,
LifecycleStep.ON_MOUNT,
LifecycleStep.ON_BIND)
}

@Test
fun onSetRootWithPreallocatedMountContent_shouldCallLifecycleMethodsInRenderCore() {
val looper = ShadowLooper.getLooperForThread(Thread.currentThread())
val info: List<StepInfo> = ArrayList()
val testLithoView =
lithoViewRule.createTestLithoView(
componentTree =
ComponentTree.create(lithoViewRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
preAllocationHandler =
PreAllocationHandler.Custom(
RunnableHandler.DefaultHandler(looper))))
.build()) {
PreallocatedMountSpecLifecycleTester.create(lithoViewRule.context).steps(info).build()
}
testLithoView.measure()
lithoViewRule.createTestLithoView(
componentTree =
ComponentTree.create(lithoViewRule.context)
.componentsConfiguration(
ComponentsConfiguration.defaultInstance.copy(
preAllocationHandler =
PreAllocationHandler.Custom(RunnableHandler.DefaultHandler(looper))))
.build()) {
PreallocatedMountSpecLifecycleTester.create(lithoViewRule.context).steps(info).build()
}

ShadowLooper.runUiThreadTasks()
assertThat(LifecycleStep.getSteps(info))
.describedAs("Should call the lifecycle methods on new instance in expected order")
.containsExactly(
LifecycleStep.ON_PREPARE,
LifecycleStep.ON_MEASURE,
LifecycleStep.ON_BOUNDS_DEFINED,
LifecycleStep.ON_ATTACHED)
LifecycleStep.ON_ATTACHED,
LifecycleStep.ON_CREATE_MOUNT_CONTENT,
LifecycleStep.ON_MOUNT,
LifecycleStep.ON_BIND)
assertThat(MountContentPools.mountContentPools.size)
.describedAs("Should contain only 1 content pool")
.isEqualTo(1)
Expand Down
5 changes: 4 additions & 1 deletion litho-it/src/test/com/facebook/litho/VisibilityEventsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import android.widget.HorizontalScrollView
import com.facebook.litho.LifecycleStep.StepInfo
import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.TestDrawableComponent
import com.facebook.litho.testing.TestViewComponent
import com.facebook.litho.testing.ViewGroupWithLithoViewChildren
Expand Down Expand Up @@ -53,7 +54,9 @@ import org.robolectric.util.ReflectionHelpers
@RunWith(LithoTestRunner::class)
class VisibilityEventsTest {

@JvmField @Rule val lithoTestRule: LithoTestRule = LithoTestRule()
@JvmField
@Rule
val lithoTestRule: LithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Test
fun testVisibleEvent() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.facebook.litho.config.ComponentsConfiguration
import com.facebook.litho.state.ComponentState
import com.facebook.litho.testing.BackgroundLayoutLooperRule
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.TestLithoView
import com.facebook.litho.testing.Whitebox
import com.facebook.litho.testing.assertj.LithoViewAssert.Companion.assertThat
Expand All @@ -55,7 +56,7 @@ class StateUpdateTest {
private var widthSpec = 0
private var heightSpec = 0

@Rule @JvmField val mLithoTestRule = LithoTestRule()
@Rule @JvmField val mLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Rule @JvmField val backgroundLayoutLooperRule = BackgroundLayoutLooperRule()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.facebook.litho.Style
import com.facebook.litho.core.height
import com.facebook.litho.core.width
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.testing.testrunner.LithoTestRunner
import com.facebook.litho.testing.unspecified
import com.facebook.rendercore.px
Expand All @@ -35,7 +36,7 @@ import org.junit.runner.RunWith
@RunWith(LithoTestRunner::class)
class VisibilityStylesTest {

@Rule @JvmField val mLithoTestRule = LithoTestRule()
@Rule @JvmField val mLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Test
fun onVisible_whenSet_firesWhenVisible() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.facebook.litho.LithoPrimitive
import com.facebook.litho.PrimitiveComponent
import com.facebook.litho.PrimitiveComponentScope
import com.facebook.litho.testing.LithoTestRule
import com.facebook.litho.testing.LithoTestRuleResizeMode
import com.facebook.litho.widget.collection.LazyList
import com.facebook.rendercore.primitives.FixedSizeLayoutBehavior
import com.facebook.rendercore.primitives.ViewAllocator
Expand All @@ -39,7 +40,7 @@ import org.robolectric.annotation.LooperMode
@RunWith(RobolectricTestRunner::class)
class RecyclerBinderPreparationOrderTest {

@JvmField @Rule val mLithoTestRule = LithoTestRule()
@JvmField @Rule val mLithoTestRule = LithoTestRule(resizeMode = LithoTestRuleResizeMode.MANUAL)

@Test
fun `default traversal order should match RecyclerView layout order for primitive`() {
Expand Down
Loading

0 comments on commit 8a99f1d

Please sign in to comment.