// 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.tasks.tab_management;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.widget.FrameLayout;
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.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.chromium.base.BaseSwitches;
import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.back_press.BackPressManager;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.data_sharing.DataSharingTabManager;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.hub.PaneHubController;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.LifecycleObserver;
import org.chromium.chrome.browser.lifecycle.NativeInitObserver;
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileProvider;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilter;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modaldialog.ModalDialogManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** Unit tests for {@link TabSwitcherPaneCoordinatorFactory}. */
@RunWith(BaseRobolectricTestRunner.class)
public class TabSwitcherPaneCoordinatorFactoryUnitTest {
private static final int TAB1_ID = 456;
private static final int TAB2_ID = 789;
private static final String TAB1_TITLE = "Hello world";
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
new ActivityScenarioRule<>(TestActivity.class);
private final OneshotSupplierImpl<ProfileProvider> mProfileProviderSupplier =
new OneshotSupplierImpl<>();
private final ObservableSupplierImpl<Boolean> mIsVisibleSupplier =
new ObservableSupplierImpl<>(true);
private final ObservableSupplierImpl<Boolean> mIsAnimatingSupplier =
new ObservableSupplierImpl<>(false);
private final ObservableSupplierImpl<PaneHubController> mPaneHubControllerSupplier =
new ObservableSupplierImpl<>();
@Mock private ActivityLifecycleDispatcher mLifecycleDispatcher;
@Mock private TabModelSelector mTabModelSelector;
@Mock private TabModelFilterProvider mTabModelFilterProvider;
@Mock private TabGroupModelFilter mTabModelFilter;
@Mock private TabModel mTabModel;
@Mock private TabContentManager mTabContentManager;
@Mock private TabCreatorManager mTabCreatorManager;
@Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
@Mock private MultiWindowModeStateDispatcher mMultiWindowModeStateDispatcher;
@Mock private ScrimCoordinator mScrimCoordinator;
@Mock private SnackbarManager mSnackbarManager;
@Mock private ModalDialogManager mModalDialogManager;
@Mock private TabSwitcherResetHandler mResetHandler;
@Mock private Callback<Integer> mOnTabClickedCallback;
@Mock private Callback<Boolean> mHairlineVisibilityCallback;
@Mock private BottomSheetController mBottomSheetController;
@Mock private DataSharingTabManager mDataSharingTabManager;
@Mock private ProfileProvider mProfileProvider;
@Mock private Profile mProfile;
@Mock private Tracker mTracker;
@Mock private BackPressManager mBackpressManager;
@Captor private ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor;
@Captor private ArgumentCaptor<LifecycleObserver> mLifecycleObserverCaptor;
private Tab mTab1;
private Activity mActivity;
private FrameLayout mParentView;
private TabSwitcherPaneCoordinatorFactory mFactory;
private ObservableSupplierImpl<TabModelFilter> mTabModelFilterSupplier =
new ObservableSupplierImpl<>();
@Before
public void setUp() {
PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
TrackerFactory.setTrackerForTests(mTracker);
mTab1 = TabUiUnitTestUtils.prepareTab(TAB1_ID, TAB1_TITLE);
mProfileProviderSupplier.set(mProfileProvider);
when(mProfile.isOffTheRecord()).thenReturn(false);
when(mProfileProvider.getOriginalProfile()).thenReturn(mProfile);
when(mTabModelSelector.getTabModelFilterProvider()).thenReturn(mTabModelFilterProvider);
when(mTabModelSelector.getModel(false)).thenReturn(mTabModel);
when(mTabModelSelector.getModels()).thenReturn(Arrays.asList(mTabModel));
when(mTabModelFilter.getTabModel()).thenReturn(mTabModel);
when(mTabModelFilterProvider.getTabModelFilter(false)).thenReturn(mTabModelFilter);
mTabModelFilterSupplier.set(mTabModelFilter);
when(mTabModelFilterProvider.getCurrentTabModelFilterSupplier())
.thenReturn(mTabModelFilterSupplier);
when(mTabModelFilter.getTabModel()).thenReturn(mTabModel);
when(mTabModel.getCount()).thenReturn(1);
when(mTabModel.getTabAt(0)).thenReturn(mTab1);
when(mTabModel.getTabById(TAB1_ID)).thenReturn(mTab1);
when(mTabModel.getProfile()).thenReturn(mProfile);
mActivityScenarioRule.getScenario().onActivity(this::onActivityReady);
}
private void onActivityReady(Activity activity) {
mActivity = activity;
mParentView = new FrameLayout(activity);
mFactory =
new TabSwitcherPaneCoordinatorFactory(
activity,
mLifecycleDispatcher,
mProfileProviderSupplier,
mTabModelSelector,
mTabContentManager,
mTabCreatorManager,
mBrowserControlsStateProvider,
mMultiWindowModeStateDispatcher,
mScrimCoordinator,
mSnackbarManager,
mModalDialogManager,
mBottomSheetController,
mDataSharingTabManager,
mBackpressManager);
}
@Test
@SmallTest
public void testCreate_NativeAlreadyInitialized() {
when(mLifecycleDispatcher.isNativeInitializationFinished()).thenReturn(true);
TabSwitcherPaneCoordinator coordinator =
mFactory.create(
mParentView,
mResetHandler,
mIsVisibleSupplier,
mIsAnimatingSupplier,
mOnTabClickedCallback,
mHairlineVisibilityCallback,
/* isIncognito= */ false,
/* onTabGroupCreation= */ null);
assertNotNull(coordinator);
TabSwitcherMessageManager messageManager = mFactory.getMessageManagerForTesting();
assertNotNull(messageManager);
assertTrue(messageManager.isNativeInitializedForTesting());
coordinator.destroy();
assertNull(mFactory.getMessageManagerForTesting());
}
@Test
@SmallTest
public void testCreateTwoCoordinators_NativeAlreadyInitialized() {
when(mLifecycleDispatcher.isNativeInitializationFinished()).thenReturn(true);
TabSwitcherPaneCoordinator coordinator1 =
mFactory.create(
mParentView,
mResetHandler,
mIsVisibleSupplier,
mIsAnimatingSupplier,
mOnTabClickedCallback,
mHairlineVisibilityCallback,
/* isIncognito= */ false,
/* onTabGroupCreation= */ null);
assertNotNull(coordinator1);
TabSwitcherMessageManager messageManager = mFactory.getMessageManagerForTesting();
assertNotNull(messageManager);
assertTrue(messageManager.isNativeInitializedForTesting());
TabSwitcherPaneCoordinator coordinator2 =
mFactory.create(
mParentView,
mResetHandler,
mIsVisibleSupplier,
mIsAnimatingSupplier,
mOnTabClickedCallback,
mHairlineVisibilityCallback,
/* isIncognito= */ false,
/* onTabGroupCreation= */ null);
assertNotNull(coordinator2);
assertEquals(messageManager, mFactory.getMessageManagerForTesting());
coordinator1.destroy();
assertNotNull(mFactory.getMessageManagerForTesting());
coordinator2.destroy();
assertNull(mFactory.getMessageManagerForTesting());
}
@Test
@SmallTest
public void testCreate_NativeNotInitialized() {
when(mLifecycleDispatcher.isNativeInitializationFinished()).thenReturn(false);
TabSwitcherPaneCoordinator coordinator =
mFactory.create(
mParentView,
mResetHandler,
mIsVisibleSupplier,
mIsAnimatingSupplier,
mOnTabClickedCallback,
mHairlineVisibilityCallback,
/* isIncognito= */ false,
/* onTabGroupCreation= */ null);
assertNotNull(coordinator);
TabSwitcherMessageManager messageManager = mFactory.getMessageManagerForTesting();
assertNotNull(messageManager);
assertFalse(messageManager.isNativeInitializedForTesting());
verify(mLifecycleDispatcher).register(mLifecycleObserverCaptor.capture());
((NativeInitObserver) mLifecycleObserverCaptor.getValue()).onFinishNativeInitialization();
verify(mLifecycleDispatcher).unregister(mLifecycleObserverCaptor.getValue());
assertTrue(messageManager.isNativeInitializedForTesting());
coordinator.destroy();
assertNull(mFactory.getMessageManagerForTesting());
}
@Test
@SmallTest
@CommandLineFlags.Add({BaseSwitches.DISABLE_LOW_END_DEVICE_MODE})
public void testTabListMode_HighEnd() {
assertEquals(TabListMode.GRID, mFactory.getTabListMode());
}
@Test
@SmallTest
@CommandLineFlags.Add({BaseSwitches.ENABLE_LOW_END_DEVICE_MODE})
public void testTabListMode_LowEnd() {
assertEquals(TabListMode.LIST, mFactory.getTabListMode());
}
@Test
@SmallTest
public void testCreateScrimCoordinatorForTablet() {
assertNotNull(TabSwitcherPaneCoordinatorFactory.createScrimCoordinatorForTablet(mActivity));
}
@Test
@SmallTest
public void testCreateTabModelFilterSupplier_AlreadyCreated() {
when(mTabModelSelector.getModels()).thenReturn(List.of(mTabModel));
var supplier = mFactory.createTabModelFilterSupplier(false);
verify(mTabModelSelector, never()).addObserver(any());
assertEquals(mTabModelFilter, supplier.get());
}
@Test
@SmallTest
public void testCreateTabModelFilterSupplier_WaitForChange() {
when(mTabModelSelector.getModels()).thenReturn(Collections.emptyList());
var supplier = mFactory.createTabModelFilterSupplier(false);
verify(mTabModelSelector).addObserver(mTabModelSelectorObserverCaptor.capture());
assertNull(supplier.get());
TabModelSelectorObserver observer = mTabModelSelectorObserverCaptor.getValue();
when(mTabModelSelector.getModels()).thenReturn(List.of(mTabModel));
observer.onChange();
verify(mTabModelSelector).removeObserver(observer);
assertEquals(mTabModelFilter, supplier.get());
}
}