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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

import static org.chromium.chrome.browser.quick_delete.QuickDeleteTabsFilter.FIFTEEN_MINUTES_IN_MS;
import static org.chromium.chrome.browser.quick_delete.QuickDeleteTabsFilter.FOUR_WEEKS_IN_MS;
import static org.chromium.chrome.browser.quick_delete.QuickDeleteTabsFilter.ONE_WEEK_IN_MS;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.browsing_data.TimePeriod;
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.tabmodel.TabClosureParams;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;

import java.util.ArrayList;
import java.util.List;

/** Robolectric tests for {@link QuickDeleteTabsFilter}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(LooperMode.Mode.PAUSED)
public class QuickDeleteTabsFilterTest {
    private static final long INITIAL_TIME_IN_MS = 1000;

    private QuickDeleteTabsFilter mQuickDeleteTabsFilter;
    private final List<MockTab> mMockTabList = new ArrayList<>();

    @Mock private TabGroupModelFilter mTabGroupModelFilterMock;
    @Mock private TabModel mTabModelMock;
    @Mock private Profile mProfileMock;

    private void createTabsAndUpdateTabModel(int countOfTabs) {
        // Create tabs.
        for (int id = 0; id < countOfTabs; id++) {
            MockTab mockTab = new MockTab(id, mProfileMock);
            mockTab.setRootId(id);
            mMockTabList.add(mockTab);
        }
        // Update the tab model.
        doReturn(countOfTabs).when(mTabModelMock).getCount();
        for (int i = 0; i < countOfTabs; i++) {
            when(mTabModelMock.getTabAt(i)).thenReturn(mMockTabList.get(i));
        }
    }

    @Before
    public void setUp() {
        initMocks(this);
        doReturn(false).when(mTabGroupModelFilterMock).isIncognito();
        doReturn(false).when(mTabModelMock).isIncognito();
        doReturn(mTabModelMock).when(mTabGroupModelFilterMock).getTabModel();
        mQuickDeleteTabsFilter = new QuickDeleteTabsFilter(mTabGroupModelFilterMock);

        doReturn(true).when(mTabGroupModelFilterMock).closeTabs(any());
    }

    @Test(expected = AssertionError.class)
    @SmallTest
    public void testIncognitoTabModel_ThrowsAssertionError() {
        doReturn(true).when(mTabGroupModelFilterMock).isIncognito();
        mQuickDeleteTabsFilter = new QuickDeleteTabsFilter(mTabGroupModelFilterMock);
    }

    @Test
    @SmallTest
    public void testAddOneTabOutside15MinutesRange() {
        createTabsAndUpdateTabModel(1);
        mMockTabList.get(0).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS);

        // 15 minutes passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.LAST_15_MINUTES);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertTrue(filteredTabs.isEmpty());
    }

    @Test
    @SmallTest
    public void testAddOneTabWithin15MinutesRange() {
        createTabsAndUpdateTabModel(1);
        mMockTabList.get(0).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS + 10);

        // 15 minutes passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.LAST_15_MINUTES);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertEquals(1, filteredTabs.size());
        assertEquals(mMockTabList.get(0), filteredTabs.get(0));
    }

    @Test
    @SmallTest
    public void testCustomTab_NotConsideredInFlow() {
        createTabsAndUpdateTabModel(1);
        // Mock the tab as custom tab.
        mMockTabList.get(0).setIsCustomTab(true);
        // Make the custom tab in the right period for consideration for quick delete.
        mMockTabList.get(0).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS + 10);

        // 15 minutes passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.LAST_15_MINUTES);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertTrue(filteredTabs.isEmpty());
    }

    @Test
    @SmallTest
    public void testAddOneTabWithinAndOneOutside15MinutesRange() {
        createTabsAndUpdateTabModel(2);

        // Tab_0: Outside 15 minutes range.
        mMockTabList.get(0).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS);

        // 15 minutes passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        // Tab_1: Within 15 minutes range.
        mMockTabList
                .get(1)
                .setLastNavigationCommittedTimestampMillis(
                        INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.LAST_15_MINUTES);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertEquals(1, filteredTabs.size());
        assertEquals(mMockTabList.get(1), filteredTabs.get(0));
    }

    @Test
    @SmallTest
    public void testClosureOfFilteredTabs_ClosesTabsFromTabModel() {
        // Test close tabs behaviour.
        createTabsAndUpdateTabModel(2);

        // Tab 1
        mMockTabList.get(0).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS + 10);
        // Tab 2
        mMockTabList.get(1).setLastNavigationCommittedTimestampMillis(INITIAL_TIME_IN_MS + 20);

        // 15 minutes passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(INITIAL_TIME_IN_MS + FIFTEEN_MINUTES_IN_MS);

        // Initiate quick delete tabs closure.
        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.LAST_15_MINUTES);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();

        mQuickDeleteTabsFilter.closeTabsFilteredForQuickDelete();
        TabClosureParams params =
                TabClosureParams.closeTabs(filteredTabs)
                        .allowUndo(false)
                        .saveToTabRestoreService(false)
                        .build();
        verify(mTabGroupModelFilterMock).closeTabs(params);
    }

    @Test(expected = IllegalStateException.class)
    @SmallTest
    public void testUnsupportedTimeRange_ThrowsException() {
        createTabsAndUpdateTabModel(1);
        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.OLDER_THAN_30_DAYS);
    }

    @Test
    @SmallTest
    public void testClosureOfFilteredTabs_ClosesTabsFromTabModel_AllTime() {
        final int countOfTabs = 5;
        createTabsAndUpdateTabModel(countOfTabs);

        // Set these mock tabs 1 week apart, total of 5 weeks.
        for (int i = 0; i < countOfTabs; ++i) {
            mMockTabList
                    .get(i)
                    .setLastNavigationCommittedTimestampMillis(
                            INITIAL_TIME_IN_MS + ONE_WEEK_IN_MS * (i + 1));
        }

        // 5 weeks passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(
                INITIAL_TIME_IN_MS + FOUR_WEEKS_IN_MS + ONE_WEEK_IN_MS);

        // Initiate quick delete tabs closure.
        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.ALL_TIME);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertEquals(5, filteredTabs.size());

        mQuickDeleteTabsFilter.closeTabsFilteredForQuickDelete();
        TabClosureParams params =
                TabClosureParams.closeTabs(filteredTabs)
                        .allowUndo(false)
                        .saveToTabRestoreService(false)
                        .build();
        verify(mTabGroupModelFilterMock).closeTabs(params);
    }

    @Test
    @SmallTest
    public void testClosureOfFilteredTabs_ClosesFourWeeksOldTabsOnlyFromTabModel_FiveWeeks() {
        final int countOfTabs = 5;
        createTabsAndUpdateTabModel(countOfTabs);

        // Set these mock tabs 1 week apart, total of 5 weeks.
        for (int i = 0; i < countOfTabs; ++i) {
            mMockTabList
                    .get(i)
                    .setLastNavigationCommittedTimestampMillis(
                            INITIAL_TIME_IN_MS + ONE_WEEK_IN_MS * (i + 1));
        }

        // 5 weeks passes...
        mQuickDeleteTabsFilter.setCurrentTimeForTesting(
                INITIAL_TIME_IN_MS + FOUR_WEEKS_IN_MS + ONE_WEEK_IN_MS);

        // Initiate quick delete tabs closure but for last four weeks only.
        mQuickDeleteTabsFilter.prepareListOfTabsToBeClosed(TimePeriod.FOUR_WEEKS);
        List<Tab> filteredTabs = mQuickDeleteTabsFilter.getListOfTabsFilteredToBeClosed();
        assertEquals(countOfTabs - 1, filteredTabs.size());
        // The oldest tab created in the first week should not be filtered out.
        assertFalse(filteredTabs.contains(mMockTabList.get(0)));

        mQuickDeleteTabsFilter.closeTabsFilteredForQuickDelete();
        TabClosureParams params =
                TabClosureParams.closeTabs(filteredTabs)
                        .allowUndo(false)
                        .saveToTabRestoreService(false)
                        .build();
        verify(mTabGroupModelFilterMock).closeTabs(params);
    }
}