/** * Copyright (c) 2015-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.react.uimanager; import android.util.DisplayMetrics; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; import com.facebook.yoga.YogaPositionType; import com.facebook.react.bridge.JavaOnlyMap; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RobolectricTestRunner; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyFloat; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mockStatic; @PrepareForTest({PixelUtil.class}) @RunWith(RobolectricTestRunner.class) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) public class LayoutPropertyApplicatorTest { @Rule public PowerMockRule rule = new PowerMockRule(); @Before public void setup() { DisplayMetricsHolder.setWindowDisplayMetrics(new DisplayMetrics()); DisplayMetricsHolder.setScreenDisplayMetrics(new DisplayMetrics()); } @After public void teardown() { DisplayMetricsHolder.setWindowDisplayMetrics(null); DisplayMetricsHolder.setScreenDisplayMetrics(null); } public ReactStylesDiffMap buildStyles(Object... keysAndValues) { return new ReactStylesDiffMap(JavaOnlyMap.of(keysAndValues)); } @Test public void testDimensions() { LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = spy( buildStyles( "width", 10.0, "height", 10.0, "left", 10.0, "top", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setStyleWidth(anyFloat()); verify(map).getFloat(eq("width"), anyFloat()); verify(reactShadowNode).setStyleHeight(anyFloat()); verify(map).getFloat(eq("height"), anyFloat()); verify(reactShadowNode).setPosition(eq(Spacing.START), anyFloat()); verify(map).getFloat(eq("left"), anyFloat()); verify(reactShadowNode).setPosition(eq(Spacing.TOP), anyFloat()); verify(map).getFloat(eq("top"), anyFloat()); reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles()); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setStyleWidth(anyFloat()); verify(map, never()).getFloat(eq("width"), anyFloat()); verify(reactShadowNode, never()).setStyleHeight(anyFloat()); verify(map, never()).getFloat(eq("height"), anyFloat()); verify(reactShadowNode, never()).setPosition(eq(Spacing.START), anyFloat()); verify(map, never()).getFloat(eq("left"), anyFloat()); verify(reactShadowNode, never()).setPosition(eq(Spacing.TOP), anyFloat()); verify(map, never()).getFloat(eq("top"), anyFloat()); } @Test public void testFlex() { LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = spy(buildStyles("flex", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setFlex(anyFloat()); verify(map).getFloat("flex", 0.f); reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles()); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setFlex(anyFloat()); verify(map, never()).getFloat("flex", 0.f); } @Test public void testPosition() { LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = spy(buildStyles( "position", "absolute", "bottom", 10.0, "right", 5.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPosition(eq(Spacing.BOTTOM), anyFloat()); verify(reactShadowNode).setPosition(eq(Spacing.END), anyFloat()); verify(reactShadowNode).setPositionType(any(YogaPositionType.class)); verify(map).getFloat("bottom", Float.NaN); verify(map).getFloat("right", Float.NaN); reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles()); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setPosition(eq(Spacing.BOTTOM), anyFloat()); verify(reactShadowNode, never()).setPosition(eq(Spacing.END), anyFloat()); verify(reactShadowNode, never()).setPositionType(any(YogaPositionType.class)); verify(map, never()).getFloat("bottom", Float.NaN); verify(map, never()).getFloat("right", Float.NaN); } @Test public void testMargin() { // margin LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = spy(buildStyles("margin", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.ALL), anyFloat()); verify(map).getFloat("margin", YogaConstants.UNDEFINED); // marginVertical reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginVertical", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.VERTICAL), anyFloat()); verify(map).getFloat("marginVertical", YogaConstants.UNDEFINED); // marginHorizontal reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginHorizontal", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.HORIZONTAL), anyFloat()); verify(map).getFloat("marginHorizontal", YogaConstants.UNDEFINED); // marginTop reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginTop", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.TOP), anyFloat()); verify(map).getFloat("marginTop", YogaConstants.UNDEFINED); // marginBottom reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginBottom", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.BOTTOM), anyFloat()); verify(map).getFloat("marginBottom", YogaConstants.UNDEFINED); // marginLeft reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginLeft", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.START), anyFloat()); verify(map).getFloat("marginLeft", YogaConstants.UNDEFINED); // marginRight reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("marginRight", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setMargin(eq(Spacing.END), anyFloat()); verify(map).getFloat("marginRight", YogaConstants.UNDEFINED); // no margin reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles()); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setMargin(anyInt(), anyFloat()); verify(map, never()).getFloat("margin", YogaConstants.UNDEFINED); } @Test public void testPadding() { // padding LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = spy(buildStyles("padding", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.ALL), anyFloat()); verify(map).getFloat("padding", YogaConstants.UNDEFINED); // paddingVertical reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingVertical", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.VERTICAL), anyFloat()); verify(map).getFloat("paddingVertical", YogaConstants.UNDEFINED); // paddingHorizontal reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingHorizontal", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.HORIZONTAL), anyFloat()); verify(map).getFloat("paddingHorizontal", YogaConstants.UNDEFINED); // paddingTop reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingTop", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.TOP), anyFloat()); verify(map).getFloat("paddingTop", YogaConstants.UNDEFINED); // paddingBottom reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingBottom", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.BOTTOM), anyFloat()); verify(map).getFloat("paddingBottom", YogaConstants.UNDEFINED); // paddingLeft reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingLeft", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.START), anyFloat()); verify(map).getFloat("paddingLeft", YogaConstants.UNDEFINED); // paddingRight reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles("paddingRight", 10.0)); reactShadowNode.updateProperties(map); verify(reactShadowNode).setPadding(eq(Spacing.END), anyFloat()); verify(map).getFloat("paddingRight", YogaConstants.UNDEFINED); // no padding reactShadowNode = spy(new LayoutShadowNode()); map = spy(buildStyles()); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setPadding(anyInt(), anyFloat()); verify(map, never()).getFloat("padding", YogaConstants.UNDEFINED); } @Test public void testEnumerations() { LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = buildStyles( "flexDirection", "column", "alignSelf", "stretch", "alignItems", "center", "justifyContent", "space_between", "position", "relative"); reactShadowNode.updateProperties(map); verify(reactShadowNode).setFlexDirection(YogaFlexDirection.COLUMN); verify(reactShadowNode).setAlignSelf(YogaAlign.STRETCH); verify(reactShadowNode).setAlignItems(YogaAlign.CENTER); verify(reactShadowNode).setJustifyContent(YogaJustify.SPACE_BETWEEN); verify(reactShadowNode).setPositionType(YogaPositionType.RELATIVE); reactShadowNode = spy(new LayoutShadowNode()); map = buildStyles(); reactShadowNode.updateProperties(map); verify(reactShadowNode, never()).setFlexDirection(any(YogaFlexDirection.class)); verify(reactShadowNode, never()).setAlignSelf(any(YogaAlign.class)); verify(reactShadowNode, never()).setAlignItems(any(YogaAlign.class)); verify(reactShadowNode, never()).setJustifyContent(any(YogaJustify.class)); verify(reactShadowNode, never()).setPositionType(any(YogaPositionType.class)); } @Test public void testPropertiesResetToDefault() { DisplayMetrics displayMetrics = new DisplayMetrics(); displayMetrics.density = 1.0f; DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics); LayoutShadowNode reactShadowNode = spy(new LayoutShadowNode()); ReactStylesDiffMap map = buildStyles( "width", 10.0, "height", 10.0, "left", 10.0, "top", 10.0, "flex", 1.0, "padding", 10.0, "marginLeft", 10.0, "borderTopWidth", 10.0, "flexDirection", "row", "alignSelf", "stretch", "alignItems", "center", "justifyContent", "space_between", "position", "absolute"); reactShadowNode.updateProperties(map); verify(reactShadowNode).setStyleWidth(10.f); verify(reactShadowNode).setStyleHeight(10.f); verify(reactShadowNode).setPosition(Spacing.START, 10.f); verify(reactShadowNode).setPosition(Spacing.TOP, 10.f); verify(reactShadowNode).setFlex(1.0f); verify(reactShadowNode).setPadding(Spacing.ALL, 10.f); verify(reactShadowNode).setMargin(Spacing.START, 10.f); verify(reactShadowNode).setBorder(Spacing.TOP, 10.f); verify(reactShadowNode).setFlexDirection(YogaFlexDirection.ROW); verify(reactShadowNode).setAlignSelf(YogaAlign.STRETCH); verify(reactShadowNode).setAlignItems(YogaAlign.CENTER); verify(reactShadowNode).setJustifyContent(YogaJustify.SPACE_BETWEEN); verify(reactShadowNode).setPositionType(YogaPositionType.ABSOLUTE); map = buildStyles( "width", null, "height", null, "left", null, "top", null, "flex", null, "padding", null, "marginLeft", null, "borderTopWidth", null, "flexDirection", null, "alignSelf", null, "alignItems", null, "justifyContent", null, "position", null); reset(reactShadowNode); reactShadowNode.updateProperties(map); verify(reactShadowNode).setStyleWidth(YogaConstants.UNDEFINED); verify(reactShadowNode).setStyleHeight(YogaConstants.UNDEFINED); verify(reactShadowNode).setPosition(Spacing.START, YogaConstants.UNDEFINED); verify(reactShadowNode).setPosition(Spacing.TOP, YogaConstants.UNDEFINED); verify(reactShadowNode).setFlex(0.f); verify(reactShadowNode).setPadding(Spacing.ALL, YogaConstants.UNDEFINED); verify(reactShadowNode).setMargin(Spacing.START, YogaConstants.UNDEFINED); verify(reactShadowNode).setBorder(Spacing.TOP, YogaConstants.UNDEFINED); verify(reactShadowNode).setFlexDirection(YogaFlexDirection.COLUMN); verify(reactShadowNode).setAlignSelf(YogaAlign.AUTO); verify(reactShadowNode).setAlignItems(YogaAlign.STRETCH); verify(reactShadowNode).setJustifyContent(YogaJustify.FLEX_START); verify(reactShadowNode).setPositionType(YogaPositionType.RELATIVE); } @Test public void testSettingDefaultStyleValues() { mockStatic(PixelUtil.class); when(PixelUtil.toPixelFromDIP(anyFloat())).thenAnswer( new Answer() { @Override public Float answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); return (Float) args[0]; } }); LayoutShadowNode[] nodes = new LayoutShadowNode[7]; for (int idx = 0; idx < nodes.length; idx++) { nodes[idx] = new LayoutShadowNode(); nodes[idx].setDefaultPadding(Spacing.START, 15); nodes[idx].setDefaultPadding(Spacing.TOP, 25); nodes[idx].setDefaultPadding(Spacing.END, 35); nodes[idx].setDefaultPadding(Spacing.BOTTOM, 45); } ReactStylesDiffMap[] mapNodes = new ReactStylesDiffMap[7]; mapNodes[0] = buildStyles("paddingLeft", 10.0, "paddingHorizontal", 5.0); mapNodes[1] = buildStyles("padding", 10.0, "paddingTop", 5.0); mapNodes[2] = buildStyles("paddingLeft", 10.0, "paddingVertical", 5.0); mapNodes[3] = buildStyles("paddingBottom", 10.0, "paddingHorizontal", 5.0); mapNodes[4] = buildStyles("padding", null, "paddingTop", 5.0); mapNodes[5] = buildStyles( "paddingRight", 10.0, "paddingHorizontal", null, "paddingVertical", 7.0); mapNodes[6] = buildStyles("margin", 5.0); for (int idx = 0; idx < nodes.length; idx++) { nodes[idx].updateProperties(mapNodes[idx]); } assertEquals(10.0, nodes[0].getPadding(Spacing.START), .0001); assertEquals(25.0, nodes[0].getPadding(Spacing.TOP), .0001); assertEquals(5.0, nodes[0].getPadding(Spacing.END), .0001); assertEquals(45.0, nodes[0].getPadding(Spacing.BOTTOM), .0001); assertEquals(10.0, nodes[1].getPadding(Spacing.START), .0001); assertEquals(5.0, nodes[1].getPadding(Spacing.TOP), .0001); assertEquals(10.0, nodes[1].getPadding(Spacing.END), .0001); assertEquals(10.0, nodes[1].getPadding(Spacing.BOTTOM), .0001); assertEquals(10.0, nodes[2].getPadding(Spacing.START), .0001); assertEquals(5.0, nodes[2].getPadding(Spacing.TOP), .0001); assertEquals(35.0, nodes[2].getPadding(Spacing.END), .0001); assertEquals(5.0, nodes[2].getPadding(Spacing.BOTTOM), .0001); assertEquals(5.0, nodes[3].getPadding(Spacing.START), .0001); assertEquals(25.0, nodes[3].getPadding(Spacing.TOP), .0001); assertEquals(5.0, nodes[3].getPadding(Spacing.END), .0001); assertEquals(10.0, nodes[3].getPadding(Spacing.BOTTOM), .0001); assertEquals(15.0, nodes[4].getPadding(Spacing.START), .0001); assertEquals(5.0, nodes[4].getPadding(Spacing.TOP), .0001); assertEquals(35.0, nodes[4].getPadding(Spacing.END), .0001); assertEquals(45.0, nodes[4].getPadding(Spacing.BOTTOM), .0001); assertEquals(15.0, nodes[5].getPadding(Spacing.START), .0001); assertEquals(7.0, nodes[5].getPadding(Spacing.TOP), .0001); assertEquals(10.0, nodes[5].getPadding(Spacing.END), .0001); assertEquals(7.0, nodes[5].getPadding(Spacing.BOTTOM), .0001); assertEquals(15.0, nodes[6].getPadding(Spacing.START), .0001); assertEquals(25.0, nodes[6].getPadding(Spacing.TOP), .0001); assertEquals(35.0, nodes[6].getPadding(Spacing.END), .0001); assertEquals(45.0, nodes[6].getPadding(Spacing.BOTTOM), .0001); } }