/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.litho; import static android.content.Context.ACCESSIBILITY_SERVICE; import static android.graphics.Color.GREEN; import static android.graphics.Color.RED; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.M; import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO; import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.facebook.litho.Column.create; import static com.facebook.litho.Layout.createAndMeasureComponent; import static com.facebook.litho.LayoutOutput.getLayoutOutput; import static com.facebook.litho.NodeInfo.ENABLED_SET_FALSE; import static com.facebook.litho.NodeInfo.FOCUS_SET_TRUE; import static com.facebook.litho.NodeInfo.SELECTED_SET_TRUE; import static com.facebook.litho.SizeSpec.AT_MOST; import static com.facebook.litho.SizeSpec.EXACTLY; import static com.facebook.litho.SizeSpec.UNSPECIFIED; import static com.facebook.litho.SizeSpec.makeSizeSpec; import static com.facebook.yoga.YogaAlign.CENTER; import static com.facebook.yoga.YogaAlign.FLEX_START; import static com.facebook.yoga.YogaEdge.ALL; import static com.facebook.yoga.YogaEdge.BOTTOM; import static com.facebook.yoga.YogaEdge.HORIZONTAL; import static com.facebook.yoga.YogaEdge.LEFT; import static com.facebook.yoga.YogaEdge.RIGHT; import static com.facebook.yoga.YogaEdge.TOP; import static com.facebook.yoga.YogaJustify.SPACE_AROUND; import static com.facebook.yoga.YogaPositionType.ABSOLUTE; import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.validateMockitoUsage; import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.util.SparseArray; import android.view.accessibility.AccessibilityManager; import androidx.annotation.Nullable; import com.facebook.litho.LayoutState.LayoutStateContext; import com.facebook.litho.testing.LithoViewRule; import com.facebook.litho.testing.TestComponent; import com.facebook.litho.testing.TestDrawableComponent; import com.facebook.litho.testing.TestLayoutComponent; import com.facebook.litho.testing.TestNullLayoutComponent; import com.facebook.litho.testing.TestSizeDependentComponent; import com.facebook.litho.testing.TestViewComponent; import com.facebook.litho.testing.Whitebox; import com.facebook.litho.testing.inlinelayoutspec.InlineLayoutSpec; import com.facebook.litho.testing.logging.TestComponentsLogger; import com.facebook.litho.testing.testrunner.LithoTestRunner; import com.facebook.litho.widget.SolidColor; import com.facebook.litho.widget.Text; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaEdge; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowAccessibilityManager; @RunWith(LithoTestRunner.class) public class LayoutStateCalculateTest { public final @Rule LithoViewRule mLithoViewRule = new LithoViewRule(); private ComponentContext mContext; @Before public void setup() throws Exception { // invdalidate the cached accessibility value before each test runs so that we don't // have a value already cached. If we don't do this, accessibility tests will fail when run // after non-accessibility tests, and vice-versa. AccessibilityUtils.invalidateCachedIsAccessibilityEnabled(); mContext = mLithoViewRule.getContext(); } @After public void validate() { validateMockitoUsage(); } @Test public void testNoUnnecessaryLayoutOutputsForLayoutSpecs() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return Column.create(c) .child(Column.create(c).child(TestDrawableComponent.create(c))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); } @Test public void testLayoutOutputsForRootInteractiveLayoutSpecs() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(TestDrawableComponent.create(c)).wrapInView().build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); } @Test public void testLayoutOutputsForSpecsWithTouchExpansion() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c).widthPx(100).heightPx(10)) .child( Row.create(c) .viewTag(new Object()) .child(TestDrawableComponent.create(c).widthPx(20).heightPx(90)) .child( create(c) .child(TestDrawableComponent.create(c)) .clickHandler(c.newEventHandler(1)) .widthPx(50) .heightPx(50) .touchExpansionPx(YogaEdge.ALL, 5))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(6); final ViewNodeInfo viewNodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(4)).getViewNodeInfo(); assertThat(viewNodeInfo.getExpandedTouchBounds()).isEqualTo(new Rect(15, -5, 75, 55)); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(4)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNotNull(); assertThat(nodeInfo.getLongClickHandler()).isNull(); assertThat(nodeInfo.getFocusChangeHandler()).isNull(); assertThat(nodeInfo.getTouchHandler()).isNull(); } @Test public void testLayoutOutputsForSpecsWithClickHandling() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .clickHandler(c.newEventHandler(1))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNotNull(); assertThat(nodeInfo.getLongClickHandler()).isNull(); assertThat(nodeInfo.getFocusChangeHandler()).isNull(); assertThat(nodeInfo.getTouchHandler()).isNull(); } @Test public void testLayoutOutputsForSpecsWithLongClickHandling() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .longClickHandler(c.newEventHandler(1))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNull(); assertThat(nodeInfo.getLongClickHandler()).isNotNull(); assertThat(nodeInfo.getFocusChangeHandler()).isNull(); assertThat(nodeInfo.getTouchHandler()).isNull(); } @Test public void testLayoutOutputsForSpecsWithFocusChangeHandling() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .focusChangeHandler(c.newEventHandler(1))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNull(); assertThat(nodeInfo.getLongClickHandler()).isNull(); assertThat(nodeInfo.getFocusChangeHandler()).isNotNull(); assertThat(nodeInfo.getTouchHandler()).isNull(); } @Test public void testLayoutOutputsForSpecsWithInterceptTouchHandling() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .interceptTouchHandler(c.newEventHandler(1))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNull(); assertThat(nodeInfo.getLongClickHandler()).isNull(); assertThat(nodeInfo.getInterceptTouchHandler()).isNotNull(); assertThat(nodeInfo.getTouchHandler()).isNull(); } @Test public void testLayoutOutputsForSpecsWithTouchHandling() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .touchHandler(c.newEventHandler(1))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getTouchHandler()).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNull(); assertThat(nodeInfo.getLongClickHandler()).isNull(); assertThat(nodeInfo.getFocusChangeHandler()).isNull(); } @Test public void testLayoutOutputsForDeepLayoutSpecs() { final int paddingSize = 5; final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .backgroundColor(0xFFFF0000) .child( Row.create(c) .justifyContent(SPACE_AROUND) .alignItems(CENTER) .positionType(ABSOLUTE) .positionPx(LEFT, 50) .positionPx(TOP, 50) .positionPx(RIGHT, 200) .positionPx(BOTTOM, 50) .child(Text.create(c).text("textLeft1")) .child(Text.create(c).text("textRight1")) .paddingPx(ALL, paddingSize) .wrapInView()) .child( Row.create(c) .justifyContent(SPACE_AROUND) .alignItems(CENTER) .positionType(ABSOLUTE) .positionPx(LEFT, 200) .positionPx(TOP, 50) .positionPx(RIGHT, 50) .positionPx(BOTTOM, 50) .child( Text.create(c) .text("textLeft2") .wrapInView() .paddingPx(ALL, paddingSize)) .child(TestViewComponent.create(c).wrapInView())) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(8); // Check quantity of HostComponents. int totalHosts = 0; for (int i = 0; i < layoutState.getMountableOutputCount(); i++) { final ComponentLifecycle lifecycle = getComponentAt(layoutState, i); if (isHostComponent(lifecycle)) { totalHosts++; } } assertThat(totalHosts).isEqualTo(3); // Check all the Layouts are in the correct position. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 2))).isTrue(); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(Text.class); assertThat(getComponentAt(layoutState, 4)).isInstanceOf(Text.class); assertThat(isHostComponent(getComponentAt(layoutState, 5))).isTrue(); assertThat(getComponentAt(layoutState, 6)).isInstanceOf(Text.class); assertThat(getComponentAt(layoutState, 7)).isInstanceOf(TestViewComponent.class); // Check the text within the TextComponents. assertThat(getTextFromTextComponent(layoutState, 3)).isEqualTo("textLeft1"); assertThat(getTextFromTextComponent(layoutState, 4)).isEqualTo("textRight1"); assertThat(getTextFromTextComponent(layoutState, 6)).isEqualTo("textLeft2"); final Rect textLayoutBounds = getLayoutOutput(layoutState.getMountableOutputAt(6)).getBounds(); final Rect textBackgroundBounds = getLayoutOutput(layoutState.getMountableOutputAt(5)).getBounds(); assertThat(textLayoutBounds.left - paddingSize).isEqualTo(textBackgroundBounds.left); assertThat(textLayoutBounds.top - paddingSize).isEqualTo(textBackgroundBounds.top); assertThat(textLayoutBounds.right + paddingSize).isEqualTo(textBackgroundBounds.right); assertThat(textLayoutBounds.bottom + paddingSize).isEqualTo(textBackgroundBounds.bottom); } @Test public void testLayoutOutputMountBounds() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .widthPx(30) .heightPx(30) .wrapInView() .child( create(c) .widthPx(10) .heightPx(10) .marginPx(ALL, 10) .wrapInView() .child(TestDrawableComponent.create(c).widthPx(10).heightPx(10))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); final Rect mountBounds = new Rect(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 30, 30)); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(10, 10, 20, 20)); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 10, 10)); } @Test public void testLayoutOutputsForDeepLayoutSpecsWithBackground() { final int paddingSize = 5; final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .backgroundColor(0xFFFF0000) .child( Row.create(c) .justifyContent(SPACE_AROUND) .alignItems(CENTER) .positionType(ABSOLUTE) .positionPx(LEFT, 50) .positionPx(TOP, 50) .positionPx(RIGHT, 200) .positionPx(BOTTOM, 50) .child(Text.create(c).text("textLeft1")) .child(Text.create(c).text("textRight1")) .backgroundColor(0xFFFF0000) .foregroundColor(0xFFFF0000) .paddingPx(ALL, paddingSize) .wrapInView()) .child( Row.create(c) .justifyContent(SPACE_AROUND) .alignItems(CENTER) .positionType(ABSOLUTE) .positionPx(LEFT, 200) .positionPx(TOP, 50) .positionPx(RIGHT, 50) .positionPx(BOTTOM, 50) .child( Text.create(c) .text("textLeft2") .wrapInView() .backgroundColor(0xFFFF0000) .paddingPx(ALL, paddingSize)) .child( TestViewComponent.create(c) .backgroundColor(0xFFFF0000) .foregroundColor(0x0000FFFF) .paddingPx(ALL, paddingSize))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Account for Android version in the foreground. If >= M the foreground is part of the // ViewLayoutOutput otherwise it has its own LayoutOutput. final boolean foregroundHasOwnOutput = SDK_INT < M; // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(foregroundHasOwnOutput ? 12 : 11); // Check quantity of HostComponents. int totalHosts = 0; for (int i = 0; i < layoutState.getMountableOutputCount(); i++) { final ComponentLifecycle lifecycle = getComponentAt(layoutState, i); if (isHostComponent(lifecycle)) { totalHosts++; } } assertThat(totalHosts).isEqualTo(3); // Check all the Layouts are in the correct position. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 2))).isTrue(); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(DrawableComponent.class); assertThat(getComponentAt(layoutState, 4)).isInstanceOf(Text.class); assertThat(getComponentAt(layoutState, 5)).isInstanceOf(Text.class); assertThat(getComponentAt(layoutState, 6)).isInstanceOf(DrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 7))).isTrue(); assertThat(getComponentAt(layoutState, 8)).isInstanceOf(DrawableComponent.class); assertThat(getComponentAt(layoutState, 9)).isInstanceOf(Text.class); assertThat(getComponentAt(layoutState, 10)).isInstanceOf(TestViewComponent.class); if (foregroundHasOwnOutput) { assertThat(getComponentAt(layoutState, 11)).isInstanceOf(DrawableComponent.class); } // Check the text within the TextComponents. assertThat(getTextFromTextComponent(layoutState, 4)).isEqualTo("textLeft1"); assertThat(getTextFromTextComponent(layoutState, 5)).isEqualTo("textRight1"); assertThat(getTextFromTextComponent(layoutState, 9)).isEqualTo("textLeft2"); // Check that the backgrounds have the same size of the components to which they are associated assertThat(layoutState.getMountableOutputAt(3).getBounds()) .isEqualTo(layoutState.getMountableOutputAt(2).getBounds()); assertThat(layoutState.getMountableOutputAt(6).getBounds()) .isEqualTo(layoutState.getMountableOutputAt(2).getBounds()); final Rect textLayoutBounds = layoutState.getMountableOutputAt(9).getBounds(); final Rect textBackgroundBounds = layoutState.getMountableOutputAt(8).getBounds(); assertThat(textLayoutBounds.left - paddingSize).isEqualTo(textBackgroundBounds.left); assertThat(textLayoutBounds.top - paddingSize).isEqualTo(textBackgroundBounds.top); assertThat(textLayoutBounds.right + paddingSize).isEqualTo(textBackgroundBounds.right); assertThat(textLayoutBounds.bottom + paddingSize).isEqualTo(textBackgroundBounds.bottom); assertThat(layoutState.getMountableOutputAt(8).getBounds()) .isEqualTo(layoutState.getMountableOutputAt(7).getBounds()); final ViewNodeInfo viewNodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(10)).getViewNodeInfo(); assertThat(viewNodeInfo).isNotNull(); assertThat(viewNodeInfo.getBackground() != null).isTrue(); if (foregroundHasOwnOutput) { assertThat(viewNodeInfo.getForeground() == null).isTrue(); } else { assertThat(viewNodeInfo.getForeground() != null).isTrue(); } assertThat(viewNodeInfo.getPaddingLeft() == paddingSize).isTrue(); assertThat(viewNodeInfo.getPaddingTop() == paddingSize).isTrue(); assertThat(viewNodeInfo.getPaddingRight() == paddingSize).isTrue(); assertThat(viewNodeInfo.getPaddingBottom() == paddingSize).isTrue(); } @Test public void testLayoutOutputsForMegaDeepLayoutSpecs() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c).wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c))) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c))) .wrapInView()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(18); // Check quantity of HostComponents. int totalHosts = 0; for (int i = 0; i < layoutState.getMountableOutputCount(); i++) { final ComponentLifecycle lifecycle = getComponentAt(layoutState, i); if (isHostComponent(lifecycle)) { totalHosts++; } } assertThat(totalHosts).isEqualTo(7); // Check all the Components match the right LayoutOutput positions. // Tree One. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 1))).isTrue(); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestDrawableComponent.class); // Tree Two. assertThat(isHostComponent(getComponentAt(layoutState, 4))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 5))).isTrue(); assertThat(getComponentAt(layoutState, 6)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 7))).isTrue(); assertThat(getComponentAt(layoutState, 8)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 9)).isInstanceOf(TestDrawableComponent.class); // Tree Three. assertThat(isHostComponent(getComponentAt(layoutState, 10))).isTrue(); assertThat(getComponentAt(layoutState, 11)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 12)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 13)).isInstanceOf(TestDrawableComponent.class); // Tree Four. assertThat(isHostComponent(getComponentAt(layoutState, 14))).isTrue(); assertThat(getComponentAt(layoutState, 15)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 16)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 17)).isInstanceOf(TestViewComponent.class); } @Test public void testLayoutOutputStableIds() { final Component component1 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c)).contentDescription("cd0")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .contentDescription("cd1")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c)) .contentDescription("cd2")) .build(); } }; final Component component2 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c)).contentDescription("cd0")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .contentDescription("cd1")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c)) .contentDescription("cd2")) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component1, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); final LayoutState sameComponentLayoutState = calculateLayoutState( getApplicationContext(), component2, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); assertThat(sameComponentLayoutState.getMountableOutputCount()) .isEqualTo(layoutState.getMountableOutputCount()); for (int i = 0; i < layoutState.getMountableOutputCount(); i++) { assertThat(getLayoutOutput(sameComponentLayoutState.getMountableOutputAt(i)).getId()) .isEqualTo(getLayoutOutput(layoutState.getMountableOutputAt(i)).getId()); } } @Test public void testLayoutOutputStableIdsForMegaDeepComponent() { final Component component1 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c).wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c))) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c))) .wrapInView()) .build(); } }; final Component component2 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c).wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c))) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c))) .wrapInView()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component1, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); final LayoutState sameComponentLayoutState = calculateLayoutState( getApplicationContext(), component2, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); assertThat(sameComponentLayoutState.getMountableOutputCount()) .isEqualTo(layoutState.getMountableOutputCount()); for (int i = 0; i < layoutState.getMountableOutputCount(); i++) { assertThat(getLayoutOutput(sameComponentLayoutState.getMountableOutputAt(i)).getId()) .isEqualTo(getLayoutOutput(layoutState.getMountableOutputAt(i)).getId()); } } @Test public void testPartiallyStableIds() { final Component component1 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .build(); } }; final Component component2 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c))) .build(); } }; final LayoutState layoutState1 = calculateLayoutState( getApplicationContext(), component1, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); final LayoutState layoutState2 = calculateLayoutState( getApplicationContext(), component2, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); assertThat(getLayoutOutput(layoutState2.getMountableOutputAt(0)).getId()) .isEqualTo(getLayoutOutput(layoutState1.getMountableOutputAt(0)).getId()); assertThat(getLayoutOutput(layoutState2.getMountableOutputAt(1)).getId()) .isEqualTo(getLayoutOutput(layoutState1.getMountableOutputAt(1)).getId()); assertThat(layoutState1.getMountableOutputCount()).isEqualTo(3); assertThat(layoutState2.getMountableOutputCount()).isEqualTo(4); } @Test public void testDifferentIds() { final Component component1 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return Column.create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .build(); } }; final Component component2 = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return Column.create(c) .child(TestDrawableComponent.create(c).wrapInView()) .child( Column.create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .build(); } }; final LayoutState layoutState1 = calculateLayoutState( getApplicationContext(), component1, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); final LayoutState layoutState2 = calculateLayoutState( getApplicationContext(), component2, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(20, EXACTLY)); assertThat(getLayoutOutput(layoutState1.getMountableOutputAt(1)).getId()) .isNotEqualTo(getLayoutOutput(layoutState2.getMountableOutputAt(1)).getId()); } @Test public void testLayoutOutputsWithInteractiveLayoutSpecAsLeafs() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .child(create(c).child(TestLayoutComponent.create(c)).wrapInView()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 2))).isTrue(); } private static ComponentLifecycle getComponentAt(final LayoutState layoutState, final int index) { return getLayoutOutput(layoutState.getMountableOutputAt(index)).getComponent(); } private static CharSequence getTextFromTextComponent( final LayoutState layoutState, final int index) { return Whitebox.getInternalState( getLayoutOutput(layoutState.getMountableOutputAt(index)).getComponent(), "text"); } private static boolean isHostComponent(final ComponentLifecycle component) { return component instanceof HostComponent; } @Test public void testNoMeasureOnNestedComponentWithSameSpecs() { final ComponentContext baseContext = new ComponentContext(getApplicationContext()); final ComponentContext c = ComponentContext.withComponentTree(baseContext, ComponentTree.create(baseContext).build()); final LayoutState layoutState = new LayoutState(c); c.setLayoutStateContext(new LayoutStateContext(layoutState)); final Size size = new Size(); final TestComponent innerComponent = TestDrawableComponent.create(c, 0, 0, false, true, true, false, false).build(); final int widthSpec = makeSizeSpec(100, EXACTLY); final int heightSpec = makeSizeSpec(100, EXACTLY); innerComponent.measure(c, widthSpec, heightSpec, size); final InternalNode internalNode = layoutState.getCachedLayout(innerComponent); internalNode.setLastWidthSpec(widthSpec); internalNode.setLastHeightSpec(heightSpec); internalNode.setLastMeasuredWidth(internalNode.getWidth()); internalNode.setLastMeasuredHeight(internalNode.getHeight()); innerComponent.resetInteractions(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(Row.create(c).child(innerComponent).widthPx(100).heightPx(100)) .build(); } }; calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(innerComponent.wasMeasureCalled()).isFalse(); } @Test public void testNoMeasureOnNestedComponentWithNewMeasureSpecExact() { final ComponentContext baseContext = new ComponentContext(getApplicationContext()); final ComponentContext c = ComponentContext.withComponentTree(baseContext, ComponentTree.create(baseContext).build()); final LayoutState layoutState = new LayoutState(c); c.setLayoutStateContext(new LayoutStateContext(layoutState)); final Size size = new Size(); final TestComponent innerComponent = TestDrawableComponent.create(c, 0, 0, false, true, true, false, false).build(); final int widthSpec = makeSizeSpec(100, AT_MOST); final int heightSpec = makeSizeSpec(100, AT_MOST); innerComponent.measure(c, widthSpec, heightSpec, size); final InternalNode internalNode = layoutState.getCachedLayout(innerComponent); internalNode.setLastWidthSpec(widthSpec); internalNode.setLastHeightSpec(heightSpec); internalNode.setLastMeasuredWidth(100); internalNode.setLastMeasuredHeight(100); innerComponent.resetInteractions(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(Row.create(c).child(innerComponent).widthPx(100).heightPx(100)) .build(); } }; calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(innerComponent.wasMeasureCalled()).isFalse(); } @Test public void testNoMeasureOnNestedComponentWithNewMeasureSpecOldUnspecified() { final ComponentContext baseContext = new ComponentContext(getApplicationContext()); final ComponentContext c = ComponentContext.withComponentTree(baseContext, ComponentTree.create(baseContext).build()); final LayoutState layoutState = new LayoutState(c); c.setLayoutStateContext(new LayoutStateContext(layoutState)); final Size size = new Size(); final TestComponent innerComponent = TestDrawableComponent.create(c, 0, 0, false, true, true, false, false).build(); final int widthSpec = makeSizeSpec(0, UNSPECIFIED); final int heightSpec = makeSizeSpec(0, UNSPECIFIED); innerComponent.measure(c, widthSpec, heightSpec, size); final InternalNode internalNode = layoutState.getCachedLayout(innerComponent); internalNode.setLastWidthSpec(widthSpec); internalNode.setLastHeightSpec(heightSpec); internalNode.setLastMeasuredWidth(99); internalNode.setLastMeasuredHeight(99); innerComponent.resetInteractions(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(innerComponent).build(); } }; calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, AT_MOST), makeSizeSpec(100, AT_MOST)); assertThat(innerComponent.wasMeasureCalled()).isFalse(); } @Test public void testNoMeasureOnNestedComponentWithOldAndNewAtMost() { final ComponentContext baseContext = new ComponentContext(getApplicationContext()); final ComponentContext c = ComponentContext.withComponentTree(baseContext, ComponentTree.create(baseContext).build()); final LayoutState layoutState = new LayoutState(c); c.setLayoutStateContext(new LayoutStateContext(layoutState)); final Size size = new Size(); final TestComponent innerComponent = TestDrawableComponent.create(c, 0, 0, false, true, true, false, false).build(); final int widthSpec = makeSizeSpec(100, AT_MOST); final int heightSpec = makeSizeSpec(100, AT_MOST); innerComponent.measure(c, widthSpec, heightSpec, size); final InternalNode internalNode = layoutState.getCachedLayout(innerComponent); internalNode.setLastWidthSpec(widthSpec); internalNode.setLastHeightSpec(heightSpec); internalNode.setLastMeasuredWidth(50); internalNode.setLastMeasuredHeight(50); innerComponent.resetInteractions(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(Row.create(c).child(innerComponent).flexShrink(0)).build(); } }; calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(50, AT_MOST), makeSizeSpec(50, AT_MOST)); assertThat(innerComponent.wasMeasureCalled()).isFalse(); } @Test public void testLayoutOutputsForTwiceNestedComponent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c).child(create(c).child(TestDrawableComponent.create(c))).wrapInView()) .child( create(c) .child(create(c).child(TestDrawableComponent.create(c))) .child(create(c).child(TestDrawableComponent.create(c)))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(5); final long hostMarkerRoot = getLayoutOutput(layoutState.getMountableOutputAt(0)).getId(); final long hostMarkerOne = getLayoutOutput(layoutState.getMountableOutputAt(1)).getId(); // First output is the inner host for the click handler assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerRoot); // Second output is the child of the inner host assertThat(getLayoutOutput(layoutState.getMountableOutputAt(2)).getHostMarker()) .isEqualTo(hostMarkerOne); // Third and fourth outputs are children of the root view. assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(4)).getHostMarker()) .isEqualTo(hostMarkerRoot); } @Test public void testLayoutOutputsForComponentWithBackgrounds() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .backgroundColor(0xFFFF0000) .foregroundColor(0xFFFF0000) .child(TestDrawableComponent.create(c)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(4); // First and third output are the background and the foreground assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(DrawableComponent.class); } @Test public void testLayoutOutputsForNonComponentClickableNode() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c)).wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .wrapInView()) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c)) .wrapInView()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(9); final long hostMarkerRoot = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker(); final long hostMarkerTwo = getLayoutOutput(layoutState.getMountableOutputAt(4)).getHostMarker(); final long hostMarkerThree = getLayoutOutput(layoutState.getMountableOutputAt(7)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(5)).getHostMarker()) .isEqualTo(hostMarkerTwo); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(8)).getHostMarker()) .isEqualTo(hostMarkerThree); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 1))).isTrue(); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 3))).isTrue(); assertThat(getComponentAt(layoutState, 4)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 5)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 6))).isTrue(); assertThat(getComponentAt(layoutState, 7)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 8)).isInstanceOf(TestViewComponent.class); } @Test public void testLayoutOutputsForNonComponentContentDescriptionNode() { enableAccessibility(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c)).contentDescription("cd0")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestDrawableComponent.create(c)) .contentDescription("cd1")) .child( create(c) .child(TestDrawableComponent.create(c)) .child(TestViewComponent.create(c)) .contentDescription("cd2")) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(9); final long hostMarkerRoot = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker(); final long hostMarkerTwo = getLayoutOutput(layoutState.getMountableOutputAt(4)).getHostMarker(); final long hostMarkerThree = getLayoutOutput(layoutState.getMountableOutputAt(7)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(5)).getHostMarker()) .isEqualTo(hostMarkerTwo); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(8)).getHostMarker()) .isEqualTo(hostMarkerThree); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 1))).isTrue(); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 3))).isTrue(); assertThat(getComponentAt(layoutState, 4)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 5)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 6))).isTrue(); assertThat(getComponentAt(layoutState, 7)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 8)).isInstanceOf(TestViewComponent.class); } @Test public void testLayoutOutputsForFocusableOnRoot() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(TestDrawableComponent.create(c)).focusable(true).build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo().getFocusState()) .isEqualTo(FOCUS_SET_TRUE); } @Test public void testLayoutOutputsForFocusable() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c)).focusable(true)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo().getFocusState()) .isEqualTo(FOCUS_SET_TRUE); } @Test public void testLayoutOutputsForSelectedOnRoot() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(TestDrawableComponent.create(c)).selected(true).build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); assertThat( getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo().getSelectedState()) .isEqualTo(SELECTED_SET_TRUE); } @Test public void testLayoutOutputsForSelected() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c).child(TestDrawableComponent.create(c)).focusable(true).selected(true)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat( getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo().getSelectedState()) .isEqualTo(SELECTED_SET_TRUE); } @Test public void testLayoutOutputsForEnabledFalseDoesntWrap() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestDrawableComponent.create(c).enabled(false))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getComponent().getSimpleName()) .isEqualTo("HostComponent"); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getComponent().getSimpleName()) .isEqualTo("TestDrawableComponent"); assertThat( LayoutOutput.isTouchableDisabled( getLayoutOutput(layoutState.getMountableOutputAt(1)).getFlags())) .isTrue(); } @Test public void testLayoutOutputsForEnabledFalseInInnerWrappedComponentDrawable() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .child( TestDrawableComponent.create(c) .clickHandler(c.newEventHandler(1)) .enabled(false))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); // Because the TestDrawableComponent is disabled, we don't wrap it in a host. assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getComponent()) .isInstanceOf(HostComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getComponent()) .isInstanceOf(TestDrawableComponent.class); assertThat( LayoutOutput.isTouchableDisabled( getLayoutOutput(layoutState.getMountableOutputAt(1)).getFlags())) .isTrue(); } @Test public void testLayoutOutputsForEnabledFalseInInnerComponentView() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(create(c).child(TestViewComponent.create(c).enabled(false))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getComponent()) .isInstanceOf(HostComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getComponent()) .isInstanceOf(TestViewComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo().getEnabledState()) .isEqualTo(ENABLED_SET_FALSE); } @Test public void testLayoutOutputsForEnabledFalseApplyToDescendent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child( create(c) .enabled(false) .child(TestViewComponent.create(c).enabled(true)) .child(TestDrawableComponent.create(c).clickHandler(c.newEventHandler(1))) .child(TestDrawableComponent.create(c).enabled(false))) .child( create(c) .child(TestViewComponent.create(c)) .child(TestDrawableComponent.create(c))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(6); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getComponent()) .isInstanceOf(HostComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getComponent()) .isInstanceOf(TestViewComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getNodeInfo().getEnabledState()) .isEqualTo(ENABLED_SET_FALSE); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(2)).getComponent()) .isInstanceOf(TestDrawableComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(2)).getNodeInfo()).isNull(); assertThat( LayoutOutput.isTouchableDisabled( getLayoutOutput(layoutState.getMountableOutputAt(2)).getFlags())) .isTrue(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getComponent()) .isInstanceOf(TestDrawableComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getNodeInfo()).isNull(); assertThat( LayoutOutput.isTouchableDisabled( getLayoutOutput(layoutState.getMountableOutputAt(3)).getFlags())) .isTrue(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(4)).getComponent()) .isInstanceOf(TestViewComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(4)).getNodeInfo()).isNull(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(5)).getComponent()) .isInstanceOf(TestDrawableComponent.class); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(5)).getNodeInfo()).isNull(); assertThat( LayoutOutput.isTouchableDisabled( getLayoutOutput(layoutState.getMountableOutputAt(5)).getFlags())) .isFalse(); } @Test public void testLayoutOutputsForAccessibilityEnabled() { enableAccessibility(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return Row.create(c) .alignItems(CENTER) .paddingDip(ALL, 10) .contentDescription("This is root view") .child(TestDrawableComponent.create(c).widthDip(30).heightDip(30)) .child( TestDrawableComponent.create(c, true, true, true, true) .flex(1) .flexBasisDip(0) .backgroundColor(RED) .marginDip(HORIZONTAL, 10)) .child( Row.create(c) .alignItems(CENTER) .paddingDip(ALL, 10) .contentDescription("This is a container") .child( TestDrawableComponent.create(c) .widthDip(30) .heightDip(30) .contentDescription("This is an image")) .child( TestDrawableComponent.create(c, true, true, true, true) .flex(1) .flexBasisDip(0) .marginDip(HORIZONTAL, 10))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(10); final long hostMarkerRoot = getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker(); final long hostMarkerOne = getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker(); final long hostMarkerTwo = getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker(); final long hostMarkerThree = getLayoutOutput(layoutState.getMountableOutputAt(7)).getHostMarker(); final long hostMarkerFour = getLayoutOutput(layoutState.getMountableOutputAt(9)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker()) .isEqualTo(hostMarkerOne); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(4)).getHostMarker()) .isEqualTo(hostMarkerOne); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker()) .isEqualTo(hostMarkerTwo); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(7)).getHostMarker()) .isEqualTo(hostMarkerThree); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(9)).getHostMarker()) .isEqualTo(hostMarkerFour); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 2))).isTrue(); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(DrawableComponent.class); assertThat(getComponentAt(layoutState, 4)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 5))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 6))).isTrue(); assertThat(getComponentAt(layoutState, 7)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 8))).isTrue(); assertThat(getComponentAt(layoutState, 9)).isInstanceOf(TestDrawableComponent.class); } @Test public void testLayoutOutputsWithImportantForAccessibility() { enableAccessibility(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .contentDescription("This is root view") .child(TestDrawableComponent.create(c).widthDip(30).heightDip(30)) .child( TestDrawableComponent.create(c, true, true, true, true) .flex(1) .flexBasisDip(0) .backgroundColor(RED) .marginDip(HORIZONTAL, 10) .importantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO)) .child( Row.create(c) .alignItems(CENTER) .paddingDip(ALL, 10) .importantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) .child( TestDrawableComponent.create(c) .widthDip(30) .heightDip(30) .importantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES) .contentDescription("This is an image"))) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(7); final long hostMarkerRoot = getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker(); final long hostMarkerOne = getLayoutOutput(layoutState.getMountableOutputAt(5)).getHostMarker(); final long hostMarkerTwo = getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(2)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(3)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(4)).getHostMarker()) .isEqualTo(hostMarkerRoot); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(5)).getHostMarker()) .isEqualTo(hostMarkerOne); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(6)).getHostMarker()) .isEqualTo(hostMarkerTwo); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(DrawableComponent.class); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestDrawableComponent.class); assertThat(isHostComponent(getComponentAt(layoutState, 4))).isTrue(); assertThat(isHostComponent(getComponentAt(layoutState, 5))).isTrue(); assertThat(getComponentAt(layoutState, 6)).isInstanceOf(TestDrawableComponent.class); assertThat(IMPORTANT_FOR_ACCESSIBILITY_AUTO) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(0)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_AUTO) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(1)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_NO) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(2)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_NO) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(3)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(4)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_YES) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(5)).getImportantForAccessibility()); assertThat(IMPORTANT_FOR_ACCESSIBILITY_YES) .isEqualTo( getLayoutOutput(layoutState.getMountableOutputAt(6)).getImportantForAccessibility()); } @Test public void testLayoutOutputsForClickHandlerAndViewTagsOnRoot() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .clickHandler(c.newEventHandler(1)) .viewTags(new SparseArray<>()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getClickHandler()).isNotNull(); assertThat(nodeInfo.getViewTags()).isNotNull(); } @Test public void testLayoutOutputsForLongClickHandlerAndViewTagsOnRoot() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .longClickHandler(c.newEventHandler(1)) .viewTags(new SparseArray<>()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); final long hostMarkerZero = getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker(); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(1)).getHostMarker()) .isEqualTo(hostMarkerZero); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(TestDrawableComponent.class); final NodeInfo nodeInfo = getLayoutOutput(layoutState.getMountableOutputAt(0)).getNodeInfo(); assertThat(nodeInfo).isNotNull(); assertThat(nodeInfo.getLongClickHandler()).isNotNull(); assertThat(nodeInfo.getViewTags()).isNotNull(); } @Test public void testLayoutOutputsForForceWrappedComponent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(TestDrawableComponent.create(c).wrapInView()).build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); assertThat(getComponentAt(layoutState, 0)).isInstanceOf(HostComponent.class); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(HostComponent.class); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); } @Test public void testLayoutOutputForRootNestedTreeComponent() { final LayoutState layoutState = calculateLayoutState( getApplicationContext(), TestSizeDependentComponent.create(new ComponentContext(getApplicationContext())) .setFixSizes(true) .setDelegate(false) .build(), -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(4); final Rect mountBounds = new Rect(); // Check host. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker()).isEqualTo(0); // Check NestedTree assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(5, 5, 55, 55)); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(5, 5, 55, 55)); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestViewComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(3)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(8, 58, 342, 78)); } @Test public void testLayoutOutputForDelegateNestedTreeComponentDelegate() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .paddingPx(ALL, 2) .child( TestSizeDependentComponent.create(c) .setFixSizes(true) .setDelegate(true) .marginPx(ALL, 11)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); final Rect mountBounds = new Rect(); // Check host. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); // Check NestedTree assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(13, 13, 63, 63)); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(13, 13, 63, 63)); } @Test public void testLayoutOutputForDelegateNestedTreeComponent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .paddingPx(ALL, 2) .child( TestSizeDependentComponent.create(c) .setFixSizes(true) .setDelegate(false) .marginPx(ALL, 11)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(4); final Rect mountBounds = new Rect(); // Check host. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker()).isEqualTo(0); // Check NestedTree assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(18, 18, 68, 68)); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(18, 18, 68, 68)); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestViewComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(3)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(21, 71, 329, 91)); } @Test public void testLayoutOutputForRootWithDelegateNestedTreeComponent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return TestSizeDependentComponent.create(c) .setFixSizes(true) .setDelegate(false) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(4); final Rect mountBounds = new Rect(); // Check host. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker()).isEqualTo(0); // Check NestedTree assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(5, 5, 55, 55)); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(5, 5, 55, 55)); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestViewComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(3)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(8, 58, 342, 78)); } @Test public void testLayoutOutputRootWithPaddingOverridingDelegateNestedTreeComponent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { final Component nestedTreeRootComponent = TestSizeDependentComponent.create(c).setFixSizes(true).setDelegate(false).build(); return Wrapper.create(c).delegate(nestedTreeRootComponent).paddingPx(ALL, 10).build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); // Check total layout outputs. assertThat(layoutState.getMountableOutputCount()).isEqualTo(4); final Rect mountBounds = new Rect(); // Check host. assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); assertThat(getLayoutOutput(layoutState.getMountableOutputAt(0)).getHostMarker()).isEqualTo(0); // Check NestedTree assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(10, 10, 60, 60)); assertThat(getComponentAt(layoutState, 2)).isInstanceOf(TestDrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(2)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(10, 10, 60, 60)); assertThat(getComponentAt(layoutState, 3)).isInstanceOf(TestViewComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(3)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(13, 63, 337, 83)); } @Test public void testLayoutOutputForRootWithNullLayout() { final Component componentWithNullLayout = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return null; } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), componentWithNullLayout, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(0); } @Test public void testLayoutComponentForNestedTreeChildWithNullLayout() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).paddingPx(ALL, 2).child(new TestNullLayoutComponent()).build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(200, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(1); final Rect mountBounds = new Rect(); assertThat(isHostComponent(getComponentAt(layoutState, 0))).isTrue(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 350, 200)); } @Test public void testMeasure() { final int width = 50; final int height = 30; final ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c).measuredWidth(width).measuredHeight(height)) .build(); } }; final InternalNode node = createAndMeasureComponent( c, component, makeSizeSpec(width, AT_MOST), makeSizeSpec(height, AT_MOST)); assertThat(node.getWidth()).isEqualTo(width); assertThat(node.getHeight()).isEqualTo(height); assertThat(node.getChildCount()).isEqualTo(1); assertThat(((InternalNode) node.getChildAt(0)).getWidth()).isEqualTo(width); assertThat(((InternalNode) node.getChildAt(0)).getHeight()).isEqualTo(height); } @Test public void testNestedTreeComponentWithDoubleMeasurementsDoesntThrow() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return Row.create(c) .alignItems(YogaAlign.STRETCH) .paddingPx(YogaEdge.ALL, 2) .child( TestSizeDependentComponent.create(c) .setFixSizes(true) .setDelegate(false) .marginPx(YogaEdge.ALL, 11)) .child(TestDrawableComponent.create(c).heightPx(200).widthPx(200)) .build(); } }; calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(350, EXACTLY), makeSizeSpec(0, UNSPECIFIED)); // Testing that is not throwing an exception. } @Test public void testLayoutOutputForRootNestedTreeComponentWithAspectRatio() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestSizeDependentComponent.create(c).widthPx(100).aspectRatio(1)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(0, UNSPECIFIED), makeSizeSpec(0, UNSPECIFIED)); final Rect mountBounds = new Rect(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 100, 100)); } @Test public void testLayoutOutputForRootNestedTreeComponentWithPercentParentSizeDefined() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .alignItems(FLEX_START) .widthPx(100) .heightPx(100) .child( TestSizeDependentComponent.create(c) .widthPercent(50) .heightPercent(50) .backgroundColor(0xFFFF0000)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(0, UNSPECIFIED), makeSizeSpec(0, UNSPECIFIED)); final Rect mountBounds = new Rect(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 100, 100)); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 50, 50)); } @Test public void testLayoutOutputForRootNestedTreeComponentWithPercent() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .alignItems(FLEX_START) .child( TestSizeDependentComponent.create(c) .setFixSizes(true) .widthPercent(50) .heightPercent(50) .backgroundColor(0xFFFF0000)) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(0, UNSPECIFIED), makeSizeSpec(0, UNSPECIFIED)); final Rect mountBounds = new Rect(); getLayoutOutput(layoutState.getMountableOutputAt(0)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 60, 86)); assertThat(getComponentAt(layoutState, 1)).isInstanceOf(DrawableComponent.class); getLayoutOutput(layoutState.getMountableOutputAt(1)).getMountBounds(mountBounds); assertThat(mountBounds).isEqualTo(new Rect(0, 0, 60, 86)); } @Test public void testLayoutOutputsForComponentWithBorderColorNoBorderWidth() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .border(Border.create(c).color(ALL, GREEN).build()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); // No layout output generated related with borders // if borderColor is supplied but not borderWidth. assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); } @Test public void testLayoutOutputsForComponentWithBorderWidthNoBorderColor() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .border(Border.create(c).widthPx(ALL, 10).build()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); // No layout output generated related with borders // if borderWidth supplied but not borderColor. assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); } @Test public void testLayoutOutputsForComponentWithBorderWidthAllAndBorderColor() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .border(Border.create(c).widthPx(ALL, 10).color(ALL, GREEN).build()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); // Output at index 1 is BorderColorDrawable component. assertThat(getComponentAt(layoutState, 2)).isInstanceOf(DrawableComponent.class); } @Test public void testLayoutOutputsForComponentWithBorderWidthTopAndBorderColor() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c) .child(TestDrawableComponent.create(c)) .border(Border.create(c).widthPx(TOP, 10).color(TOP, GREEN).build()) .build(); } }; final LayoutState layoutState = calculateLayoutState( getApplicationContext(), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(layoutState.getMountableOutputCount()).isEqualTo(3); // Output at index 1 is BorderColorDrawable component. assertThat(getComponentAt(layoutState, 2)).isInstanceOf(DrawableComponent.class); } @Test public void testWillRenderLayoutsOnce() { ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component componentSpy = spy(TestLayoutComponent.create(c, 0, 0, true, true, true, false).build()); Component.willRender(c, componentSpy); final InternalNode cachedLayout = componentSpy.getLayoutCreatedInWillRenderForTesting(); assertThat(cachedLayout).isNotNull(); calculateLayoutState( c.getAndroidContext(), componentSpy, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); assertThat(componentSpy.getLayoutCreatedInWillRenderForTesting()).isNull(); verify(componentSpy, times(1)).updateInternalChildState((ComponentContext) any()); } @Test public void testResolveLayoutUsesWillRenderResult() { ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component component = TestLayoutComponent.create(c, 0, 0, true, true, true, false).build(); Component.willRender(c, component); final InternalNode cachedLayout = component.getLayoutCreatedInWillRenderForTesting(); assertThat(cachedLayout).isNotNull(); InternalNode result = Layout.create(c, component); assertThat(result).isEqualTo(cachedLayout); assertThat(component.getLayoutCreatedInWillRenderForTesting()).isNull(); } @Test public void testNewLayoutBuilderUsesWillRenderResult() { ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component component = TestLayoutComponent.create(c, 0, 0, true, true, true, false).build(); Component.willRender(c, component); final InternalNode cachedLayout = component.getLayoutCreatedInWillRenderForTesting(); assertThat(cachedLayout).isNotNull(); InternalNode result = Layout.create(c, component); assertThat(result).isEqualTo(cachedLayout); assertThat(component.getLayoutCreatedInWillRenderForTesting()).isNull(); } @Test public void testCreateLayoutUsesWillRenderResult() { ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component component = TestLayoutComponent.create(c, 0, 0, true, true, true, false).build(); Component.willRender(c, component); final InternalNode cachedLayout = component.getLayoutCreatedInWillRenderForTesting(); assertThat(cachedLayout).isNotNull(); InternalNode result = Layout.create(c, component); assertThat(result).isEqualTo(cachedLayout); assertThat(component.getLayoutCreatedInWillRenderForTesting()).isNull(); } @Test public void testWillRenderLayoutsOnceInColumn() { ComponentContext c = new ComponentContext(getApplicationContext()); final Component componentSpy = spy(TestLayoutComponent.create(c, 0, 0, true, true, true, false).build()); final Component root = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { Component.willRender(c, componentSpy); return Column.create(c).child(componentSpy).build(); } }; calculateLayoutState( c.getAndroidContext(), root, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY)); verify(componentSpy, times(1)).updateInternalChildState((ComponentContext) any()); } @Test public void testWillRenderTwiceDoesNotReCreateLayout() { ComponentContext c = new ComponentContext(getApplicationContext()); c.setLayoutStateContextForTesting(); final Component component = TestLayoutComponent.create(c, 0, 0, true, true, true, false).build(); Component.willRender(c, component); final InternalNode cachedLayout = component.getLayoutCreatedInWillRenderForTesting(); assertThat(cachedLayout).isNotNull(); assertThat(Component.willRender(c, component)).isTrue(); assertThat(component.getLayoutCreatedInWillRenderForTesting()).isEqualTo(cachedLayout); } @Test public void testComponentsLoggerCanReturnNullPerfEventsDuringLayout() { final Component component = new InlineLayoutSpec() { @Override protected Component onCreateLayout(final ComponentContext c) { return create(c).child(TestDrawableComponent.create(c)).wrapInView().build(); } }; final ComponentsLogger logger = new TestComponentsLogger() { @Override public @Nullable PerfEvent newPerformanceEvent(ComponentContext c, int eventId) { return null; } }; final LayoutState layoutState = LayoutState.calculate( new ComponentContext(getApplicationContext(), "test", logger), component, -1, makeSizeSpec(100, EXACTLY), makeSizeSpec(100, EXACTLY), LayoutState.CalculateLayoutSource.TEST); assertThat(layoutState.getMountableOutputCount()).isEqualTo(2); } @Test public void whenAccessibleChildNodeExists_ParentNodeShouldImplementVirtualViews() { enableAccessibility(); final Component component = Text.create(mContext).text("hello world").build(); mLithoViewRule.setRoot(component).attachToWindow().measure().layout(); assertThat(mLithoViewRule.getLithoView().implementsVirtualViews()) .describedAs("The parent output of the Text must implement virtual views") .isTrue(); } @Test public void whenNoAccessibleChildNodeExists_ParentNodeShouldNotImplementVirtualViews() { enableAccessibility(); final Component component = SolidColor.create(mContext).color(Color.BLACK).build(); mLithoViewRule.setRoot(component).attachToWindow().measure().layout(); assertThat(mLithoViewRule.getLithoView().implementsVirtualViews()) .describedAs("The parent output of the drawable must not implement virtual views") .isFalse(); } @Test public void onMountItemUpdatesImplementVirtualViews_ComponentHostShouldAlsoUpdate() { enableAccessibility(); mLithoViewRule .setRoot(Text.create(mContext).text("hello world").build()) .attachToWindow() .measure() .layout(); assertThat(mLithoViewRule.getLithoView().implementsVirtualViews()) .describedAs("The parent output of the Text must implement virtual views") .isTrue(); mLithoViewRule .setRootAndSizeSpec( SolidColor.create(mContext).color(Color.BLACK).build(), SizeSpec.makeSizeSpec(100, EXACTLY), SizeSpec.makeSizeSpec(100, EXACTLY)) .measure() .layout(); assertThat(mLithoViewRule.getLithoView().implementsVirtualViews()) .describedAs("The parent output of the drawable must not implement virtual views") .isFalse(); mLithoViewRule .setRootAndSizeSpec( Column.create(mContext) .child(Text.create(mContext).text("hello world").build()) .child(SolidColor.create(mContext).color(Color.BLACK).build()) .build(), SizeSpec.makeSizeSpec(100, EXACTLY), SizeSpec.makeSizeSpec(200, EXACTLY)) .attachToWindow() .measure() .layout(); assertThat(mLithoViewRule.getLithoView().implementsVirtualViews()) .describedAs("The root output must not implement virtual views") .isFalse(); final ComponentHost host = (ComponentHost) mLithoViewRule.getLithoView().getChildAt(0); assertThat(host.implementsVirtualViews()) .describedAs("The parent output of the Text must implement virtual views") .isTrue(); } private void enableAccessibility() { final ShadowAccessibilityManager manager = Shadows.shadowOf( (AccessibilityManager) getApplicationContext().getSystemService(ACCESSIBILITY_SERVICE)); manager.setEnabled(true); manager.setTouchExplorationEnabled(true); } private LayoutState calculateLayoutState( final Context context, final Component component, final int componentTreeId, final int widthSpec, final int heightSpec) { return LayoutState.calculate( new ComponentContext(context), component, componentTreeId, widthSpec, heightSpec, LayoutState.CalculateLayoutSource.TEST); } }