chromium/base/test/android/javatests/src/org/chromium/base/test/transit/ViewSpec.java

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base.test.transit;

import static androidx.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.allOf;

import android.view.View;

import androidx.test.espresso.Espresso;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.action.ViewActions;

import org.hamcrest.Matcher;
import org.hamcrest.StringDescription;

import org.chromium.base.test.util.ViewPrinter;

/** A spec to generate ViewElements representing a view characteristic of a ConditionalState. */
public class ViewSpec {

    private final Matcher<View> mViewMatcher;
    private final String mMatcherDescription;

    /** Create a ViewSpec from a Matcher<View>. */
    public static ViewSpec viewSpec(Matcher<View> viewMatcher) {
        return new ViewSpec(viewMatcher);
    }

    /** Create a ViewSpec of a View that matches multiple Matchers<View>. */
    @SafeVarargs
    public static ViewSpec viewSpec(Matcher<View>... viewMatchers) {
        return new ViewSpec(allOf(viewMatchers));
    }

    private ViewSpec(Matcher<View> viewMatcher) {
        mViewMatcher = viewMatcher;

        // Capture the description as soon as possible to compare ViewSpecs added to different
        // states by their description. Espresso Matcher descriptions are not stable: the integer
        // resource ids are translated when a View is provided. See examples in
        // https://crbug.com/41494895#comment7.
        mMatcherDescription = StringDescription.toString(mViewMatcher);
    }

    /**
     * @return the Matcher<View> used to create this element
     */
    public Matcher<View> getViewMatcher() {
        return mViewMatcher;
    }

    /**
     * @return a stable String describing the Matcher<View>.
     */
    public String getMatcherDescription() {
        return mMatcherDescription;
    }

    /** Start an Espresso interaction with a displayed View that matches this ViewSpec's Matcher. */
    public ViewInteraction onView() {
        return Espresso.onView(
                allOf(mViewMatcher, isDisplayingAtLeast(ViewElement.MIN_DISPLAYED_PERCENT)));
    }

    /** Perform an Espresso ViewAction on a displayed View that matches this ViewSpec's Matcher. */
    public ViewInteraction perform(ViewAction action) {
        return onView().perform(action);
    }

    /** Perform an Espresso click() on a displayed View that matches this ViewSpec's Matcher. */
    public ViewInteraction click() {
        return onView().perform(ViewActions.click());
    }

    /**
     * Print the whole View hierarchy that contains the View matched to this ViewElement.
     *
     * <p>For debugging.
     */
    public void printFromRoot() {
        perform(
                new ViewAction() {
                    @Override
                    public Matcher<View> getConstraints() {
                        return instanceOf(View.class);
                    }

                    @Override
                    public String getDescription() {
                        return "print the View hierarchy for debugging";
                    }

                    @Override
                    public void perform(UiController uiController, View view) {
                        ViewPrinter.printView(view.getRootView());
                    }
                });
    }
}