chromium/chrome/browser/image_descriptions/android/java/src/org/chromium/chrome/browser/image_descriptions/ImageDescriptionsDialogTest.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.image_descriptions;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.view.View;
import android.widget.CheckBox;

import androidx.test.filters.SmallTest;

import org.junit.After;
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.MockitoAnnotations;

import org.chromium.base.ThreadUtils;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.device.DeviceConditions;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileJni;
import org.chromium.components.browser_ui.modaldialog.AppModalPresenter;
import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.components.user_prefs.UserPrefsJni;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.ConnectionType;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;

/** Unit tests for {@link ImageDescriptionsDialog} */
@RunWith(BaseJUnit4ClassRunner.class)
public class ImageDescriptionsDialogTest extends BlankUiTestActivityTestCase {
    @Rule public JniMocker mJniMocker = new JniMocker();

    @Mock private ImageDescriptionsControllerDelegate mDelegate;

    @Mock private UserPrefs.Natives mUserPrefsJniMock;

    @Mock private Profile mProfile;
    @Mock private Profile.Natives mProfileJniMock;

    @Mock private PrefService mPrefService;

    @Mock private WebContents mWebContents;

    private SharedPreferencesManager mManager;
    private ImageDescriptionsController mController;

    private ModalDialogManager mModalDialogManager;
    private ModalDialogManager.Presenter mAppModalPresenter;

    @Before
    public void setUp() throws Exception {
        super.setUpTest();
        MockitoAnnotations.initMocks(this);

        mJniMocker.mock(ProfileJni.TEST_HOOKS, mProfileJniMock);
        when(mProfileJniMock.fromWebContents(mWebContents)).thenReturn(mProfile);
        when(mProfile.getOriginalProfile()).thenReturn(mProfile);

        mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
        when(mUserPrefsJniMock.get(mProfile)).thenReturn(mPrefService);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mAppModalPresenter = new AppModalPresenter(getActivity());
                    mModalDialogManager =
                            new ModalDialogManager(
                                    mAppModalPresenter, ModalDialogManager.ModalDialogType.APP);
                });

        mManager = ChromeSharedPreferences.getInstance();
        mController = ImageDescriptionsController.getInstance();
        mController.setDelegateForTesting(mDelegate);
    }

    @After
    public void tearDown() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> DeviceConditions.sForceConnectionTypeForTesting = false);
    }

    // Helper methods for driving dialog control

    private void showDialog() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID))
                            .thenReturn(false);
                    mManager.writeInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT, 0);
                    mManager.writeBoolean(
                            ChromePreferenceKeys.IMAGE_DESCRIPTIONS_DONT_ASK_AGAIN, false);
                    mController.onImageDescriptionsMenuItemSelected(
                            getActivity(), mModalDialogManager, mWebContents);
                });
    }

    private void showDialogWithDontAskAgainVisible() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    when(mPrefService.getBoolean(Pref.ACCESSIBILITY_IMAGE_LABELS_ENABLED_ANDROID))
                            .thenReturn(false);
                    mManager.writeInt(ChromePreferenceKeys.IMAGE_DESCRIPTIONS_JUST_ONCE_COUNT, 5);
                    mController.onImageDescriptionsMenuItemSelected(
                            getActivity(), mModalDialogManager, mWebContents);
                });
    }

    private void clickPositiveButton() {
        onView(withId(R.id.positive_button)).perform(click());
    }

    private void clickNegativeButton() {
        onView(withId(R.id.negative_button)).inRoot(isDialog()).perform(click());
    }

    // Helper methods for assertions

    private void assertVisibleEnabledAndChecked_RadioButton(View view, String prefix) {
        Assert.assertEquals(View.VISIBLE, view.getVisibility());
        Assert.assertTrue(prefix + " should be enabled", view.isEnabled());
        Assert.assertTrue(
                prefix + " should be checked", ((RadioButtonWithDescription) view).isChecked());
    }

    private void assertVisibleEnabledAndUnchecked_RadioButton(View view, String prefix) {
        Assert.assertEquals(View.VISIBLE, view.getVisibility());
        Assert.assertTrue(prefix + " should be enabled", view.isEnabled());
        Assert.assertFalse(
                prefix + " should be unchecked", ((RadioButtonWithDescription) view).isChecked());
    }

    private void assertVisibleEnabledAndChecked_CheckBox(View view, String prefix) {
        Assert.assertEquals(View.VISIBLE, view.getVisibility());
        Assert.assertTrue(prefix + " should be enabled", view.isEnabled());
        Assert.assertTrue(prefix + " should be checked", ((CheckBox) view).isChecked());
    }

    private void assertVisibleEnabledAndUnchecked_CheckBox(View view, String prefix) {
        Assert.assertEquals(View.VISIBLE, view.getVisibility());
        Assert.assertTrue(prefix + " should be enabled", view.isEnabled());
        Assert.assertFalse(prefix + " should be unchecked", ((CheckBox) view).isChecked());
    }

    @Test
    @SmallTest
    public void testHeaderAndButtonContent() {
        showDialog();
        onView(allOf(isDescendantOfA(withId(R.id.title_container)), withId(R.id.title)))
                .inRoot(isDialog())
                .check(matches(withText("Get image descriptions?")));
        onView(withId(R.id.image_descriptions_dialog_content))
                .check(
                        matches(
                                withText(
                                        "Images are sent to Google to improve descriptions for"
                                                + " you.")));
        onView(withId(R.id.positive_button)).check(matches(withText("Get descriptions")));
        onView(withId(R.id.negative_button)).check(matches(withText("No thanks")));
    }

    @Test
    @SmallTest
    public void testRadioButtonState_initialDefaultDialog() {
        showDialog();

        // "Just once" should be visible, enabled, and checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_just_once))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Just once",
                                    ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndChecked_RadioButton(view, "Just once");
                        });

        // "Always" should be visible, enabled, and unchecked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Always", ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndUnchecked_RadioButton(view, "Always");
                        });
    }

    @Test
    @SmallTest
    public void testCheckBoxState_userSelectsAlwaysOption() {
        showDialog();

        // "Always" should be visible, enabled, and unchecked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Always", ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndUnchecked_RadioButton(view, "Always");
                        });

        // "Only on Wi-Fi" option should be gone
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals(View.GONE, view.getVisibility());
                        });

        // Click the "Always" option, then "Only on Wi-Fi" option should appear and be checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always)).perform(click());
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals("Only on Wi-Fi", ((CheckBox) view).getText());
                            assertVisibleEnabledAndChecked_CheckBox(view, "Only on Wi-Fi");
                        });
    }

    @Test
    @SmallTest
    public void testCheckBoxState_userTogglesOnlyOnWifi() {
        showDialog();

        // Click the "Always" option, then "Only on Wi-Fi" option should appear and be checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .inRoot(isDialog())
                .perform(click());
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals("Only on Wi-Fi", ((CheckBox) view).getText());
                            assertVisibleEnabledAndChecked_CheckBox(view, "Only on Wi-Fi");
                        });

        // Uncheck the "Only on Wi-Fi" option, switch radio buttons, then switch back
        onView(withId(R.id.image_descriptions_dialog_check_box)).perform(click());
        onView(withId(R.id.image_descriptions_dialog_radio_button_just_once)).perform(click());
        onView(withId(R.id.image_descriptions_dialog_radio_button_always)).perform(click());

        // The "Only on Wi-Fi" option should reappear, and still be unchecked
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals("Only on Wi-Fi", ((CheckBox) view).getText());
                            assertVisibleEnabledAndUnchecked_CheckBox(view, "Only on Wi-Fi");
                        });
    }

    @Test
    @SmallTest
    public void testCheckBoxState_dontAskAgainOptionVisible() {
        showDialogWithDontAskAgainVisible();

        // "Just once" should be visible, enabled, and checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_just_once))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Just once",
                                    ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndChecked_RadioButton(view, "Just once");
                        });

        // The "Dont ask again" option should be visible, enabled, and unchecked
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Dont ask again",
                                    ((CheckBox) view)
                                            .getText()
                                            .toString()
                                            .replaceAll("[^a-zA-Z\\s]", ""));
                            assertVisibleEnabledAndUnchecked_CheckBox(view, "Dont ask again");
                        });
    }

    @Test
    @SmallTest
    public void testCheckBoxState_userTogglesDontAskAgain() {
        showDialogWithDontAskAgainVisible();

        // The "Dont ask again" option should be visible, enabled, and unchecked
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Dont ask again",
                                    ((CheckBox) view)
                                            .getText()
                                            .toString()
                                            .replaceAll("[^a-zA-Z\\s]", ""));
                            assertVisibleEnabledAndUnchecked_CheckBox(view, "Dont ask again");
                        });

        // Check the "Dont ask again" option, switch radio buttons, then switch back
        onView(withId(R.id.image_descriptions_dialog_check_box)).perform(click());
        onView(withId(R.id.image_descriptions_dialog_radio_button_always)).perform(click());
        onView(withId(R.id.image_descriptions_dialog_radio_button_just_once)).perform(click());

        // The "Dont ask again" option should reappear, and still be checked
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Dont ask again",
                                    ((CheckBox) view)
                                            .getText()
                                            .toString()
                                            .replaceAll("[^a-zA-Z\\s]", ""));
                            assertVisibleEnabledAndChecked_CheckBox(view, "Dont ask again");
                        });
    }

    @Test
    @SmallTest
    public void testUserInteraction_userClicksNoThanks() {
        showDialog();

        // User clicks "No thanks", dialog should dismiss with no action taken.
        clickNegativeButton();

        verify(mDelegate, never()).enableImageDescriptions(any());
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean(), any());
        verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean(), any());
    }

    @Test
    @SmallTest
    public void testUserInteraction_userGetsDescriptions_justOnce() {
        showDialog();

        // "Just once" should be visible, enabled, and checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_just_once))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Just once",
                                    ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndChecked_RadioButton(view, "Just once");
                        });

        // User clicks "Get descriptions"
        clickPositiveButton();

        verify(mDelegate, never()).enableImageDescriptions(any());
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean(), any());
        verify(mDelegate, times(1)).getImageDescriptionsJustOnce(false, mWebContents);

        onView(withText(R.string.image_descriptions_toast_just_once))
                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
                .check(matches(isDisplayed()));
    }

    @Test
    @SmallTest
    public void testUserInteraction_userGetsDescriptions_justOnceDontAskAgain() {
        showDialogWithDontAskAgainVisible();

        // The "Dont ask again" option should be visible, enabled, and unchecked
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .inRoot(isDialog())
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Dont ask again",
                                    ((CheckBox) view)
                                            .getText()
                                            .toString()
                                            .replaceAll("[^a-zA-Z\\s]", ""));
                            assertVisibleEnabledAndUnchecked_CheckBox(view, "Dont ask again");
                        });

        // Check the "Dont ask again" option
        onView(withId(R.id.image_descriptions_dialog_check_box)).perform(click());

        // User clicks "Get descriptions"
        clickPositiveButton();

        verify(mDelegate, never()).enableImageDescriptions(any());
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, never()).setOnlyOnWifiRequirement(anyBoolean(), any());
        verify(mDelegate, times(1)).getImageDescriptionsJustOnce(true, mWebContents);

        onView(withText(R.string.image_descriptions_toast_just_once))
                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
                .check(matches(isDisplayed()));
    }

    @Test
    @SmallTest
    public void testUserInteraction_userGetsDescriptions_always() {
        showDialog();

        // User clicks on the "Always" option, then turns off the "Only on Wi-Fi" option
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .inRoot(isDialog())
                .perform(click());
        onView(withId(R.id.image_descriptions_dialog_check_box)).perform(click());

        // Confirm state
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .check(
                        (view, e) -> {
                            Assert.assertEquals(
                                    "Always", ((RadioButtonWithDescription) view).getPrimaryText());
                            assertVisibleEnabledAndChecked_RadioButton(view, "Always");
                        });
        onView(withId(R.id.image_descriptions_dialog_check_box))
                .check(
                        (view, e) -> {
                            Assert.assertEquals("Only on Wi-Fi", ((CheckBox) view).getText());
                            assertVisibleEnabledAndUnchecked_CheckBox(view, "Only on Wi-Fi");
                        });

        // User clicks "Get descriptions"
        clickPositiveButton();

        verify(mDelegate, times(1)).enableImageDescriptions(mProfile);
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, times(1)).setOnlyOnWifiRequirement(false, mProfile);
        verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean(), any());

        onView(withText(R.string.image_descriptions_toast_on))
                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
                .check(matches(isDisplayed()));
    }

    @Test
    @SmallTest
    public void testUserInteraction_userGetsDescriptions_alwaysOnlyOnWifi() {
        showDialog();

        // User clicks on the "Always" option, keeps the "Only on Wi-Fi" option checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .inRoot(isDialog())
                .perform(click());

        // Setup wifi condition.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    DeviceConditions.sForceConnectionTypeForTesting = true;
                    DeviceConditions.mConnectionTypeForTesting = ConnectionType.CONNECTION_WIFI;
                });

        // User clicks "Get descriptions"
        clickPositiveButton();

        verify(mDelegate, times(1)).enableImageDescriptions(mProfile);
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, times(1)).setOnlyOnWifiRequirement(true, mProfile);
        verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean(), any());

        onView(withText(R.string.image_descriptions_toast_on))
                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
                .check(matches(isDisplayed()));
    }

    @Test
    @SmallTest
    public void testUserInteraction_userGetsDescriptions_alwaysOnlyOnWifi_noWifi()
            throws Exception {
        showDialog();

        // User clicks on the "Always" option, keeps the "Only on Wi-Fi" option checked
        onView(withId(R.id.image_descriptions_dialog_radio_button_always))
                .inRoot(isDialog())
                .perform(click());

        // Setup no wifi condition.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    DeviceConditions.sForceConnectionTypeForTesting = true;
                    DeviceConditions.mConnectionTypeForTesting = ConnectionType.CONNECTION_NONE;
                });

        // User clicks "Get descriptions"
        clickPositiveButton();

        verify(mDelegate, times(1)).enableImageDescriptions(mProfile);
        verify(mDelegate, never()).disableImageDescriptions(any());
        verify(mDelegate, times(1)).setOnlyOnWifiRequirement(true, mProfile);
        verify(mDelegate, never()).getImageDescriptionsJustOnce(anyBoolean(), any());

        onView(withText(R.string.image_descriptions_toast_on_no_wifi))
                .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
                .check(matches(isDisplayed()));
    }
}