chromium/chrome/browser/touch_to_fill/password_manager/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillRenderTest.java

// Copyright 2022 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.touch_to_fill;

import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.CREDENTIAL;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.FORMATTED_ORIGIN;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.ON_CLICK_LISTENER;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.SHOW_SUBMIT_BUTTON;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.FooterProperties.MANAGE_BUTTON_TEXT;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.IMAGE_DRAWABLE_ID;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.SUBTITLE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties.TITLE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.SHEET_ITEMS;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.WebAuthnCredentialProperties.SHOW_WEBAUTHN_SUBMIT_BUTTON;
import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.WebAuthnCredentialProperties.WEBAUTHN_CREDENTIAL;
import static org.chromium.ui.base.LocalizationUtils.setRtlForTesting;

import static java.util.Arrays.asList;

import android.view.View;
import android.view.ViewGroup;

import androidx.test.filters.MediumTest;

import org.junit.After;
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.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
import org.chromium.chrome.browser.password_manager.GetLoginMatchType;
import org.chromium.chrome.browser.password_manager.PasswordManagerResourceProvider;
import org.chromium.chrome.browser.password_manager.PasswordManagerResourceProviderFactory;
import org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties;
import org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.FooterProperties;
import org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.HeaderProperties;
import org.chromium.chrome.browser.touch_to_fill.data.Credential;
import org.chromium.chrome.browser.touch_to_fill.data.WebauthnCredential;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.test.util.RenderTestRule.Component;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;

import java.util.Arrays;
import java.util.List;

/** These tests render screenshots of touch to fill sheet and compare them to a gold standard. */
@RunWith(ParameterizedRunner.class)
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@DoNotBatch(reason = "The tests can't be batched because they run for different set-ups.")
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class TouchToFillRenderTest {
    @ParameterAnnotations.ClassParameter
    private static List<ParameterSet> sClassParams =
            Arrays.asList(
                    new ParameterSet().value(false, false).name("Default"),
                    new ParameterSet().value(false, true).name("RTL"),
                    new ParameterSet().value(true, false).name("NightMode"));

    private static final GURL TEST_URL = JUnitTestGURLs.EXAMPLE_URL;
    private static final Credential ARON =
            new Credential("אהרן", "S3cr3t", "אהרן", "", "example.com", GetLoginMatchType.EXACT, 0);
    private static final Credential BOB =
            new Credential("Bob", "*****", "Bob", "", "example.com", GetLoginMatchType.EXACT, 0);
    private static final Credential MARIAM =
            new Credential("مريم", "***", "مريم", "", "example.com", GetLoginMatchType.EXACT, 0);
    private static final byte[] RANDOM_ID = new byte[] {0};
    private static final WebauthnCredential BATMAN =
            new WebauthnCredential("example.com", RANDOM_ID, RANDOM_ID, "batman");
    private static final WebauthnCredential PETROL =
            new WebauthnCredential("example.com", RANDOM_ID, RANDOM_ID, "petrol");
    private static final WebauthnCredential SPOR =
            new WebauthnCredential("example.com", RANDOM_ID, RANDOM_ID, "spor");

    @Mock private Callback<Integer> mDismissHandler;
    @Mock private Callback<Credential> mCredentialCallback;

    private PropertyModel mModel;
    private TouchToFillView mTouchToFillView;
    private BottomSheetController mBottomSheetController;
    PasswordManagerResourceProvider mResourceProvider;

    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    @Rule
    public final ChromeRenderTestRule mRenderTestRule =
            ChromeRenderTestRule.Builder.withPublicCorpus()
                    .setRevision(8)
                    .setBugComponent(Component.UI_BROWSER_AUTOFILL)
                    .build();

    public TouchToFillRenderTest(boolean nightModeEnabled, boolean useRtlLayout) {
        setRtlForTesting(useRtlLayout);
        ChromeNightModeTestUtils.setUpNightModeForChromeActivity(nightModeEnabled);
        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
        mRenderTestRule.setVariantPrefix(useRtlLayout ? "RTL" : "LTR");
    }

    @Before
    public void setUp() throws InterruptedException {
        MockitoAnnotations.initMocks(this);
        mActivityTestRule.startMainActivityOnBlankPage();
        mActivityTestRule.waitForActivityCompletelyLoaded();
        mBottomSheetController =
                mActivityTestRule
                        .getActivity()
                        .getRootUiCoordinatorForTesting()
                        .getBottomSheetController();
        mResourceProvider = PasswordManagerResourceProviderFactory.create();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModel = TouchToFillProperties.createDefaultModel(mDismissHandler);
                    mTouchToFillView =
                            new TouchToFillView(
                                    mActivityTestRule.getActivity(), mBottomSheetController);
                    TouchToFillCoordinator.setUpModelChangeProcessors(mModel, mTouchToFillView);
                });
    }

    @After
    public void tearDown() {
        setRtlForTesting(false);
        try {
            ApplicationTestUtils.finishActivity(mActivityTestRule.getActivity());
        } catch (Exception e) {
            // Activity was already closed (e.g. due to last test tearing down the suite).
        }
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
                });
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsOneCredentialModern() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS).addAll(asList(buildCredentialItem(ARON)));
                    addButton(ARON);
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        View bottomSheetView = mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet);
        mRenderTestRule.render(bottomSheetView, "ttf_shows_one_credential_modern_ui");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsOneCredentialModernHalfState() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS).addAll(asList(buildCredentialItem(ARON)));
                    addButton(ARON);
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        ViewGroup bottomSheetParentView =
                (ViewGroup)
                        mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet).getParent();
        mRenderTestRule.render(
                bottomSheetParentView, "ttf_shows_one_credential_modern_ui_half_state");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsTwoCredentialsModern() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS);
                    mModel.get(SHEET_ITEMS)
                            .addAll(asList(buildCredentialItem(ARON), buildCredentialItem(BOB)));
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        View bottomSheetView = mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet);
        mRenderTestRule.render(bottomSheetView, "ttf_shows_two_credentials_modern_ui");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsTwoCredentialsModernHalfState() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS);
                    mModel.get(SHEET_ITEMS)
                            .addAll(asList(buildCredentialItem(ARON), buildCredentialItem(BOB)));
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        ViewGroup bottomSheetParentView =
                (ViewGroup)
                        mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet).getParent();
        mRenderTestRule.render(
                bottomSheetParentView, "ttf_shows_two_credentials_modern_ui_half_state");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsThreeCredentialsModern() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS)
                            .addAll(
                                    asList(
                                            buildCredentialItem(ARON),
                                            buildCredentialItem(BOB),
                                            buildCredentialItem(MARIAM)));
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        View bottomSheetView = mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet);
        mRenderTestRule.render(bottomSheetView, "ttf_shows_three_credentials_modern_ui");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsThreeCredentialsModernHalfState() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS)
                            .addAll(
                                    asList(
                                            buildCredentialItem(ARON),
                                            buildCredentialItem(BOB),
                                            buildCredentialItem(MARIAM)));
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        ViewGroup bottomSheetParentView =
                (ViewGroup)
                        mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet).getParent();
        mRenderTestRule.render(
                bottomSheetParentView, "ttf_shows_three_credentials_modern_ui_half_state");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    public void testShowsThreeCredentialsWhenThereAreFiveModernHalfState() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    addHeader(
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.touch_to_fill_sheet_uniform_title));
                    mModel.get(SHEET_ITEMS)
                            .addAll(
                                    asList(
                                            buildWebAuthnCredentialItem(BATMAN),
                                            buildWebAuthnCredentialItem(PETROL),
                                            buildWebAuthnCredentialItem(SPOR),
                                            buildCredentialItem(ARON),
                                            buildCredentialItem(BOB)));
                    addFooter();
                    mModel.set(VISIBLE, true);
                });

        BottomSheetTestSupport.waitForOpen(mBottomSheetController);

        ViewGroup bottomSheetParentView =
                (ViewGroup)
                        mActivityTestRule.getActivity().findViewById(R.id.bottom_sheet).getParent();
        mRenderTestRule.render(
                bottomSheetParentView,
                "ttf_shows_three_credentials_when_there_are_five_modern_ui_half_state");
    }

    private MVCListAdapter.ListItem buildCredentialItem(Credential credential) {
        return buildSheetItem(
                TouchToFillProperties.ItemType.CREDENTIAL, credential, mCredentialCallback, false);
    }

    private MVCListAdapter.ListItem buildWebAuthnCredentialItem(WebauthnCredential credential) {
        return new MVCListAdapter.ListItem(
                TouchToFillProperties.ItemType.WEBAUTHN_CREDENTIAL,
                new PropertyModel.Builder(
                                TouchToFillProperties.WebAuthnCredentialProperties.ALL_KEYS)
                        .with(WEBAUTHN_CREDENTIAL, credential)
                        .with(SHOW_WEBAUTHN_SUBMIT_BUTTON, false)
                        .build());
    }

    private static MVCListAdapter.ListItem buildSheetItem(
            @TouchToFillProperties.ItemType int itemType,
            Credential credential,
            Callback<Credential> callback,
            boolean showSubmitButton) {
        return new MVCListAdapter.ListItem(
                itemType,
                new PropertyModel.Builder(TouchToFillProperties.CredentialProperties.ALL_KEYS)
                        .with(CREDENTIAL, credential)
                        .with(ON_CLICK_LISTENER, callback)
                        .with(FORMATTED_ORIGIN, credential.getDisplayName())
                        .with(SHOW_SUBMIT_BUTTON, showSubmitButton)
                        .build());
    }

    private void addHeader(String title) {
        mModel.get(SHEET_ITEMS)
                .add(
                        new MVCListAdapter.ListItem(
                                TouchToFillProperties.ItemType.HEADER,
                                new PropertyModel.Builder(HeaderProperties.ALL_KEYS)
                                        .with(TITLE, title)
                                        .with(SUBTITLE, TEST_URL.getSpec())
                                        .with(
                                                IMAGE_DRAWABLE_ID,
                                                mResourceProvider.getPasswordManagerIcon())
                                        .build()));
    }

    private void addButton(Credential credential) {
        mModel.get(SHEET_ITEMS)
                .add(
                        new MVCListAdapter.ListItem(
                                TouchToFillProperties.ItemType.FILL_BUTTON,
                                new PropertyModel.Builder(CredentialProperties.ALL_KEYS)
                                        .with(CREDENTIAL, ARON)
                                        .with(
                                                ON_CLICK_LISTENER,
                                                (Credential clickedCredential) -> {})
                                        .with(FORMATTED_ORIGIN, credential.getDisplayName())
                                        .with(SHOW_SUBMIT_BUTTON, true)
                                        .build()));
    }

    private void addFooter() {
        mModel.get(SHEET_ITEMS)
                .add(
                        new MVCListAdapter.ListItem(
                                TouchToFillProperties.ItemType.FOOTER,
                                new PropertyModel.Builder(FooterProperties.ALL_KEYS)
                                        .with(
                                                MANAGE_BUTTON_TEXT,
                                                mActivityTestRule
                                                        .getActivity()
                                                        .getString(R.string.manage_passwords))
                                        .build()));
    }
}