chromium/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/TileInteractionDelegateTest.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.chrome.browser.suggestions.tile;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.shadows.ShadowLooper;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.preloading.AndroidPrerenderManager;
import org.chromium.chrome.browser.preloading.AndroidPrerenderManagerJni;
import org.chromium.chrome.browser.suggestions.SiteSuggestion;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.url.GURL;

import java.util.concurrent.TimeUnit;

/** Tests for {@link TileInteractionDelegateTest}. */
@RunWith(BaseRobolectricTestRunner.class)
@EnableFeatures(ChromeFeatureList.NEW_TAB_PAGE_ANDROID_TRIGGER_FOR_PRERENDER2)
public class TileInteractionDelegateTest {

    private class TileGroupForTest extends TileGroup {
        private Tile mTile;

        public TileGroupForTest(
                TileRenderer tileRenderer,
                SuggestionsUiDelegate uiDelegate,
                ContextMenuManager contextMenuManager,
                Delegate tileGroupDelegate,
                Observer observer,
                OfflinePageBridge offlinePageBridge) {
            super(
                    tileRenderer,
                    uiDelegate,
                    contextMenuManager,
                    tileGroupDelegate,
                    observer,
                    offlinePageBridge);
        }

        public void setTileForTesting(Tile tile) {
            mTile = tile;
        }

        @Override
        protected @Nullable Tile findTile(SiteSuggestion suggestion) {
            return mTile;
        }
    }

    @Mock Tile mTile;
    @Mock SuggestionsTileView mTileView;
    @Mock SiteSuggestion mData;
    @Mock SuggestionsUiDelegate mSuggestionsUiDelegate;
    @Mock ContextMenuManager mContextMenuManager;
    @Mock TileGroup.Delegate mTileGroupDelegate;
    @Mock OfflinePageBridge mOfflinePageBridge;
    @Mock private Runnable mSnapshotTileGridChangedRunnable;
    @Mock private Runnable mTileCountChangedRunnable;
    @Mock private TileGroup.Observer mTileGroupObserver;
    @Mock private TileRenderer mTileRenderer;
    @Mock private AndroidPrerenderManager mAndroidPrerenderManager;
    @Mock private AndroidPrerenderManager.Natives mNativeMock;

    @Rule public JniMocker jniMocker = new JniMocker();

    @Captor
    ArgumentCaptor<View.OnTouchListener> mOnTouchListenerCaptor =
            ArgumentCaptor.forClass(View.OnTouchListener.class);

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        when(mTile.getUrl()).thenReturn(new GURL("https://example.com"));
        when(mTile.getData()).thenReturn(mData);
        when(mAndroidPrerenderManager.startPrerendering(any())).thenReturn(true);
        jniMocker.mock(AndroidPrerenderManagerJni.TEST_HOOKS, mNativeMock);
    }

    @Test
    public void testTileInteractionDelegateTaken() {
        HistogramWatcher.Builder histogramWatcherBuilder = HistogramWatcher.newBuilder();

        histogramWatcherBuilder.expectIntRecord(
                "Prerender.Experimental.NewTabPage.TouchDuration.Taken", 0);
        histogramWatcherBuilder.expectNoRecords(
                "Prerender.Experimental.NewTabPage.TouchDuration.NotTaken");

        HistogramWatcher histogramWatcher = histogramWatcherBuilder.build();

        TileGroup tileGroup =
                new TileGroup(
                        mTileRenderer,
                        mSuggestionsUiDelegate,
                        mContextMenuManager,
                        mTileGroupDelegate,
                        mTileGroupObserver,
                        mOfflinePageBridge);
        tileGroup.onIconMadeAvailable(new GURL("https://example.com"));
        TileGroup.TileSetupDelegate tileSetupCallback = tileGroup.getTileSetupDelegate();
        TileGroup.TileInteractionDelegate tileInteractionDelegate =
                tileSetupCallback.createInteractionDelegate(mTile, mTileView);

        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
        verify(mTileView).setOnTouchListener(mOnTouchListenerCaptor.capture());
        mOnTouchListenerCaptor.getValue().onTouch(mTileView, event);
        tileInteractionDelegate.onClick(mTileView);

        histogramWatcher.assertExpected();
    }

    @Test
    public void testTileInteractionDelegateNotTaken() {
        HistogramWatcher.Builder histogramWatcherBuilder = HistogramWatcher.newBuilder();

        histogramWatcherBuilder.expectIntRecord(
                "Prerender.Experimental.NewTabPage.TouchDuration.NotTaken", 0);
        histogramWatcherBuilder.expectNoRecords(
                "Prerender.Experimental.NewTabPage.TouchDuration.Taken");

        HistogramWatcher histogramWatcher = histogramWatcherBuilder.build();

        TileGroup tileGroup =
                new TileGroup(
                        mTileRenderer,
                        mSuggestionsUiDelegate,
                        mContextMenuManager,
                        mTileGroupDelegate,
                        mTileGroupObserver,
                        mOfflinePageBridge);
        tileGroup.onIconMadeAvailable(new GURL("https://example.com"));
        TileGroup.TileSetupDelegate tileSetupCallback = tileGroup.getTileSetupDelegate();
        tileSetupCallback.createInteractionDelegate(mTile, mTileView);
        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
        MotionEvent cancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
        verify(mTileView).setOnTouchListener(mOnTouchListenerCaptor.capture());
        mOnTouchListenerCaptor.getValue().onTouch(mTileView, event);
        mOnTouchListenerCaptor.getValue().onTouch(mTileView, cancelEvent);

        histogramWatcher.assertExpected();
    }

    @Test
    public void testTileInteractionTriggerPrerendering() {
        AndroidPrerenderManager.setAndroidPrerenderManagerForTesting(mAndroidPrerenderManager);
        TileGroupForTest tileGroup =
                new TileGroupForTest(
                        mTileRenderer,
                        mSuggestionsUiDelegate,
                        mContextMenuManager,
                        mTileGroupDelegate,
                        mTileGroupObserver,
                        mOfflinePageBridge);
        tileGroup.setTileForTesting(mTile);
        tileGroup.onIconMadeAvailable(new GURL("https://example.com"));
        TileGroup.TileSetupDelegate tileSetupCallback = tileGroup.getTileSetupDelegate();
        tileSetupCallback.createInteractionDelegate(mTile, mTileView);

        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
        MotionEvent cancelEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
        verify(mTileView).setOnTouchListener(mOnTouchListenerCaptor.capture());
        mOnTouchListenerCaptor.getValue().onTouch(mTileView, event);
        ShadowLooper.idleMainLooper(200, TimeUnit.MILLISECONDS);
        Mockito.verify(mAndroidPrerenderManager, Mockito.timeout(1000))
                .startPrerendering(ArgumentMatchers.any());
        // The second onTouch with the same tile should be considered to be duplicate and should be
        // skipped by maybePrerender, and this should not cause any error.
        mOnTouchListenerCaptor.getValue().onTouch(mTileView, event);

        mOnTouchListenerCaptor.getValue().onTouch(mTileView, cancelEvent);
        // mPrerenderStarted in TileInteractionDelegateImpl is true, stopPrerendering should be
        // called.
        Mockito.verify(mAndroidPrerenderManager).stopPrerendering();
        AndroidPrerenderManager.clearAndroidPrerenderManagerForTesting();
    }
}