/** * 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.Rect; import android.graphics.drawable.Drawable; import android.view.View; import com.facebook.litho.testing.ComponentTestHelper; import com.facebook.litho.testing.TestComponent; import com.facebook.litho.testing.TestDrawableComponent; import com.facebook.litho.testing.TestViewComponent; import com.facebook.litho.testing.shadows.LayoutDirectionViewGroupShadow; import com.facebook.litho.testing.shadows.LayoutDirectionViewShadow; import com.facebook.litho.testing.testrunner.ComponentsTestRunner; import com.facebook.litho.testing.util.InlineLayoutSpec; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaDirection; import com.facebook.yoga.YogaEdge; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static org.junit.Assert.assertEquals; @Config( manifest = Config.NONE, sdk = LOLLIPOP, shadows = { LayoutDirectionViewShadow.class, LayoutDirectionViewGroupShadow.class}) @RunWith(ComponentsTestRunner.class) public class LayoutDirectionTest { private ComponentContext mContext; @Before public void setup() { mContext = new ComponentContext(RuntimeEnvironment.application); } /** * Test that view mount items are laid out in the correct positions for LTR and RTL layout * directions. */ @Test public void testViewChildrenLayoutDirection() { final TestComponent child1 = TestViewComponent.create(mContext, true, true, true, false) .build(); final TestComponent child2 = TestViewComponent.create(mContext, true, true, true, false) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.LTR) .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10)) .build(); } }, 20, 10); View view1 = lithoView.getChildAt(0); View view2 = lithoView.getChildAt(1); assertEquals( new Rect(0, 0, 10, 10), new Rect( view1.getLeft(), view1.getTop(), view1.getRight(), view1.getBottom())); assertEquals( new Rect(10, 0, 20, 10), new Rect( view2.getLeft(), view2.getTop(), view2.getRight(), view2.getBottom())); ComponentTestHelper.mountComponent( mContext, lithoView, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.RTL) .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10)) .build(); } }, 20, 10); view1 = lithoView.getChildAt(0); view2 = lithoView.getChildAt(1); assertEquals( new Rect(10, 0, 20, 10), new Rect( view1.getLeft(), view1.getTop(), view1.getRight(), view1.getBottom())); assertEquals( new Rect(0, 0, 10, 10), new Rect( view2.getLeft(), view2.getTop(), view2.getRight(), view2.getBottom())); } /** * Test that drawable mount items are laid out in the correct positions for LTR and RTL layout * directions. */ @Test public void testDrawableChildrenLayoutDirection() { final TestComponent child1 = TestDrawableComponent.create(mContext) .build(); final TestComponent child2 = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.LTR) .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10)) .build(); } }, 20, 10); Drawable drawable1 = lithoView.getDrawables().get(0); Drawable drawable2 = lithoView.getDrawables().get(1); assertEquals(new Rect(0, 0, 10, 10), drawable1.getBounds()); assertEquals(new Rect(10, 0, 20, 10), drawable2.getBounds()); ComponentTestHelper.mountComponent( mContext, lithoView, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.RTL) .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10)) .build(); } }, 20, 10); drawable1 = lithoView.getDrawables().get(0); drawable2 = lithoView.getDrawables().get(1); assertEquals(new Rect(10, 0, 20, 10), drawable1.getBounds()); assertEquals(new Rect(0, 0, 10, 10), drawable2.getBounds()); } /** * Test that layout direction is propagated properly throughout a component hierarchy. This is the * default behaviour of layout direction. */ @Test public void testInheritLayoutDirection() { final TestComponent child1 = TestDrawableComponent.create(mContext) .build(); final TestComponent child2 = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.RTL) .child( Row.create(c) .wrapInView() .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10))) .build(); } }, 20, 10); final ComponentHost host = (ComponentHost) lithoView.getChildAt(0); final Drawable drawable1 = host.getDrawables().get(0); final Drawable drawable2 = host.getDrawables().get(1); assertEquals(new Rect(10, 0, 20, 10), drawable1.getBounds()); assertEquals(new Rect(0, 0, 10, 10), drawable2.getBounds()); } /** * Test that layout direction is correctly set on child components when it differs from the layout * direction of it's parent. */ @Test public void testNestedComponentWithDifferentLayoutDirection() { final TestComponent child1 = TestDrawableComponent.create(mContext) .build(); final TestComponent child2 = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Row.create(c) .layoutDirection(YogaDirection.RTL) .child( Row.create(c) .layoutDirection(YogaDirection.LTR) .wrapInView() .child( Layout.create(c, child1) .widthPx(10) .heightPx(10)) .child( Layout.create(c, child2) .widthPx(10) .heightPx(10))) .build(); } }, 20, 10); final ComponentHost host = (ComponentHost) lithoView.getChildAt(0); final Drawable drawable1 = host.getDrawables().get(0); final Drawable drawable2 = host.getDrawables().get(1); assertEquals(new Rect(0, 0, 10, 10), drawable1.getBounds()); assertEquals(new Rect(10, 0, 20, 10), drawable2.getBounds()); } /** * Test that margins on START and END are correctly applied to the correct side of the component * depending upon the applied layout direction. */ @Test public void testMargin() { final TestComponent child = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .layoutDirection(YogaDirection.LTR) .child( Layout.create(c, child) .widthPx(10) .heightPx(10) .marginPx(YogaEdge.START, 10) .marginPx(YogaEdge.END, 20)) .build(); } }, 40, 10); Drawable drawable = lithoView.getDrawables().get(0); assertEquals(new Rect(10, 0, 20, 10), drawable.getBounds()); ComponentTestHelper.mountComponent( mContext, lithoView, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .layoutDirection(YogaDirection.RTL) .child( Layout.create(c, child) .widthPx(10) .heightPx(10) .marginPx(YogaEdge.START, 10) .marginPx(YogaEdge.END, 20)) .build(); } }, 40, 10); drawable = lithoView.getDrawables().get(0); assertEquals(new Rect(20, 0, 30, 10), drawable.getBounds()); } /** * Test that paddings on START and END are correctly applied to the correct side of the component * depending upon the applied layout direction. */ @Test public void testPadding() { final TestComponent child = TestDrawableComponent.create(mContext) .build(); final LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .layoutDirection(YogaDirection.LTR) .paddingPx(YogaEdge.START, 10) .paddingPx(YogaEdge.END, 20) .child( Layout.create(c, child) .widthPx(10) .heightPx(10)) .build(); } }, 40, 10); Drawable drawable = lithoView.getDrawables().get(0); assertEquals(new Rect(10, 0, 20, 10), drawable.getBounds()); ComponentTestHelper.mountComponent( mContext, lithoView, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .layoutDirection(YogaDirection.RTL) .paddingPx(YogaEdge.START, 10) .paddingPx(YogaEdge.END, 20) .child( Layout.create(c, child) .widthPx(10) .heightPx(10)) .build(); } }, 40, 10); drawable = lithoView.getDrawables().get(0); assertEquals(new Rect(20, 0, 30, 10), drawable.getBounds()); } /** * Tests to make sure the layout direction set on the component tree is correctly propagated to * mounted views. */ @Test public void testLayoutDirectionPropagation() { final TestComponent child = TestViewComponent.create(mContext) .build(); LithoView lithoView = ComponentTestHelper.mountComponent( mContext, new InlineLayoutSpec() { @Override protected ComponentLayout onCreateLayout(ComponentContext c) { return Column.create(c) .layoutDirection(YogaDirection.RTL) .child(child) .build(); } }); final View childView = lithoView.getChildAt(0); assertEquals(View.LAYOUT_DIRECTION_RTL, childView.getLayoutDirection()); } }