chromium/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorTabObserverTest.java

// Copyright 2014 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 androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import org.chromium.base.ObserverList.RewindableIterator;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.browser.profiles.Profile;
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.TabLaunchType;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tab.TabTestUtils;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.content_public.browser.LoadUrlParams;

import java.util.HashSet;
import java.util.Set;

/** Tests for the TabModelSelectorTabObserver. */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class TabModelSelectorTabObserverTest {
    @Mock private Profile mProfile;
    @Mock private Profile mIncognitoProfile;
    private int mTabId;

    @ClassRule
    public static final TabModelSelectorObserverTestRule sTestRule =
            new TabModelSelectorObserverTestRule();

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        Mockito.when(mIncognitoProfile.isOffTheRecord()).thenReturn(true);
    }

    @Test
    @SmallTest
    public void testAddingTab() {
        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        Tab tab = createTestTab(false);
        assertTabDoesNotHaveObserver(tab, observer, /* checkUnregistration= */ false);
        addTab(sTestRule.getNormalTabModel(), tab);
        assertTabHasObserver(tab, observer);
        destroyObserver(observer);
    }

    @Test
    @SmallTest
    public void testClosingTab() {
        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        Tab tab = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), tab);
        assertTabHasObserver(tab, observer);
        closeTab(sTestRule.getNormalTabModel(), tab);
        assertTabDoesNotHaveObserver(tab, observer, true);
        destroyObserver(observer);
    }

    @Test
    @SmallTest
    public void testRemovingTab() {
        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        Tab tab = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), tab);
        assertTabHasObserver(tab, observer);
        removeTab(sTestRule.getNormalTabModel(), tab);
        assertTabDoesNotHaveObserver(tab, observer, true);
        destroyObserver(observer);
    }

    @Test
    @SmallTest
    public void testPreExistingTabs() {
        Tab normalTab1 = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), normalTab1);
        Tab normalTab2 = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), normalTab2);

        Tab incognitoTab1 = createTestTab(true);
        addTab(sTestRule.getIncognitoTabModel(), incognitoTab1);
        Tab incognitoTab2 = createTestTab(true);
        addTab(sTestRule.getIncognitoTabModel(), incognitoTab2);

        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        assertTabHasObserver(normalTab1, observer);
        assertTabHasObserver(normalTab2, observer);
        assertTabHasObserver(incognitoTab1, observer);
        assertTabHasObserver(incognitoTab2, observer);
        destroyObserver(observer);
    }

    @Test
    @SmallTest
    public void testDestroyRemovesObserver() {
        Tab normalTab1 = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), normalTab1);
        Tab incognitoTab1 = createTestTab(true);
        addTab(sTestRule.getIncognitoTabModel(), incognitoTab1);

        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        assertTabHasObserver(normalTab1, observer);
        assertTabHasObserver(incognitoTab1, observer);

        destroyObserver(observer);
        assertTabDoesNotHaveObserver(normalTab1, observer, true);
        assertTabDoesNotHaveObserver(incognitoTab1, observer, true);
    }

    @Test
    @SmallTest
    public void testObserverAddedBeforeInitialize() {
        TabModelSelectorBase selector =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return new TabModelSelectorBase(null, TabGroupModelFilter::new, false) {
                                @Override
                                public void requestToShowTab(Tab tab, int type) {}

                                @Override
                                public boolean isSessionRestoreInProgress() {
                                    return false;
                                }

                                @Override
                                public Tab openNewTab(
                                        LoadUrlParams loadUrlParams,
                                        @TabLaunchType int type,
                                        Tab parent,
                                        boolean incognito) {
                                    return null;
                                }
                            };
                        });
        TestTabModelSelectorTabObserver observer = createTabModelSelectorTabObserver();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    selector.initialize(
                            sTestRule.getNormalTabModel(), sTestRule.getIncognitoTabModel());
                });

        Tab normalTab1 = createTestTab(false);
        addTab(sTestRule.getNormalTabModel(), normalTab1);
        assertTabHasObserver(normalTab1, observer);

        Tab incognitoTab1 = createTestTab(true);
        addTab(sTestRule.getIncognitoTabModel(), incognitoTab1);
        assertTabHasObserver(incognitoTab1, observer);
        destroyObserver(observer);
    }

    private TestTabModelSelectorTabObserver createTabModelSelectorTabObserver() {
        final TestTabModelSelectorTabObserver observer =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> new TestTabModelSelectorTabObserver(sTestRule.getSelector()));
        // Initially tabs are added in deferred state, wait for this to complete before proceeding
        // to ensure all tabs are registered. In production the observer should only ever be
        // interacted with on the UI thread so this is a non-issue. However, in this test asserts
        // may run on the instrumentation thread.
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            observer.isDeferredInitializationFinishedForTesting(),
                            Matchers.is(true));
                });
        return observer;
    }

    private void destroyObserver(TestTabModelSelectorTabObserver observer) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    observer.destroy();
                });
    }

    private Tab createTestTab(boolean incognito) {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    return new MockTab(
                            Tab.INVALID_TAB_ID, incognito ? mIncognitoProfile : mProfile);
                });
    }

    private static void addTab(TabModel tabModel, Tab tab) {
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        tabModel.addTab(
                                tab,
                                0,
                                TabLaunchType.FROM_LINK,
                                TabCreationState.LIVE_IN_FOREGROUND));
    }

    private static void closeTab(TabModel tabModel, Tab tab) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> tabModel.closeTabs(TabClosureParams.closeTab(tab).allowUndo(false).build()));
    }

    private static void removeTab(TabModel tabModel, Tab tab) {
        ThreadUtils.runOnUiThreadBlocking(() -> tabModel.removeTab(tab));
    }

    private static class TestTabModelSelectorTabObserver extends TabModelSelectorTabObserver {
        public final Set<Tab> mRegisteredTabs = new HashSet<>();
        public final Set<Tab> mUnregisteredTabs = new HashSet<>();

        public TestTabModelSelectorTabObserver(TabModelSelectorBase selector) {
            super(selector);
        }

        @Override
        protected void onTabRegistered(Tab tab) {
            mRegisteredTabs.add(tab);
        }

        @Override
        protected void onTabUnregistered(Tab tab) {
            mUnregisteredTabs.add(tab);
        }

        private boolean isRegisteredTab(Tab tab) {
            return mRegisteredTabs.contains(tab);
        }

        private boolean isUnregisteredTab(Tab tab) {
            return mUnregisteredTabs.contains(tab);
        }
    }

    private void assertTabHasObserver(Tab tab, TestTabModelSelectorTabObserver observer) {
        Assert.assertTrue(tabHasObserver(tab, observer));
        Assert.assertTrue(
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return observer.isRegisteredTab(tab);
                        }));
    }

    private void assertTabDoesNotHaveObserver(
            Tab tab, TestTabModelSelectorTabObserver observer, boolean checkUnregistration) {
        Assert.assertFalse(tabHasObserver(tab, observer));
        if (!checkUnregistration) return;
        Assert.assertTrue(
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return observer.isUnregisteredTab(tab);
                        }));
    }

    private static boolean tabHasObserver(Tab tab, TestTabModelSelectorTabObserver observer) {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    RewindableIterator<TabObserver> tabObservers =
                            TabTestUtils.getTabObservers(tab);
                    tabObservers.rewind();
                    boolean found = false;
                    while (tabObservers.hasNext()) found |= observer.equals(tabObservers.next());
                    return found;
                });
    }
}