chromium/chrome/android/java/src/org/chromium/chrome/browser/hub/HubProviderUnitTest.java

// Copyright 2023 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.hub;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;

import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.shadows.ShadowLooper;

import org.chromium.base.Callback;
import org.chromium.base.supplier.LazyOneshotSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.profiles.ProfileProvider;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
import org.chromium.ui.base.TestActivity;

/** Unit tests for {@link HubProvider}. */
@RunWith(BaseRobolectricTestRunner.class)
public class HubProviderUnitTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Rule
    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
            new ActivityScenarioRule<>(TestActivity.class);

    private final ObservableSupplierImpl<Integer> mTabCountSupplier =
            new ObservableSupplierImpl<>();
    private final ObservableSupplierImpl<Tab> mTabSupplierMock = new ObservableSupplierImpl<>();
    private final ObservableSupplierImpl<TabModel> mTabModelSupplier =
            new ObservableSupplierImpl<>();
    private final ObservableSupplierImpl<DisplayButtonData> mReferenceButtonDataSupplier =
            new ObservableSupplierImpl<>();
    private final OneshotSupplierImpl<ProfileProvider> mProfileProviderSupplier =
            new OneshotSupplierImpl<>();

    @Mock private Callback<HubManager> mHubManagerCallback;
    @Mock private DisplayButtonData mReferenceButtonData;
    @Mock private TabModel mTabModel;
    @Mock private TabModelSelector mTabModelSelector;
    @Mock private Pane mMockTabSwitcherPane;
    @Mock private Pane mMockIncognitoTabSwitcherPane;
    @Mock private Pane mMockBookmarksPane;
    @Mock private BackPressManager mBackPressManagerMock;
    @Mock private MenuOrKeyboardActionController mMenuOrKeyboardActionController;
    @Mock private SnackbarManager mSnackbarManager;
    @Mock private MenuButtonCoordinator mMenuButtonCoordinator;

    private Activity mActivity;
    private HubProvider mHubProvider;

    @Before
    public void setUp() {
        when(mTabModelSelector.getModel(false)).thenReturn(mTabModel);
        when(mTabModelSelector.getModel(true)).thenReturn(mTabModel);
        when(mTabModelSelector.getCurrentTabModelSupplier()).thenReturn(mTabModelSupplier);
        mTabModelSupplier.set(mTabModel);

        mReferenceButtonDataSupplier.set(mReferenceButtonData);
        when(mMockTabSwitcherPane.getPaneId()).thenReturn(PaneId.TAB_SWITCHER);
        when(mMockTabSwitcherPane.getReferenceButtonDataSupplier())
                .thenReturn(mReferenceButtonDataSupplier);
        when(mMockIncognitoTabSwitcherPane.getPaneId()).thenReturn(PaneId.INCOGNITO_TAB_SWITCHER);
        when(mMockIncognitoTabSwitcherPane.getReferenceButtonDataSupplier())
                .thenReturn(mReferenceButtonDataSupplier);
        when(mMockBookmarksPane.getPaneId()).thenReturn(PaneId.BOOKMARKS);
        when(mMockBookmarksPane.getReferenceButtonDataSupplier())
                .thenReturn(mReferenceButtonDataSupplier);

        when(mTabModelSelector.getCurrentTabSupplier()).thenReturn(mTabSupplierMock);
        when(mTabModelSelector.getCurrentModelTabCountSupplier()).thenReturn(mTabCountSupplier);
        mTabCountSupplier.set(0);
        mActivityScenarioRule.getScenario().onActivity(this::onActivity);
    }

    private void onActivity(Activity activity) {
        mActivity = activity;

        mHubProvider =
                new HubProvider(
                        mActivity,
                        mProfileProviderSupplier,
                        new DefaultPaneOrderController(),
                        mBackPressManagerMock,
                        mMenuOrKeyboardActionController,
                        () -> mSnackbarManager,
                        () -> mTabModelSelector,
                        () -> mMenuButtonCoordinator);
    }

    @Test
    @SmallTest
    public void testHubProvider() {
        PaneListBuilder builder = mHubProvider.getPaneListBuilder();

        var hubManagerSupplier = mHubProvider.getHubManagerSupplier();
        assertNotNull(hubManagerSupplier);
        assertFalse(hubManagerSupplier.hasValue());
        hubManagerSupplier.onAvailable(mHubManagerCallback);

        builder.registerPane(
                PaneId.TAB_SWITCHER, LazyOneshotSupplier.fromValue(mMockTabSwitcherPane));
        builder.registerPane(
                PaneId.INCOGNITO_TAB_SWITCHER,
                LazyOneshotSupplier.fromValue(mMockIncognitoTabSwitcherPane));
        builder.registerPane(PaneId.BOOKMARKS, LazyOneshotSupplier.fromValue(mMockBookmarksPane));
        assertFalse(builder.isBuilt());

        HubManager hubManager = hubManagerSupplier.get();
        assertNotNull(hubManager);
        assertTrue(hubManagerSupplier.hasValue());
        assertTrue(builder.isBuilt());

        PaneManager paneManager = hubManager.getPaneManager();
        assertNotNull(paneManager);

        ShadowLooper.runUiThreadTasks();
        when(mTabModelSelector.isIncognitoSelected()).thenReturn(false);

        paneManager.focusPane(PaneId.TAB_SWITCHER);
        assertEquals(mMockTabSwitcherPane, paneManager.getFocusedPaneSupplier().get());
        verify(mTabModelSelector, never()).commitAllTabClosures();
        verify(mTabModelSelector, never()).selectModel(anyBoolean());

        paneManager.focusPane(PaneId.BOOKMARKS);
        assertEquals(mMockBookmarksPane, paneManager.getFocusedPaneSupplier().get());
        verify(mTabModelSelector, never()).commitAllTabClosures();
        verify(mTabModelSelector, never()).selectModel(anyBoolean());

        HistogramWatcher watcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Android.TabSwitcher.IncognitoClickedIsEmpty", true);
        paneManager.focusPane(PaneId.INCOGNITO_TAB_SWITCHER);
        assertEquals(mMockIncognitoTabSwitcherPane, paneManager.getFocusedPaneSupplier().get());
        verify(mTabModelSelector).commitAllTabClosures();
        verify(mTabModelSelector).selectModel(true);
        watcher.assertExpected();

        when(mTabModelSelector.isIncognitoSelected()).thenReturn(true);

        paneManager.focusPane(PaneId.TAB_SWITCHER);
        assertEquals(mMockTabSwitcherPane, paneManager.getFocusedPaneSupplier().get());
        verify(mTabModelSelector, times(2)).commitAllTabClosures();
        verify(mTabModelSelector).selectModel(false);

        verify(mHubManagerCallback).bind(any());

        mHubProvider.destroy();
    }

    @Test
    @SmallTest
    public void testHubProviderDestroyBeforeOnAvailable() {
        PaneListBuilder builder = mHubProvider.getPaneListBuilder();

        var hubManagerSupplier = mHubProvider.getHubManagerSupplier();
        assertNotNull(hubManagerSupplier);
        assertFalse(hubManagerSupplier.hasValue());

        builder.registerPane(
                PaneId.TAB_SWITCHER, LazyOneshotSupplier.fromValue(mMockTabSwitcherPane));
        builder.registerPane(
                PaneId.INCOGNITO_TAB_SWITCHER,
                LazyOneshotSupplier.fromValue(mMockIncognitoTabSwitcherPane));
        builder.registerPane(PaneId.BOOKMARKS, LazyOneshotSupplier.fromValue(mMockBookmarksPane));
        assertFalse(builder.isBuilt());

        HubManager hubManager = hubManagerSupplier.get();
        assertNotNull(hubManager);
        assertTrue(hubManagerSupplier.hasValue());
        assertTrue(builder.isBuilt());

        PaneManager paneManager = hubManager.getPaneManager();
        assertNotNull(paneManager);

        // This shouldn't crash.
        mHubProvider.destroy();

        ShadowLooper.runUiThreadTasks();
    }

    @Test
    @SmallTest
    public void testHubProviderDestroyBeforeCreation() {
        PaneListBuilder builder = mHubProvider.getPaneListBuilder();

        var hubManagerSupplier = mHubProvider.getHubManagerSupplier();
        assertNotNull(hubManagerSupplier);
        assertFalse(hubManagerSupplier.hasValue());
        hubManagerSupplier.onAvailable(mHubManagerCallback);

        builder.registerPane(
                PaneId.TAB_SWITCHER, LazyOneshotSupplier.fromValue(mMockTabSwitcherPane));
        builder.registerPane(
                PaneId.INCOGNITO_TAB_SWITCHER,
                LazyOneshotSupplier.fromValue(mMockIncognitoTabSwitcherPane));
        builder.registerPane(PaneId.BOOKMARKS, LazyOneshotSupplier.fromValue(mMockBookmarksPane));
        assertFalse(builder.isBuilt());

        assertFalse(hubManagerSupplier.hasValue());
        mHubProvider.destroy();
        ShadowLooper.runUiThreadTasks();

        assertFalse(hubManagerSupplier.hasValue());
        verify(mHubManagerCallback, never()).onResult(any());
        verify(mHubManagerCallback, never()).bind(any());
    }
}