chromium/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/AddToBookmarksToolbarButtonControllerUnitTest.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.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Looper;

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

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;

import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.UserActionTester;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.init.ActivityLifecycleDispatcherImpl;
import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.toolbar.ButtonData;
import org.chromium.chrome.browser.toolbar.ButtonDataProvider.ButtonDataObserver;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.base.TestActivity;

/** Unit tests for {@link AddToBookmarksToolbarButtonController} */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
public class AddToBookmarksToolbarButtonControllerUnitTest {

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

    @Mock private Tab mTab;
    @Mock private Tracker mTracker;
    @Mock private TabBookmarker mTabBookmarker;
    @Mock private BookmarkModel mBookmarkModel;
    @Mock private ActivityLifecycleDispatcherImpl mActivityLifecycleDispatcher;
    @Captor private ArgumentCaptor<ConfigurationChangedObserver> mConfigurationChangedObserver;

    private ObservableSupplierImpl<Tab> mTabSupplier;
    private ObservableSupplierImpl<BookmarkModel> mBookmarkModelSupplier;

    private UserActionTester mActionTester;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mActionTester = new UserActionTester();

        mTabSupplier = new ObservableSupplierImpl<>();
        mTabSupplier.set(mTab);

        when(mBookmarkModel.isBookmarkModelLoaded()).thenReturn(true);
        mBookmarkModelSupplier = new ObservableSupplierImpl<>();
        mBookmarkModelSupplier.set(mBookmarkModel);
    }

    @After
    public void tearDown() throws Exception {
        mActionTester.tearDown();
    }

    /** Sets device qualifiers and notifies the activity about configuration change. */
    private void applyQualifiers(Activity activity, String qualifiers) {
        RuntimeEnvironment.setQualifiers(qualifiers);
        Configuration configuration = Resources.getSystem().getConfiguration();
        // ChromeTabbedActivity takes care of calling onConfigurationChanged on
        // ActivityLifecycleDispatcher, but this is a TestActivity so we do it manually.
        activity.onConfigurationChanged(configuration);
        mConfigurationChangedObserver.getValue().onConfigurationChanged(configuration);
    }

    private AddToBookmarksToolbarButtonController createController(Activity activity) {
        AddToBookmarksToolbarButtonController addToBookmarksToolbarButtonController =
                new AddToBookmarksToolbarButtonController(
                        mTabSupplier,
                        activity,
                        mActivityLifecycleDispatcher,
                        () -> mTabBookmarker,
                        () -> mTracker,
                        mBookmarkModelSupplier);
        verify(mActivityLifecycleDispatcher).register(mConfigurationChangedObserver.capture());
        return addToBookmarksToolbarButtonController;
    }

    @Test
    @Config(qualifiers = "w390dp-h820dp-land")
    public void testButtonData_shownOnPhone() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            when(mTab.getContext()).thenReturn(activity);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);
                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Assert.assertTrue(buttonData.canShow());
                            Assert.assertTrue(buttonData.isEnabled());
                            Assert.assertNotNull(buttonData.getButtonSpec());

                            applyQualifiers(activity, "+port");

                            Assert.assertTrue(
                                    addToBookmarksToolbarButtonController.get(mTab).canShow());
                        });
    }

    @Test
    @Config(qualifiers = "w600dp-h820dp")
    public void testButtonData_notShownOnTablet() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            when(mTab.getContext()).thenReturn(activity);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);
                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Assert.assertFalse(buttonData.canShow());
                            applyQualifiers(activity, "+land");

                            Assert.assertFalse(buttonData.canShow());
                        });
    }

    @Test
    @Config(qualifiers = "w600dp-h820dp")
    public void testButtonData_visibilityChangesOnScreenSizeUpdate() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            when(mTab.getContext()).thenReturn(activity);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);
                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Assert.assertFalse(buttonData.canShow());
                            applyQualifiers(activity, "w390dp-h820dp");

                            Assert.assertTrue(buttonData.canShow());
                        });
    }

    @Test
    public void testOnClick() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            when(mTab.getContext()).thenReturn(activity);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);
                            addToBookmarksToolbarButtonController.onClick(null);

                            Assert.assertEquals(
                                    1,
                                    mActionTester.getActionCount(
                                            "MobileTopToolbarAddToBookmarksButton"));
                            verify(mTracker)
                                    .notifyEvent(
                                            EventConstants
                                                    .ADAPTIVE_TOOLBAR_CUSTOMIZATION_ADD_TO_BOOKMARKS_OPENED);
                            verify(mTabBookmarker).addOrEditBookmark(mTab);
                        });
    }

    @Test
    public void testIconUpdate_whenTabChanges() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            Tab anotherTab = mock(Tab.class);
                            when(mTab.getContext()).thenReturn(activity);
                            when(anotherTab.getContext()).thenReturn(activity);
                            // Set the current tab as not bookmarked, and a second tab as
                            // bookmarked.
                            when(mBookmarkModel.hasBookmarkIdForTab(mTab)).thenReturn(false);
                            when(mBookmarkModel.hasBookmarkIdForTab(anotherTab)).thenReturn(true);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);

                            ButtonDataObserver mockButtonObserver = mock(ButtonDataObserver.class);
                            addToBookmarksToolbarButtonController.addObserver(mockButtonObserver);

                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Drawable originalDrawable = buttonData.getButtonSpec().getDrawable();
                            String originalDescription =
                                    buttonData.getButtonSpec().getContentDescription();

                            // Change the active tab to the one already bookmarked.
                            mTabSupplier.set(anotherTab);

                            buttonData = addToBookmarksToolbarButtonController.get(anotherTab);

                            // The icon and description should be different.
                            Assert.assertNotEquals(
                                    originalDescription,
                                    buttonData.getButtonSpec().getContentDescription());
                            Assert.assertNotEquals(
                                    originalDrawable, buttonData.getButtonSpec().getDrawable());
                            // The provider should have notified of a change.
                            verify(mockButtonObserver).buttonDataChanged(true);
                        });
    }

    @Test
    public void testIconUpdate_whenUrlChanges() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            ArgumentCaptor<TabObserver> tabObserverCaptor =
                                    ArgumentCaptor.forClass(TabObserver.class);
                            when(mTab.getContext()).thenReturn(activity);
                            // Set the current tab as not bookmarked.
                            when(mBookmarkModel.hasBookmarkIdForTab(mTab)).thenReturn(false);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);

                            // If an ObservableSupplier already has a value then its change callback
                            // will be called immediately as a separate task, idle the main looper
                            // to ensure all Observable suppliers raise their events.
                            Shadows.shadowOf(Looper.getMainLooper()).idle();

                            verify(mTab).addObserver(tabObserverCaptor.capture());

                            ButtonDataObserver mockButtonObserver = mock(ButtonDataObserver.class);
                            addToBookmarksToolbarButtonController.addObserver(mockButtonObserver);

                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Drawable originalDrawable = buttonData.getButtonSpec().getDrawable();
                            String originalDescription =
                                    buttonData.getButtonSpec().getContentDescription();

                            // Set the current tab as bookmarked.
                            when(mBookmarkModel.hasBookmarkIdForTab(mTab)).thenReturn(true);
                            // Send an event notifying that the URL changed on the current tab.
                            tabObserverCaptor.getValue().onUrlUpdated(mTab);

                            buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            // The icon and description should be different.
                            Assert.assertNotEquals(
                                    originalDescription,
                                    buttonData.getButtonSpec().getContentDescription());
                            Assert.assertNotEquals(
                                    originalDrawable, buttonData.getButtonSpec().getDrawable());
                            // The provider should have notified of a change.
                            verify(mockButtonObserver).buttonDataChanged(true);
                        });
    }

    @Test
    public void testIconUpdate_whenBookmarkModelChanges() {
        mActivityScenarioRule
                .getScenario()
                .onActivity(
                        activity -> {
                            ArgumentCaptor<BookmarkModelObserver> bookmarkModelObserverCaptor =
                                    ArgumentCaptor.forClass(BookmarkModelObserver.class);
                            when(mTab.getContext()).thenReturn(activity);
                            // Set the current tab as not bookmarked.
                            when(mBookmarkModel.hasBookmarkIdForTab(mTab)).thenReturn(false);
                            AddToBookmarksToolbarButtonController
                                    addToBookmarksToolbarButtonController =
                                            createController(activity);

                            // If an ObservableSupplier already has a value then its change callback
                            // will be called immediately as a separate task, idle the main looper
                            // to ensure all Observable suppliers raise their events.
                            Shadows.shadowOf(Looper.getMainLooper()).idle();

                            verify(mBookmarkModel)
                                    .addObserver(bookmarkModelObserverCaptor.capture());

                            ButtonDataObserver mockButtonObserver = mock(ButtonDataObserver.class);
                            addToBookmarksToolbarButtonController.addObserver(mockButtonObserver);

                            ButtonData buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            Drawable originalDrawable = buttonData.getButtonSpec().getDrawable();
                            String originalDescription =
                                    buttonData.getButtonSpec().getContentDescription();

                            when(mBookmarkModel.hasBookmarkIdForTab(mTab)).thenReturn(true);
                            // Send and event notifying that something changed in the bookmark
                            // model.
                            bookmarkModelObserverCaptor.getValue().bookmarkModelChanged();

                            buttonData = addToBookmarksToolbarButtonController.get(mTab);

                            // The icon and description should be different.
                            Assert.assertNotEquals(
                                    originalDescription,
                                    buttonData.getButtonSpec().getContentDescription());
                            Assert.assertNotEquals(
                                    originalDrawable, buttonData.getButtonSpec().getDrawable());
                            // The provider should have notified of a change.
                            verify(mockButtonObserver).buttonDataChanged(true);
                        });
    }
}