chromium/components/webapps/browser/android/java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java

// Copyright 2019 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.components.webapps;

import android.app.Activity;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.Robolectric;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;

/** Tests for the {@link AddToHomescreenDialogView} class. */
@RunWith(BaseRobolectricTestRunner.class)
public class AddToHomescreenDialogViewTest {
    private AddToHomescreenDialogView mAddToHomescreenDialogView;
    private CallbackHelper mAddCallback = new CallbackHelper();
    private CallbackHelper mTitleClickCallback = new CallbackHelper();
    private CallbackHelper mDismissCallback = new CallbackHelper();
    private MockModalDialogManager mModalDialogManager = new MockModalDialogManager();

    private static final String TEST_TITLE = "YouTube";
    private static final String TEST_URL = "youtube.com";
    private static final String TEST_NATIVE_ADD_TEXT = "Install";

    private class MockModalDialogManager extends ModalDialogManager {
        private PropertyModel mShownDialogModel;
        private PropertyModel mDismissedDialogModel;
        private int mDismissalCause;

        public MockModalDialogManager() {
            super(Mockito.mock(Presenter.class), 0);
        }

        @Override
        public void showDialog(PropertyModel model, int dialogType) {
            mShownDialogModel = model;
        }

        @Override
        public void dismissDialog(PropertyModel model, int dismissalCause) {
            mDismissedDialogModel = model;
            mDismissalCause = dismissalCause;
            model.get(ModalDialogProperties.CONTROLLER).onDismiss(model, dismissalCause);
        }

        public @DialogDismissalCause int getDismissalCause() {
            return mDismissalCause;
        }

        public PropertyModel getDismissedDialogModel() {
            return mDismissedDialogModel;
        }

        public PropertyModel getShownDialogModel() {
            return mShownDialogModel;
        }
    }

    public void setUpDialog() {
        // Create and show the view.
        Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
        mAddToHomescreenDialogView =
                new AddToHomescreenDialogView(
                        activity,
                        mModalDialogManager,
                        new AddToHomescreenViewDelegate() {
                            @Override
                            public void onAddToHomescreen(String title, @AppType int type) {
                                mAddCallback.notifyCalled();
                            }

                            @Override
                            public boolean onAppDetailsRequested() {
                                mTitleClickCallback.notifyCalled();
                                return true;
                            }

                            @Override
                            public void onViewDismissed() {
                                mDismissCallback.notifyCalled();
                            }
                        });
    }

    @Test
    @Feature({"Webapp"})
    public void testLoadingState() {
        setUpDialog();
        // Assert dialog is showing.
        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        Assert.assertNotNull(shownDialogModel);

        // Assert views exist.
        View parentView = mAddToHomescreenDialogView.getParentViewForTest();
        Assert.assertNotNull(parentView);
        Assert.assertNotNull(parentView.findViewById(R.id.spinny));
        Assert.assertNotNull(parentView.findViewById(R.id.icon));
        Assert.assertNotNull(parentView.findViewById(R.id.shortcut_name));
        Assert.assertNotNull(parentView.findViewById(R.id.app_info));
        Assert.assertNotNull(parentView.findViewById(R.id.app_name));
        Assert.assertNotNull(parentView.findViewById(R.id.origin));
        Assert.assertNotNull(parentView.findViewById(R.id.control_rating));
        Assert.assertNotNull(parentView.findViewById(R.id.play_logo));

        // Test visibility when loading: app/shortcut info is hidden until setType is called.
        assertVisibility(R.id.spinny, true);
        assertVisibility(R.id.icon, false);
        assertVisibility(R.id.shortcut_name, false);
        assertVisibility(R.id.app_info, false);

        // Assert dialog buttons text.
        Assert.assertEquals(
                "Add", shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
        Assert.assertEquals(
                "Cancel", shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_TEXT));

        // Assert 'Add' is disabled and 'Cancel' is enabled.
        Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
        Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_DISABLED));
    }

    @Test
    @Feature({"Webapp"})
    public void testLoadingStatePwa() {
        setUpDialog();
        // Assert dialog is showing.
        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        Assert.assertNotNull(shownDialogModel);

        // Assert views exist.
        View parentView = mAddToHomescreenDialogView.getParentViewForTest();
        Assert.assertNotNull(parentView);
        Assert.assertNotNull(parentView.findViewById(R.id.spinny));
        Assert.assertNotNull(parentView.findViewById(R.id.icon));
        Assert.assertNotNull(parentView.findViewById(R.id.shortcut_name));
        Assert.assertNotNull(parentView.findViewById(R.id.app_info));
        Assert.assertNotNull(parentView.findViewById(R.id.app_name));
        Assert.assertNotNull(parentView.findViewById(R.id.origin));
        Assert.assertNotNull(parentView.findViewById(R.id.control_rating));
        Assert.assertNotNull(parentView.findViewById(R.id.play_logo));

        // Test visibility when loading: app/shortcut info is hidden until setType is called.
        assertVisibility(R.id.spinny, true);
        assertVisibility(R.id.icon, false);
        assertVisibility(R.id.shortcut_name, false);
        assertVisibility(R.id.app_info, false);

        // Assert dialog buttons text.
        Assert.assertEquals(
                "Add", shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
        Assert.assertEquals(
                "Cancel", shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_TEXT));

        // Assert 'Install' is disabled and 'Cancel' is enabled.
        Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
        Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_DISABLED));
    }

    /** Tests the view for {@link AppType#WEBAPK}. */
    @Test
    @Feature({"Webapp"})
    public void testWebAPK() {
        setUpDialog();
        initDialogView(AppType.WEBAPK);
        mAddToHomescreenDialogView.setUrl(TEST_URL);

        assertVisibility(R.id.spinny, false);
        assertVisibility(R.id.icon, true);
        assertVisibility(R.id.app_info, true);
        assertVisibility(R.id.shortcut_name, false);
        assertVisibility(R.id.app_name, true);
        assertVisibility(R.id.homebrew_name, false);
        assertVisibility(R.id.origin, true);
        assertVisibility(R.id.control_rating, false);
        assertVisibility(R.id.play_logo, false);

        Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.app_name));
        Assert.assertEquals(TEST_URL, getTextForViewWithId(R.id.origin));
    }

    /** Tests the view for {@link AppType#WEBAPK_DIY}. */
    @Test
    @Feature({"Webapp"})
    public void testDiyWebAPK() {
        setUpDialog();
        initDialogView(AppType.WEBAPK_DIY);
        mAddToHomescreenDialogView.setUrl(TEST_URL);

        assertVisibility(R.id.spinny, false);
        assertVisibility(R.id.icon, true);
        assertVisibility(R.id.app_info, true);
        assertVisibility(R.id.shortcut_name, false);
        assertVisibility(R.id.app_name, false);
        assertVisibility(R.id.homebrew_name, true);
        assertVisibility(R.id.origin, true);
        assertVisibility(R.id.control_rating, false);
        assertVisibility(R.id.play_logo, false);

        Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.app_name));
        Assert.assertEquals(TEST_URL, getTextForViewWithId(R.id.origin));
    }

    /** Tests the view for {@link AppType#SHORTCUT}. */
    @Test
    @Feature({"Webapp"})
    public void testShortcut() {
        setUpDialog();
        initDialogView(AppType.SHORTCUT);

        assertVisibility(R.id.spinny, false);
        assertVisibility(R.id.icon, true);
        assertVisibility(R.id.app_info, false);
        assertVisibility(R.id.shortcut_name, true);
        assertVisibility(R.id.app_name, false);
        assertVisibility(R.id.homebrew_name, false);
        assertVisibility(R.id.origin, false);
        assertVisibility(R.id.control_rating, false);
        assertVisibility(R.id.play_logo, false);

        Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.shortcut_name));
    }

    /** Tests the view for {@link AppType#NATIVE}. */
    @Test
    @Feature({"Webapp"})
    public void testNativeApp() {
        setUpDialog();
        initDialogView(AppType.NATIVE);
        mAddToHomescreenDialogView.setNativeAppRating(2.3f);
        mAddToHomescreenDialogView.setNativeInstallButtonText(TEST_NATIVE_ADD_TEXT);

        assertVisibility(R.id.spinny, false);
        assertVisibility(R.id.icon, true);
        assertVisibility(R.id.app_info, true);
        assertVisibility(R.id.shortcut_name, false);
        assertVisibility(R.id.app_name, true);
        assertVisibility(R.id.homebrew_name, false);
        assertVisibility(R.id.origin, false);
        assertVisibility(R.id.control_rating, true);
        assertVisibility(R.id.play_logo, true);

        Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.shortcut_name));

        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        Assert.assertEquals(
                TEST_NATIVE_ADD_TEXT,
                shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
    }

    @Test
    @Feature({"Webapp"})
    public void testAddButtonState() {
        setUpDialog();
        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();

        for (int i = 0; i <= AppType.MAX_VALUE; i++) {
            mAddToHomescreenDialogView.setType(i);

            mAddToHomescreenDialogView.setTitle("");
            mAddToHomescreenDialogView.setCanSubmit(false);
            Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
            mAddToHomescreenDialogView.setCanSubmit(true);
            Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));

            mAddToHomescreenDialogView.setTitle(TEST_TITLE);
            mAddToHomescreenDialogView.setCanSubmit(false);
            Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
            mAddToHomescreenDialogView.setCanSubmit(true);
            Assert.assertFalse(
                    shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
        }
    }

    @Test
    @Feature({"Webapp"})
    public void testAddButtonStateEditTitle() {
        setUpDialog();
        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        mAddToHomescreenDialogView.setTitle(TEST_TITLE);

        for (int i = 0; i <= AppType.MAX_VALUE; i++) {
            mAddToHomescreenDialogView.setType(i);
            TextView titleText = mAddToHomescreenDialogView.getAppNameView();
            // Only run when title is editable.
            if (titleText instanceof EditText) {
                titleText.setText("");
                mAddToHomescreenDialogView.setCanSubmit(false);
                Assert.assertTrue(
                        shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
                mAddToHomescreenDialogView.setCanSubmit(true);
                Assert.assertTrue(
                        shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));

                titleText.setText(TEST_TITLE);
                mAddToHomescreenDialogView.setCanSubmit(false);
                Assert.assertTrue(
                        shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
                mAddToHomescreenDialogView.setCanSubmit(true);
                Assert.assertFalse(
                        shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
            }
        }
    }

    /** Tests whether the callback for clicking on the title or icon functions correctly. */
    @Test
    @Feature({"Webapp"})
    public void testTitleClickCallback() {
        setUpDialog();
        initDialogView(AppType.NATIVE);

        Assert.assertEquals(0, mTitleClickCallback.getCallCount());
        mAddToHomescreenDialogView
                .getParentViewForTest()
                .findViewById(R.id.app_name)
                .performClick();
        mAddToHomescreenDialogView.getParentViewForTest().findViewById(R.id.icon).performClick();
        Assert.assertEquals(2, mTitleClickCallback.getCallCount());
        Assert.assertEquals(2, mDismissCallback.getCallCount());
        Assert.assertNotNull(mModalDialogManager.getDismissedDialogModel());
        Assert.assertEquals(
                mModalDialogManager.getDismissalCause(), DialogDismissalCause.ACTION_ON_CONTENT);
    }

    /** Tests whether the callback for dismissal functions correctly. */
    @Test
    @Feature({"Webapp"})
    public void testDismissCallback() {
        setUpDialog();
        initDialogView(AppType.NATIVE);

        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        Assert.assertEquals(0, mDismissCallback.getCallCount());
        shownDialogModel
                .get(ModalDialogProperties.CONTROLLER)
                .onClick(shownDialogModel, ModalDialogProperties.ButtonType.NEGATIVE);
        Assert.assertEquals(1, mDismissCallback.getCallCount());
    }

    /** Tests whether the callback for clicking on the 'Add' button functions correctly. */
    @Test
    @Feature({"Webapp"})
    public void testInstallCallback() {
        setUpDialog();
        initDialogView(AppType.WEBAPK);
        PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
        Assert.assertEquals(0, mAddCallback.getCallCount());
        shownDialogModel
                .get(ModalDialogProperties.CONTROLLER)
                .onClick(shownDialogModel, ModalDialogProperties.ButtonType.POSITIVE);
        Assert.assertEquals(1, mAddCallback.getCallCount());
    }

    /** Tests whether the done editor action for shortcut name dismisses the dialog correctly. */
    @Test
    @Feature({"Webapp"})
    public void testShortcutNameEditorAction() {
        setUpDialog();
        initDialogView(AppType.SHORTCUT);

        EditText shortcutNameInput =
                mAddToHomescreenDialogView.getParentViewForTest().findViewById(R.id.shortcut_name);

        Assert.assertEquals(0, mModalDialogManager.getDismissalCause());

        // Without any text, the done action shouldn't cause the dialog to dismiss.
        shortcutNameInput.setText("");
        shortcutNameInput.onEditorAction(EditorInfo.IME_ACTION_DONE);
        Assert.assertEquals(0, mModalDialogManager.getDismissalCause());

        // With text, the done action should cause the dialog to dismiss as if the positive button
        // is clicked.
        shortcutNameInput.setText("Hello world");
        shortcutNameInput.onEditorAction(EditorInfo.IME_ACTION_DONE);
        Assert.assertEquals(
                DialogDismissalCause.POSITIVE_BUTTON_CLICKED,
                mModalDialogManager.getDismissalCause());
    }

    private void initDialogView(@AppType int appType) {
        mAddToHomescreenDialogView.setType(appType);
        mAddToHomescreenDialogView.setTitle(TEST_TITLE);
        mAddToHomescreenDialogView.setIcon(null, false);
        mAddToHomescreenDialogView.setCanSubmit(true);
    }

    private void assertVisibility(int viewId, boolean isVisible) {
        Assert.assertEquals(
                isVisible,
                View.VISIBLE
                        == mAddToHomescreenDialogView
                                .getParentViewForTest()
                                .findViewById(viewId)
                                .getVisibility());
    }

    private String getTextForViewWithId(int viewId) {
        return ((TextView) mAddToHomescreenDialogView.getParentViewForTest().findViewById(viewId))
                .getText()
                .toString();
    }
}