chromium/components/browser_ui/modaldialog/android/java/src/org/chromium/components/browser_ui/modaldialog/ModalDialogTestUtils.java

// Copyright 2018 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.browser_ui.modaldialog;

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

import android.app.Activity;
import android.content.res.Resources;

import androidx.annotation.Nullable;

import org.junit.Assert;

import org.chromium.base.ThreadUtils;
import org.chromium.components.browser_ui.modaldialog.test.R;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.ViewUtils;

import java.util.List;

/** Utility methods and classes for testing modal dialogs. */
public class ModalDialogTestUtils {
    /** Test observer that notifies dialog dismissal. */
    public interface TestDialogDismissedObserver {
        /**
         * Called when dialog is dismissed.
         * @param dismissalCause The dismissal cause.
         */
        void onDialogDismissed(@DialogDismissalCause int dismissalCause);
    }

    /** @return A {@link PropertyModel} of a modal dialog that is used for testing. */
    public static PropertyModel createDialog(
            Activity activity,
            ModalDialogManager manager,
            String title,
            @Nullable TestDialogDismissedObserver observer) {
        return createDialog(
                activity,
                manager,
                title,
                observer,
                ModalDialogProperties.ButtonStyles.PRIMARY_OUTLINE_NEGATIVE_OUTLINE);
    }

    /**
     * @return A {@link PropertyModel} of a modal dialog that is used for testing with
     *         primary or negative button filled.
     */
    public static PropertyModel createDialog(
            Activity activity,
            ModalDialogManager manager,
            String title,
            @Nullable TestDialogDismissedObserver observer,
            @ModalDialogProperties.ButtonStyles int buttonStyles) {
        return createDialog(
                activity,
                manager,
                title,
                observer,
                buttonStyles,
                ModalDialogProperties.DialogStyles.NORMAL);
    }

    /**
     * @return A {@link PropertyModel} of a modal dialog that is used for testing with
     *         dialog style
     */
    public static PropertyModel createDialogWithDialogStyle(
            Activity activity,
            ModalDialogManager manager,
            String title,
            @Nullable TestDialogDismissedObserver observer,
            @ModalDialogProperties.DialogStyles int dialogStyles) {
        return createDialog(
                activity,
                manager,
                title,
                observer,
                ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE,
                dialogStyles);
    }

    /**
     * @return A {@link PropertyModel} of a modal dialog that is used for testing with primary or
     *     negative button filled and dialog style.
     */
    public static PropertyModel createDialog(
            Activity activity,
            ModalDialogManager manager,
            String title,
            @Nullable TestDialogDismissedObserver observer,
            @ModalDialogProperties.ButtonStyles int buttonStyles,
            @ModalDialogProperties.DialogStyles int dialogStyles) {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    ModalDialogProperties.Controller controller =
                            new ModalDialogProperties.Controller() {
                                @Override
                                public void onDismiss(
                                        PropertyModel model,
                                        @DialogDismissalCause int dismissalCause) {
                                    if (observer != null) {
                                        observer.onDialogDismissed(dismissalCause);
                                    }
                                }

                                @Override
                                public void onClick(PropertyModel model, int buttonType) {
                                    switch (buttonType) {
                                        case ModalDialogProperties.ButtonType.POSITIVE:
                                        case ModalDialogProperties.ButtonType.NEGATIVE:
                                            manager.dismissDialog(
                                                    model, DialogDismissalCause.UNKNOWN);
                                            break;
                                        default:
                                            Assert.fail("Unknown button type: " + buttonType);
                                    }
                                }
                            };
                    Resources resources = activity.getResources();
                    return new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
                            .with(ModalDialogProperties.CONTROLLER, controller)
                            .with(ModalDialogProperties.TITLE, title)
                            .with(
                                    ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                                    resources,
                                    R.string.ok)
                            .with(
                                    ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
                                    resources,
                                    R.string.cancel)
                            .with(ModalDialogProperties.BUTTON_STYLES, buttonStyles)
                            .with(ModalDialogProperties.DIALOG_STYLES, dialogStyles)
                            .build();
                });
    }

    /**
     * Shows a dialog on the specified {@link ModalDialogManager} on the UI thread and waits for it
     * to be visible.
     * @param manager The {@link ModalDialogManager} used to show the dialog.
     * @param model The {@link PropertyModel} for the dialog to show.
     * @param dialogType The {@link ModalDialogType} of the dialog to show.
     */
    public static void showDialog(
            ModalDialogManager manager, PropertyModel model, @ModalDialogType int dialogType) {
        showDialog(manager, model, dialogType, true);
    }

    /**
     * Shows a dialog on the specified {@link ModalDialogManager} on the UI thread.
     *
     * @param manager The {@link ModalDialogManager} used to show the dialog.
     * @param model The {@link PropertyModel} for the dialog to show.
     * @param dialogType The {@link ModalDialogType} of the dialog to show.
     * @param waitForShow Whether to wait for the dialog to be shown. Use false if the enqueued
     *     dialog is not expected to show immediately.
     */
    public static void showDialog(
            ModalDialogManager manager,
            PropertyModel model,
            @ModalDialogType int dialogType,
            boolean waitForShow) {
        ThreadUtils.runOnUiThreadBlocking(() -> manager.showDialog(model, dialogType));
        if (waitForShow) {
            ViewUtils.waitForVisibleView(withId(R.id.modal_dialog_view));
        }
    }

    /**
     * Shows a dialog that's in the root view in the specified {@link ModalDialogManager} on the UI
     * thread.
     *
     * @param manager The {@link ModalDialogManager} used to show the dialog.
     * @param model The {@link PropertyModel} for the dialog to show.
     * @param dialogType The {@link ModalDialogType} of the dialog to show.
     */
    public static void showDialogInRoot(
            ModalDialogManager manager, PropertyModel model, @ModalDialogType int dialogType) {
        ThreadUtils.runOnUiThreadBlocking(() -> manager.showDialog(model, dialogType));
        ViewUtils.waitForDialogViewCheckingState(
                withId(R.id.modal_dialog_view), ViewUtils.VIEW_VISIBLE);
    }

    /** Checks whether the number of pending dialogs of a specified type is as expected. */
    public static void checkPendingSize(
            ModalDialogManager manager, @ModalDialogType int dialogType, int expected) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List list = manager.getPendingDialogsForTest(dialogType);
                    Assert.assertEquals(expected, list != null ? list.size() : 0);
                });
    }

    /**
     * Checks whether the current presenter of the {@link ModalDialogManager} is as expected. If
     * {@code dialogType} is null, then the expected current presenter should be null.
     */
    public static void checkCurrentPresenter(
            ModalDialogManager manager, @Nullable Integer dialogType) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (dialogType == null) {
                        Assert.assertFalse(manager.isShowing());
                        Assert.assertNull(manager.getCurrentPresenterForTest());
                    } else {
                        Assert.assertTrue(manager.isShowing());
                        Assert.assertEquals(
                                manager.getPresenterForTest(dialogType),
                                manager.getCurrentPresenterForTest());
                    }
                });
    }

    /**
     * Checks whether the dialog dismissal cause is as expected. If {@code expectedDismissalCause}
     * is null, then the check is skipped.
     */
    public static void checkDialogDismissalCause(
            @Nullable Integer expectedDismissalCause, @DialogDismissalCause int dismissalCause) {
        if (expectedDismissalCause == null) return;
        Assert.assertEquals(expectedDismissalCause.intValue(), dismissalCause);
    }

    /**
     * @param modelBuilder The builder for the modal dialog view model.
     * @param view The {@link ModalDialogView} that should be bound.
     * @return The {@link PropertyModel} that binds the {@code view}.
     */
    public static PropertyModel createModel(
            PropertyModel.Builder modelBuilder, ModalDialogView view) {
        return ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PropertyModel model = modelBuilder.build();
                    PropertyModelChangeProcessor.create(model, view, new ModalDialogViewBinder());
                    return model;
                });
    }
}