chromium/chrome/android/features/keyboard_accessory/junit/src/org/chromium/chrome/browser/keyboard_accessory/all_passwords_bottom_sheet/AllPasswordsBottomSheetControllerTest.java

// Copyright 2020 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.keyboard_accessory.all_passwords_bottom_sheet;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.CredentialProperties.CREDENTIAL;
import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.DISMISS_HANDLER;
import static org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.VISIBLE;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.keyboard_accessory.all_passwords_bottom_sheet.AllPasswordsBottomSheetProperties.ItemType;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
import org.chromium.ui.modelutil.ListModel;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.ArrayList;
import java.util.List;

/** Controller tests for the all passwords bottom sheet. */
@RunWith(BaseRobolectricTestRunner.class)
@EnableFeatures(ChromeFeatureList.FILLING_PASSWORDS_FROM_ANY_ORIGIN)
public class AllPasswordsBottomSheetControllerTest {
    private static final Credential ANA =
            new Credential(
                    /* username= */ "[email protected]",
                    /* password= */ "S3cr3t",
                    /* formattedUsername= */ "[email protected]",
                    /* originUrl= */ "https://m.domain.xyz/",
                    /* isAndroidCredential= */ false,
                    /* appDisplayName= */ "",
                    /* isPlusAddressUsername= */ true);
    private static final Credential BOB =
            new Credential(
                    /* username= */ "Bob",
                    /* password= */ "*****",
                    /* formattedUsername= */ "Bob",
                    /* originUrl= */ "https://subdomain.example.xyz",
                    /* isAndroidCredential= */ false,
                    /* appDisplayName= */ "",
                    /* isPlusAddressUsername= */ false);
    private static final Credential CARL =
            new Credential(
                    /* username= */ "Carl",
                    /* password= */ "G3h3!m",
                    /* formattedUsername= */ "Carl",
                    /* originUrl= */ "https://www.origin.xyz",
                    /* isAndroidCredential= */ false,
                    /* appDisplayName= */ "",
                    /* isPlusAddressUsername= */ false);
    private static final List<Credential> TEST_CREDENTIALS = List.of(BOB, CARL, ANA);
    private static final boolean IS_PASSWORD_FIELD = true;
    private static final String EXAMPLE_ORIGIN = "https://m.example.com/";

    @Rule public JniMocker mJniMocker = new JniMocker();
    @Mock private UrlUtilities.Natives mUrlUtilitiesJniMock;
    @Mock private AllPasswordsBottomSheetCoordinator.Delegate mMockDelegate;

    private AllPasswordsBottomSheetMediator mMediator;
    private PropertyModel mModel;
    private ListModel<ListItem> mListModel;
    private PropertyModel mModalDialogModel;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
        mMediator = new AllPasswordsBottomSheetMediator();
        mModel =
                AllPasswordsBottomSheetProperties.createDefaultModel(
                        EXAMPLE_ORIGIN, mMediator::onDismissed, mMediator::onQueryTextChange);
        mListModel = new ListModel<>();
        mMediator.initialize(mMockDelegate, mModel, mListModel);

        when(mUrlUtilitiesJniMock.getDomainAndRegistry(anyString(), anyBoolean()))
                .then(inv -> getDomainAndRegistry(inv.getArgument(0)));
    }

    @Test
    public void testCreatesValidDefaultModel() {
        assertNotNull(mListModel);
        assertNotNull(mModel.get(DISMISS_HANDLER));
        assertThat(mModel.get(VISIBLE), is(false));
    }

    @Test
    public void testShowCredentialSetsCredentialListModel() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);

        ListModel<ListItem> itemList = mListModel;
        assertThat(itemList.size(), is(3));

        assertThat(itemList.get(0).type, is(ItemType.CREDENTIAL));
        assertThat(itemList.get(0).model.get(CREDENTIAL), is(ANA));
        assertThat(itemList.get(1).type, is(ItemType.CREDENTIAL));
        assertThat(itemList.get(1).model.get(CREDENTIAL), is(BOB));
        assertThat(itemList.get(2).type, is(ItemType.CREDENTIAL));
        assertThat(itemList.get(2).model.get(CREDENTIAL), is(CARL));
    }

    @Test
    public void testOnCredentialSelected() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);
        CredentialFillRequest request = new CredentialFillRequest(TEST_CREDENTIALS.get(1), true);
        mMediator.onCredentialSelected(request);
        assertThat(mModel.get(VISIBLE), is(false));
        verify(mMockDelegate).onCredentialSelected(request);
    }

    @Test
    public void testOnDismiss() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);
        mMediator.onDismissed(BottomSheetController.StateChangeReason.BACK_PRESS);
        assertThat(mModel.get(VISIBLE), is(false));
        verify(mMockDelegate).onDismissed();
    }

    @Test
    public void testSearchFilterByUsername() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);
        mMediator.onQueryTextChange("Bob");
        assertThat(mListModel.size(), is(1));
    }

    @Test
    public void testSearchFilterByURL() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);
        mMediator.onQueryTextChange("subdomain");
        assertThat(mListModel.size(), is(1));
    }

    @Test
    public void testCredentialSortedByOrigin() {
        mMediator.showCredentials(new ArrayList<>(TEST_CREDENTIALS), IS_PASSWORD_FIELD);

        ListModel<ListItem> itemList = mListModel;

        assertThat(itemList.get(0).model.get(CREDENTIAL), is(ANA));
        assertThat(itemList.get(1).model.get(CREDENTIAL), is(BOB));
        assertThat(itemList.get(2).model.get(CREDENTIAL), is(CARL));
    }

    /**
     * Helper to get organization-identifying host from URLs. The real implementation calls {@link
     * UrlUtilities}. It's not useful to actually reimplement it, so just return a string in a
     * trivial way.
     *
     * @param origin A URL.
     * @return The organization-identifying host from the given URL.
     */
    private String getDomainAndRegistry(String origin) {
        return origin.replaceAll(".*\\.(.+\\.[^.]+$)", "$1");
    }
}