chromium/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkUtilsTest.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.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;

import android.app.Activity;

import androidx.test.ext.junit.rules.ActivityScenarioRule;

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

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.commerce.ShoppingServiceFactory;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.page_image_service.ImageServiceBridge;
import org.chromium.chrome.browser.page_image_service.ImageServiceBridgeJni;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.components.bookmarks.BookmarkId;
import org.chromium.components.bookmarks.BookmarkType;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.commerce.core.ShoppingService;
import org.chromium.components.favicon.LargeIconBridge;
import org.chromium.components.favicon.LargeIconBridgeJni;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.profile_metrics.BrowserProfileType;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.ui.base.TestActivity;
import org.chromium.url.GURL;

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

    @Rule
    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
            new ActivityScenarioRule<>(TestActivity.class);

    @Mock private SnackbarManager mSnackbarManager;
    @Mock private Tracker mTracker;
    @Mock private LargeIconBridge mLargeIconBridge;
    @Mock private LargeIconBridge.Natives mLargeIconBridgeNatives;
    @Mock private ImageServiceBridge.Natives mImageServiceBridgeJni;
    @Mock private Profile mProfile;
    @Mock private Tab mTab;
    @Mock private BottomSheetController mBottomSheetController;
    @Mock private Callback<BookmarkId> mBookmarkIdCallback;
    @Mock private ShoppingService mShoppingService;
    @Mock private IdentityServicesProvider mIdentityServicesProvider;
    @Mock private IdentityManager mIdentityManager;

    private Activity mActivity;
    private FakeBookmarkModel mBookmarkModel;
    private CoreAccountInfo mAccountInfo =
            CoreAccountInfo.createFromEmailAndGaiaId("[email protected]", "testGaiaId");

    @Before
    public void setup() {
        mBookmarkModel = FakeBookmarkModel.createModel();

        TrackerFactory.setTrackerForTests(mTracker);
        ShoppingServiceFactory.setShoppingServiceForTesting(mShoppingService);
        IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);

        mJniMocker.mock(LargeIconBridgeJni.TEST_HOOKS, mLargeIconBridgeNatives);
        mJniMocker.mock(ImageServiceBridgeJni.TEST_HOOKS, mImageServiceBridgeJni);
        doReturn(mIdentityManager).when(mIdentityServicesProvider).getIdentityManager(any());
        doReturn(mAccountInfo).when(mIdentityManager).getPrimaryAccountInfo(anyInt());
        doReturn(mProfile).when(mTab).getProfile();
        doReturn(false).when(mProfile).isOffTheRecord();

        mActivityScenarioRule.getScenario().onActivity(this::onActivity);
    }

    private void onActivity(Activity activity) {
        mActivity = activity;
    }

    @Test
    public void testAddToReadingList() {
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectIntRecords("Bookmarks.AddBookmarkType", BookmarkType.READING_LIST)
                        .expectIntRecords(
                                "Bookmarks.AddedPerProfileType", BrowserProfileType.REGULAR)
                        .build();

        BookmarkModel bookmarkModel = FakeBookmarkModel.createModel();
        BookmarkUtils.addToReadingList(
                mActivity,
                bookmarkModel,
                "Test title",
                new GURL("https://test.com"),
                mSnackbarManager,
                mProfile,
                mBottomSheetController);
        // Normally, a snackbar is shown.
        verify(mSnackbarManager).showSnackbar(any());
        verify(mTracker).notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);

        histograms.assertExpected();
    }

    @Test
    public void testAddToReadingList_withAccountBookmarks() {
        mBookmarkModel.setAreAccountBookmarkFoldersActive(true);
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectIntRecords("Bookmarks.AddBookmarkType", BookmarkType.READING_LIST)
                        .expectIntRecords(
                                "Bookmarks.AddedPerProfileType", BrowserProfileType.REGULAR)
                        .build();

        BookmarkUtils.addToReadingList(
                mActivity,
                mBookmarkModel,
                "Test title",
                new GURL("https://test.com"),
                mSnackbarManager,
                mProfile,
                mBottomSheetController);
        // When account bookmarks are enabled, reading list saves use the regular save flow.
        verify(mBottomSheetController).requestShowContent(any(), anyBoolean());
        verify(mTracker).notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);

        histograms.assertExpected();
    }

    @Test
    public void testAddOrEditBookmark() {
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectIntRecords("Bookmarks.AddBookmarkType", BookmarkType.NORMAL)
                        .expectIntRecords(
                                "Bookmarks.AddedPerProfileType", BrowserProfileType.REGULAR)
                        .build();

        doReturn("test title").when(mTab).getTitle();
        doReturn(new GURL("https://test.com")).when(mTab).getOriginalUrl();
        BookmarkUtils.addOrEditBookmark(
                null,
                mBookmarkModel,
                mTab,
                mBottomSheetController,
                mActivity,
                BookmarkType.NORMAL,
                mBookmarkIdCallback,
                /* fromExplicitTrackUi= */ false);

        histograms.assertExpected();
    }

    @Test
    public void testAddOrEditBookmark_readingList() {
        HistogramWatcher histograms =
                HistogramWatcher.newBuilder()
                        .expectIntRecords("Bookmarks.AddBookmarkType", BookmarkType.READING_LIST)
                        .expectIntRecords(
                                "Bookmarks.AddedPerProfileType", BrowserProfileType.REGULAR)
                        .build();

        doReturn("test title").when(mTab).getTitle();
        doReturn(new GURL("https://test.com")).when(mTab).getOriginalUrl();
        BookmarkUtils.addOrEditBookmark(
                null,
                mBookmarkModel,
                mTab,
                mBottomSheetController,
                mActivity,
                BookmarkType.READING_LIST,
                mBookmarkIdCallback,
                /* fromExplicitTrackUi= */ false);

        histograms.assertExpected();
    }

    @Test
    public void testCanAddFolderToParent() {
        assertFalse(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getRootFolderId()));
        assertTrue(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getDesktopFolderId()));
        assertTrue(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getMobileFolderId()));
        assertFalse(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getLocalOrSyncableReadingListFolder()));
        assertFalse(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getAccountReadingListFolder()));
        assertFalse(
                BookmarkUtils.canAddFolderToParent(
                        mBookmarkModel, mBookmarkModel.getPartnerFolderId()));

        BookmarkId folder =
                mBookmarkModel.addFolder(mBookmarkModel.getMobileFolderId(), 0, "folder");
        assertTrue(BookmarkUtils.canAddFolderToParent(mBookmarkModel, folder));

        BookmarkId managedFolder =
                mBookmarkModel.addManagedFolder(mBookmarkModel.getMobileFolderId(), "managed");
        assertFalse(BookmarkUtils.canAddFolderToParent(mBookmarkModel, managedFolder));
    }
}