com.facebook.litho.testing.viewtree.ViewTreeAssert.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.litho.testing.viewtree.ViewTreeAssert.java

Source

/*
 * 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.graphics.drawable.Drawable;
import android.view.View;
import android.widget.TextView;

import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Java6Assertions;
import org.robolectric.RuntimeEnvironment;

import static com.facebook.litho.testing.viewtree.ViewExtractors.GET_TEXT_FUNCTION;
import static com.facebook.litho.testing.viewtree.ViewPredicates.hasTextMatchingPredicate;
import static com.facebook.litho.testing.viewtree.ViewPredicates.hasVisibleId;
import static com.facebook.litho.testing.viewtree.ViewPredicates.isVisible;

/**
 * Assertions which require checking an entire view tree
 *
 * NOTE: Assertions looking for visible attributes are limited to checking the visibility of the
 * nodes, but do not check actual layout. So a visible view might have 0 pixels available for it
 * in actual app code and still pass the checks done here
 */
public final class ViewTreeAssert extends AbstractAssert<ViewTreeAssert, ViewTree> {

    private ViewTreeAssert(final ViewTree actual) {
        super(actual, ViewTreeAssert.class);
    }

    public static ViewTreeAssert assertThat(final ViewTree actual) {
        return new ViewTreeAssert(actual);
    }

    /**
     * Tests if any view in the hierarchy under the root, for which the path is visible, has the
     * requested piece of text as its text
     *
     * @param text the text to search for
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleText(final String text) {
        final ImmutableList<View> path = getPathToVisibleText(text);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage(path == null ? getHasVisibleTextErrorMessage(text) : "").isNotNull();

        return this;
    }

    private String getHasVisibleTextErrorMessage(final String text) {
        String errorMsg = String.format("Cannot find text \"%s\" in view hierarchy:%n%s. ", text,
                actual.makeString(GET_TEXT_FUNCTION));

        final ImmutableList<View> similarPath = getPathToVisibleSimilarText(text);
        if (similarPath != null) {
            errorMsg += String.format("\nHowever, a near-match was found: \"%s\"",
                    GET_TEXT_FUNCTION.apply(similarPath.get(similarPath.size() - 1)));
        } else {
            errorMsg += "\nNo near-match was found.";
        }
        return errorMsg;
    }

    /**
     * Tests if any view in the hierarchy under the root, for which the path is visible, has the
     * requested piece of text as its text and has a tag set on that TextView with the given tag id
     * and tag value.
     *
     * @param text the text to search for
     * @param tagId the tag to look for on the TextView containing the searched text
     * @param tagValue the expected value of the tag associated with tagId
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleTextWithTag(final String text, final int tagId, final Object tagValue) {
        final ImmutableList<View> path = getPathToVisibleTextWithTag(text, tagId, tagValue);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage(
                        "Cannot find text \"%s\" with tagId \"%d\" and value:%s in view hierarchy:%n%s", text,
                        tagId, tagValue.toString(), actual.makeString(GET_TEXT_FUNCTION))
                .isNotNull();

        return this;
    }

    /**
     * Tests if any view has visible text identified by the resource id
     *
     * @param resourceId resource id of the text
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleText(final int resourceId) {
        return hasVisibleText(RuntimeEnvironment.application.getResources().getString(resourceId));
    }

    /**
     * Tests that all views in the hierarchy under the root, for which the path is visible, do not
     * have text equal to the given string
     *
     * @param text the text to search for
     * @return the assertions object
     */
    public ViewTreeAssert doesNotHaveVisibleText(final String text) {
        final ImmutableList<View> path = getPathToVisibleText(text);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Found text \"%s\" in view hierarchy for path: %s", text, makeString(path))
                .isNull();

        return this;
    }

    /**
     * Tests if any view hierarchy under the root has the given view tag and value.
     * @param tagId the id to look for
     * @param tagValue the value that the id should have
     * @return the assertions object
     */
    public ViewTreeAssert hasViewTag(final int tagId, final Object tagValue) {
        final ImmutableList<View> path = getPathToViewTag(tagId, tagValue);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Cannot find tag id \"%d\" with tag value \"%s\" in view hierarchy:%n%s",
                        tagId, tagValue, actual.makeString(ViewExtractors.generateGetViewTagFunction(tagId)))
                .isNotNull();

        return this;
    }

    /**
     * Tests if any view hierarchy under the root has the given contentDescription.
     * @param contentDescription the contentDescription to search for
     * @return the assertions object
     */
    public ViewTreeAssert hasContentDescription(final String contentDescription) {
        final ImmutableList<View> path = getPathToContentDescription(contentDescription);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Cannot find content description \"%s\" in view hierarchy:%n%s",
                        contentDescription, actual.makeString(ViewExtractors.GET_CONTENT_DESCRIPTION_FUNCTION))
                .isNotNull();

        return this;
    }

    /**
     * Tests that all views in the hierarchy under the root, for which the path is visible, do not
     * have text equal to the string matching the given resource id
     *
     * @param resourceId resource id of the text
     * @return the assertions object
     */
    public ViewTreeAssert doesNotHaveVisibleText(final int resourceId) {
        return doesNotHaveVisibleText(RuntimeEnvironment.application.getResources().getString(resourceId));
    }

    /**
     * Tests if any view hierarchy under the root has the given contentDescription.
     * @param resourceId the resId of the contentDescription to search for
     * @return the assertions object
     */
    public ViewTreeAssert hasContentDescription(final int resourceId) {
        return hasContentDescription(RuntimeEnvironment.application.getResources().getString(resourceId));
    }

    private ImmutableList<View> getPathToVisibleSimilarText(final String text) {
        return actual.findChild(Predicates.and(isVisible(), hasTextMatchingPredicate(new Predicate<String>() {
            @Override
            public boolean apply(@Nullable final String input) {
                final int maxEditDistance = Math.max(3, text.length() / 4);
                return LevenshteinDistance.getLevenshteinDistance(text, input, maxEditDistance) <= maxEditDistance;
            }
        })), ViewPredicates.isVisible());
    }

    private ImmutableList<View> getPathToVisibleText(final String text) {
        return actual.findChild(ViewPredicates.hasVisibleText(text), ViewPredicates.isVisible());
    }

    private ImmutableList<View> getPathToVisibleTextWithTag(final String text, final int tagId,
            final Object tagValue) {
        return actual.findChild(ViewPredicates.hasVisibleTextWithTag(text, tagId, tagValue),
                ViewPredicates.isVisible());
    }

    private ImmutableList<View> getPathToViewTag(final int tagId, final Object tagValue) {
        return actual.findChild(ViewPredicates.hasTag(tagId, tagValue));
    }

    private ImmutableList<View> getPathToContentDescription(final String contentDescription) {
        return actual.findChild(ViewPredicates.hasContentDescription(contentDescription));
    }

    /**
     * Tests if any view in the hierarchy under the root, for which the path is visible, has text that
     * matches the given regular expression
     *
     * @param pattern the regular expression to match against
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleTextMatching(final String pattern) {
        final ImmutableList<View> path = getPathToVisibleMatchingText(pattern);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Cannot find text matching \"%s\" in view hierarchy:%n%s", pattern,
                        actual.makeString(GET_TEXT_FUNCTION))
                .isNotNull();

        return this;
    }

    /**
     * Tests that all views in the hierarchy under the root, for which the path is visible, do not
     * have text that matches against the given regular expression
     *
     * @param pattern the regular expression to match against
     * @return the assertions object(
     */
    public ViewTreeAssert doesNotHaveVisibleTextMatching(final String pattern) {
        final ImmutableList<View> path = getPathToVisibleMatchingText(pattern);

        Java6Assertions.assertThat(path).overridingErrorMessage(
                "Found pattern \"%s\" in view hierarchy for path: %s", pattern, makeString(path)).isNull();

        return this;
    }

    /**
     * Tests that all views in the hierarchy under the root, for which the path is visible, do not
     * have any text appearing on them
     *
     * @return the assertions object
     */
    public ViewTreeAssert doesNotHaveVisibleText() {
        final ImmutableList<View> path = getPathToVisibleMatchingText(".+");

        Java6Assertions.assertThat(path).overridingErrorMessage("Found text \"%s\" in view hierarchy for path: %s",
                getTextProof(path), makeString(path)).isNull();

        return this;
    }

    private String getTextProof(@Nullable final ImmutableList<View> path) {
        if (path == null) {
            return "";
        }

        final View last = path.get(path.size() - 1);
        return ((TextView) last).getText().toString();
    }

    private ImmutableList<View> getPathToVisibleMatchingText(final String pattern) {
        return actual.findChild(ViewPredicates.hasVisibleMatchingText(pattern), ViewPredicates.isVisible());
    }

    private String makeString(final Iterable<View> path) {
        return path != null ? Joiner.on(" -> ").join(path) : "";
    }

    /**
     * Tests if any view in the hierarchy under the root, for which the path is visible, is displaying
     * the requested drawable by the given resource id.
     *
     * For this assertion to work, Robolectric must be immediately available and be able to load the
     * drawable corresponding to this resource id.
     *
     * @param resourceId the resource id of the drawable to look for
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleDrawable(final int resourceId) {
        hasVisibleDrawable(RuntimeEnvironment.application.getResources().getDrawable(resourceId));
        return this;
    }

    /**
     * Tests if any view in the hierarchy under the root, for which the path is visible, is displaying
     * the requested drawable
     *
     * @param drawable the drawable to look for
     * @return the assertions object
     */
    public ViewTreeAssert hasVisibleDrawable(final Drawable drawable) {
        final ImmutableList<View> path = getPathToVisibleWithDrawable(drawable);

        Java6Assertions.assertThat(path).overridingErrorMessage("Did not find drawable %s in view hierarchy:%n%s",
                drawable, actual.makeString(ViewExtractors.GET_DRAWABLE_FUNCTION)).isNotNull();

        return this;
    }

    /**
     * Tests all views in the hierarchy under the root, for which the path is visible, do not have
     * the requested drawable by the given resource id.
     * For this assertion to work, Robolectric must be immediately available and be able to load the
     * drawable corresponding to this resource id.
     *
     * @param resourceId the resource id of the drawable to look for
     * @return the assertions object
     */
    public ViewTreeAssert doesNotHaveVisibleDrawable(final int resourceId) {
        doesNotHaveVisibleDrawable(RuntimeEnvironment.application.getResources().getDrawable(resourceId));
        return this;
    }

    /**
     * Tests all views in the hierarchy under the root, for which the path is visible, are not
     * displaying the requested drawable
     *
     * @param drawable the drawable to look for
     * @return the assertions object
     */
    public ViewTreeAssert doesNotHaveVisibleDrawable(final Drawable drawable) {
        final ImmutableList<View> path = getPathToVisibleWithDrawable(drawable);

        Java6Assertions.assertThat(path).overridingErrorMessage("Found drawable %s in view hierarchy:%n%s",
                drawable, actual.makeString(ViewExtractors.GET_DRAWABLE_FUNCTION)).isNull();

        return this;
    }

    /** Whether there is a visible view in the hierarchy with the given id. */
    public ViewTreeAssert hasVisibleViewWithId(final int viewId) {
        final ImmutableList<View> path = getPathToVisibleWithId(viewId);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Did not find visible view with id \"%s=%d\":%n%s",
                        ViewTreeUtil.getResourceName(viewId), viewId,
                        actual.makeString(ViewExtractors.GET_VIEW_ID_FUNCTION))
                .isNotNull();

        return this;
    }

    /** Whether there is not a visible view in the hierarchy with the given id. */
    public ViewTreeAssert doesNotHaveVisibleViewWithId(final int viewId) {
        final ImmutableList<View> path = getPathToVisibleWithId(viewId);

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Found visible view with id \"%s=%d\":%n%s",
                        ViewTreeUtil.getResourceName(viewId), viewId,
                        actual.makeString(ViewExtractors.GET_VIEW_ID_FUNCTION))
                .isNull();

        return this;
    }

    public <V extends View> ViewTreeAssert hasVisible(final Class<V> clazz, final Predicate<V> predicate) {
        final Predicate<View> conjunction = Predicates.and(Predicates.instanceOf(clazz), ViewPredicates.isVisible(),
                (Predicate<View>) predicate);

        final ImmutableList<View> path = actual.findChild(conjunction, ViewPredicates.isVisible());

        Java6Assertions.assertThat(path)
                .overridingErrorMessage(
                        "Did not find view for which given predicate is true in view hierarchy:%n%s",
                        actual.makeString(null))
                .isNotNull();

        return this;
    }

    public <V extends View> ViewTreeAssert doesNotHaveVisible(final Class<V> clazz, final Predicate<V> predicate) {
        final Predicate<View> conjunction = Predicates.and(Predicates.instanceOf(clazz), ViewPredicates.isVisible(),
                (Predicate<View>) predicate);

        final ImmutableList<View> path = actual.findChild(conjunction, ViewPredicates.isVisible());

        Java6Assertions.assertThat(path)
                .overridingErrorMessage("Found a view for which given predicate is true in view hierarchy:%n%s",
                        actual.makeString(null))
                .isNull();

        return this;
    }

    private ImmutableList<View> getPathToVisibleWithDrawable(final Drawable drawable) {
        return actual.findChild(ViewPredicates.hasVisibleDrawable(drawable), ViewPredicates.isVisible());
    }

    private ImmutableList<View> getPathToVisibleWithId(final int viewId) {
        return actual.findChild(hasVisibleId(viewId), isVisible());
    }
}