chromium/chrome/browser/ui/android/plus_addresses/junit/src/org/chromium/chrome/browser/ui/plus_addresses/PlusAddressCreationBottomSheetContentTest.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.ui.plus_addresses;

import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;

import android.app.Activity;
import android.text.style.ClickableSpan;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowView;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.ContentPriority;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent.HeightMode;
import org.chromium.ui.base.TestActivity;
import org.chromium.ui.widget.LoadingView;
import org.chromium.ui.widget.TextViewWithClickableSpans;
import org.chromium.url.GURL;

import java.util.concurrent.TimeoutException;

@RunWith(BaseRobolectricTestRunner.class)
@Config(
        manifest = Config.NONE,
        shadows = {ShadowView.class})
@LooperMode(LooperMode.Mode.LEGACY)
public class PlusAddressCreationBottomSheetContentTest {
    private static final String MODAL_TITLE = "lorem ipsum title";
    private static final String MODAL_PLUS_ADDRESS_DESCRIPTION = "lorem ipsum description";
    private static final String MODAL_PLUS_ADDRESS_NOTICE =
            "lorem ipsum description <link>test link</link>";
    private static final String MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER = "placeholder";
    private static final String MODAL_OK = "ok";
    private static final String MODAL_CANCEL = "ok";
    private static final String MODAL_PROPOSED_PLUS_ADDRESS = "[email protected]";
    private static final String MODAL_ERROR_MESSAGE = "error! <link>test link</link>";
    private static final String MODAL_FORMATTED_ERROR_MESSAGE = "error! test link";
    private static final GURL LEARN_MORE_URL = new GURL("learn.more.com");
    private static final GURL ERROR_URL = new GURL("bug.com");
    private static final boolean REFRESH_SUPPORTED = true;

    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Mock private PlusAddressCreationDelegate mDelegate;

    private Activity mActivity;
    private PlusAddressCreationBottomSheetContent mBottomSheetContent;

    @Before
    public void setUp() {
        mActivity = Robolectric.setupActivity(TestActivity.class);
        // Disabling animations is necessary to avoid running into issues with
        // delayed hiding of loading views.
        LoadingView.setDisableAnimationForTest(true);
        mBottomSheetContent =
                new PlusAddressCreationBottomSheetContent(
                        mActivity,
                        MODAL_TITLE,
                        MODAL_PLUS_ADDRESS_DESCRIPTION,
                        MODAL_PLUS_ADDRESS_NOTICE,
                        MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER,
                        MODAL_OK,
                        MODAL_CANCEL,
                        MODAL_ERROR_MESSAGE,
                        LEARN_MORE_URL,
                        ERROR_URL,
                        REFRESH_SUPPORTED);
        mBottomSheetContent.setDelegate(mDelegate);
    }

    @Test
    @SmallTest
    public void testBottomSheetStrings() {
        TextView modalTitleView =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_notice_title);
        TextViewWithClickableSpans modalDescriptionView =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_modal_explanation);
        TextView modalPlusAddressPlaceholderView =
                mBottomSheetContent.getContentView().findViewById(R.id.proposed_plus_address);
        Button modalConfirmButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_confirm_button);

        Assert.assertEquals(modalTitleView.getText().toString(), MODAL_TITLE);
        Assert.assertEquals(
                modalDescriptionView.getText().toString(), MODAL_PLUS_ADDRESS_DESCRIPTION);
        Assert.assertEquals(
                modalPlusAddressPlaceholderView.getText().toString(),
                MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER);
        Assert.assertEquals(modalConfirmButton.getText().toString(), MODAL_OK);

        // Validate updates to the bottomsheet.
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);
        Assert.assertEquals(
                modalPlusAddressPlaceholderView.getText().toString(), MODAL_PROPOSED_PLUS_ADDRESS);
    }

    @Test
    @SmallTest
    public void testRefreshButton_RefreshSupported() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);

        mBottomSheetContent.hideRefreshButton();
        Assert.assertEquals(refreshIcon.getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    public void testRefreshButton_RefreshNotSupported() {
        PlusAddressCreationBottomSheetContent bottomSheetContent =
                new PlusAddressCreationBottomSheetContent(
                        mActivity,
                        MODAL_TITLE,
                        MODAL_PLUS_ADDRESS_DESCRIPTION,
                        MODAL_PLUS_ADDRESS_NOTICE,
                        MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER,
                        MODAL_OK,
                        MODAL_CANCEL,
                        MODAL_ERROR_MESSAGE,
                        LEARN_MORE_URL,
                        ERROR_URL,
                        /* refreshSupported= */ false);
        ImageView refreshIcon =
                bottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    public void testFirstTimeUsage() {
        TextView firstTimeNotice =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_first_time_use_notice);
        Button cancelButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_cancel_button);
        Assert.assertEquals(firstTimeNotice.getVisibility(), View.VISIBLE);
        Assert.assertEquals(cancelButton.getVisibility(), View.VISIBLE);

        cancelButton.callOnClick();
        verify(mDelegate).onCanceled();
    }

    @Test
    @SmallTest
    public void testSecondTimeUsage() {
        PlusAddressCreationBottomSheetContent bottomSheetContent =
                new PlusAddressCreationBottomSheetContent(
                        mActivity,
                        MODAL_TITLE,
                        MODAL_PLUS_ADDRESS_DESCRIPTION,
                        /* plusAddressNotice= */ null,
                        MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER,
                        MODAL_OK,
                        MODAL_CANCEL,
                        MODAL_ERROR_MESSAGE,
                        LEARN_MORE_URL,
                        ERROR_URL,
                        /* refreshSupported= */ false);
        TextView firstTimeNotice =
                bottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_first_time_use_notice);
        Button cancelButton =
                bottomSheetContent.getContentView().findViewById(R.id.plus_address_cancel_button);
        Assert.assertEquals(firstTimeNotice.getVisibility(), View.GONE);
        Assert.assertEquals(cancelButton.getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    public void testRefreshButton_NotClickableUntilPlusAddressIsSet() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);
        refreshIcon.callOnClick();
        verifyNoInteractions(mDelegate);
    }

    @Test
    @SmallTest
    public void testRefreshButton_ClickableAfterPlusAddressIsSet() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);

        refreshIcon.callOnClick();
        verify(mDelegate).onRefreshClicked();
    }

    @Test
    @SmallTest
    public void testRefreshButton_OnlyOneClickIsHandledPerRefresh() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);

        refreshIcon.callOnClick();
        refreshIcon.callOnClick();
        verify(mDelegate).onRefreshClicked();
    }

    @Test
    @SmallTest
    public void testRefreshButton_RefreshSeveralTimes() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);

        refreshIcon.callOnClick();
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);

        refreshIcon.callOnClick();
        verify(mDelegate, times(2)).onRefreshClicked();
    }

    @Test
    @SmallTest
    public void testRefreshButton_HideRefreshButton() {
        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertEquals(refreshIcon.getVisibility(), View.VISIBLE);

        mBottomSheetContent.hideRefreshButton();
        Assert.assertEquals(refreshIcon.getVisibility(), View.GONE);
    }

    @Test
    @SmallTest
    public void testConfirmButton_disabledUntilPlusAddressIsSet() {
        Button modalConfirmButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_confirm_button);

        Assert.assertFalse(modalConfirmButton.isEnabled());
        // Update the bottomsheet to show the plus address.
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);
        Assert.assertTrue(modalConfirmButton.isEnabled());

        // Updating it while the button is enabled doesn't have an effect.
        mBottomSheetContent.setProposedPlusAddress("[email protected]");
        Assert.assertTrue(modalConfirmButton.isEnabled());
    }

    @Test
    @SmallTest
    public void testConfirmButton_disabledIfConfirmRequestFails() {
        Button modalConfirmButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_confirm_button);
        // Set the plus address to enable the Confirm button.
        mBottomSheetContent.setProposedPlusAddress(MODAL_PROPOSED_PLUS_ADDRESS);
        Assert.assertTrue(modalConfirmButton.isEnabled());

        // Assume a Confirm request was made and failed.
        mBottomSheetContent.showError();
        Assert.assertFalse(modalConfirmButton.isEnabled());
    }

    @Test
    @SmallTest
    public void testShowError_displaysErrorMessage() {
        TextView modalPlusAddressPlaceholderView =
                mBottomSheetContent.getContentView().findViewById(R.id.proposed_plus_address);
        Assert.assertEquals(
                modalPlusAddressPlaceholderView.getText().toString(),
                MODAL_PROPOSED_PLUS_ADDRESS_PLACEHOLDER);
        Assert.assertEquals(modalPlusAddressPlaceholderView.getVisibility(), View.VISIBLE);

        TextViewWithClickableSpans plusAddressErrorReportView =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_modal_error_report);
        Assert.assertEquals(plusAddressErrorReportView.getVisibility(), View.GONE);

        mBottomSheetContent.showError();
        Assert.assertEquals(
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.proposed_plus_address_container)
                        .getVisibility(),
                View.GONE);
        Assert.assertEquals(plusAddressErrorReportView.getVisibility(), View.VISIBLE);
        Assert.assertEquals(
                plusAddressErrorReportView.getText().toString(), MODAL_FORMATTED_ERROR_MESSAGE);
    }

    @Test
    @SmallTest
    public void testBottomsheetLinkClicked_callsDelegateOpenErrorReportLink() {
        TextViewWithClickableSpans errorReportInstruction =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_modal_error_report);
        ClickableSpan[] spans = errorReportInstruction.getClickableSpans();
        Assert.assertEquals(spans.length, 1);
        spans[0].onClick(errorReportInstruction);

        verify(mDelegate).openUrl(ERROR_URL);
    }

    @Test
    @SmallTest
    public void testLearnMoreLickClicked_callsDelegateOpenLearnMoreLink() {
        TextViewWithClickableSpans learnMoreInstruction =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_first_time_use_notice);
        ClickableSpan[] spans = learnMoreInstruction.getClickableSpans();
        Assert.assertEquals(spans.length, 1);
        spans[0].onClick(learnMoreInstruction);

        verify(mDelegate).openUrl(LEARN_MORE_URL);
    }

    @Test
    @SmallTest
    public void testOnConfirmButtonClicked_setsRefreshIconToDisabledColor() {
        Button modalConfirmButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_confirm_button);
        modalConfirmButton.callOnClick();

        ImageView refreshIcon =
                mBottomSheetContent.getContentView().findViewById(R.id.refresh_plus_address_icon);
        Assert.assertFalse(refreshIcon.isEnabled());

        verify(mDelegate).onConfirmRequested();

        // Clicking the refresh icon while the confirmation is ongoing does not
        // call the delegate.
        refreshIcon.callOnClick();
        verify(mDelegate, never()).onRefreshClicked();
    }

    @Test
    @SmallTest
    public void testOnConfirmButtonClicked_showsLoadingIndicator() throws TimeoutException {
        LoadingView loadingView =
                mBottomSheetContent
                        .getContentView()
                        .findViewById(R.id.plus_address_creation_loading_view);

        // Before clicking confirm, there is no loading indicator, but both
        // a confirmation and a cancel button.
        Assert.assertEquals(loadingView.getVisibility(), View.GONE);
        Button modalConfirmButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_confirm_button);
        Button modalCancelButton =
                mBottomSheetContent.getContentView().findViewById(R.id.plus_address_cancel_button);
        Assert.assertEquals(modalConfirmButton.getVisibility(), View.VISIBLE);
        Assert.assertEquals(modalCancelButton.getVisibility(), View.VISIBLE);

        // Show the loading indicator and hide the buttons once we click the confirm button.
        modalConfirmButton.callOnClick();
        verify(mDelegate).onConfirmRequested();
        Assert.assertEquals(modalConfirmButton.getVisibility(), View.GONE);
        Assert.assertEquals(modalCancelButton.getVisibility(), View.GONE);
        Assert.assertEquals(loadingView.getVisibility(), View.VISIBLE);

        // Hide the loading indicator and resurface the buttons if we show an error.
        mBottomSheetContent.showError();
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Assert.assertEquals(loadingView.getVisibility(), View.GONE);
        Assert.assertEquals(modalConfirmButton.getVisibility(), View.VISIBLE);
        Assert.assertEquals(modalCancelButton.getVisibility(), View.VISIBLE);
    }

    @Test
    @SmallTest
    public void testBottomSheetOverriddenAttributes() {
        Assert.assertEquals(mBottomSheetContent.getToolbarView(), null);
        Assert.assertEquals(mBottomSheetContent.getPriority(), ContentPriority.HIGH);
        Assert.assertEquals(mBottomSheetContent.swipeToDismissEnabled(), true);
        Assert.assertEquals(mBottomSheetContent.getPeekHeight(), HeightMode.DISABLED);
        Assert.assertEquals(mBottomSheetContent.getHalfHeightRatio(), HeightMode.DISABLED, 0.1);
        Assert.assertEquals(mBottomSheetContent.getFullHeightRatio(), HeightMode.WRAP_CONTENT, 0.1);
        Assert.assertEquals(
                mBottomSheetContent.getSheetContentDescriptionStringId(),
                R.string.plus_address_bottom_sheet_content_description);
        Assert.assertEquals(
                mBottomSheetContent.getSheetFullHeightAccessibilityStringId(),
                R.string.plus_address_bottom_sheet_content_description);
        Assert.assertEquals(
                mBottomSheetContent.getSheetClosedAccessibilityStringId(),
                R.string.plus_address_bottom_sheet_content_description);
    }
}