chromium/content/public/android/javatests/src/org/chromium/content/browser/accessibility/AccessibilityContentShellTestUtils.java

// Copyright 2021 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.content.browser.accessibility;

import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;

import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;

/** Helper class with various testing util methods for content shell accessibility tests. */
public class AccessibilityContentShellTestUtils {
    // Common test output error messages
    public static final String ANP_ERROR =
            "Could not find AccessibilityNodeProvider object for WebContentsAccessibilityImpl";
    public static final String NODE_TIMEOUT_ERROR =
            "Could not find specified node before polling timeout.";
    public static final String END_OF_TEST_ERROR =
            "Did not receive kEndOfTest signal before polling timeout.";
    public static final String READY_FOR_TEST_ERROR =
            "Did not receive kReadyForTest signal before polling timeout.";

    /**
     * Basic interface to define a way to match |AccessibilityNodeInfo| objects based on the
     * expected value of a given element of type T.
     * @param <T>               Generic type of the element for matching
     */
    public interface AccessibilityNodeInfoMatcher<T> {
        /**
         * Method used to check if a node matches the given element requirement.
         *
         * @param node              The node to test match with
         * @param element           The element and type we are matching against
         * @return                  True/false for whether or not the node is a match
         */
        boolean matches(AccessibilityNodeInfoCompat node, T element);
    }

    // Helper methods for common matching conditions for AccessibilityNodeInfo objects.

    static AccessibilityNodeInfoMatcher<Integer> sInputTypeMatcher =
            (node, type) -> node.getInputType() == type;

    static AccessibilityNodeInfoMatcher<String> sClassNameMatcher =
            (node, className) -> node.getClassName().equals(className);

    static AccessibilityNodeInfoMatcher<String> sTextMatcher =
            (node, text) -> {
                if (node.getText() == null) return false;

                return node.getText().toString().equals(text);
            };

    static AccessibilityNodeInfoMatcher<String> sTextOrContentDescriptionMatcher =
            (node, text) -> {
                // If there is no content description, rely only on text
                if (node.getContentDescription() == null) {
                    return text.equals(node.getText());
                }

                // If there is no text, rely only on content description
                if (node.getText() == null) {
                    return text.equals(node.getContentDescription());
                }

                return text.equals(node.getText()) || text.equals(node.getContentDescription());
            };

    static AccessibilityNodeInfoMatcher<String> sTextVisibleToUserMatcher =
            (node, text) -> {
                if (node.getText() == null) return false;

                return node.getText().toString().equals(text) && node.isVisibleToUser();
            };

    static AccessibilityNodeInfoMatcher<String> sRangeInfoMatcher =
            (node, element) -> node.getRangeInfo() != null;

    static AccessibilityNodeInfoMatcher<String> sViewIdResourceNameMatcher =
            (node, text) -> {
                if (node.getViewIdResourceName() == null) return false;

                return node.getViewIdResourceName().equals(text);
            };

    /**
     * Main AccessibilityDelegate for accessibility content shell tests.
     *
     * The delegate will set values in the |AccessibilityContentShellTestData| singleton based
     * on the event type. The method will always return |false| so that the AccessibilityEvent
     * is not actually sent to AT, which would make the test fail.
     */
    public static View.AccessibilityDelegate sContentShellDelegate =
            new View.AccessibilityDelegate() {
                @Override
                public boolean onRequestSendAccessibilityEvent(
                        ViewGroup host, View child, AccessibilityEvent event) {
                    AccessibilityContentShellTestData data =
                            AccessibilityContentShellTestData.getInstance();

                    // Switch on eventType and save relevant data as needed.
                    switch (event.getEventType()) {
                            // Save the text of proactive announcements.
                        case AccessibilityEvent.TYPE_ANNOUNCEMENT:
                            {
                                data.setAnnouncementText(event.getText().get(0).toString());
                                break;
                            }

                            // Save the traverse and selection indices during text traversal.
                        case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
                            {
                                data.setSelectionFromIndex(event.getFromIndex());
                                data.setSelectionToIndex(event.getToIndex());
                                data.setReceivedSelectionEvent(true);
                                break;
                            }
                        case AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
                            {
                                data.setTraverseFromIndex(event.getFromIndex());
                                data.setTraverseToIndex(event.getToIndex());
                                data.setReceivedTraversalEvent(true);
                                break;
                            }

                            // Save that a particular type of event has been sent.
                        case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
                            {
                                data.setReceivedAccessibilityFocusEvent(true);
                                break;
                            }
                        case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                            {
                                data.incrementWindowContentChangedCount();
                                break;
                            }
                        case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                            {
                                data.setReceivedEvent(true);
                                break;
                            }

                            // Currently unused/ignored for content shell test purposes.
                        case AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT:
                        case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
                        case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
                        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
                        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
                        case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
                        case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
                        case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
                        case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
                        case AccessibilityEvent.TYPE_VIEW_CLICKED:
                        case AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED:
                        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
                        case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
                        case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
                        case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
                        case AccessibilityEvent.TYPE_VIEW_SELECTED:
                        case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                        case AccessibilityEvent.TYPE_WINDOWS_CHANGED:
                        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                            break;
                    }

                    // Return false so that an accessibility event is not actually sent.
                    return false;
                }
            };
}