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

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.DESKTOP_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.FOLDER_BOOKMARK_ID_A;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.MOBILE_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.OTHER_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.PARTNER_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.READING_LIST_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.ROOT_BOOKMARK_ID;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_A;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_B;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_C;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_D;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_E;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_F;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_G;
import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.URL_BOOKMARK_ID_H;

import androidx.annotation.Nullable;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.chrome.browser.bookmarks.BookmarkUiPrefs.BookmarkRowSortOrder;
import org.chromium.chrome.browser.commerce.ShoppingFeatures;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkItem;
import org.chromium.components.commerce.core.ShoppingService;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.power_bookmarks.PowerBookmarkMeta;
import org.chromium.components.power_bookmarks.PowerBookmarkType;
import org.chromium.components.power_bookmarks.ShoppingSpecifics;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/** Unit tests for {@link ImprovedBookmarkQueryHandler}. */
@Batch(Batch.UNIT_TESTS)
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ImprovedBookmarkQueryHandlerTest {
    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Mock private BookmarkModel mBookmarkModel;
    @Mock private Tracker mTracker;
    @Mock private Profile mProfile;
    @Mock private BookmarkUiPrefs mBookmarkUiPrefs;
    @Mock private ShoppingService mShoppingService;

    private ImprovedBookmarkQueryHandler mHandler;

    @Before
    public void setup() {
        doReturn(false).when(mBookmarkModel).areAccountBookmarkFoldersActive();
        TrackerFactory.setTrackerForTests(mTracker);
        SharedBookmarkModelMocks.initMocks(mBookmarkModel);

        mHandler =
                new ImprovedBookmarkQueryHandler(
                        mBookmarkModel, mBookmarkUiPrefs, mShoppingService);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_chronological() {
        doReturn(BookmarkRowSortOrder.CHRONOLOGICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        DESKTOP_BOOKMARK_ID,
                        OTHER_BOOKMARK_ID,
                        MOBILE_BOOKMARK_ID,
                        READING_LIST_BOOKMARK_ID,
                        PARTNER_BOOKMARK_ID);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_withAccountFolders() {
        FakeBookmarkModel fakeBookmarkModel = FakeBookmarkModel.createModel();
        fakeBookmarkModel.setAreAccountBookmarkFoldersActive(true);
        mHandler =
                new ImprovedBookmarkQueryHandler(
                        fakeBookmarkModel, mBookmarkUiPrefs, mShoppingService);

        doReturn(BookmarkRowSortOrder.CHRONOLOGICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        null,
                        fakeBookmarkModel.getAccountOtherFolderId(),
                        fakeBookmarkModel.getAccountDesktopFolderId(),
                        fakeBookmarkModel.getAccountMobileFolderId(),
                        fakeBookmarkModel.getAccountReadingListFolder(),
                        null,
                        fakeBookmarkModel.getOtherFolderId(),
                        fakeBookmarkModel.getDesktopFolderId(),
                        fakeBookmarkModel.getMobileFolderId(),
                        fakeBookmarkModel.getLocalOrSyncableReadingListFolder());
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_reverseChronological() {
        doReturn(BookmarkRowSortOrder.REVERSE_CHRONOLOGICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        PARTNER_BOOKMARK_ID,
                        READING_LIST_BOOKMARK_ID,
                        MOBILE_BOOKMARK_ID,
                        OTHER_BOOKMARK_ID,
                        DESKTOP_BOOKMARK_ID);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_alphabetical() {
        doReturn(BookmarkRowSortOrder.ALPHABETICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        DESKTOP_BOOKMARK_ID,
                        MOBILE_BOOKMARK_ID,
                        OTHER_BOOKMARK_ID,
                        PARTNER_BOOKMARK_ID,
                        READING_LIST_BOOKMARK_ID);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_reverseAlphabetical() {
        doReturn(BookmarkRowSortOrder.REVERSE_ALPHABETICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        READING_LIST_BOOKMARK_ID,
                        PARTNER_BOOKMARK_ID,
                        OTHER_BOOKMARK_ID,
                        MOBILE_BOOKMARK_ID,
                        DESKTOP_BOOKMARK_ID);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_rootFolder_recentlyUsed() {
        doReturn(BookmarkRowSortOrder.RECENTLY_USED)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();

        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(ROOT_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        DESKTOP_BOOKMARK_ID,
                        OTHER_BOOKMARK_ID,
                        MOBILE_BOOKMARK_ID,
                        READING_LIST_BOOKMARK_ID,
                        PARTNER_BOOKMARK_ID);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_readingList() {
        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForParent(READING_LIST_BOOKMARK_ID);
        List<BookmarkId> expected = Arrays.asList(null, URL_BOOKMARK_ID_E, null, URL_BOOKMARK_ID_D);
        verifyBookmarkIds(expected, result);
        verify(mBookmarkUiPrefs, never()).getBookmarkRowSortOrder();
    }

    @Test
    public void testBuildBookmarkListForParent_manualOrdering() {
        List<BookmarkListEntry> result = mHandler.buildBookmarkListForParent(MOBILE_BOOKMARK_ID);
        List<BookmarkId> expected =
                Arrays.asList(
                        FOLDER_BOOKMARK_ID_A,
                        URL_BOOKMARK_ID_A,
                        URL_BOOKMARK_ID_F,
                        URL_BOOKMARK_ID_G,
                        URL_BOOKMARK_ID_H);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForParent_withShoppingFilter() {
        ShoppingFeatures.setShoppingListEligibleForTesting(true);
        doReturn(true).when(mShoppingService).isShoppingListEligible();

        ShoppingSpecifics trackedShoppingSpecifics =
                ShoppingSpecifics.newBuilder().setProductClusterId(1).build();
        PowerBookmarkMeta shoppingMetaTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(trackedShoppingSpecifics)
                        .build();
        doReturn(true)
                .when(mShoppingService)
                .isSubscribedFromCache(
                        PowerBookmarkUtils.createCommerceSubscriptionForShoppingSpecifics(
                                trackedShoppingSpecifics));
        doReturn(shoppingMetaTracked).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_A);
        PowerBookmarkMeta shoppingMetaNotTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(
                                ShoppingSpecifics.newBuilder().setProductClusterId(2).build())
                        .build();
        doReturn(shoppingMetaNotTracked)
                .when(mBookmarkModel)
                .getPowerBookmarkMeta(URL_BOOKMARK_ID_B);
        PowerBookmarkMeta metaNoShopping = PowerBookmarkMeta.newBuilder().build();
        doReturn(metaNoShopping).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_C);
        doReturn(null).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_D);

        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForParent(
                        MOBILE_BOOKMARK_ID, Collections.singleton(PowerBookmarkType.SHOPPING));
        verifyBookmarkIds(Collections.singletonList(URL_BOOKMARK_ID_A), result);
    }

    @Test
    public void testSearch() {
        doReturn(BookmarkRowSortOrder.ALPHABETICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();
        // Order these initially in a non-alphabetical order.
        List<BookmarkId> queryIds =
                Arrays.asList(
                        URL_BOOKMARK_ID_D,
                        URL_BOOKMARK_ID_A,
                        URL_BOOKMARK_ID_C,
                        URL_BOOKMARK_ID_B,
                        URL_BOOKMARK_ID_E);
        doReturn(queryIds)
                .when(mBookmarkModel)
                .searchBookmarks(ArgumentMatchers.any(), ArgumentMatchers.anyInt());

        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForSearch("Url", /* powerFilter= */ null);
        List<BookmarkId> expected =
                Arrays.asList(
                        URL_BOOKMARK_ID_A,
                        URL_BOOKMARK_ID_B,
                        URL_BOOKMARK_ID_C,
                        URL_BOOKMARK_ID_D,
                        URL_BOOKMARK_ID_E);
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testSearchWithShoppingFilter() {
        ShoppingFeatures.setShoppingListEligibleForTesting(true);
        doReturn(true).when(mShoppingService).isShoppingListEligible();

        List<BookmarkId> queryIds =
                Arrays.asList(
                        URL_BOOKMARK_ID_A, URL_BOOKMARK_ID_B, URL_BOOKMARK_ID_C, URL_BOOKMARK_ID_D);
        doReturn(queryIds)
                .when(mBookmarkModel)
                .searchBookmarks(ArgumentMatchers.eq("test"), ArgumentMatchers.anyInt());
        ShoppingSpecifics trackedShoppingSpecifics =
                ShoppingSpecifics.newBuilder().setProductClusterId(1).build();
        PowerBookmarkMeta shoppingMetaTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(trackedShoppingSpecifics)
                        .build();
        doReturn(true)
                .when(mShoppingService)
                .isSubscribedFromCache(
                        PowerBookmarkUtils.createCommerceSubscriptionForShoppingSpecifics(
                                trackedShoppingSpecifics));
        doReturn(shoppingMetaTracked).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_A);
        PowerBookmarkMeta shoppingMetaNotTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(
                                ShoppingSpecifics.newBuilder().setProductClusterId(2).build())
                        .build();
        doReturn(shoppingMetaNotTracked)
                .when(mBookmarkModel)
                .getPowerBookmarkMeta(URL_BOOKMARK_ID_B);
        PowerBookmarkMeta metaNoShopping = PowerBookmarkMeta.newBuilder().build();
        doReturn(metaNoShopping).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_C);
        doReturn(null).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_D);

        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForSearch(
                        "test", Collections.singleton(PowerBookmarkType.SHOPPING));
        verifyBookmarkIds(Collections.singletonList(URL_BOOKMARK_ID_A), result);
    }

    @Test
    public void testSearchWithShoppingFilter_shoppingListNotEligible() {
        ShoppingFeatures.setShoppingListEligibleForTesting(false);

        List<BookmarkId> queryIds =
                Arrays.asList(
                        URL_BOOKMARK_ID_A, URL_BOOKMARK_ID_B, URL_BOOKMARK_ID_C, URL_BOOKMARK_ID_D);
        doReturn(queryIds)
                .when(mBookmarkModel)
                .searchBookmarks(ArgumentMatchers.any(), ArgumentMatchers.anyInt());
        ShoppingSpecifics trackedShoppingSpecifics =
                ShoppingSpecifics.newBuilder().setProductClusterId(1).build();
        PowerBookmarkMeta shoppingMetaTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(trackedShoppingSpecifics)
                        .build();
        doReturn(true)
                .when(mShoppingService)
                .isSubscribedFromCache(
                        PowerBookmarkUtils.createCommerceSubscriptionForShoppingSpecifics(
                                trackedShoppingSpecifics));
        doReturn(shoppingMetaTracked).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_A);
        PowerBookmarkMeta shoppingMetaNotTracked =
                PowerBookmarkMeta.newBuilder()
                        .setShoppingSpecifics(
                                ShoppingSpecifics.newBuilder().setProductClusterId(2).build())
                        .build();
        doReturn(shoppingMetaNotTracked)
                .when(mBookmarkModel)
                .getPowerBookmarkMeta(URL_BOOKMARK_ID_B);
        PowerBookmarkMeta metaNoShopping = PowerBookmarkMeta.newBuilder().build();
        doReturn(metaNoShopping).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_C);
        doReturn(null).when(mBookmarkModel).getPowerBookmarkMeta(URL_BOOKMARK_ID_D);

        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForSearch(
                        "", Collections.singleton(PowerBookmarkType.SHOPPING));
        verifyBookmarkIds(Collections.emptyList(), result);
    }

    @Test
    public void testBuildBookmarkListForSearch_empty() {
        doReturn(Arrays.asList(FOLDER_BOOKMARK_ID_A, URL_BOOKMARK_ID_A))
                .when(mBookmarkModel)
                .searchBookmarks(anyString(), anyInt());
        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForSearch("", /* powerFilter= */ null);
        assertEquals(0, result.size());
    }

    @Test
    public void testBuildBookmarkListForFolderSelect_rootFolder() {
        FakeBookmarkModel fakeBookmarkModel = FakeBookmarkModel.createModel();
        mHandler =
                new ImprovedBookmarkQueryHandler(
                        fakeBookmarkModel, mBookmarkUiPrefs, mShoppingService);

        doReturn(BookmarkRowSortOrder.ALPHABETICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();
        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForFolderSelect(mBookmarkModel.getRootFolderId());
        List<BookmarkId> expected =
                Arrays.asList(
                        fakeBookmarkModel.getDesktopFolderId(),
                        fakeBookmarkModel.getMobileFolderId(),
                        fakeBookmarkModel.getOtherFolderId(),
                        fakeBookmarkModel.getLocalOrSyncableReadingListFolder());
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void
            testBuildBookmarkListForFolderSelect_rootFolder_alphabetical_WithAccountBookmarks() {
        FakeBookmarkModel fakeBookmarkModel = FakeBookmarkModel.createModel();
        fakeBookmarkModel.setAreAccountBookmarkFoldersActive(true);
        mHandler =
                new ImprovedBookmarkQueryHandler(
                        fakeBookmarkModel, mBookmarkUiPrefs, mShoppingService);

        doReturn(BookmarkRowSortOrder.ALPHABETICAL)
                .when(mBookmarkUiPrefs)
                .getBookmarkRowSortOrder();
        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForFolderSelect(mBookmarkModel.getRootFolderId());
        List<BookmarkId> expected =
                Arrays.asList(
                        null,
                        fakeBookmarkModel.getAccountDesktopFolderId(),
                        fakeBookmarkModel.getAccountMobileFolderId(),
                        fakeBookmarkModel.getAccountOtherFolderId(),
                        fakeBookmarkModel.getAccountReadingListFolder(),
                        null,
                        fakeBookmarkModel.getDesktopFolderId(),
                        fakeBookmarkModel.getMobileFolderId(),
                        fakeBookmarkModel.getOtherFolderId(),
                        fakeBookmarkModel.getLocalOrSyncableReadingListFolder());
        verifyBookmarkIds(expected, result);
    }

    @Test
    public void testBuildBookmarkListForFolderSelect_rootFolder_manual_WithAccountBookmarks() {
        FakeBookmarkModel fakeBookmarkModel = FakeBookmarkModel.createModel();
        fakeBookmarkModel.setAreAccountBookmarkFoldersActive(true);
        mHandler =
                new ImprovedBookmarkQueryHandler(
                        fakeBookmarkModel, mBookmarkUiPrefs, mShoppingService);

        doReturn(BookmarkRowSortOrder.MANUAL).when(mBookmarkUiPrefs).getBookmarkRowSortOrder();
        List<BookmarkListEntry> result =
                mHandler.buildBookmarkListForFolderSelect(mBookmarkModel.getRootFolderId());
        List<BookmarkId> expected =
                Arrays.asList(
                        null,
                        fakeBookmarkModel.getAccountOtherFolderId(),
                        fakeBookmarkModel.getAccountDesktopFolderId(),
                        fakeBookmarkModel.getAccountMobileFolderId(),
                        fakeBookmarkModel.getAccountReadingListFolder(),
                        null,
                        fakeBookmarkModel.getOtherFolderId(),
                        fakeBookmarkModel.getDesktopFolderId(),
                        fakeBookmarkModel.getMobileFolderId(),
                        fakeBookmarkModel.getLocalOrSyncableReadingListFolder());
        verifyBookmarkIds(expected, result);
    }

    private void verifyBookmarkIds(
            List<BookmarkId> expectedList, List<BookmarkListEntry> actualList) {
        assertEquals("Lists differ in size", expectedList.size(), actualList.size());
        for (int i = 0; i < expectedList.size(); ++i) {
            final @Nullable BookmarkId expectedId = expectedList.get(i);
            final @Nullable BookmarkItem actualBookmarkItem = actualList.get(i).getBookmarkItem();
            final @Nullable BookmarkId actualBookmarkId =
                    actualBookmarkItem == null ? null : actualBookmarkItem.getId();
            assertEquals("Mismatch at index: " + i, expectedId, actualBookmarkId);
        }
    }
}