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

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.autofill.editors.EditorProperties.CANCEL_RUNNABLE;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.DONE_RUNNABLE;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.DropdownFieldProperties.DROPDOWN_KEY_VALUE_LIST;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.EDITOR_FIELDS;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.FieldProperties.ERROR_MESSAGE;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.FieldProperties.IS_REQUIRED;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.FieldProperties.LABEL;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.FieldProperties.VALUE;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.ItemType.DROPDOWN;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.ItemType.TEXT_INPUT;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.SHOW_REQUIRED_INDICATOR;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.TextFieldProperties.TEXT_FIELD_TYPE;
import static org.chromium.chrome.browser.autofill.editors.EditorProperties.setDropdownKey;

import android.app.Activity;

import androidx.test.filters.SmallTest;

import org.hamcrest.Matcher;
import org.junit.After;
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.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillAddress;
import org.chromium.chrome.browser.autofill.AutofillProfileBridge;
import org.chromium.chrome.browser.autofill.AutofillProfileBridge.AutofillAddressUiComponent;
import org.chromium.chrome.browser.autofill.AutofillProfileBridgeJni;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PhoneNumberUtil;
import org.chromium.chrome.browser.autofill.PhoneNumberUtilJni;
import org.chromium.chrome.browser.autofill.SubKeyRequesterFactory;
import org.chromium.chrome.browser.autofill.editors.EditorDialogView;
import org.chromium.chrome.browser.autofill.editors.EditorProperties.DropdownKeyValue;
import org.chromium.chrome.browser.autofill.editors.EditorProperties.FieldItem;
import org.chromium.components.autofill.AutofillProfile;
import org.chromium.components.autofill.FieldType;
import org.chromium.components.autofill.SubKeyRequester;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.modelutil.ListModel;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/** Unit tests for {@link AddressEditor}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class AddressEditorTest {
    private static final Locale DEFAULT_LOCALE = Locale.getDefault();
    private static final List<AutofillAddressUiComponent> SUPPORTED_ADDRESS_FIELDS =
            List.of(
                    new AutofillAddressUiComponent(
                            FieldType.NAME_FULL,
                            /* label= */ "full name label",
                            /* isRequired= */ false,
                            /* isFullLine= */ true),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_STATE,
                            /* label= */ "admin area label",
                            /* isRequired= */ true,
                            /* isFullLine= */ true),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_CITY,
                            /* label= */ "locality label",
                            /* isRequired= */ true,
                            /* isFullLine= */ false),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY,
                            /* label= */ "dependent locality label",
                            /* isRequired= */ true,
                            /* isFullLine= */ false),
                    new AutofillAddressUiComponent(
                            FieldType.COMPANY_NAME,
                            /* label= */ "organization label",
                            /* isRequired= */ false,
                            /* isFullLine= */ true),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_SORTING_CODE,
                            /* label= */ "sorting code label",
                            /* isRequired= */ false,
                            /* isFullLine= */ false),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_ZIP,
                            /* label= */ "postal code label",
                            /* isRequired= */ true,
                            /* isFullLine= */ false),
                    new AutofillAddressUiComponent(
                            FieldType.ADDRESS_HOME_STREET_ADDRESS,
                            /* label= */ "street address label",
                            /* isRequired= */ true,
                            /* isFullLine= */ true));

    private static final AutofillProfile sProfile =
            AutofillProfile.builder()
                    .setFullName("Seb Doe")
                    .setCompanyName("Google")
                    .setStreetAddress("111 First St")
                    .setRegion("CA")
                    .setLocality("Los Angeles")
                    .setPostalCode("90291")
                    .setCountryCode("US")
                    .setPhoneNumber("650-253-0000")
                    .setEmailAddress("[email protected]")
                    .setLanguageCode("en-US")
                    .build();

    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private AutofillProfileBridge.Natives mAutofillProfileBridgeJni;
    @Mock private PhoneNumberUtil.Natives mPhoneNumberUtilJni;

    @Mock private PersonalDataManager mPersonalDataManager;
    @Mock private EditorDialogView mEditorDialog;
    @Mock private SubKeyRequester mSubKeyRequester;
    @Mock private Callback<AutofillAddress> mDoneCallback;
    @Mock private Callback<AutofillAddress> mCancelCallback;

    @Captor private ArgumentCaptor<AutofillAddress> mAddressCapture;

    // Note: can't initialize this list statically because of how Robolectric
    // initializes Android library dependencies.
    private final List<DropdownKeyValue> mSupportedCountries =
            List.of(
                    new DropdownKeyValue("US", "United States"),
                    new DropdownKeyValue("DE", "Germany"),
                    new DropdownKeyValue("CU", "Cuba"));

    private Activity mActivity;
    private AddressEditor mAddressEditor;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        Locale.setDefault(Locale.US);

        // PersonalDataManagerFactory.setInstanceForTesting(mPersonalDataManager);
        when(mPersonalDataManager.getDefaultCountryCodeForNewAddress()).thenReturn("US");
        mJniMocker.mock(AutofillProfileBridgeJni.TEST_HOOKS, mAutofillProfileBridgeJni);
        doAnswer(
                        invocation -> {
                            List<Integer> requiredFields =
                                    (List<Integer>) invocation.getArguments()[1];
                            requiredFields.addAll(
                                    List.of(
                                            FieldType.NAME_FULL,
                                            FieldType.ADDRESS_HOME_CITY,
                                            FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY,
                                            FieldType.ADDRESS_HOME_ZIP));
                            return null;
                        })
                .when(mAutofillProfileBridgeJni)
                .getRequiredFields(anyString(), anyList());
        mJniMocker.mock(PhoneNumberUtilJni.TEST_HOOKS, mPhoneNumberUtilJni);
        when(mPhoneNumberUtilJni.isPossibleNumber(anyString(), anyString())).thenReturn(true);

        mActivity = Robolectric.setupActivity(TestActivity.class);

        SubKeyRequesterFactory.setInstanceForTesting(mSubKeyRequester);

        setUpSupportedCountries(mSupportedCountries);

        when(mEditorDialog.getContext()).thenReturn(mActivity);
    }

    @After
    public void tearDown() {
        // Reset default values to avoid changing them for other batched tests.
        Locale.setDefault(DEFAULT_LOCALE);
    }

    private void setUpSupportedCountries(List<DropdownKeyValue> supportedCountries) {
        doAnswer(
                        invocation -> {
                            List<String> contryCodes = (List<String>) invocation.getArguments()[0];
                            List<String> contryNames = (List<String>) invocation.getArguments()[1];

                            for (DropdownKeyValue keyValue : supportedCountries) {
                                contryCodes.add(keyValue.getKey());
                                contryNames.add(keyValue.getValue().toString());
                            }

                            return null;
                        })
                .when(mAutofillProfileBridgeJni)
                .getSupportedCountries(anyList(), anyList());
    }

    private void setUpAddressUiComponents(
            List<AutofillAddressUiComponent> addressUiComponents, String countryCode) {
        doAnswer(
                        invocation -> {
                            List<Integer> componentIds =
                                    (List<Integer>) invocation.getArguments()[3];
                            List<String> componentNames =
                                    (List<String>) invocation.getArguments()[4];
                            List<Integer> componentRequired =
                                    (List<Integer>) invocation.getArguments()[5];
                            List<Integer> componentLength =
                                    (List<Integer>) invocation.getArguments()[6];

                            for (AutofillAddressUiComponent component : addressUiComponents) {
                                componentIds.add(component.id);
                                componentNames.add(component.label);
                                componentRequired.add(component.isRequired ? 1 : 0);
                                componentLength.add(component.isFullLine ? 1 : 0);
                            }
                            return "EN";
                        })
                .when(mAutofillProfileBridgeJni)
                .getAddressUiComponents(
                        eq(countryCode),
                        anyString(),
                        anyInt(),
                        anyList(),
                        anyList(),
                        anyList(),
                        anyList());
    }

    private static void validateTextField(
            FieldItem fieldItem,
            String value,
            int textFieldType,
            String label,
            boolean isRequired,
            boolean isFullLine) {
        assertEquals(TEXT_INPUT, fieldItem.type);
        assertEquals(isFullLine, fieldItem.isFullLine);

        PropertyModel field = fieldItem.model;
        assertEquals(value, field.get(VALUE));
        assertEquals(textFieldType, field.get(TEXT_FIELD_TYPE));
        assertEquals(label, field.get(LABEL));
        assertEquals(isRequired, field.get(IS_REQUIRED));
    }

    private void validateShownFields(PropertyModel editorModel, AutofillProfile profile) {
        assertNotNull(editorModel);
        ListModel<FieldItem> editorFields = editorModel.get(EDITOR_FIELDS);
        // editorFields[0] - country dropdown.
        // editorFields[1] - full name field.
        // editorFields[2] - admin area field.
        // editorFields[3] - locality field.
        // editorFields[4] - dependent locality field.
        // editorFields[5] - organization field.
        // editorFields[6] - sorting code field.
        // editorFields[7] - postal code field.
        // editorFields[8] - street address field.
        // editorFields[9] - phone number field.
        assertEquals(10, editorFields.size());

        // Fields obtained from backend must be placed after the country dropdown.
        validateTextField(
                editorFields.get(1),
                profile.getFullName(),
                FieldType.NAME_FULL,
                /* label= */ "full name label",
                /* isRequired= */ true,
                /* isFullLine= */ true);
        validateTextField(
                editorFields.get(2),
                profile.getRegion(),
                FieldType.ADDRESS_HOME_STATE,
                /* label= */ "admin area label",
                /* isRequired= */ true,
                /* isFullLine= */ true);
        // Locality field is forced to occupy full line.
        validateTextField(
                editorFields.get(3),
                profile.getLocality(),
                FieldType.ADDRESS_HOME_CITY,
                /* label= */ "locality label",
                /* isRequired= */ true,
                /* isFullLine= */ true);

        // Note: dependent locality is a required field for address profiles stored in Google
        // account, but it's still marked as optional by the editor when the corresponding field in
        // the existing address profile is empty. It is considered required for new address
        // profiles.
        validateTextField(
                editorFields.get(4),
                profile.getDependentLocality(),
                FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY,
                /* label= */ "dependent locality label",
                /* isRequired= */ true,
                /* isFullLine= */ true);

        validateTextField(
                editorFields.get(5),
                profile.getCompanyName(),
                FieldType.COMPANY_NAME,
                /* label= */ "organization label",
                /* isRequired= */ false,
                /* isFullLine= */ true);

        validateTextField(
                editorFields.get(6),
                profile.getSortingCode(),
                FieldType.ADDRESS_HOME_SORTING_CODE,
                /* label= */ "sorting code label",
                /* isRequired= */ false,
                /* isFullLine= */ false);
        validateTextField(
                editorFields.get(7),
                profile.getPostalCode(),
                FieldType.ADDRESS_HOME_ZIP,
                /* label= */ "postal code label",
                /* isRequired= */ true,
                /* isFullLine= */ false);
        validateTextField(
                editorFields.get(8),
                profile.getStreetAddress(),
                FieldType.ADDRESS_HOME_STREET_ADDRESS,
                /* label= */ "street address label",
                /* isRequired= */ true,
                /* isFullLine= */ true);
        validateTextField(
                editorFields.get(9),
                profile.getPhoneNumber(),
                FieldType.PHONE_HOME_WHOLE_NUMBER,
                mActivity.getString(R.string.autofill_profile_editor_phone_number),
                /* isRequired= */ true,
                /* isFullLine= */ true);
    }

    private void validateErrorMessages(PropertyModel editorModel, boolean errorsPresent) {
        assertNotNull(editorModel);
        ListModel<FieldItem> editorFields = editorModel.get(EDITOR_FIELDS);
        assertEquals(10, editorFields.size());

        Matcher<String> requiredFieldMatcher =
                errorsPresent ? not(isEmptyString()) : anyOf(nullValue(), isEmptyString());
        assertThat(editorFields.get(0).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(1).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(2).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(3).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(4).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(
                editorFields.get(5).model.get(ERROR_MESSAGE), anyOf(nullValue(), isEmptyString()));
        assertThat(
                editorFields.get(6).model.get(ERROR_MESSAGE), anyOf(nullValue(), isEmptyString()));
        assertThat(editorFields.get(7).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(8).model.get(ERROR_MESSAGE), requiredFieldMatcher);
        assertThat(editorFields.get(9).model.get(ERROR_MESSAGE), requiredFieldMatcher);
    }

    @Test
    @SmallTest
    public void validateRequiredFieldIndicator() {
        setUpAddressUiComponents(new ArrayList(), /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, sProfile, mPersonalDataManager), unused -> {});

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        assertTrue(mAddressEditor.getEditorModelForTesting().get(SHOW_REQUIRED_INDICATOR));
    }

    @Test
    @SmallTest
    public void validateDefaultFields() {
        setUpAddressUiComponents(new ArrayList(), /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, sProfile, mPersonalDataManager), unused -> {});

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        ListModel<FieldItem> editorFields =
                mAddressEditor.getEditorModelForTesting().get(EDITOR_FIELDS);
        // Following values are set regardless of the UI components list
        // received from backend when nicknames are disabled:
        // editorFields[0] - country dropdown.
        // editorFields[1] - phone field.
        assertEquals(2, editorFields.size());

        FieldItem countryDropdownItem = editorFields.get(0);
        assertEquals(countryDropdownItem.type, DROPDOWN);
        assertTrue(countryDropdownItem.isFullLine);

        PropertyModel countryDropdown = countryDropdownItem.model;
        assertEquals(
                countryDropdown.get(VALUE),
                AutofillAddress.getCountryCode(sProfile, mPersonalDataManager));
        assertEquals(
                countryDropdown.get(LABEL),
                mActivity.getString(R.string.autofill_profile_editor_country));
        assertEquals(
                mSupportedCountries.size(), countryDropdown.get(DROPDOWN_KEY_VALUE_LIST).size());
        assertThat(
                mSupportedCountries,
                containsInAnyOrder(countryDropdown.get(DROPDOWN_KEY_VALUE_LIST).toArray()));

        validateTextField(
                editorFields.get(1),
                sProfile.getPhoneNumber(),
                FieldType.PHONE_HOME_WHOLE_NUMBER,
                mActivity.getString(R.string.autofill_profile_editor_phone_number),
                /* isRequired= */ true,
                /* isFullLine= */ true);
    }

    @Test
    @SmallTest
    public void validateAdminAreaDropdown() {
        // Configure only admin area field to keep the test focused.
        setUpAddressUiComponents(
                List.of(
                        new AutofillAddressUiComponent(
                                FieldType.ADDRESS_HOME_STATE,
                                "admin area label",
                                /* isRequired= */ true,
                                /* isFullLine= */ true)),
                /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(
                                    new String[] {"CA", "NY", "TX"},
                                    new String[] {"California", "New York", "Texas"});
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());
        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, sProfile, mPersonalDataManager), unused -> {});

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        ListModel<FieldItem> editorFields =
                mAddressEditor.getEditorModelForTesting().get(EDITOR_FIELDS);
        // Following values are set regardless of the UI components list
        // received from backend when nicknames are disabled:
        // editorFields[0] - country dropdown.
        // editorFields[1] - admin area dropdown.
        // editorFields[2] - phone field.
        assertEquals(3, editorFields.size());

        FieldItem adminAreaDropdownItem = editorFields.get(1);
        assertEquals(DROPDOWN, adminAreaDropdownItem.type);
        assertTrue(adminAreaDropdownItem.isFullLine);

        PropertyModel adminAreaDropdown = adminAreaDropdownItem.model;
        List<DropdownKeyValue> adminAreas =
                List.of(
                        new DropdownKeyValue("CA", "California"),
                        new DropdownKeyValue("NY", "New York"),
                        new DropdownKeyValue("TX", "Texas"));
        assertThat(
                adminAreas,
                containsInAnyOrder(adminAreaDropdown.get(DROPDOWN_KEY_VALUE_LIST).toArray()));

        assertEquals(adminAreaDropdown.get(VALUE), sProfile.getRegion());
        assertEquals(adminAreaDropdown.get(LABEL), "admin area label");
    }

    @Test
    @SmallTest
    public void validateShownFields_NewAddressProfile() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(null, unused -> {});

        validateShownFields(
                mAddressEditor.getEditorModelForTesting(), AutofillProfile.builder().build());
    }

    @Test
    @SmallTest
    public void validateShownFields_ExistingAddressProfile() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, sProfile, mPersonalDataManager), unused -> {});

        validateShownFields(mAddressEditor.getEditorModelForTesting(), sProfile);
    }

    @Test
    @SmallTest
    public void edit_ChangeCountry_FieldsSetChanges() {
        setUpAddressUiComponents(
                List.of(
                        new AutofillAddressUiComponent(
                                FieldType.ADDRESS_HOME_SORTING_CODE,
                                "sorting code label",
                                /* isRequired= */ false,
                                /* isFullLine= */ true)),
                /* countryCode= */ "US");
        setUpAddressUiComponents(
                List.of(
                        new AutofillAddressUiComponent(
                                FieldType.ADDRESS_HOME_STREET_ADDRESS,
                                "street address label",
                                /* isRequired= */ true,
                                /* isFullLine= */ true)),
                /* countryCode= */ "DE");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());
        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(null, unused -> {});

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        ListModel<FieldItem> editorFields =
                mAddressEditor.getEditorModelForTesting().get(EDITOR_FIELDS);

        // editorFields[0] - country dropdown.
        // editorFields[1] - sorting code field.
        // editorFields[2] - phone number field.
        assertEquals(3, editorFields.size());
        assertThat(
                StreamSupport.stream(
                                Spliterators.spliteratorUnknownSize(
                                        editorFields.iterator(), Spliterator.ORDERED),
                                false)
                        .skip(1)
                        .map(
                                item -> {
                                    return item.model.get(TEXT_FIELD_TYPE);
                                })
                        .collect(Collectors.toList()),
                containsInAnyOrder(
                        FieldType.ADDRESS_HOME_SORTING_CODE, FieldType.PHONE_HOME_WHOLE_NUMBER));
        PropertyModel countryDropdown = editorFields.get(0).model;

        setDropdownKey(countryDropdown, "DE");
        ListModel<FieldItem> editorFieldsGermany =
                mAddressEditor.getEditorModelForTesting().get(EDITOR_FIELDS);
        // editorFields[0] - country dropdown.
        // editorFields[1] - street address field.
        // editorFields[2] - phone number field.
        assertEquals(3, editorFieldsGermany.size());
        assertThat(
                StreamSupport.stream(
                                Spliterators.spliteratorUnknownSize(
                                        editorFieldsGermany.iterator(), Spliterator.ORDERED),
                                false)
                        .skip(1)
                        .map(
                                item -> {
                                    return item.model.get(TEXT_FIELD_TYPE);
                                })
                        .collect(Collectors.toList()),
                containsInAnyOrder(
                        FieldType.ADDRESS_HOME_STREET_ADDRESS, FieldType.PHONE_HOME_WHOLE_NUMBER));
    }

    @Test
    @SmallTest
    public void edit_AlterAddressProfile_Cancel() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());
        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, new AutofillProfile(sProfile), mPersonalDataManager),
                mDoneCallback,
                mCancelCallback);

        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        assertNotNull(editorModel);
        ListModel<FieldItem> editorFields = editorModel.get(EDITOR_FIELDS);
        assertEquals(10, editorFields.size());

        // Verify behaviour only on the relevant subset of fields.
        editorFields.get(1).model.set(VALUE, "New Name");
        editorFields.get(2).model.set(VALUE, "New admin area");
        editorFields.get(3).model.set(VALUE, "New locality");
        editorModel.get(CANCEL_RUNNABLE).run();

        verify(mDoneCallback, times(0)).onResult(any());
        verify(mCancelCallback, times(1)).onResult(any());
    }

    @Test
    @SmallTest
    public void edit_AlterAddressProfile_CommitChanges() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, new AutofillProfile(sProfile), mPersonalDataManager),
                mDoneCallback,
                mCancelCallback);

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        ListModel<FieldItem> editorFields = editorModel.get(EDITOR_FIELDS);
        assertEquals(10, editorFields.size());

        // Verify behaviour only on the relevant subset of fields.
        editorFields.get(3).model.set(VALUE, "New locality");
        editorFields.get(4).model.set(VALUE, "New dependent locality");
        editorFields.get(5).model.set(VALUE, "New organization");
        editorModel.get(DONE_RUNNABLE).run();

        verify(mDoneCallback, times(1)).onResult(mAddressCapture.capture());
        verify(mCancelCallback, times(0)).onResult(any());
        AutofillAddress address = mAddressCapture.getValue();
        assertNotNull(address);
        assertEquals("New locality", address.getProfile().getLocality());
        assertEquals("New dependent locality", address.getProfile().getDependentLocality());
        assertEquals("New organization", address.getProfile().getCompanyName());
    }

    @Test
    @SmallTest
    public void edit_AlterAddressProfile_CommitChanges_InvisibleFieldsNotReset() {
        // Make all fields optional to avoid setting them manually.
        doNothing().when(mAutofillProfileBridgeJni).getRequiredFields(anyString(), anyList());
        // Whitelist only full name, admin area and locality.
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS.subList(0, 3), /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, new AutofillProfile(sProfile), mPersonalDataManager),
                mDoneCallback,
                mCancelCallback);

        assertNotNull(mAddressEditor.getEditorModelForTesting());
        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        ListModel<FieldItem> editorFields = editorModel.get(EDITOR_FIELDS);
        // editorFields[0] - country dropdown.
        // editorFields[1] - full name field.
        // editorFields[2] - admin area field.
        // editorFields[3] - locality field.
        // editorFields[4] - phone number field.
        assertEquals(5, editorFields.size());

        editorModel.get(DONE_RUNNABLE).run();
        verify(mDoneCallback, times(1)).onResult(mAddressCapture.capture());
        verify(mCancelCallback, times(0)).onResult(any());

        AutofillAddress address = mAddressCapture.getValue();
        assertNotNull(address);
        AutofillProfile profile = address.getProfile();
        assertEquals(profile.getStreetAddress(), "111 First St");
        assertEquals(profile.getDependentLocality(), "");
        assertEquals(profile.getCompanyName(), "Google");
        assertEquals(profile.getPostalCode(), "90291");
        assertEquals(profile.getSortingCode(), "");
    }

    @Test
    @SmallTest
    public void edit_NewAddressProfile_NoInitialValidation() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(null, unused -> {});

        validateErrorMessages(
                mAddressEditor.getEditorModelForTesting(), /* errorsPresent= */ false);
    }

    @Test
    @SmallTest
    public void edit_NewAddressProfile_FieldsAreValidatedAfterSave() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(null, unused -> {});

        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        assertNotNull(editorModel);
        editorModel.get(DONE_RUNNABLE).run();

        validateErrorMessages(mAddressEditor.getEditorModelForTesting(), /* errorsPresent= */ true);
    }

    @Test
    @SmallTest
    public void edit_AccountAddressProfile_FieldsAreImmediatelyValidated() {
        AutofillProfile profile = new AutofillProfile(sProfile);
        profile.setInfo(FieldType.ADDRESS_HOME_STATE, "");
        profile.setInfo(FieldType.ADDRESS_HOME_CITY, "");
        profile.setInfo(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY, "");
        profile.setInfo(FieldType.ADDRESS_HOME_ZIP, "");
        profile.setInfo(FieldType.ADDRESS_HOME_STREET_ADDRESS, "");

        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, profile, mPersonalDataManager), unused -> {});

        validateErrorMessages(mAddressEditor.getEditorModelForTesting(), /* errorsPresent= */ true);
    }

    @Test
    @SmallTest
    public void edit_AccountAddressProfile_FieldsAreValidatedAfterSave() {
        AutofillProfile profile = new AutofillProfile(sProfile);
        profile.setInfo(FieldType.ADDRESS_HOME_STATE, "");
        profile.setInfo(FieldType.ADDRESS_HOME_CITY, "");
        profile.setInfo(FieldType.ADDRESS_HOME_DEPENDENT_LOCALITY, "");
        profile.setInfo(FieldType.ADDRESS_HOME_ZIP, "");
        profile.setInfo(FieldType.ADDRESS_HOME_STREET_ADDRESS, "");

        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, profile, mPersonalDataManager), unused -> {});

        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        assertNotNull(editorModel);
        editorModel.get(DONE_RUNNABLE).run();

        validateErrorMessages(mAddressEditor.getEditorModelForTesting(), /* errorsPresent= */ true);
    }

    @Test
    @SmallTest
    public void edit_AccountAddressProfile_EmptyFieldsAreValidatedAfterSave() {
        setUpAddressUiComponents(SUPPORTED_ADDRESS_FIELDS, /* countryCode= */ "US");
        doAnswer(
                        unused -> {
                            mAddressEditor.onSubKeysReceived(null, null);
                            return null;
                        })
                .when(mSubKeyRequester)
                .getRegionSubKeys(anyString(), any());

        mAddressEditor = new AddressEditor(mPersonalDataManager, /* saveToDisk= */ false);
        mAddressEditor.setEditorDialog(mEditorDialog);
        mAddressEditor.edit(
                new AutofillAddress(mActivity, new AutofillProfile(sProfile), mPersonalDataManager),
                unused -> {});

        PropertyModel editorModel = mAddressEditor.getEditorModelForTesting();
        assertNotNull(editorModel);

        ListModel<FieldItem> model = editorModel.get(EDITOR_FIELDS);
        assertEquals(10, model.size());
        for (FieldItem item : model) {
            if (item.model.get(IS_REQUIRED)) {
                item.model.set(VALUE, "");
            }
        }

        editorModel.get(DONE_RUNNABLE).run();

        validateErrorMessages(mAddressEditor.getEditorModelForTesting(), /* errorsPresent= */ true);
    }
}