chromium/content/public/android/junit/src/org/chromium/content/browser/GestureListenerManagerImplUnitTest.java

// Copyright 2022 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;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;

import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ALL_UPDATES;
import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.NONE;
import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ON_SCROLL_END;

import android.view.ViewGroup;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.blink.mojom.EventType;
import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
import org.chromium.content.browser.webcontents.WebContentsImpl;
import org.chromium.content.browser.webcontents.WebContentsImpl.UserDataFactory;
import org.chromium.content_public.browser.ContentFeatureList;
import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.ui.base.ViewAndroidDelegate;

/** Unit test for {@link GestureListenerManagerImpl}. */
@RunWith(BaseRobolectricTestRunner.class)
@DisableFeatures({ContentFeatureList.CONTINUE_GESTURE_ON_LOSING_FOCUS})
@EnableFeatures({ContentFeatureList.HIDE_PASTE_POPUP_ON_GSB})
public class GestureListenerManagerImplUnitTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock WebContentsImpl mWebContents;
    @Mock ViewGroup mViewGroup;
    @Mock GestureListenerManagerImpl.Natives mMockJniGestureListenerManager;
    @Mock GestureStateListener mGestureStateListener;

    private GestureListenerManagerImpl mGestureManager;

    @Before
    public void setup() {
        mJniMocker.mock(GestureListenerManagerImplJni.TEST_HOOKS, mMockJniGestureListenerManager);
        doReturn(1L).when(mMockJniGestureListenerManager).init(any(), any());

        setupMockWebContents();

        mGestureManager = new GestureListenerManagerImpl(mWebContents);
        mGestureManager.addListener(mGestureStateListener, ALL_UPDATES);
    }

    @Test
    public void verticalScrollDirectionChanged() {
        GestureStateListener listener1 = Mockito.mock(GestureStateListener.class);
        GestureStateListener listener2 = Mockito.mock(GestureStateListener.class);

        mGestureManager.addListener(listener1);
        mGestureManager.addListener(listener2);

        // Verify all listener gets the update.
        mGestureManager.onVerticalScrollDirectionChanged(true, 0.1f);
        verify(mGestureStateListener).onVerticalScrollDirectionChanged(true, 0.1f);
        verify(listener1).onVerticalScrollDirectionChanged(true, 0.1f);
        verify(listener2).onVerticalScrollDirectionChanged(true, 0.1f);
    }

    @Test
    public void scrollBegin() {
        // In production the call started from cpp, and passed into Java via JNI. In this test
        // we start the scroll from Java directly.
        mGestureManager.onScrollBegin(true);
        Assert.assertTrue("Scroll should started.", mGestureManager.isScrollInProgress());
        verify(mGestureStateListener).onScrollStarted(anyInt(), anyInt(), eq(true));

        mGestureManager.onEventAck(EventType.GESTURE_SCROLL_UPDATE, /* consumed= */ true);
        verify(mGestureStateListener).onScrollUpdateGestureConsumed();
        Mockito.reset(mGestureStateListener);
        mGestureManager.onEventAck(EventType.GESTURE_SCROLL_UPDATE, /* consumed= */ true);
        verify(mGestureStateListener).onScrollUpdateGestureConsumed();

        mGestureManager.onEventAck(EventType.GESTURE_SCROLL_END, /* consumed= */ true);
        verify(mGestureStateListener).onScrollEnded(anyInt(), anyInt());
    }

    @Test
    public void scrollBeginThenAbort() {
        // In production the call started from cpp, and passed into Java via JNI. In this test
        // we start the scroll from Java directly.
        mGestureManager.onScrollBegin(true);
        Assert.assertTrue("Scroll should started.", mGestureManager.isScrollInProgress());
        verify(mGestureStateListener).onScrollStarted(anyInt(), anyInt(), eq(true));

        mGestureManager.resetPopupsAndInput(false);
        verify(mGestureStateListener).onScrollEnded(anyInt(), anyInt());
    }

    @Test
    public void flingBegin() {
        // In production the call started from cpp, and passed into Java via JNI. In this test
        // we start the scroll from Java directly.
        mGestureManager.onFlingStart(true);
        Assert.assertTrue("Fling should started.", mGestureManager.hasActiveFlingScroll());
        verify(mGestureStateListener).onFlingStartGesture(anyInt(), anyInt(), eq(true));

        mGestureManager.onFlingEnd();
        verify(mGestureStateListener).onFlingEndGesture(anyInt(), anyInt());
    }

    @Test
    public void updateFrequency() {
        // This will be ALL_UPDATES because of the listener we add in setup.
        Assert.assertEquals(
                ALL_UPDATES, mGestureManager.getRootScrollOffsetUpdateFrequencyForTesting());

        // Adding listeners with lower frequency will not change the result.
        mGestureManager.addListener(new GestureStateListener() {}, NONE);
        mGestureManager.addListener(new GestureStateListener() {}, NONE);
        mGestureManager.addListener(new GestureStateListener() {}, ON_SCROLL_END);
        Assert.assertEquals(
                ALL_UPDATES, mGestureManager.getRootScrollOffsetUpdateFrequencyForTesting());

        // Now, remove the ALL_UPDATES listener. This will leave us with ON_SCROLL_END.
        mGestureManager.removeListener(mGestureStateListener);
        Assert.assertEquals(
                ON_SCROLL_END, mGestureManager.getRootScrollOffsetUpdateFrequencyForTesting());
    }

    private void setupMockWebContents() {
        ViewAndroidDelegate viewAndroidDelegate =
                ViewAndroidDelegate.createBasicDelegate(mViewGroup);
        RenderCoordinatesImpl renderCoordinates = new RenderCoordinatesImpl();
        doReturn(viewAndroidDelegate).when(mWebContents).getViewAndroidDelegate();
        doReturn(renderCoordinates).when(mWebContents).getRenderCoordinates();

        // Setup UserData involved in the scrolling process.
        doAnswer(
                        invocation -> {
                            if (invocation.getArgument(0) == SelectionPopupControllerImpl.class) {
                                return SelectionPopupControllerImpl.createForTesting(mWebContents);
                            }
                            UserDataFactory factory = invocation.getArgument(1);
                            return factory.create(mWebContents);
                        })
                .when(mWebContents)
                .getOrSetUserData(/* key= */ any(), /* userDataFactory= */ any());
    }
}