Skip to content

Commit

Permalink
support full view hierarchy for litho in adb dumpsys activity
Browse files Browse the repository at this point in the history
Summary:
- Dump full Litho view hierarchy from `LithoView`
- This enable to get full UI view hierarchy when using `adb shell dumpsys activity top`
- Mimics the regular Android view toString for Litho components so the same parsing logic can be used for native android and litho
- Preserver the output of `GetViewHierarchyHandler` with minor changes
- Not all flags are correct but I have what I need to run E2E test and we can improve it later
- will be used by FB Lite to run full E2E tests without selendroid

Reviewed By: marcelogomez

Differential Revision: D7084371

fbshipit-source-id: 76715558fc90918ef558218aceafc1b4e6978639
  • Loading branch information
Arthur Teplitzki authored and facebook-github-bot committed Feb 27, 2018
1 parent 915816d commit ff961bf
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 40 deletions.
5 changes: 5 additions & 0 deletions litho-core/src/main/java/com/facebook/litho/LithoView.java
Original file line number Diff line number Diff line change
Expand Up @@ -773,4 +773,9 @@ public interface LayoutManagerOverrideParams {

int getHeightMeasureSpec();
}

@Override
public String toString() {
return LithoViewTestHelper.viewToString(this, true);
}
}
100 changes: 67 additions & 33 deletions litho-core/src/main/java/com/facebook/litho/LithoViewTestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewParent;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.litho.config.ComponentsConfiguration;
import com.facebook.proguard.annotations.DoNotStrip;
Expand Down Expand Up @@ -91,67 +93,99 @@ public static Deque<TestItem> findTestItems(LithoView lithoView, String testKey)
return lithoView.findTestItems(testKey);
}

@DoNotStrip
public static String viewToString(LithoView view) {
return viewToString(view, false);
}

/**
* Provide a nested string representation of a LithoView and its nested
* components for debugging purposes.
* Provide a nested string representation of a LithoView and its nested components for debugging
* purposes.
*
* @param view A Litho view with mounted components.
* @param embedded if the call is embedded in "adb dumpsys activity"
*/
@DoNotStrip
public static String viewToString(LithoView view) {
return viewToString(DebugComponent.getRootInstance(view), 0);
}

private static String viewToString(@Nullable DebugComponent debugComponent, int depth) {
if (debugComponent == null) {
return "";
public static String viewToString(LithoView view, boolean embedded) {
int left = 0;
int top = 0;
int depth = 0;
if (embedded) {
left = view.getLeft();
top = view.getTop();
depth = 2;
ViewParent parent = view.getParent();
while (parent != null) {
depth++;
parent = parent.getParent();
}
}

final StringBuilder sb = new StringBuilder();
viewToString(left, top, DebugComponent.getRootInstance(view), sb, embedded, depth);
return sb.toString();
}

if (depth > 0) {
sb.append('\n');
}

for (int i = 0; i < depth; i++) {
sb.append(" ");
private static void viewToString(
int left,
int top,
@Nullable DebugComponent debugComponent,
StringBuilder sb,
boolean embedded,
int depth) {
if (debugComponent == null) {
return;
}

sb.append("litho.");
sb.append(debugComponent.getComponent().getSimpleName());

final Rect bounds = debugComponent.getBounds();
sb.append('{');
sb.append(Integer.toHexString(debugComponent.hashCode()));
sb.append(' ');

final LithoView lithoView = debugComponent.getLithoView();
final DebugLayoutNode layout = debugComponent.getLayoutNode();
sb.append(lithoView != null && lithoView.getVisibility() == View.VISIBLE ? "V" : ".");
sb.append(layout != null && layout.getFocusable() ? "F" : ".");
sb.append(lithoView != null && lithoView.isEnabled() ? "E" : ".");
sb.append(".");
sb.append(lithoView != null && lithoView.isHorizontalScrollBarEnabled() ? "H" : ".");
sb.append(lithoView != null && lithoView.isVerticalScrollBarEnabled() ? "V" : ".");
sb.append(layout != null && layout.getClickHandler() != null ? "C" : ".");
sb.append(". .. ");

sb.append(bounds.left);
sb.append(", ");
sb.append(bounds.top);
sb.append(" - ");
sb.append(bounds.right);
sb.append(", ");
sb.append(bounds.bottom);
final Rect bounds = debugComponent.getBounds();
sb.append(left + bounds.left);
sb.append(",");
sb.append(top + bounds.top);
sb.append("-");
sb.append(left + bounds.right);
sb.append(",");
sb.append(top + bounds.bottom);

final String testKey = debugComponent.getTestKey();
if (!TextUtils.isEmpty(testKey)) {
sb.append(String.format(" testKey=\"%s\"", testKey));
if (testKey != null && !TextUtils.isEmpty(testKey)) {
sb.append(String.format(" litho:id/%s", testKey.replace(' ', '_')));
}

final String textContent = debugComponent.getTextContent();
if (!TextUtils.isEmpty(textContent)) {
sb.append(String.format(" text=\"%s\"", textContent));
if (textContent != null && !TextUtils.isEmpty(textContent)) {
sb.append(String.format(" text=\"%s\"", textContent.replace("\n", "").replace("\"", "")));
}

final DebugLayoutNode layout = debugComponent.getLayoutNode();
if (layout != null && layout.getClickHandler() != null) {
if (!embedded && layout != null && layout.getClickHandler() != null) {
sb.append(" [clickable]");
}

sb.append('}');

for (DebugComponent child : debugComponent.getChildComponents()) {
sb.append(viewToString(child, depth + 1));
sb.append("\n");
for (int i = 0; i <= depth; i++) {
sb.append(" ");
}
viewToString(0, 0, child, sb, embedded, depth + 1);
}

return sb.toString();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ protected Component onCreateLayout(ComponentContext c) {
final String string = LithoViewTestHelper.viewToString(lithoView);

assertThat(string)
.isEqualTo("InlineLayout{0, 0 - 100, 100}\n" + " TestDrawableComponent{0, 0 - 100, 100}");
.containsPattern(
"litho.InlineLayout\\{\\w{8} V.E..... .. 0,0-100,100\\}\n"
+ " litho.TestDrawableComponent\\{\\w{8} V.E..... .. 0,0-100,100\\}");
}

@Test
Expand Down Expand Up @@ -97,9 +99,9 @@ protected Component onCreateLayout(ComponentContext c) {

final String string = LithoViewTestHelper.viewToString(lithoView);
assertThat(string)
.isEqualTo(
"InlineLayout{0, 0 - 100, 100}\n"
+ " TestDrawableComponent{0, 0 - 100, 100 testKey=\"test-drawable\"}\n"
+ " Text{0, 100 - 100, 100 text=\"Hello, World\"}");
.containsPattern(
"litho.InlineLayout\\{\\w{8} V.E..... .. 0,0-100,100\\}\n"
+ " litho.TestDrawableComponent\\{\\w{8} V.E..... .. 0,0-100,100 litho:id/test-drawable\\}\n"
+ " litho.Text\\{\\w{8} V.E..... .. 0,100-100,100 text=\"Hello, World\"\\}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ protected Component onCreateLayout(ComponentContext c) {
LithoAssertions.assertThat(layout).has(subComponentWith(c, textEquals("Doesn't match.")));
} catch (final AssertionError assertionError) {
LithoAssertions.assertThat(assertionError)
.hasMessageContaining("Text{0, 0 - 100, 100 text=\"Hello, World!\"");
.hasMessageContaining(" 0,0-100,100 text=\"Hello, World!\"");
}

// Verify that resetting the representation in the same
Expand All @@ -67,7 +67,7 @@ protected Component onCreateLayout(ComponentContext c) {
LithoAssertions.assertThat(layout).has(subComponentWith(c, textEquals("Doesn't match.")));
} catch (final AssertionError assertionError) {
LithoAssertions.assertThat(assertionError.getMessage())
.doesNotContain("Text{0, 0 - 100, 100 text=\"Hello, World!\"");
.doesNotContain(" 0,0-100,100 text=\"Hello, World!\"");
}
}
}

0 comments on commit ff961bf

Please sign in to comment.