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

// Copyright 2020 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 androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.BaseJUnit4ClassRunner;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/** Test suite to ensure that |AccessibilityEventDispatcher| behaves appropriately. */
@RunWith(BaseJUnit4ClassRunner.class)
public class AccessibilityEventDispatcherTest {
    private AccessibilityEventDispatcher mDispatcher;
    private Map<Integer, Integer> mEventDelays = new HashMap<Integer, Integer>();
    private Set<Integer> mViewIndependentEvents = new HashSet<Integer>();

    // Helper member variables for testing.
    private boolean mRunnablePosted;
    private boolean mRunnableRemoved;
    private boolean mEventDispatched;

    /**
     * Test setup, run before each test. Creates a HashMap of eventType's to delay (fake values),
     * and creates an |AccessibilityEventDispatcher| with a Client that tracks method calls.
     */
    @Before
    public void setUp() {
        // Mock two eventTypes to throttle
        mEventDelays.put(2, 10);
        mEventDelays.put(3, 5000);

        // Create a dispatcher, and track which callback methods have been called with booleans
        mDispatcher =
                new AccessibilityEventDispatcher(
                        new AccessibilityEventDispatcher.Client() {
                            @Override
                            public void postRunnable(Runnable toPost, long delayInMilliseconds) {
                                mRunnablePosted = true;
                            }

                            @Override
                            public void removeRunnable(Runnable toRemove) {
                                mRunnableRemoved = true;
                            }

                            @Override
                            public boolean dispatchEvent(int virtualViewId, int eventType) {
                                mEventDispatched = true;
                                return true;
                            }
                        },
                        mEventDelays,
                        mViewIndependentEvents,
                        new HashSet<Integer>(Arrays.asList(1, 2, 3)));

        mRunnablePosted = false;
        mRunnableRemoved = false;
        mEventDispatched = false;
    }

    /** Test enqueue properly ignores events not being throttled and acts like a pass-through. */
    @Test
    @SmallTest
    public void testEnqueue_notThrottle() {
        mDispatcher.enqueueEvent(1, 1);

        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertFalse(mRunnableRemoved);
    }

    /**
     * Test enqueue sends first event of a given type when no previous events of that type have
     * been sent, i.e. we do not throttle the first event.
     */
    @Test
    @SmallTest
    public void testEnqueue_noPreviousEvents() {
        mDispatcher.enqueueEvent(1, 2);

        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);
    }

    /**
     * Test enqueue sends events of a given type when the previous event of that type has been
     * sent longer ago than our delay time.
     *
     * @throws InterruptedException        Thread.sleep()
     */
    @Test
    @SmallTest
    public void testEnqueue_noRecentPreviousEvents() throws InterruptedException {
        // Send first event through as normal
        mDispatcher.enqueueEvent(1, 2);
        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);

        // Reset trackers
        mRunnablePosted = false;
        mRunnableRemoved = false;
        mEventDispatched = false;

        // Wait 5 seconds so next event won't be throttled
        Thread.sleep(5000);

        // Send another event and it should pass through as normal (we waited longer than delay)
        mDispatcher.enqueueEvent(1, 2);
        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);
    }

    /**
     * Test enqueue posts a |Runnable| for events of a given type that come in quick succession,
     * i.e. we are throttling events and queueing them for later sending.
     */
    @Test
    @SmallTest
    public void testEnqueue_recentEventsInQueue() {
        // Send first event through as normal
        mDispatcher.enqueueEvent(1, 3);
        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);

        // Send a series of more events rapidly
        mDispatcher.enqueueEvent(1, 3);
        mDispatcher.enqueueEvent(1, 3);
        mDispatcher.enqueueEvent(1, 3);
        Assert.assertTrue(mRunnablePosted);

        // Reset trackers
        mRunnablePosted = false;
        mRunnableRemoved = false;
        mEventDispatched = false;

        // Send final event, ensure runnable is posted but nothing was dispatched
        mDispatcher.enqueueEvent(1, 3);
        Assert.assertFalse(mEventDispatched);
        Assert.assertTrue(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);
    }

    /** Test enqueue will drop events that are not part of the relevant events type set. */
    @Test
    @SmallTest
    public void testEnqueue_relevantEventsCheck() {
        // Create a set of relevant events and pass it to the dispatcher.
        Set<Integer> relevantEvents = new HashSet<Integer>();
        relevantEvents.add(3);
        mDispatcher.updateRelevantEventTypes(relevantEvents);

        // Send a relevant event type and ensure it is dispatched.
        mDispatcher.enqueueEvent(1, 3);
        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);

        // Reset trackers.
        mRunnablePosted = false;
        mRunnableRemoved = false;
        mEventDispatched = false;

        // Send a not relevant event type and ensure it is dropped.
        mDispatcher.enqueueEvent(1, 2);
        Assert.assertFalse(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertFalse(mRunnableRemoved);
    }

    /** Test the creation of uuid for view independent throttling. */
    @Test
    @SmallTest
    public void testUuid_creation() {
        // Add a view independent event type.
        mViewIndependentEvents.add(3);

        // Send first event through as normal.
        mDispatcher.enqueueEvent(1, 3);
        Assert.assertTrue(mEventDispatched);
        Assert.assertFalse(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);

        // Send a series of events from various views of the same type.
        mDispatcher.enqueueEvent(2, 3);
        mDispatcher.enqueueEvent(3, 3);
        mDispatcher.enqueueEvent(4, 3);
        Assert.assertTrue(mRunnablePosted);

        // Reset trackers.
        mRunnablePosted = false;
        mRunnableRemoved = false;
        mEventDispatched = false;

        // Send final event, ensure runnable is posted but nothing was dispatched.
        mDispatcher.enqueueEvent(5, 3);
        Assert.assertFalse(mEventDispatched);
        Assert.assertTrue(mRunnablePosted);
        Assert.assertTrue(mRunnableRemoved);
    }
}