chromium/chrome/android/junit/src/org/chromium/chrome/browser/tabmodel/ArchivedTabModelSelectorImplTest.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.tabmodel;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;

import org.chromium.base.Callback;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.flags.ActivityType;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileProvider;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabCreationState;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.test.util.browser.tabmodel.MockTabCreatorManager;
import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel;
import org.chromium.ui.base.WindowAndroid;

import java.lang.ref.WeakReference;

/** Unit tests for {@link TabModelSelectorImpl}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ArchivedTabModelSelectorImplTest {
    // Test activity type that does not restore tab on cold restart.
    // Any type other than ActivityType.TABBED works.
    private static final @ActivityType int NO_RESTORE_TYPE = ActivityType.CUSTOM_TAB;

    @Mock private TabContentManager mMockTabContentManager;
    @Mock private TabDelegateFactory mTabDelegateFactory;
    @Mock private NextTabPolicySupplier mNextTabPolicySupplier;

    @Mock
    private IncognitoTabModelObserver.IncognitoReauthDialogDelegate
            mIncognitoReauthDialogDelegateMock;

    @Mock private Callback<TabModel> mTabModelSupplierObserverMock;
    @Mock private Callback<Tab> mTabSupplierObserverMock;
    @Mock private Callback<Integer> mTabCountSupplierObserverMock;
    @Mock private TabModelSelectorObserver mTabModelSelectorObserverMock;
    @Mock private ProfileProvider mProfileProvider;
    @Mock private Profile mProfile;
    @Mock private Profile mIncognitoProfile;
    @Mock private Context mContext;

    private ArchivedTabModelSelectorImpl mTabModelSelector;
    private MockTabCreatorManager mTabCreatorManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        doReturn(true).when(mIncognitoProfile).isOffTheRecord();
        mTabCreatorManager = new MockTabCreatorManager();

        AsyncTabParamsManager realAsyncTabParamsManager =
                AsyncTabParamsManagerFactory.createAsyncTabParamsManager();
        mTabModelSelector =
                new ArchivedTabModelSelectorImpl(
                        mProfile,
                        mTabCreatorManager,
                        (tabModel) -> new TabGroupModelFilter(tabModel),
                        mNextTabPolicySupplier,
                        realAsyncTabParamsManager);
        assertTrue(currentTabModelSupplierHasObservers());
        assertNull(mTabModelSelector.getCurrentTabModelSupplier().get());
        assertNull(mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter());

        mTabCreatorManager.initialize(mTabModelSelector);
        mTabModelSelector.onNativeLibraryReadyInternal(
                mMockTabContentManager,
                new MockTabModel(mProfile, null),
                new MockTabModel(mIncognitoProfile, null));

        assertEquals(
                mTabModelSelector.getModel(/* isIncognito= */ false),
                mTabModelSelector.getCurrentTabModelSupplier().get());
        assertEquals(
                mTabModelSelector.getCurrentModel(),
                mTabModelSelector.getCurrentTabModelSupplier().get());
        assertEquals(
                mTabModelSelector.getCurrentModel(),
                mTabModelSelector
                        .getTabModelFilterProvider()
                        .getCurrentTabModelFilter()
                        .getTabModel());
    }

    @After
    public void tearDown() {
        mTabModelSelector.destroy();
        assertFalse(currentTabModelSupplierHasObservers());
    }

    @Test
    public void testCurrentTabSupplier() {
        mTabModelSelector.getCurrentTabSupplier().addObserver(mTabSupplierObserverMock);
        assertNull(mTabModelSelector.getCurrentTabSupplier().get());

        MockTab normalTab = new MockTab(1, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(
                        normalTab,
                        0,
                        TabLaunchType.FROM_CHROME_UI,
                        TabCreationState.LIVE_IN_FOREGROUND);
        mTabModelSelector.getModel(false).setIndex(0, TabSelectionType.FROM_USER);
        assertEquals(normalTab, mTabModelSelector.getModel(false).getCurrentTabSupplier().get());
        assertEquals(normalTab, mTabModelSelector.getCurrentTabSupplier().get());
        assertEquals(
                mTabModelSelector.getModel(false),
                mTabModelSelector
                        .getTabModelFilterProvider()
                        .getCurrentTabModelFilter()
                        .getTabModel());
        ShadowLooper.runUiThreadTasks();
        verify(mTabSupplierObserverMock).onResult(eq(normalTab));
        mTabModelSelector.getCurrentTabSupplier().removeObserver(mTabSupplierObserverMock);
    }

    @Test
    public void testCurrentModelTabCountSupplier() {
        mTabModelSelector
                .getCurrentModelTabCountSupplier()
                .addObserver(mTabCountSupplierObserverMock);
        assertEquals(0, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
        ShadowLooper.runUiThreadTasks();
        verify(mTabCountSupplierObserverMock).onResult(0);

        MockTab normalTab1 = new MockTab(1, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(
                        normalTab1,
                        0,
                        TabLaunchType.FROM_CHROME_UI,
                        TabCreationState.LIVE_IN_FOREGROUND);
        ShadowLooper.runUiThreadTasks();
        verify(mTabCountSupplierObserverMock).onResult(1);
        assertEquals(1, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());

        MockTab normalTab2 = new MockTab(2, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(
                        normalTab2,
                        0,
                        TabLaunchType.FROM_CHROME_UI,
                        TabCreationState.LIVE_IN_FOREGROUND);
        ShadowLooper.runUiThreadTasks();
        verify(mTabCountSupplierObserverMock).onResult(2);
        assertEquals(2, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());

        mTabModelSelector.getModel(false).removeTab(normalTab1);
        mTabModelSelector.getModel(false).removeTab(normalTab2);
        ShadowLooper.runUiThreadTasks();
        assertEquals(0, mTabModelSelector.getCurrentModelTabCountSupplier().get().intValue());
        verify(mTabCountSupplierObserverMock, times(2)).onResult(0);

        mTabModelSelector
                .getCurrentModelTabCountSupplier()
                .removeObserver(mTabCountSupplierObserverMock);
    }

    @Test
    public void testTabActivityAttachmentChanged_detaching() {
        MockTab tab = new MockTab(1, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
        tab.updateAttachment(null, null);

        Assert.assertEquals(
                "detaching a tab should result in it being removed from the model",
                0,
                mTabModelSelector.getModel(false).getCount());
    }

    @Test
    public void testTabActivityAttachmentChanged_movingWindows() {
        MockTab tab = new MockTab(1, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);
        WindowAndroid window = mock(WindowAndroid.class);
        WeakReference<Context> weakContext = new WeakReference<>(mContext);
        when(window.getContext()).thenReturn(weakContext);
        tab.updateAttachment(window, mTabDelegateFactory);

        Assert.assertEquals(
                "moving a tab between windows shouldn't remove it from the model",
                1,
                mTabModelSelector.getModel(false).getCount());
    }

    @Test
    public void testTabActivityAttachmentChanged_detachingWhileReparentingInProgress() {
        MockTab tab = new MockTab(1, mProfile);
        mTabModelSelector
                .getModel(false)
                .addTab(tab, 0, TabLaunchType.FROM_CHROME_UI, TabCreationState.LIVE_IN_FOREGROUND);

        mTabModelSelector.enterReparentingMode();
        tab.updateAttachment(null, null);

        Assert.assertEquals(
                "tab shouldn't be removed while reparenting is in progress",
                1,
                mTabModelSelector.getModel(false).getCount());
    }

    private boolean currentTabModelSupplierHasObservers() {
        return ((ObservableSupplierImpl<?>) mTabModelSelector.getCurrentTabModelSupplier())
                .hasObservers();
    }
}