/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho; import android.graphics.Color; import com.facebook.litho.testing.ComponentTestHelper; import com.facebook.litho.testing.TestComponent; import com.facebook.litho.testing.TestDrawableComponent; import com.facebook.litho.testing.testrunner.ComponentsTestRunner; import com.facebook.litho.testing.util.InlineLayoutSpec; import com.facebook.litho.widget.SolidColor; import com.facebook.litho.widget.Text; import com.facebook.yoga.YogaAlign; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.facebook.litho.FrameworkLogEvents.EVENT_PREPARE_MOUNT; import static com.facebook.litho.FrameworkLogEvents.PARAM_MOVED_COUNT; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(ComponentsTestRunner.class) public class MountStateRemountInPlaceTest { private ComponentContext mContext; private ComponentsLogger mComponentsLogger; @Before public void setup() { mComponentsLogger = spy(new TestComponentsLogger()); when(mComponentsLogger.newEvent(any(int.class))).thenCallRealMethod(); when(mComponentsLogger.newPerformanceEvent(any(int.class))).thenCallRealMethod(); mContext = new ComponentContext(RuntimeEnvironment.application, "tag", mComponentsLogger); } @Test public void testMountUnmountWithShouldUpdate() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .unique() .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .unique() .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertTrue(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithNoShouldUpdate() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertFalse(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithNoShouldUpdateAndDifferentSize() { final TestComponent firstComponent = TestDrawableComponent .create( mContext, 0, 0, true, true, true, false, false, true /*isMountSizeDependent*/) .measuredHeight(10) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent .create( mContext, 0, 0, true, true, true, false, false, true /*isMountSizeDependent*/) .measuredHeight(11) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertTrue(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithNoShouldUpdateAndSameSize() { final TestComponent firstComponent = TestDrawableComponent .create( mContext, 0, 0, true, true, true, false, false, true /*isMountSizeDependent*/) .measuredHeight(10) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent .create( mContext, 0, 0, true, true, true, false, false, true /*isMountSizeDependent*/) .measuredHeight(10) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertFalse(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithNoShouldUpdateAndDifferentMeasures() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( new LithoView(mContext), ComponentTree.create(mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }) .incrementalMount(false) .layoutDiffing(false) .build(), makeMeasureSpec(100, AT_MOST), makeMeasureSpec(100, AT_MOST)); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .widthPx(10) .heightPx(10) .build(); } }); assertTrue(lithoView.isLayoutRequested()); assertFalse(secondComponent.wasOnMountCalled()); assertFalse(secondComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithNoShouldUpdateAndSameMeasures() { final TestComponent firstComponent = TestDrawableComponent.create(mContext, 0, 0, true, true, true, false, false, true) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( new LithoView(mContext), ComponentTree.create(mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }) .incrementalMount(false) .layoutDiffing(false) .build(), makeMeasureSpec(100, EXACTLY), makeMeasureSpec(100, EXACTLY)); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .widthPx(10) .heightPx(10) .build(); } }); assertFalse(lithoView.isLayoutRequested()); assertTrue(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testRebindWithNoShouldUpdateAndSameMeasures() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( new LithoView(mContext), ComponentTree.create(mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }) .incrementalMount(false) .layoutDiffing(false) .build(), makeMeasureSpec(100, EXACTLY), makeMeasureSpec(100, EXACTLY)); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .widthPx(10) .heightPx(10) .build(); } }); assertFalse(lithoView.isLayoutRequested()); assertFalse(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithSkipShouldUpdate() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .color(Color.BLACK) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .color(Color.BLACK) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertFalse(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountWithSkipShouldUpdateAndRemount() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .color(Color.BLACK) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .color(Color.WHITE) .build(); lithoView.getComponentTree().setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }); assertTrue(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testMountUnmountDoesNotSkipShouldUpdateAndRemount() { final TestComponent firstComponent = TestDrawableComponent.create(mContext) .unique() .build(); final LithoView firstLithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create(mContext) .unique() .build(); final ComponentTree secondTree = ComponentTree.create( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }) .incrementalMount(false) .layoutDiffing(false) .build(); secondTree.setSizeSpec(100, 100); final TestComponent thirdComponent = TestDrawableComponent.create(mContext) .build(); secondTree.setRoot(new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(thirdComponent) .build(); } }); ComponentTestHelper.mountComponent(firstLithoView, secondTree); assertTrue(thirdComponent.wasOnMountCalled()); assertTrue(thirdComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testSkipShouldUpdateAndRemountForUnsupportedComponent() { final TestComponent firstComponent = TestDrawableComponent.create( mContext, false, true, true, false, false) .build(); final LithoView firstLithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(firstComponent) .build(); } }); assertTrue(firstComponent.wasOnMountCalled()); assertTrue(firstComponent.wasOnBindCalled()); assertFalse(firstComponent.wasOnUnmountCalled()); final TestComponent secondComponent = TestDrawableComponent.create( mContext, false, true, true, false, false) .build(); final ComponentTree secondTree = ComponentTree.create( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child(secondComponent) .build(); } }) .incrementalMount(false) .layoutDiffing(false) .build(); secondTree.setSizeSpec(100, 100); ComponentTestHelper.mountComponent(firstLithoView, secondTree); assertTrue(secondComponent.wasOnMountCalled()); assertTrue(secondComponent.wasOnBindCalled()); assertTrue(firstComponent.wasOnUnmountCalled()); } @Test public void testRemountSameSubTreeWithDifferentParentHost() { final TestComponent firstComponent = TestDrawableComponent.create( mContext, false, true, true, false, false) .build(); InlineLayoutSpec firstLayout = new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child( Column.create(c) .clickHandler(c.newEventHandler(3)) .child( Text.create(c).text("test"))) .child( Column.create(c) .clickHandler(c.newEventHandler(2)) .child( Text.create(c).text("test2")) .child( Column.create(c) .clickHandler(c.newEventHandler(1)) .child( firstComponent) .child( SolidColor.create(c).color(Color.GREEN)))) .build(); } }; final InlineLayoutSpec secondLayout = new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .child( Column.create(c) .clickHandler(c.newEventHandler(3)) .child( Text.create(c).text("test")) .child( Column.create(c) .clickHandler(c.newEventHandler(1)) .child( firstComponent) .child( SolidColor.create(c).color(Color.GREEN)))) .child( Column.create(c) .clickHandler(c.newEventHandler(2)) .child( Text.create(c).text("test2"))) .build(); } }; ComponentTree tree = ComponentTree.create(mContext, firstLayout) .incrementalMount(false) .layoutDiffing(false) .build(); LithoView cv = new LithoView(mContext); ComponentTestHelper.mountComponent(cv, tree); tree.setRoot(secondLayout); final LogEvent event = mComponentsLogger.newPerformanceEvent(EVENT_PREPARE_MOUNT); event.addParam(PARAM_MOVED_COUNT, "2"); verify(mComponentsLogger).log(eq(event)); } }