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

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

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

import androidx.test.filters.SmallTest;

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.Robolectric;
import org.robolectric.annotation.Config;

import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.autofill.editors.AddressEditorCoordinator;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
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.components.autofill.AutofillProfile;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.sync.SyncService;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.test.util.modaldialog.FakeModalDialogManager;

/** Unit tests for {@link SaveUpdateAddressProfilePrompt}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@EnableFeatures({ChromeFeatureList.AUTOFILL_ADDRESS_PROFILE_SAVE_PROMPT_NICKNAME_SUPPORT})
public class SaveUpdateAddressProfilePromptTest {
    private static final long NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER = 100L;
    private static final boolean NO_MIGRATION = false;
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private SaveUpdateAddressProfilePromptController.Natives mPromptControllerJni;
    @Mock private AutofillProfileBridge.Natives mAutofillProfileBridgeJni;
    @Mock private PersonalDataManager mPersonalDataManager;
    @Mock private Profile mProfile;
    @Mock private AddressEditorCoordinator mAddressEditor;
    @Mock private IdentityServicesProvider mIdentityServicesProvider;
    @Mock private IdentityManager mIdentityManager;
    @Mock private SyncService mSyncService;

    @Captor private ArgumentCaptor<Callback<AutofillAddress>> mCallbackCaptor;

    private Activity mActivity;
    private SaveUpdateAddressProfilePromptController mPromptController;
    private FakeModalDialogManager mModalDialogManager;
    private SaveUpdateAddressProfilePrompt mPrompt;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        PersonalDataManagerFactory.setInstanceForTesting(mPersonalDataManager);
        when(mPersonalDataManager.getDefaultCountryCodeForNewAddress()).thenReturn("US");
        SyncServiceFactory.setInstanceForTesting(mSyncService);
        IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
        when(mIdentityServicesProvider.getIdentityManager(any())).thenReturn(mIdentityManager);

        mActivity = Robolectric.setupActivity(TestActivity.class);

        mPromptController =
                SaveUpdateAddressProfilePromptController.create(
                        NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER);
        mJniMocker.mock(
                SaveUpdateAddressProfilePromptControllerJni.TEST_HOOKS, mPromptControllerJni);
        mJniMocker.mock(AutofillProfileBridgeJni.TEST_HOOKS, mAutofillProfileBridgeJni);
    }

    private void createAndShowPrompt(boolean isUpdate) {
        createAndShowPrompt(isUpdate, NO_MIGRATION);
    }

    private void createAndShowPrompt(boolean isUpdate, boolean isMigrationToAccount) {
        AutofillProfile dummyProfile = AutofillProfile.builder().build();
        mModalDialogManager = new FakeModalDialogManager(ModalDialogType.APP);
        mPrompt =
                new SaveUpdateAddressProfilePrompt(
                        mPromptController,
                        mModalDialogManager,
                        mActivity,
                        mProfile,
                        dummyProfile,
                        isUpdate,
                        isMigrationToAccount);
        mPrompt.setAddressEditorForTesting(mAddressEditor);
        mPrompt.show();
    }

    private void validateTextView(TextView view, String text) {
        assertNotNull(view);
        assertEquals(text, view.getText());
    }

    @Test
    @SmallTest
    public void dialogShown() {
        createAndShowPrompt(false);
        assertNotNull(mModalDialogManager.getShownDialogModel());
    }

    @Test
    @SmallTest
    public void positiveButtonPressed() {
        createAndShowPrompt(false);
        assertNotNull(mModalDialogManager.getShownDialogModel());
        mModalDialogManager.clickPositiveButton();
        assertNull(mModalDialogManager.getShownDialogModel());
        verify(mPromptControllerJni, times(1))
                .onUserAccepted(eq(NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER), any());
        verify(mPromptControllerJni, times(1))
                .onPromptDismissed(eq(NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER), any());
    }

    @Test
    @SmallTest
    public void negativeButtonPressed() {
        createAndShowPrompt(false);

        assertNotNull(mModalDialogManager.getShownDialogModel());
        mModalDialogManager.clickNegativeButton();
        assertNull(mModalDialogManager.getShownDialogModel());
        verify(mPromptControllerJni, times(1))
                .onUserDeclined(eq(NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER), any());
        verify(mPromptControllerJni, times(1))
                .onPromptDismissed(eq(NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER), any());
    }

    @Test
    @SmallTest
    public void dialogDismissed() {
        createAndShowPrompt(false);
        assertNotNull(mModalDialogManager.getShownDialogModel());
        // Simulate dialog dismissal by native.
        mPrompt.dismiss();
        assertNull(mModalDialogManager.getShownDialogModel());
        // Check that callback was still called when the dialog is dismissed.
        verify(mPromptControllerJni, times(1))
                .onPromptDismissed(eq(NATIVE_SAVE_UPDATE_ADDRESS_PROFILE_PROMPT_CONTROLLER), any());
    }

    @Test
    @SmallTest
    public void dialogStrings() {
        createAndShowPrompt(false);

        PropertyModel propertyModel = mModalDialogManager.getShownDialogModel();

        mPrompt.setDialogDetails("title", "positive button text", "negative button text");
        assertEquals("title", propertyModel.get(ModalDialogProperties.TITLE));
        assertEquals(
                "positive button text",
                propertyModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
        assertEquals(
                "negative button text",
                propertyModel.get(ModalDialogProperties.NEGATIVE_BUTTON_TEXT));
    }

    @Test
    @SmallTest
    public void dialogStrings_SourceNotice() {
        createAndShowPrompt(false, true);
        View dialog = mPrompt.getDialogViewForTesting();

        mPrompt.setSourceNotice(null);
        assertEquals(
                View.GONE,
                dialog.findViewById(R.id.autofill_address_profile_prompt_source_notice)
                        .getVisibility());

        mPrompt.setSourceNotice("");
        assertEquals(
                View.GONE,
                dialog.findViewById(R.id.autofill_address_profile_prompt_source_notice)
                        .getVisibility());

        mPrompt.setSourceNotice("source notice");
        assertEquals(
                View.VISIBLE,
                dialog.findViewById(R.id.autofill_address_profile_prompt_source_notice)
                        .getVisibility());
        validateTextView(
                dialog.findViewById(R.id.autofill_address_profile_prompt_source_notice),
                "source notice");
    }

    @Test
    @SmallTest
    public void dialogStrings_SaveAddress() {
        createAndShowPrompt(false);

        View dialog = mPrompt.getDialogViewForTesting();

        mPrompt.setSaveOrMigrateDetails("address", "email", "phone");
        validateTextView(dialog.findViewById(R.id.address), "address");
        validateTextView(dialog.findViewById(R.id.email), "email");
        validateTextView(dialog.findViewById(R.id.phone), "phone");
    }

    @Test
    @SmallTest
    public void dialogStrings_UpdateAddress() {
        createAndShowPrompt(true);

        View dialog = mPrompt.getDialogViewForTesting();

        mPrompt.setUpdateDetails("subtitle", "old details", "new details");
        validateTextView(dialog.findViewById(R.id.subtitle), "subtitle");
        validateTextView(dialog.findViewById(R.id.details_old), "old details");
        validateTextView(dialog.findViewById(R.id.details_new), "new details");
    }

    @Test
    @SmallTest
    public void showHeaders() {
        createAndShowPrompt(true);

        View dialog = mPrompt.getDialogViewForTesting();

        mPrompt.setUpdateDetails("subtitle", "", "new details");
        assertEquals(dialog.findViewById(R.id.header_new).getVisibility(), View.GONE);
        assertEquals(dialog.findViewById(R.id.header_old).getVisibility(), View.GONE);
        assertEquals(dialog.findViewById(R.id.no_header_space).getVisibility(), View.VISIBLE);

        mPrompt.setUpdateDetails("subtitle", "old details", "new details");
        assertEquals(dialog.findViewById(R.id.header_new).getVisibility(), View.VISIBLE);
        assertEquals(dialog.findViewById(R.id.header_old).getVisibility(), View.VISIBLE);
        assertEquals(dialog.findViewById(R.id.no_header_space).getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    @DisableFeatures(ChromeFeatureList.AUTOFILL_ADDRESS_PROFILE_SAVE_PROMPT_NICKNAME_SUPPORT)
    public void setupAddressNickname_FeatureDisabled() {
        createAndShowPrompt(false);

        View dialog = mPrompt.getDialogViewForTesting();
        assertEquals(dialog.findViewById(R.id.nickname_input_layout).getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    public void setupAddressNickname_FeatureEnabled() {
        createAndShowPrompt(false);

        View dialog = mPrompt.getDialogViewForTesting();
        TextView nicknameInput = dialog.findViewById(R.id.nickname_input);

        assertEquals(nicknameInput.getVisibility(), View.VISIBLE);
        assertEquals(nicknameInput.getHint(), "Add a label");

        nicknameInput.requestFocus();
        assertEquals(nicknameInput.getHint(), "Label");

        nicknameInput.setText("Text");
        nicknameInput.clearFocus();
        assertEquals(nicknameInput.getHint(), "Label");

        nicknameInput.requestFocus();
        assertEquals(nicknameInput.getHint(), "Label");

        nicknameInput.setText("");
        nicknameInput.clearFocus();
        assertEquals(nicknameInput.getHint(), "Add a label");
    }

    @Test
    @SmallTest
    public void setupAddressNickname_NoNicknamesDuringUpdate() {
        createAndShowPrompt(true);

        View dialog = mPrompt.getDialogViewForTesting();
        assertNull(dialog.findViewById(R.id.nickname_input_layout));
    }

    @Test
    @SmallTest
    public void clickEditButton() {
        createAndShowPrompt(true);
        View dialog = mPrompt.getDialogViewForTesting();
        ImageButton editButton = dialog.findViewById(R.id.edit_button);
        editButton.performClick();
        verify(mAddressEditor).showEditorDialog();
    }
}