blob: 2122198b8aa6ba82a41451aacc15ae4950f9b393 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/translate/translate_bubble_view.h"
#include <memory>
#include <utility>
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/translate/translate_bubble_model.h"
#include "chrome/browser/ui/translate/translate_bubble_view_state_transition.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/range/range.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/button/menu_button.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/widget/widget.h"
namespace {
class MockTranslateBubbleModel : public TranslateBubbleModel {
public:
explicit MockTranslateBubbleModel(TranslateBubbleModel::ViewState view_state)
: view_state_transition_(view_state),
error_type_(translate::TranslateErrors::NONE),
source_language_index_(1),
target_language_index_(2),
never_translate_language_(false),
never_translate_site_(false),
should_show_always_translate_sortcut_(false),
should_always_translate_(false),
always_translate_checked_(false),
set_always_translate_called_count_(0),
translate_called_(false),
revert_translation_called_(false),
translation_declined_(false),
source_language_index_on_translation_(-1),
target_language_index_on_translation_(-1),
can_blocklist_site_(true) {}
TranslateBubbleModel::ViewState GetViewState() const override {
return view_state_transition_.view_state();
}
void SetViewState(TranslateBubbleModel::ViewState view_state) override {
view_state_transition_.SetViewState(view_state);
}
void ShowError(translate::TranslateErrors::Type error_type) override {
error_type_ = error_type;
}
void GoBackFromAdvanced() override {
view_state_transition_.GoBackFromAdvanced();
}
int GetNumberOfSourceLanguages() const override { return 1000; }
int GetNumberOfTargetLanguages() const override { return 1000; }
std::u16string GetSourceLanguageNameAt(int index) const override {
return u"English";
}
std::u16string GetTargetLanguageNameAt(int index) const override {
return u"English";
}
std::string GetSourceLanguageCode() const override {
if (source_language_index_ == 0)
return "und";
return "eng-US";
}
int GetSourceLanguageIndex() const override { return source_language_index_; }
void UpdateSourceLanguageIndex(int index) override {
source_language_index_ = index;
}
int GetTargetLanguageIndex() const override { return target_language_index_; }
void UpdateTargetLanguageIndex(int index) override {
target_language_index_ = index;
}
void DeclineTranslation() override { translation_declined_ = true; }
bool ShouldNeverTranslateLanguage() override {
return never_translate_language_;
}
void SetNeverTranslateLanguage(bool value) override {
never_translate_language_ = value;
}
bool ShouldNeverTranslateSite() override { return never_translate_site_; }
void SetNeverTranslateSite(bool value) override {
never_translate_site_ = value;
}
bool ShouldAlwaysTranslateBeCheckedByDefault() const override {
return always_translate_checked_;
}
bool ShouldShowAlwaysTranslateShortcut() const override {
return should_show_always_translate_sortcut_;
}
void SetShouldShowAlwaysTranslateShortcut(bool value) {
should_show_always_translate_sortcut_ = value;
}
bool ShouldAlwaysTranslate() const override {
return should_always_translate_;
}
void SetAlwaysTranslate(bool value) override {
should_always_translate_ = value;
set_always_translate_called_count_++;
}
void Translate() override {
translate_called_ = true;
source_language_index_on_translation_ = source_language_index_;
target_language_index_on_translation_ = target_language_index_;
}
void RevertTranslation() override { revert_translation_called_ = true; }
void OnBubbleClosing() override {}
bool IsPageTranslatedInCurrentLanguages() const override {
return source_language_index_on_translation_ == source_language_index_ &&
target_language_index_on_translation_ == target_language_index_;
}
bool CanBlocklistSite() override { return can_blocklist_site_; }
void SetCanBlocklistSite(bool value) { can_blocklist_site_ = value; }
void ReportUIInteraction(translate::UIInteraction ui_interaction) override {}
TranslateBubbleViewStateTransition view_state_transition_;
translate::TranslateErrors::Type error_type_;
int source_language_index_;
int target_language_index_;
bool never_translate_language_;
bool never_translate_site_;
bool should_show_always_translate_sortcut_;
bool should_always_translate_;
bool always_translate_checked_;
int set_always_translate_called_count_;
bool translate_called_;
bool revert_translation_called_;
bool translation_declined_;
int source_language_index_on_translation_;
int target_language_index_on_translation_;
bool can_blocklist_site_;
};
} // namespace
class TranslateBubbleViewTest : public ChromeViewsTestBase {
public:
TranslateBubbleViewTest() {}
protected:
void SetUp() override {
ChromeViewsTestBase::SetUp();
// The bubble needs the parent as an anchor.
anchor_widget_ = CreateTestWidget(views::Widget::InitParams::TYPE_WINDOW);
anchor_widget_->Show();
mock_model_ = new MockTranslateBubbleModel(
TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE);
}
void CreateAndShowBubble() {
std::unique_ptr<TranslateBubbleModel> model(mock_model_);
bubble_ = new TranslateBubbleView(anchor_widget_->GetContentsView(),
std::move(model),
translate::TranslateErrors::NONE, NULL);
views::BubbleDialogDelegateView::CreateBubble(bubble_)->Show();
}
void PressButton(TranslateBubbleView::ButtonID id) {
views::Button* button =
static_cast<views::Button*>(bubble_->GetViewByID(id));
views::test::ButtonTestApi(button).NotifyClick(ui::KeyEvent(
ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::DomCode::ENTER, ui::EF_NONE));
}
void TearDown() override {
bubble_->GetWidget()->CloseNow();
anchor_widget_.reset();
ChromeViewsTestBase::TearDown();
}
bool denial_button_clicked() { return mock_model_->translation_declined_; }
void TriggerOptionsMenu() {
PressButton(TranslateBubbleView::BUTTON_ID_OPTIONS_MENU);
}
ui::SimpleMenuModel* options_menu_model() {
return bubble_->options_menu_model_.get();
}
std::unique_ptr<views::Widget> anchor_widget_;
MockTranslateBubbleModel* mock_model_;
TranslateBubbleView* bubble_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(TranslateBubbleViewTest, TranslateButton) {
CreateAndShowBubble();
EXPECT_FALSE(mock_model_->translate_called_);
// Press the "Translate" button.
bubble_->TabSelectedAt(1);
EXPECT_TRUE(mock_model_->translate_called_);
}
TEST_F(TranslateBubbleViewTest, OptionsMenuNeverTranslateLanguage) {
CreateAndShowBubble();
EXPECT_FALSE(bubble_->GetWidget()->IsClosed());
EXPECT_FALSE(mock_model_->never_translate_language_);
EXPECT_FALSE(denial_button_clicked());
TriggerOptionsMenu();
const int index = bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::NEVER_TRANSLATE_LANGUAGE);
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_TRUE(denial_button_clicked());
EXPECT_TRUE(mock_model_->never_translate_language_);
EXPECT_TRUE(bubble_->GetWidget()->IsClosed());
}
TEST_F(TranslateBubbleViewTest, OptionsMenuNeverTranslateSite) {
// NEVER_TRANSLATE_SITE should only show up for sites that can be blocklisted.
mock_model_->SetCanBlocklistSite(true);
CreateAndShowBubble();
EXPECT_FALSE(mock_model_->never_translate_site_);
EXPECT_FALSE(denial_button_clicked());
EXPECT_FALSE(bubble_->GetWidget()->IsClosed());
TriggerOptionsMenu();
const int index = bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::NEVER_TRANSLATE_SITE);
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_TRUE(denial_button_clicked());
EXPECT_TRUE(mock_model_->never_translate_site_);
EXPECT_TRUE(bubble_->GetWidget()->IsClosed());
}
TEST_F(TranslateBubbleViewTest, AlwaysTranslateCheckboxShortcut) {
mock_model_->SetShouldShowAlwaysTranslateShortcut(true);
CreateAndShowBubble();
// Click the "Always Translate" checkbox. Changing the state of this checkbox
// should affect the model right away.
// Check the initial state.
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
EXPECT_FALSE(bubble_->always_translate_checkbox_->GetChecked());
// Click the checkbox. The state is saved.
PressButton(TranslateBubbleView::BUTTON_ID_ALWAYS_TRANSLATE);
EXPECT_TRUE(mock_model_->should_always_translate_);
EXPECT_EQ(1, mock_model_->set_always_translate_called_count_);
EXPECT_TRUE(bubble_->always_translate_checkbox_->GetChecked());
EXPECT_TRUE(mock_model_->translate_called_);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_TRANSLATING,
bubble_->GetViewState());
EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
static_cast<size_t>(1));
}
TEST_F(TranslateBubbleViewTest, AlwaysTranslateCheckboxAndCloseButton) {
CreateAndShowBubble();
bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE);
// Click the "Always Translate" checkbox. Changing the state of this checkbox
// should NOT affect the model after pressing the cancel button.
// Check the initial state.
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
EXPECT_FALSE(bubble_->advanced_always_translate_checkbox_->GetChecked());
// Click the checkbox. The state is not saved yet.
bubble_->advanced_always_translate_checkbox_->SetChecked(true);
PressButton(TranslateBubbleView::BUTTON_ID_ALWAYS_TRANSLATE);
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
// Click the cancel button. The state is not saved.
PressButton(TranslateBubbleView::BUTTON_ID_CLOSE);
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
}
TEST_F(TranslateBubbleViewTest, AlwaysTranslateCheckboxAndDoneButton) {
CreateAndShowBubble();
bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE);
// Click the "Always Translate" checkbox. Changing the state of this checkbox
// should affect the model after pressing the done button.
// Check the initial state.
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
EXPECT_FALSE(bubble_->advanced_always_translate_checkbox_->GetChecked());
// Click the checkbox. The state is not saved yet.
PressButton(TranslateBubbleView::BUTTON_ID_ALWAYS_TRANSLATE);
EXPECT_FALSE(mock_model_->should_always_translate_);
EXPECT_EQ(0, mock_model_->set_always_translate_called_count_);
// Click the done button. The state is saved.
PressButton(TranslateBubbleView::BUTTON_ID_DONE);
EXPECT_TRUE(mock_model_->should_always_translate_);
EXPECT_EQ(1, mock_model_->set_always_translate_called_count_);
}
TEST_F(TranslateBubbleViewTest, SourceDoneButton) {
CreateAndShowBubble();
bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE);
// Click the "Done" button to translate. The selected languages by the user
// are applied.
EXPECT_FALSE(mock_model_->translate_called_);
bubble_->source_language_combobox_->SetSelectedIndex(10);
bubble_->SourceLanguageChanged();
bubble_->target_language_combobox_->SetSelectedIndex(20);
bubble_->TargetLanguageChanged();
PressButton(TranslateBubbleView::BUTTON_ID_DONE);
EXPECT_TRUE(mock_model_->translate_called_);
EXPECT_EQ(10, mock_model_->source_language_index_);
EXPECT_EQ(20, mock_model_->target_language_index_);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE,
bubble_->GetViewState());
}
TEST_F(TranslateBubbleViewTest, TargetDoneButton) {
CreateAndShowBubble();
bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);
// Click the "Done" button to translate. The selected languages by the user
// are applied.
EXPECT_FALSE(mock_model_->translate_called_);
bubble_->source_language_combobox_->SetSelectedIndex(10);
bubble_->SourceLanguageChanged();
bubble_->target_language_combobox_->SetSelectedIndex(20);
bubble_->TargetLanguageChanged();
PressButton(TranslateBubbleView::BUTTON_ID_DONE);
EXPECT_TRUE(mock_model_->translate_called_);
EXPECT_EQ(10, mock_model_->source_language_index_);
EXPECT_EQ(20, mock_model_->target_language_index_);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE,
bubble_->GetViewState());
}
TEST_F(TranslateBubbleViewTest, DoneButtonWithoutTranslating) {
CreateAndShowBubble();
// Translate the page once.
mock_model_->Translate();
EXPECT_TRUE(mock_model_->translate_called_);
// Set translation called to false so it can be verified that translation is
// not called again.
mock_model_->translate_called_ = false;
// Click the "Done" button with the current language pair. This time,
// translation is not performed and the view state will stay at the translated
// view.
PressButton(TranslateBubbleView::BUTTON_ID_DONE);
EXPECT_FALSE(mock_model_->translate_called_);
// The page is already in the translated languages if Done doesn't trigger a
// translation. Clicking done ensures that the UI is in the after translation
// state.
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE,
bubble_->GetViewState());
}
TEST_F(TranslateBubbleViewTest, OptionsMenuRespectsBlocklistSite) {
mock_model_->SetCanBlocklistSite(false);
CreateAndShowBubble();
TriggerOptionsMenu();
// NEVER_TRANSLATE_SITE shouldn't show up for sites that can't be blocklisted.
EXPECT_EQ(-1, bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::NEVER_TRANSLATE_SITE));
// Verify that the menu is populated so previous check makes sense.
EXPECT_GE(bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::NEVER_TRANSLATE_LANGUAGE),
0);
}
TEST_F(TranslateBubbleViewTest, MenuOptionsHiddenOnUnknownSource) {
// Set source language to "Unknown".
mock_model_->UpdateSourceLanguageIndex(0);
CreateAndShowBubble();
TriggerOptionsMenu();
// NEVER_TRANSLATE_LANGUAGE and ALWAYS_TRANSLATE_LANGUAGE shouldn't show when
// the source language is "Unknown".
EXPECT_EQ(-1, bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::NEVER_TRANSLATE_LANGUAGE));
EXPECT_EQ(-1, bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::ALWAYS_TRANSLATE_LANGUAGE));
// Verify that the menu is populated so previous checks make sense.
EXPECT_GE(bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::CHANGE_TARGET_LANGUAGE),
0);
}
TEST_F(TranslateBubbleViewTest, AlwaysTranslateLanguageMenuItem) {
CreateAndShowBubble();
TriggerOptionsMenu();
const int index = bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::ALWAYS_TRANSLATE_LANGUAGE);
EXPECT_FALSE(mock_model_->ShouldAlwaysTranslate());
EXPECT_FALSE(bubble_->options_menu_model_->IsItemCheckedAt(index));
EXPECT_FALSE(mock_model_->translate_called_);
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_TRUE(mock_model_->ShouldAlwaysTranslate());
EXPECT_TRUE(mock_model_->translate_called_);
// Toggle ShouldAlwaysTranslate and make sure Translation doesn't happen
// again.
mock_model_->translate_called_ = false;
// Revert Always Translate.
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_FALSE(mock_model_->translate_called_);
// Recheck Always Translate.
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_FALSE(mock_model_->translate_called_);
// Go back to untranslated page, since the *language* should still always
// be translated (and this "untranslate" is temporary) the option should now
// be checked and it should be possible to disable it from the menu.
bubble_->TabSelectedAt(0);
EXPECT_TRUE(mock_model_->revert_translation_called_);
TriggerOptionsMenu();
EXPECT_TRUE(mock_model_->ShouldAlwaysTranslate());
EXPECT_TRUE(bubble_->options_menu_model_->IsItemCheckedAt(index));
// Translate should not be called when disabling always-translate. The page is
// not currently in a translated state and nothing needs to be reverted.
// translate_called_ is set back to false just to make sure it's not being
// called again.
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_FALSE(mock_model_->ShouldAlwaysTranslate());
EXPECT_FALSE(mock_model_->translate_called_);
TriggerOptionsMenu();
EXPECT_FALSE(bubble_->options_menu_model_->IsItemCheckedAt(index));
}
TEST_F(TranslateBubbleViewTest, AlwaysTranslateTriggerTranslation) {
CreateAndShowBubble();
TriggerOptionsMenu();
const int index = bubble_->options_menu_model_->GetIndexOfCommandId(
TranslateBubbleView::ALWAYS_TRANSLATE_LANGUAGE);
EXPECT_FALSE(mock_model_->ShouldAlwaysTranslate());
EXPECT_FALSE(bubble_->options_menu_model_->IsItemCheckedAt(index));
EXPECT_FALSE(mock_model_->translate_called_);
bubble_->options_menu_model_->ActivatedAt(index);
EXPECT_TRUE(mock_model_->ShouldAlwaysTranslate());
EXPECT_TRUE(mock_model_->translate_called_);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_TRANSLATING,
mock_model_->GetViewState());
// De-select Always Translate while the page stays translated and in state
// VIEW_STATE_TRANSLATING
mock_model_->SetAlwaysTranslate(false);
mock_model_->RevertTranslation();
EXPECT_TRUE(mock_model_->revert_translation_called_);
// Select Always Translate Again should trigger translation
bubble_->options_menu_model_->ActivatedAt(index);
mock_model_->SetAlwaysTranslate(true);
EXPECT_TRUE(mock_model_->ShouldAlwaysTranslate());
EXPECT_TRUE(mock_model_->translate_called_);
EXPECT_TRUE(mock_model_->IsPageTranslatedInCurrentLanguages());
}
TEST_F(TranslateBubbleViewTest, TabSelectedAfterTranslation) {
CreateAndShowBubble();
EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
static_cast<size_t>(0));
mock_model_->Translate();
EXPECT_TRUE(mock_model_->translate_called_);
bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE);
EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
static_cast<size_t>(1));
}
TEST_F(TranslateBubbleViewTest, ShowOriginalUpdatesViewState) {
CreateAndShowBubble();
// Translate.
bubble_->TabSelectedAt(1);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_TRANSLATING,
bubble_->GetViewState());
// Show Original.
bubble_->TabSelectedAt(0);
EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE,
bubble_->GetViewState());
}