/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho;
import android.graphics.Color;
import android.support.v4.view.ViewCompat;
import com.facebook.litho.testing.testrunner.ComponentsTestRunner;
import com.facebook.litho.widget.Text;
import com.facebook.yoga.YogaAlign;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaEdge;
import com.facebook.yoga.YogaPositionType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.reflect.Whitebox;
import org.robolectric.RuntimeEnvironment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(ComponentsTestRunner.class)
public class InternalNodeTest {
private static final int LIFECYCLE_TEST_ID = 1;
private final ComponentLifecycle mLifecycle = new ComponentLifecycle() {
@Override
int getId() {
return LIFECYCLE_TEST_ID;
}
};
private static class TestComponent<L extends ComponentLifecycle> extends Component<L> {
public TestComponent(L component) {
super(component);
}
@Override
public String getSimpleName() {
return "TestComponent";
}
}
private InternalNode mNode;
@Before
public void setup() {
mNode = ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
}
@Test
public void testLayoutDirectionFlag() {
mNode.layoutDirection(YogaDirection.INHERIT);
assertTrue(isFlagSet(mNode, "PFLAG_LAYOUT_DIRECTION_IS_SET"));
clearFlag(mNode, "PFLAG_LAYOUT_DIRECTION_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testAlignSelfFlag() {
mNode.alignSelf(YogaAlign.STRETCH);
assertTrue(isFlagSet(mNode, "PFLAG_ALIGN_SELF_IS_SET"));
clearFlag(mNode, "PFLAG_ALIGN_SELF_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testPositionTypeFlag() {
mNode.positionType(YogaPositionType.ABSOLUTE);
assertTrue(isFlagSet(mNode, "PFLAG_POSITION_TYPE_IS_SET"));
clearFlag(mNode, "PFLAG_POSITION_TYPE_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testFlexFlag() {
mNode.flex(1.5f);
assertTrue(isFlagSet(mNode, "PFLAG_FLEX_IS_SET"));
clearFlag(mNode, "PFLAG_FLEX_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testFlexGrowFlag() {
mNode.flexGrow(1.5f);
assertTrue(isFlagSet(mNode, "PFLAG_FLEX_GROW_IS_SET"));
clearFlag(mNode, "PFLAG_FLEX_GROW_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testFlexShrinkFlag() {
mNode.flexShrink(1.5f);
assertTrue(isFlagSet(mNode, "PFLAG_FLEX_SHRINK_IS_SET"));
clearFlag(mNode, "PFLAG_FLEX_SHRINK_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testFlexBasisFlag() {
mNode.flexBasisPx(1);
assertTrue(isFlagSet(mNode, "PFLAG_FLEX_BASIS_IS_SET"));
clearFlag(mNode, "PFLAG_FLEX_BASIS_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testImportantForAccessibilityFlag() {
mNode.importantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
assertTrue(isFlagSet(mNode, "PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET"));
clearFlag(mNode, "PFLAG_IMPORTANT_FOR_ACCESSIBILITY_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testDuplicateParentStateFlag() {
mNode.duplicateParentState(false);
assertTrue(isFlagSet(mNode, "PFLAG_DUPLICATE_PARENT_STATE_IS_SET"));
clearFlag(mNode, "PFLAG_DUPLICATE_PARENT_STATE_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testMarginFlag() {
mNode.marginPx(YogaEdge.ALL, 3);
assertTrue(isFlagSet(mNode, "PFLAG_MARGIN_IS_SET"));
clearFlag(mNode, "PFLAG_MARGIN_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testPaddingFlag() {
mNode.paddingPx(YogaEdge.ALL, 3);
assertTrue(isFlagSet(mNode, "PFLAG_PADDING_IS_SET"));
clearFlag(mNode, "PFLAG_PADDING_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testBorderWidthFlag() {
mNode.borderWidthPx(YogaEdge.ALL, 3);
assertTrue(isFlagSet(mNode, "PFLAG_BORDER_WIDTH_IS_SET"));
clearFlag(mNode, "PFLAG_BORDER_WIDTH_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testBorderColorFlag() {
mNode.borderColor(Color.GREEN);
assertTrue(isFlagSet(mNode, "PFLAG_BORDER_COLOR_IS_SET"));
clearFlag(mNode, "PFLAG_BORDER_COLOR_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testPositionFlag() {
mNode.positionPx(YogaEdge.ALL, 3);
assertTrue(isFlagSet(mNode, "PFLAG_POSITION_IS_SET"));
clearFlag(mNode, "PFLAG_POSITION_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testWidthFlag() {
mNode.widthPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_WIDTH_IS_SET"));
clearFlag(mNode, "PFLAG_WIDTH_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testMinWidthFlag() {
mNode.minWidthPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_MIN_WIDTH_IS_SET"));
clearFlag(mNode, "PFLAG_MIN_WIDTH_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testMaxWidthFlag() {
mNode.maxWidthPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_MAX_WIDTH_IS_SET"));
clearFlag(mNode, "PFLAG_MAX_WIDTH_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testHeightFlag() {
mNode.heightPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_HEIGHT_IS_SET"));
clearFlag(mNode, "PFLAG_HEIGHT_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testMinHeightFlag() {
mNode.minHeightPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_MIN_HEIGHT_IS_SET"));
clearFlag(mNode, "PFLAG_MIN_HEIGHT_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testMaxHeightFlag() {
mNode.maxHeightPx(4);
assertTrue(isFlagSet(mNode, "PFLAG_MAX_HEIGHT_IS_SET"));
clearFlag(mNode, "PFLAG_MAX_HEIGHT_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testBackgroundFlag() {
mNode.backgroundColor(0xFFFF0000);
assertTrue(isFlagSet(mNode, "PFLAG_BACKGROUND_IS_SET"));
clearFlag(mNode, "PFLAG_BACKGROUND_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testForegroundFlag() {
mNode.foregroundColor(0xFFFF0000);
assertTrue(isFlagSet(mNode, "PFLAG_FOREGROUND_IS_SET"));
clearFlag(mNode, "PFLAG_FOREGROUND_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void testAspectRatioFlag() {
mNode.aspectRatio(1);
assertTrue(isFlagSet(mNode, "PFLAG_ASPECT_RATIO_IS_SET"));
clearFlag(mNode, "PFLAG_ASPECT_RATIO_IS_SET");
assertEmptyFlags(mNode);
}
public void testTransitionKeyFlag() {
mNode.transitionKey("key");
assertTrue(isFlagSet(mNode, "PFLAG_TRANSITION_KEY_IS_SET"));
assertTrue(mNode.isForceViewWrapping());
clearFlag(mNode, "PFLAG_TRANSITION_KEY_IS_SET");
assertEmptyFlags(mNode);
}
@Test
public void setNestedTreeDoesntTransferLayoutDirectionIfExplicitlySetOnNestedNode() {
InternalNode holderNode =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
InternalNode nestedTree =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
nestedTree.layoutDirection(YogaDirection.RTL);
holderNode.calculateLayout();
holderNode.setNestedTree(nestedTree);
assertFalse(isFlagSet(holderNode, "PFLAG_LAYOUT_DIRECTION_IS_SET"));
assertEquals(
YogaDirection.INHERIT,
holderNode.getStyleDirection());
assertEquals(
YogaDirection.RTL,
nestedTree.getStyleDirection());
}
@Test
public void testCopyIntoTrasferLayoutDirectionIfNotSetOnTheHolderOrOnTheNestedTree() {
InternalNode holderNode =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
InternalNode nestedTree =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
holderNode.calculateLayout();
holderNode.copyInto(nestedTree);
assertFalse(isFlagSet(holderNode, "PFLAG_LAYOUT_DIRECTION_IS_SET"));
assertTrue(isFlagSet(nestedTree, "PFLAG_LAYOUT_DIRECTION_IS_SET"));
}
@Test
public void testCopyIntoNestedTreeTransferLayoutDirectionIfExplicitlySetOnHolderNode() {
InternalNode holderNode =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
InternalNode nestedTree =
ComponentsPools.acquireInternalNode(
new ComponentContext(RuntimeEnvironment.application),
RuntimeEnvironment.application.getResources());
holderNode.layoutDirection(YogaDirection.RTL);
holderNode.calculateLayout();
holderNode.copyInto(nestedTree);
assertEquals(
YogaDirection.RTL,
nestedTree.getStyleDirection());
}
@Test
public void testComponentCreateAndRetrieveCachedLayout() {
final ComponentContext c = new ComponentContext(RuntimeEnvironment.application);
final int unspecifiedSizeSpec = SizeSpec.makeSizeSpec(0, SizeSpec.UNSPECIFIED);
final int exactSizeSpec = SizeSpec.makeSizeSpec(50, SizeSpec.EXACTLY);
final Component<Text> textComponent = Text.create(c)
.textSizePx(16)
.text("test")
.build();
final Size textSize = new Size();
textComponent.measure(
c,
exactSizeSpec,
unspecifiedSizeSpec,
textSize);
assertTrue(textComponent.hasCachedLayout());
InternalNode cachedLayout = textComponent.getCachedLayout();
assertNotNull(cachedLayout);
assertEquals(exactSizeSpec, cachedLayout.getLastWidthSpec());
assertEquals(unspecifiedSizeSpec, cachedLayout.getLastHeightSpec());
textComponent.clearCachedLayout();
assertFalse(textComponent.hasCachedLayout());
}
@Test
public void testContextSpecificComponentAssertionPasses() {
InternalNode.assertContextSpecificStyleNotSet(mNode);
}
@Test
public void testContextSpecificComponentAssertionFailFormatting() {
final Component testComponent = new TestComponent<>(mLifecycle);
mNode.alignSelf(YogaAlign.AUTO);
mNode.flex(1f);
mNode.appendComponent(testComponent);
String error = "";
try {
InternalNode.assertContextSpecificStyleNotSet(mNode);
} catch (IllegalStateException e) {
error = e.getMessage();
}
assertTrue(
"The error message contains the attributes set",
error.contains("alignSelf, flex"));
}
private static boolean isFlagSet(InternalNode internalNode, String flagName) {
long flagPosition = Whitebox.getInternalState(InternalNode.class, flagName);
long flags = Whitebox.getInternalState(internalNode, "mPrivateFlags");
return ((flags & flagPosition) != 0);
}
private static void clearFlag(InternalNode internalNode, String flagName) {
long flagPosition = Whitebox.getInternalState(InternalNode.class, flagName);
long flags = Whitebox.getInternalState(internalNode, "mPrivateFlags");
flags &= ~flagPosition;
Whitebox.setInternalState(internalNode, "mPrivateFlags", flags);
}
private static void assertEmptyFlags(InternalNode internalNode) {
assertTrue(((long) Whitebox.getInternalState(internalNode, "mPrivateFlags")) == 0);
}
}