/* * 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.testing.viewtree; import javax.annotation.Nullable; import android.view.View; import android.view.ViewGroup; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; /** * This is a helper class to allow asserting on view trees and recursively * verify predicates on its nodes within the narrow abilities that * Robolectric affords us. */ public final class ViewTree { private final View mView; public static ViewTree of(View view) { return new ViewTree(view); } private ViewTree(View view) { mView = view; } /** * @return the view group used to generate this tree */ public View getRoot() { return mView; } /** * Find a view in the hierarchy for which the given predicate is true * * @param predicate the predicate to find a view upholding * @return null if no such view is found, or a list showing the path in the hierarchy to the * view for which the predicate holds */ @Nullable public ImmutableList<View> findChild(Predicate<View> predicate) { return findChild(mView, predicate, Predicates.<ViewGroup>alwaysTrue()); } /** * Find a view in the hierarchy for which the given predicate is true, while only check children * of nodes as directed by the additional shouldCheckChildren predicate * * @param predicate the predicate to find a view upholding * @param shouldCheckChildren a predicate to decide whether to * @return null if no such view is found, or a list showing the path in the hierarchy to the * view for which the predicate holds */ @Nullable public ImmutableList<View> findChild( Predicate<View> predicate, Predicate<? super ViewGroup> shouldCheckChildren) { return findChild(mView, predicate, shouldCheckChildren); } /** * Generates a string describing the views tree using the views' toString methods and an extra * information function. * * The output is a string, with each view of the tree in its own line, indented according to its * depth in the tree, and then the extra information supplied by teh function. * * This can be used, for example, to print all views and their respective text and is useful * for when assertions fail. * * @param extraTextFunction the function returning extra information to print per view, or null * if not extra information should be printed * @return a string describing the tree */ public String makeString(Function<View, String> extraTextFunction) { return makeString(extraTextFunction, mView, 0); } private String makeString(Function<View, String> extraTextFunction, View view, int depth) { final StringBuilder builder = new StringBuilder(); if (depth > 0) { builder.append('\n'); } for (int i = 0; i < depth; i++) { builder.append(" "); } builder.append(getViewString(view)); String extra = extraTextFunction != null ? extraTextFunction.apply(view) : null; if (extra != null) { builder.append(" ("); builder.append(extra); builder.append(")"); } if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; for (int i = 0; i < viewGroup.getChildCount(); i++) { View child = viewGroup.getChildAt(i); builder.append(makeString(extraTextFunction, child, depth + 1)); } } return builder.toString(); } private String getViewString(View view) { String string = view.toString(); return removePrefix(removePrefix(string, "android.widget."), "android.view."); } private static String removePrefix(String string, String prefix) { return string.startsWith(prefix) ? string.substring(prefix.length()) : string; } @Nullable private ImmutableList<View> findChild( View root, Predicate<View> predicate, Predicate<? super ViewGroup> shouldCheckChildren) { if (predicate.apply(root)) { return ImmutableList.of(root); } if (root instanceof ViewGroup && shouldCheckChildren.apply((ViewGroup) root)) { ViewGroup viewGroup = (ViewGroup) root; for (int i = 0; i < viewGroup.getChildCount(); i++) { View child = viewGroup.getChildAt(i); ImmutableList<View> result = findChild(child, predicate, shouldCheckChildren); if (result != null) { return ImmutableList.<View>builder() .add(root) .addAll(result) .build(); } } } return null; } }