// Copyright 2013 The Chromium Authors
// 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/memory/raw_ptr.h"
#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/views/controls/hover_button.h"
#include "chrome/browser/ui/views/translate/translate_language_search_view.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/strings/grit/components_strings.h"
#include "components/translate/core/browser/translate_prefs.h"
#include "components/translate/core/common/translate_features.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.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/scroll_view.h"
#include "ui/views/controls/styled_label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"

namespace {

class MockTranslateBubbleModel : public TranslateBubbleModel {
 public:
  explicit MockTranslateBubbleModel(
      TranslateBubbleModel::ViewState view_state) {
    DCHECK_NE(VIEW_STATE_SOURCE_LANGUAGE, view_state);
    DCHECK_NE(VIEW_STATE_TARGET_LANGUAGE, view_state);
    current_view_state_ = view_state;
  }

  TranslateBubbleModel::ViewState GetViewState() const override {
    return current_view_state_;
  }

  void SetViewState(TranslateBubbleModel::ViewState view_state) override {
    current_view_state_ = view_state;
  }

  void ShowError(translate::TranslateErrors error_type) override {
    error_type_ = error_type;
  }

  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 {
    if (index == 10) {
      return u"Spanish";
    }
    if (index == 20) {
      return u"French";
    }
    return u"English";
  }

  std::optional<size_t> GetTargetLanguageIndexForCode(
      const std::string& language_code) const override {
    if (language_code == "es") {
      return 10;
    }
    if (language_code == "fr") {
      return 20;
    }
    return 1;
  }

  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 CanAddSiteToNeverPromptList() override {
    return can_add_site_to_never_prompt_list;
  }

  void SetCanAddSiteToNeverPromptList(bool value) {
    can_add_site_to_never_prompt_list = value;
  }

  void ReportUIInteraction(translate::UIInteraction ui_interaction) override {}

  void ReportUIChange(bool is_ui_shown) override {}

  ViewState current_view_state_;
  translate::TranslateErrors error_type_ = translate::TranslateErrors::NONE;
  int source_language_index_ = 1;
  int target_language_index_ = 2;
  bool never_translate_language_ = false;
  bool never_translate_site_ = false;
  bool should_show_always_translate_sortcut_ = false;
  bool should_always_translate_ = false;
  bool always_translate_checked_ = false;
  int set_always_translate_called_count_ = 0;
  bool translate_called_ = false;
  bool revert_translation_called_ = false;
  bool translation_declined_ = false;
  int source_language_index_on_translation_ = -1;
  int target_language_index_on_translation_ = -1;
  bool can_add_site_to_never_prompt_list = true;
};

views::Textfield* GetSearchField(views::View* search_view) {
  if (search_view->children().empty()) {
    return nullptr;
  }
  views::View* search_container = search_view->children()[0];
  for (views::View* child : search_container->children()) {
    if (views::IsViewClass<views::Textfield>(child)) {
      return static_cast<views::Textfield*>(child);
    }
  }
  return nullptr;
}

views::BoxLayoutView* GetListView(views::View* search_view) {
  if (search_view->children().size() < 2) {
    return nullptr;
  }
  views::View* scroll_view = search_view->children()[1];
  if (views::IsViewClass<views::ScrollView>(scroll_view)) {
    return views::AsViewClass<views::BoxLayoutView>(
        static_cast<views::ScrollView*>(scroll_view)->contents());
  }
  return nullptr;
}

views::TabbedPane* GetTabbedPane(views::View* view) {
  if (views::IsViewClass<views::TabbedPane>(view)) {
    return static_cast<views::TabbedPane*>(view);
  }
  for (views::View* child : view->children()) {
    views::TabbedPane* tabbed_pane = GetTabbedPane(child);
    if (tabbed_pane) {
      return tabbed_pane;
    }
  }
  return nullptr;
}

}  // namespace

class TranslateBubbleViewTest : public ChromeViewsTestBase {
 public:
  TranslateBubbleViewTest() = default;

 protected:
  void SetUp() override {
    ChromeViewsTestBase::SetUp();

    // The bubble needs the parent as an anchor.
    anchor_widget_ =
        CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET,
                         views::Widget::InitParams::TYPE_WINDOW);
    anchor_widget_->Show();
    web_contents_ =
        content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);

    mock_model_ = new MockTranslateBubbleModel(
        TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE);
  }

  void CreateAndShowBubble() {
    std::unique_ptr<TranslateBubbleModel> model(mock_model_);
    bubble_ = new TranslateBubbleView(
        views::BubbleAnchor(anchor_widget_->GetContentsView()),
        std::move(model), translate::TranslateErrors::NONE, web_contents_.get(),
        base::DoNothing());
    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::EventType::kKeyPressed, ui::VKEY_RETURN,
                     ui::DomCode::ENTER, ui::EF_NONE));
  }

  void TearDown() override {
    mock_model_ = nullptr;
    if (bubble_) {
      bubble_.ExtractAsDangling()->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();
  }

  void SwitchView(TranslateBubbleModel::ViewState view_state) {
    bubble_->SwitchView(view_state);
  }

  views::View* advanced_view_source() { return bubble_->advanced_view_source_; }

  views::View* advanced_view_target() { return bubble_->advanced_view_target_; }

  TranslateLanguageSearchView* translate_language_search_view() {
    return bubble_->translate_language_search_view_;
  }

  TestingProfile profile_;
  content::RenderViewHostTestEnabler test_render_host_factories_;
  std::unique_ptr<content::WebContents> web_contents_;
  std::unique_ptr<views::Widget> anchor_widget_;
  raw_ptr<MockTranslateBubbleModel> mock_model_ = nullptr;
  raw_ptr<TranslateBubbleView> bubble_ = nullptr;
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(TranslateBubbleViewTest, TargetLanguageTabTriggersTranslate) {
  CreateAndShowBubble();
  EXPECT_FALSE(mock_model_->translate_called_);

  // Press the target language tab to start translation.
  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 size_t index =
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kNeverTranslateLanguage))
          .value();
  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_->SetCanAddSiteToNeverPromptList(true);
  CreateAndShowBubble();

  EXPECT_FALSE(mock_model_->never_translate_site_);
  EXPECT_FALSE(denial_button_clicked());
  EXPECT_FALSE(bubble_->GetWidget()->IsClosed());

  TriggerOptionsMenu();
  const size_t index =
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kNeverTranslateSite))
          .value();
  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(), 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, SourceResetButton) {
  CreateAndShowBubble();
  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE);

  // If there is no change in language selection, the reset button should be
  // disabled.
  EXPECT_FALSE(bubble_->advanced_reset_button_source_->GetEnabled());

  // Change the language selection. The reset button should be enabled.
  bubble_->source_language_combobox_->SetSelectedIndex(10);
  bubble_->SourceLanguageChanged();
  EXPECT_EQ(10u, bubble_->source_language_combobox_->GetSelectedIndex());
  EXPECT_TRUE(bubble_->advanced_reset_button_source_->GetEnabled());

  // Press the reset button. Language should change back to initial selection.
  PressButton(TranslateBubbleView::BUTTON_ID_RESET);
  EXPECT_EQ(1u, bubble_->source_language_combobox_->GetSelectedIndex());
  EXPECT_FALSE(bubble_->advanced_reset_button_source_->GetEnabled());
}

TEST_F(TranslateBubbleViewTest, TargetResetButton) {
  CreateAndShowBubble();
  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  // If there is no change in language selection, the reset button should be
  // disabled.
  EXPECT_FALSE(bubble_->advanced_reset_button_target_->GetEnabled());

  // Change the language selection. The reset button should be enabled.
  bubble_->target_language_combobox_->SetSelectedIndex(10);
  bubble_->TargetLanguageChanged();
  EXPECT_EQ(10u, bubble_->target_language_combobox_->GetSelectedIndex());
  EXPECT_TRUE(bubble_->advanced_reset_button_target_->GetEnabled());

  // Press the reset button. Language should change back to initial selection.
  PressButton(TranslateBubbleView::BUTTON_ID_RESET);
  EXPECT_EQ(2u, bubble_->target_language_combobox_->GetSelectedIndex());
  EXPECT_FALSE(bubble_->advanced_reset_button_target_->GetEnabled());
}

// TODO(crbug.com/548467453): Re-enable this test.
#if BUILDFLAG(IS_WIN)
#define MAYBE_LazyViewInitialization DISABLED_LazyViewInitialization
#else
#define MAYBE_LazyViewInitialization LazyViewInitialization
#endif
TEST_F(TranslateBubbleViewTest, MAYBE_LazyViewInitialization) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();

  // Initially on VIEW_STATE_BEFORE_TRANSLATE, advanced views should not be
  // created.
  EXPECT_EQ(nullptr, advanced_view_source());
  EXPECT_EQ(nullptr, advanced_view_target());
  EXPECT_EQ(nullptr, translate_language_search_view());

  // Switching to target language view creates the advanced target view.
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);
  EXPECT_NE(nullptr, advanced_view_target());
  EXPECT_NE(nullptr, translate_language_search_view());
  EXPECT_EQ(nullptr, advanced_view_source());

  // Switching to source language view creates the advanced source view.
  SwitchView(TranslateBubbleModel::VIEW_STATE_SOURCE_LANGUAGE);
  EXPECT_NE(nullptr, advanced_view_source());
}

TEST_F(TranslateBubbleViewTest, SourceDoneButton) {
  CreateAndShowBubble();
  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);
  bubble_->target_language_combobox_->SetSelectedIndex(20);
  bubble_->TargetLanguageChanged();
  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();
  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_SOURCE_LANGUAGE);
  bubble_->source_language_combobox_->SetSelectedIndex(10);
  bubble_->SourceLanguageChanged();
  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_->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;

  // Switch to target language view where the "Done" button is located.
  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  // 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_->SetCanAddSiteToNeverPromptList(false);
  CreateAndShowBubble();

  TriggerOptionsMenu();
  // NEVER_TRANSLATE_SITE shouldn't show up for sites that can't be blocklisted.
  EXPECT_FALSE(
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kNeverTranslateSite))
          .has_value());
  // Verify that the menu is populated so previous check makes sense.
  EXPECT_TRUE(
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kNeverTranslateLanguage))
          .has_value());
}

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_FALSE(
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kNeverTranslateLanguage))
          .has_value());
  EXPECT_FALSE(
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kAlwaysTranslateLanguage))
          .has_value());
  // Verify that the menu is populated so previous checks make sense.
  EXPECT_TRUE(
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kChangeSourceLanguage))
          .has_value());
}

TEST_F(TranslateBubbleViewTest, AlwaysTranslateLanguageMenuItem) {
  CreateAndShowBubble();

  TriggerOptionsMenu();
  const size_t index =
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kAlwaysTranslateLanguage))
          .value();

  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 size_t index =
      bubble_->options_menu_model_
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kAlwaysTranslateLanguage))
          .value();

  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));
  bubble_->SwitchView(TranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE);
  EXPECT_EQ(bubble_->tabbed_pane_->GetSelectedTabIndex(),
            static_cast<size_t>(1));
}

TEST_F(TranslateBubbleViewTest, SourceLanguageTabUpdatesViewState) {
  CreateAndShowBubble();
  // Select target language tab to translate.
  bubble_->TabSelectedAt(1);
  EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_TRANSLATING,
            bubble_->GetViewState());

  // Select source language tab to revert translation.
  bubble_->TabSelectedAt(0);
  EXPECT_EQ(TranslateBubbleModel::VIEW_STATE_BEFORE_TRANSLATE,
            bubble_->GetViewState());
}

TEST_F(TranslateBubbleViewTest, ChangeTargetLanguageMenuHiddenWhenButtonShown) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(translate::kTranslateLanguageSearchUI);

  // Ensure the "Always Translate" checkbox is not shown.
  mock_model_->SetShouldShowAlwaysTranslateShortcut(false);
  CreateAndShowBubble();
  TriggerOptionsMenu();

  // The option should be removed from the three dot menu.
  EXPECT_FALSE(
      options_menu_model()
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kChangeTargetLanguage))
          .has_value());
}

TEST_F(TranslateBubbleViewTest,
       ChangeTargetLanguageMenuRetainedWhenCheckboxShown) {
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(translate::kTranslateLanguageSearchUI);

  // Force the "Always Translate" checkbox to be shown.
  mock_model_->SetShouldShowAlwaysTranslateShortcut(true);
  CreateAndShowBubble();
  TriggerOptionsMenu();

  // The option should appear in the three dot menu.
  EXPECT_TRUE(
      options_menu_model()
          ->GetIndexOfCommandId(static_cast<int>(
              TranslateBubbleView::OptionsMenuItem::kChangeTargetLanguage))
          .has_value());
}

// TODO(crbug.com/550460296): Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_RecentLanguagesShowUpInSearchView \
  DISABLED_RecentLanguagesShowUpInSearchView
#else
#define MAYBE_RecentLanguagesShowUpInSearchView \
  RecentLanguagesShowUpInSearchView
#endif
TEST_F(TranslateBubbleViewTest, MAYBE_RecentLanguagesShowUpInSearchView) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  // Mock some recent languages in prefs.
  std::unique_ptr<translate::TranslatePrefs> translate_prefs =
      std::make_unique<translate::TranslatePrefs>(profile_.GetPrefs());
  translate_prefs->SetRecentTargetLanguage("es");
  translate_prefs->SetRecentTargetLanguage("fr");

  CreateAndShowBubble();
  // Switch to target language view (which creates the search view).
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  views::BoxLayoutView* list_view = GetListView(search_view);
  ASSERT_TRUE(list_view);
  // Verify that the list view contains the recent languages.
  std::vector<std::u16string> button_texts;
  for (views::View* child : list_view->children()) {
    if (views::IsViewClass<HoverButton>(child)) {
      button_texts.emplace_back(static_cast<HoverButton*>(child)->GetText());
    }
  }

  EXPECT_THAT(button_texts, testing::IsSupersetOf({u"French", u"Spanish"}));
}

// TODO(crbug.com/550460296): Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_NoResultsMessageShowsWhenQueryHasNoMatches \
  DISABLED_NoResultsMessageShowsWhenQueryHasNoMatches
#else
#define MAYBE_NoResultsMessageShowsWhenQueryHasNoMatches \
  NoResultsMessageShowsWhenQueryHasNoMatches
#endif
TEST_F(TranslateBubbleViewTest,
       MAYBE_NoResultsMessageShowsWhenQueryHasNoMatches) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  views::Textfield* search_field = GetSearchField(search_view);
  ASSERT_TRUE(search_field);

  // Set the search field text to a query with no matches.
  search_field->SetText(u"invalid_language_name");
  search_view->ContentsChanged(search_field,
                               std::u16string(search_field->GetText()));

  views::BoxLayoutView* list_view = GetListView(search_view);
  ASSERT_TRUE(list_view);

  // Verify that there are no HoverButtons.
  for (views::View* child : list_view->children()) {
    EXPECT_FALSE(views::IsViewClass<HoverButton>(child));
  }

  // Verify that the "No Results Found" label is shown.
  ASSERT_EQ(list_view->children().size(), 1u);
  views::Label* label =
      static_cast<views::Label*>(list_view->children().front());
  EXPECT_EQ(label->GetText(),
            l10n_util::GetStringUTF16(IDS_TRANSLATE_BUBBLE_NO_RESULTS));
}

// TODO(crbug.com/550460296): Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_ClickingLanguageClearsListViewWithoutNoResults \
  DISABLED_ClickingLanguageClearsListViewWithoutNoResults
#else
#define MAYBE_ClickingLanguageClearsListViewWithoutNoResults \
  ClickingLanguageClearsListViewWithoutNoResults
#endif
TEST_F(TranslateBubbleViewTest,
       MAYBE_ClickingLanguageClearsListViewWithoutNoResults) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  views::BoxLayoutView* list_view = GetListView(search_view);
  ASSERT_TRUE(list_view);

  // Get the first HoverButton in the list view.
  ASSERT_FALSE(list_view->children().empty());
  HoverButton* button_to_click =
      views::AsViewClass<HoverButton>(list_view->children().front());
  ASSERT_TRUE(button_to_click);

  // Click the button.
  views::test::ButtonTestApi(button_to_click)
      .NotifyClick(ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_RETURN,
                                ui::DomCode::ENTER, ui::EF_NONE));

  // Verify that the list view is now empty.
  EXPECT_TRUE(list_view->children().empty());
}

// TODO(crbug.com/527306844): Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_ListShowsUpAgainWhenTypingAfterLanguageSelection \
  DISABLED_ListShowsUpAgainWhenTypingAfterLanguageSelection
#else
#define MAYBE_ListShowsUpAgainWhenTypingAfterLanguageSelection \
  ListShowsUpAgainWhenTypingAfterLanguageSelection
#endif
TEST_F(TranslateBubbleViewTest,
       MAYBE_ListShowsUpAgainWhenTypingAfterLanguageSelection) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  views::Textfield* search_field = GetSearchField(search_view);
  ASSERT_TRUE(search_field);

  views::BoxLayoutView* list_view = GetListView(search_view);
  ASSERT_TRUE(list_view);

  // 1. Click on a language button.
  ASSERT_FALSE(list_view->children().empty());
  HoverButton* button_to_click =
      views::AsViewClass<HoverButton>(list_view->children().front());
  ASSERT_TRUE(button_to_click);
  std::u16string language_name = std::u16string(button_to_click->GetText());

  views::test::ButtonTestApi(button_to_click)
      .NotifyClick(ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_RETURN,
                                ui::DomCode::ENTER, ui::EF_NONE));

  // Verify list is empty.
  EXPECT_TRUE(list_view->children().empty());

  // 2. Simulate user typing (deleting characters) in the search field.
  ASSERT_GT(language_name.length(), 2u);
  std::u16string partial_query =
      language_name.substr(0, language_name.length() - 2);
  search_field->SetText(partial_query);
  search_view->ContentsChanged(search_field,
                               std::u16string(search_field->GetText()));

  // Verify the list shows up again with matching language buttons.
  std::vector<std::u16string> button_texts;
  for (views::View* child : list_view->children()) {
    if (views::IsViewClass<HoverButton>(child)) {
      button_texts.emplace_back(static_cast<HoverButton*>(child)->GetText());
    }
  }
  EXPECT_FALSE(button_texts.empty());
  EXPECT_THAT(button_texts, testing::Contains(language_name));
}

TEST_F(TranslateBubbleViewTest, SearchNoResultsMessage) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  // Type a query that matches no languages (e.g. "xyz").
  search_view->ContentsChanged(nullptr, u"xyz");

  views::BoxLayoutView* list_view = GetListView(search_view);
  ASSERT_TRUE(list_view);

  // Verify that the list view only contains the "No Results Found" label.
  ASSERT_EQ(1u, list_view->children().size());
  views::View* child = list_view->children().front();
  ASSERT_TRUE(views::IsViewClass<views::Label>(child));
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_TRANSLATE_BUBBLE_NO_RESULTS),
            static_cast<views::Label*>(child)->GetText());
}

TEST_F(TranslateBubbleViewTest, InitialFocus) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();

  // The initially focused view should be the selected tab (index 0).
  views::View* initially_focused = bubble_->GetInitiallyFocusedView();
  ASSERT_TRUE(initially_focused);
  views::TabbedPane* tabbed_pane = GetTabbedPane(bubble_);
  ASSERT_TRUE(tabbed_pane);
  EXPECT_EQ(tabbed_pane->GetTabAt(0), initially_focused);
}

// TODO(crbug.com/550460296): Re-enable this test.
#if BUILDFLAG(IS_WIN) && !defined(NDEBUG)
#define MAYBE_SearchFieldFocusedOnSwitchToTargetLanguageView \
  DISABLED_SearchFieldFocusedOnSwitchToTargetLanguageView
#else
#define MAYBE_SearchFieldFocusedOnSwitchToTargetLanguageView \
  SearchFieldFocusedOnSwitchToTargetLanguageView
#endif
TEST_F(TranslateBubbleViewTest,
       MAYBE_SearchFieldFocusedOnSwitchToTargetLanguageView) {
  base::test::ScopedFeatureList features(translate::kTranslateLanguageSearchUI);
  CreateAndShowBubble();
  SwitchView(TranslateBubbleModel::VIEW_STATE_TARGET_LANGUAGE);

  TranslateLanguageSearchView* search_view = translate_language_search_view();
  ASSERT_TRUE(search_view);

  views::Textfield* search_field = GetSearchField(search_view);
  ASSERT_TRUE(search_field);

  EXPECT_TRUE(search_field->HasFocus());
}
