// 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.components.translate;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.app.Activity;
import android.content.Context;
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.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.chromium.base.UnownedUserDataHost;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.components.messages.DismissReason;
import org.chromium.components.messages.MessageBannerProperties;
import org.chromium.components.messages.MessageDispatcher;
import org.chromium.components.messages.MessageIdentifier;
import org.chromium.components.messages.MessageScopeType;
import org.chromium.components.messages.PrimaryActionClickBehavior;
import org.chromium.components.messages.PrimaryWidgetAppearance;
import org.chromium.components.messages.SecondaryMenuMaxSize;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.ref.WeakReference;
/** Unit tests for TranslateMessage. */
@RunWith(BaseRobolectricTestRunner.class)
public final class TranslateMessageTest {
private static final long NATIVE_TRANSLATE_MESSAGE = 1337;
private static final int DISMISSAL_DURATION_SECONDS = 15;
private static final String TITLE_BEFORE_TRANSLATE = "Translate Page?";
private static final String TITLE_AFTER_TRANSLATE = "Page Translated";
private static final String PRIMARY_TEXT_TRANSLATE = "Translate";
private static final String PRIMARY_TEXT_UNDO = "Undo";
private static final String DESCRIPTION = "French to English";
@Rule public JniMocker mJniMocker = new JniMocker();
@Mock WebContents mWebContents;
@Mock TranslateMessage.Natives mMockJni;
@Mock Context mContext;
@Mock MessageDispatcher mMessageDispatcher;
@Captor ArgumentCaptor<PropertyModel> mPropertyModelCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mJniMocker.mock(TranslateMessageJni.TEST_HOOKS, mMockJni);
}
@Test
@SmallTest
public void testCreateWithNullWindowAndroid() {
doReturn(null).when(mWebContents).getTopLevelNativeWindow();
Assert.assertNull(
TranslateMessage.create(
mWebContents, NATIVE_TRANSLATE_MESSAGE, DISMISSAL_DURATION_SECONDS));
}
@Test
@SmallTest
public void testCreateWithNullActivityWeakReference() {
WindowAndroid windowAndroid = Mockito.mock(WindowAndroid.class);
doReturn(null).when(windowAndroid).getActivity();
doReturn(windowAndroid).when(mWebContents).getTopLevelNativeWindow();
Assert.assertNull(
TranslateMessage.create(
mWebContents, NATIVE_TRANSLATE_MESSAGE, DISMISSAL_DURATION_SECONDS));
}
@Test
@SmallTest
public void testCreateWithNullActivity() {
WindowAndroid windowAndroid = Mockito.mock(WindowAndroid.class);
doReturn(new WeakReference<Activity>(null)).when(windowAndroid).getActivity();
doReturn(windowAndroid).when(mWebContents).getTopLevelNativeWindow();
Assert.assertNull(
TranslateMessage.create(
mWebContents, NATIVE_TRANSLATE_MESSAGE, DISMISSAL_DURATION_SECONDS));
}
@Test
@SmallTest
public void testCreateWithNullMessageDispatcher() {
Activity activity = Mockito.mock(Activity.class);
WindowAndroid windowAndroid = Mockito.mock(WindowAndroid.class);
doReturn(new WeakReference<Activity>(activity)).when(windowAndroid).getActivity();
doReturn(windowAndroid).when(mWebContents).getTopLevelNativeWindow();
doReturn(new UnownedUserDataHost()).when(windowAndroid).getUnownedUserDataHost();
Assert.assertNull(
TranslateMessage.create(
mWebContents, NATIVE_TRANSLATE_MESSAGE, DISMISSAL_DURATION_SECONDS));
}
@Test
@SmallTest
public void testFullTranslateFlowThenDismissViaGesture() {
TranslateMessage translateMessage =
new TranslateMessage(
mContext,
mMessageDispatcher,
mWebContents,
NATIVE_TRANSLATE_MESSAGE,
DISMISSAL_DURATION_SECONDS);
// Show the before translate message.
translateMessage.showMessage(
TITLE_BEFORE_TRANSLATE,
DESCRIPTION,
PRIMARY_TEXT_TRANSLATE,
/* hasOverflowMenu= */ true);
verify(mMessageDispatcher)
.enqueueMessage(
mPropertyModelCaptor.capture(),
eq(mWebContents),
eq(MessageScopeType.NAVIGATION),
/* highPriority= */ eq(false));
PropertyModel messageProperties = mPropertyModelCaptor.getValue();
assertHasCommonProperties(messageProperties);
assertHasOverflowMenuProperties(messageProperties);
Assert.assertEquals(
TITLE_BEFORE_TRANSLATE, messageProperties.get(MessageBannerProperties.TITLE));
Assert.assertEquals(
DESCRIPTION, messageProperties.get(MessageBannerProperties.DESCRIPTION));
Assert.assertEquals(
PrimaryWidgetAppearance.BUTTON_IF_TEXT_IS_SET,
messageProperties.get(MessageBannerProperties.PRIMARY_WIDGET_APPEARANCE));
Assert.assertEquals(
PRIMARY_TEXT_TRANSLATE,
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT));
// Show the translation-in-progress state upon clicking "Translate".
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
translateMessage.showMessage(
TITLE_BEFORE_TRANSLATE,
DESCRIPTION,
"",
/* hasOverflowMenu= */ true);
return null;
}
})
.when(mMockJni)
.handlePrimaryAction(NATIVE_TRANSLATE_MESSAGE);
Assert.assertEquals(
Integer.valueOf(PrimaryActionClickBehavior.DO_NOT_DISMISS),
messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).get());
verifyNoMoreInteractions(mMessageDispatcher);
assertHasCommonProperties(messageProperties);
assertHasOverflowMenuProperties(messageProperties);
Assert.assertEquals(
TITLE_BEFORE_TRANSLATE, messageProperties.get(MessageBannerProperties.TITLE));
Assert.assertEquals(
DESCRIPTION, messageProperties.get(MessageBannerProperties.DESCRIPTION));
Assert.assertEquals(
PrimaryWidgetAppearance.PROGRESS_SPINNER,
messageProperties.get(MessageBannerProperties.PRIMARY_WIDGET_APPEARANCE));
// Show the after translate message.
translateMessage.showMessage(
TITLE_AFTER_TRANSLATE, DESCRIPTION, PRIMARY_TEXT_UNDO, /* hasOverflowMenu= */ true);
verifyNoMoreInteractions(mMessageDispatcher);
assertHasCommonProperties(messageProperties);
assertHasOverflowMenuProperties(messageProperties);
Assert.assertEquals(
TITLE_AFTER_TRANSLATE, messageProperties.get(MessageBannerProperties.TITLE));
Assert.assertEquals(
DESCRIPTION, messageProperties.get(MessageBannerProperties.DESCRIPTION));
Assert.assertEquals(
PrimaryWidgetAppearance.BUTTON_IF_TEXT_IS_SET,
messageProperties.get(MessageBannerProperties.PRIMARY_WIDGET_APPEARANCE));
Assert.assertEquals(
PRIMARY_TEXT_UNDO,
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT));
messageProperties.get(MessageBannerProperties.ON_DISMISSED).onResult(DismissReason.GESTURE);
verify(mMockJni).handleDismiss(NATIVE_TRANSLATE_MESSAGE, DismissReason.GESTURE);
}
@Test
@SmallTest
public void testShowMessageWithoutOverflowMenu() {
TranslateMessage translateMessage =
new TranslateMessage(
mContext,
mMessageDispatcher,
mWebContents,
NATIVE_TRANSLATE_MESSAGE,
DISMISSAL_DURATION_SECONDS);
// Show the message.
translateMessage.showMessage(
TITLE_BEFORE_TRANSLATE,
DESCRIPTION,
PRIMARY_TEXT_TRANSLATE,
/* hasOverflowMenu= */ false);
verify(mMessageDispatcher)
.enqueueMessage(
mPropertyModelCaptor.capture(),
eq(mWebContents),
eq(MessageScopeType.NAVIGATION),
/* highPriority= */ eq(false));
PropertyModel messageProperties = mPropertyModelCaptor.getValue();
assertHasCommonProperties(messageProperties);
Assert.assertFalse(
messageProperties
.getAllSetProperties()
.contains(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID));
Assert.assertEquals(
TITLE_BEFORE_TRANSLATE, messageProperties.get(MessageBannerProperties.TITLE));
Assert.assertEquals(
DESCRIPTION, messageProperties.get(MessageBannerProperties.DESCRIPTION));
Assert.assertEquals(
PrimaryWidgetAppearance.BUTTON_IF_TEXT_IS_SET,
messageProperties.get(MessageBannerProperties.PRIMARY_WIDGET_APPEARANCE));
Assert.assertEquals(
PRIMARY_TEXT_TRANSLATE,
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT));
}
@Test
@SmallTest
public void testDismissFromNative() {
TranslateMessage translateMessage =
new TranslateMessage(
mContext,
mMessageDispatcher,
mWebContents,
NATIVE_TRANSLATE_MESSAGE,
DISMISSAL_DURATION_SECONDS);
translateMessage.showMessage(
TITLE_BEFORE_TRANSLATE,
DESCRIPTION,
PRIMARY_TEXT_TRANSLATE,
/* hasOverflowMenu= */ true);
verify(mMessageDispatcher)
.enqueueMessage(
mPropertyModelCaptor.capture(),
eq(mWebContents),
eq(MessageScopeType.NAVIGATION),
/* highPriority= */ eq(false));
PropertyModel messageProperties = mPropertyModelCaptor.getValue();
// Call the ON_DISMISSED callback from MessageDispatcher.dismissMessage.
doAnswer(
new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
messageProperties
.get(MessageBannerProperties.ON_DISMISSED)
.onResult(DismissReason.DISMISSED_BY_FEATURE);
return null;
}
})
.when(mMessageDispatcher)
.dismissMessage(messageProperties, DismissReason.DISMISSED_BY_FEATURE);
translateMessage.dismiss();
verify(mMockJni)
.handleDismiss(NATIVE_TRANSLATE_MESSAGE, DismissReason.DISMISSED_BY_FEATURE);
}
@Test
@SmallTest
public void testClearNativePointer() {
TranslateMessage translateMessage =
new TranslateMessage(
mContext,
mMessageDispatcher,
mWebContents,
NATIVE_TRANSLATE_MESSAGE,
DISMISSAL_DURATION_SECONDS);
translateMessage.showMessage(
TITLE_BEFORE_TRANSLATE,
DESCRIPTION,
PRIMARY_TEXT_TRANSLATE,
/* hasOverflowMenu= */ true);
verify(mMessageDispatcher)
.enqueueMessage(
mPropertyModelCaptor.capture(),
eq(mWebContents),
eq(MessageScopeType.NAVIGATION),
/* highPriority= */ eq(false));
PropertyModel messageProperties = mPropertyModelCaptor.getValue();
translateMessage.clearNativePointer();
// No native methods should be called after clearing the native pointer.
messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).get();
messageProperties.get(MessageBannerProperties.ON_DISMISSED).onResult(DismissReason.GESTURE);
Assert.assertNull(
translateMessage.handleSecondaryMenuItemClicked(
new TranslateMessage.MenuItem("More languages", "", false, 2, "")));
verifyNoMoreInteractions(mMockJni);
}
@Test
@SmallTest
public void testConstructMenuItemArray() {
final String[] titles = new String[] {"foo", "bar", "", "English", "French"};
final String[] subtitles = new String[] {"", "", "", "", "Français"};
final boolean[] hasCheckmarks = new boolean[] {false, true, false, false, false};
final int[] overflowMenuItemIds = new int[] {0, 1, 2, 3, 4};
final String[] languageCodes = new String[] {"", "", "", "en", "fr"};
TranslateMessage.MenuItem[] menuItems =
TranslateMessage.constructMenuItemArray(
titles, subtitles, hasCheckmarks, overflowMenuItemIds, languageCodes);
Assert.assertEquals(titles.length, menuItems.length);
for (int i = 0; i < menuItems.length; ++i) {
Assert.assertEquals(titles[i], menuItems[i].title);
Assert.assertEquals(subtitles[i], menuItems[i].subtitle);
Assert.assertEquals(hasCheckmarks[i], menuItems[i].hasCheckmark);
Assert.assertEquals(overflowMenuItemIds[i], menuItems[i].overflowMenuItemId);
Assert.assertEquals(languageCodes[i], menuItems[i].languageCode);
}
}
private static void assertHasCommonProperties(PropertyModel messageProperties) {
Assert.assertEquals(
MessageIdentifier.TRANSLATE,
messageProperties.get(MessageBannerProperties.MESSAGE_IDENTIFIER));
Assert.assertEquals(
R.drawable.infobar_translate_compact,
messageProperties.get(MessageBannerProperties.ICON_RESOURCE_ID));
Assert.assertEquals(
MessageBannerProperties.TINT_NONE,
messageProperties.get(MessageBannerProperties.ICON_TINT_COLOR));
Assert.assertEquals(
DISMISSAL_DURATION_SECONDS,
messageProperties.get(MessageBannerProperties.DISMISSAL_DURATION));
Assert.assertNotNull(messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION));
Assert.assertNotNull(messageProperties.get(MessageBannerProperties.ON_DISMISSED));
}
private static void assertHasOverflowMenuProperties(PropertyModel messageProperties) {
Assert.assertEquals(
R.drawable.settings_cog,
messageProperties.get(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID));
Assert.assertNotNull(
messageProperties.get(MessageBannerProperties.SECONDARY_MENU_BUTTON_DELEGATE));
Assert.assertEquals(
SecondaryMenuMaxSize.LARGE,
messageProperties.get(MessageBannerProperties.SECONDARY_MENU_MAX_SIZE));
Assert.assertEquals(
MessageIdentifier.TRANSLATE,
messageProperties.get(MessageBannerProperties.MESSAGE_IDENTIFIER));
}
}