chromium/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ActionConfirmationManagerUnitTest.java

// Copyright 2024 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.tasks.tab_management;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;

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.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.tab.MockTab;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager.ConfirmationResult;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.sync.DataType;
import org.chromium.components.sync.SyncService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.Controller;
import org.chromium.ui.modelutil.PropertyModel;

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

/** Unit tests for {@link ActionConfirmationManager}. */
@RunWith(BaseRobolectricTestRunner.class)
public class ActionConfirmationManagerUnitTest {
    private static final String TEST_EMAIL = "[email protected]";
    private static final String GROUP_TITLE = "Group1";

    private static final int TAB1_ID = 1;
    private static final int TAB2_ID = 2;
    private static final int TAB3_ID = 3;

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Rule public JniMocker mJniMocker = new JniMocker();

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

    @Mock private Profile mProfile;
    @Mock private Activity mActivity;
    @Mock private TabGroupModelFilter mTabGroupModelFilter;
    @Mock private ModalDialogManager mModalDialogManager;
    @Mock private Callback<Integer> mOnResult;
    @Mock private SyncService mSyncService;
    @Mock private PrefService mPrefService;
    @Mock private UserPrefs.Natives mUserPrefsJni;
    @Mock private IdentityServicesProvider mIdentityServicesProvider;
    @Mock private IdentityManager mIdentityManager;
    @Mock private CoreAccountInfo mCoreAccountInfo;

    @Captor private ArgumentCaptor<PropertyModel> mPropertyModelArgumentCaptor;

    @Before
    public void setUp() {
        mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJni);

        MockTab tab1 = new MockTab(TAB1_ID, mProfile);
        when(mTabGroupModelFilter.getRelatedTabList(TAB1_ID)).thenReturn(Arrays.asList(tab1));
        MockTab tab2 = new MockTab(TAB2_ID, mProfile);
        MockTab tab3 = new MockTab(TAB3_ID, mProfile);
        tab3.setRootId(TAB2_ID);
        when(mTabGroupModelFilter.getRelatedTabList(TAB2_ID)).thenReturn(Arrays.asList(tab2, tab3));

        SyncServiceFactory.setInstanceForTesting(mSyncService);
        when(mUserPrefsJni.get(mProfile)).thenReturn(mPrefService);

        IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
        when(mIdentityServicesProvider.getIdentityManager(mProfile)).thenReturn(mIdentityManager);
        when(mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN))
                .thenReturn(mCoreAccountInfo);

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

    private void onActivity(TestActivity activity) {
        mActivity = activity;
        mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
    }

    @Test
    public void testProcessDeleteGroupAttempt_Positive() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processDeleteGroupAttempt(mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessUngroupAttempt_Positive() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processUngroupAttempt(mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessRemoveTabAttempt_Positive() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessRemoveTabAttempt_Negative() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_NEGATIVE);
    }

    @Test
    public void testProcessRemoveTabAttempt_PartialGroup() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB2_ID), mOnResult);
        verify(mModalDialogManager, never()).showDialog(any(), anyInt());
        verify(mOnResult).onResult(ConfirmationResult.IMMEDIATE_CONTINUE);
    }

    @Test
    public void testProcessRemoveTabAttempt_PrefSet() {
        when(mPrefService.getBoolean(anyString())).thenReturn(true);
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager, never()).showDialog(any(), anyInt());
        verify(mOnResult).onResult(ConfirmationResult.IMMEDIATE_CONTINUE);
    }

    @Test
    public void testProcessRemoveTabAttempt_CheckBoxOnPositive() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        CheckBox stopShowingCheckBox = customView.findViewById(R.id.stop_showing_check_box);
        stopShowingCheckBox.setChecked(true);
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);

        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
        verify(mPrefService).setBoolean(any(), eq(true));
    }

    @Test
    public void testProcessRemoveTabAttempt_CheckBoxOnNegative() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        CheckBox stopShowingCheckBox = customView.findViewById(R.id.stop_showing_check_box);
        stopShowingCheckBox.setChecked(true);
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);

        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_NEGATIVE);
        verify(mPrefService, never()).setBoolean(any(), anyBoolean());
    }

    @Test
    public void testProcessRemoveTabAttempt_NoIdentityManager() {
        when(mIdentityServicesProvider.getIdentityManager(mProfile)).thenReturn(mIdentityManager);

        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());

        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        TextView descriptionTextView = customView.findViewById(R.id.description_text_view);
        assertEquals(
                "This will permanently delete the group from your device",
                descriptionTextView.getText());
    }

    @Test
    public void testProcessRemoveTabAttempt_NoSignIn() {
        when(mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN)).thenReturn(null);

        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());

        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        TextView descriptionTextView = customView.findViewById(R.id.description_text_view);
        assertEquals(
                "This will permanently delete the group from your device",
                descriptionTextView.getText());
    }

    @Test
    public void testProcessRemoveTabAttempt_SignInButNoSync() {
        when(mCoreAccountInfo.getEmail()).thenReturn(TEST_EMAIL);
        when(mSyncService.getActiveDataTypes()).thenReturn(Collections.emptySet());

        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());

        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        TextView descriptionTextView = customView.findViewById(R.id.description_text_view);
        assertEquals(
                "This will permanently delete the group from your device",
                descriptionTextView.getText());
    }

    @Test
    public void testProcessRemoveTabAttempt_SignInAndSync() {
        when(mCoreAccountInfo.getEmail()).thenReturn(TEST_EMAIL);
        when(mSyncService.getActiveDataTypes())
                .thenReturn(Collections.singleton(DataType.SAVED_TAB_GROUP));

        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processRemoveTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());

        View customView =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CUSTOM_VIEW);
        TextView descriptionTextView = customView.findViewById(R.id.description_text_view);
        assertEquals(
                "This will delete the group from all devices signed into [email protected]",
                descriptionTextView.getText());
    }

    @Test
    public void testProcessCloseTabAttempt_Positive() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processCloseTabAttempt(mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessCloseTabAttempt_PositiveFullGroup() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processCloseTabAttempt(Arrays.asList(TAB1_ID), mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessCloseTabAttempt_PartialGroup() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processCloseTabAttempt(Arrays.asList(TAB2_ID), mOnResult);
        verify(mModalDialogManager, never()).showDialog(any(), anyInt());
        verify(mOnResult).onResult(ConfirmationResult.IMMEDIATE_CONTINUE);
    }

    @Test
    public void testProcessDeleteSharedGroupAttempt() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processDeleteSharedGroupAttempt(GROUP_TITLE, mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }

    @Test
    public void testProcessDeleteSharedGroupAttempt_Negative() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processDeleteSharedGroupAttempt(GROUP_TITLE, mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_NEGATIVE);
    }

    @Test
    public void testProcessLeaveGroupAttempt() {
        ActionConfirmationManager actionConfirmationManager =
                new ActionConfirmationManager(
                        mProfile, mActivity, mTabGroupModelFilter, mModalDialogManager);
        actionConfirmationManager.processLeaveGroupAttempt(GROUP_TITLE, mOnResult);
        verify(mModalDialogManager).showDialog(mPropertyModelArgumentCaptor.capture(), anyInt());
        Controller controller =
                mPropertyModelArgumentCaptor.getValue().get(ModalDialogProperties.CONTROLLER);
        controller.onDismiss(
                mPropertyModelArgumentCaptor.getValue(),
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
        verify(mOnResult).onResult(ConfirmationResult.CONFIRMATION_POSITIVE);
    }
}