chromium/chrome/browser/ash/input_method/assistive_suggester_unittest.cc

// 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.

#include "chrome/browser/ash/input_method/assistive_suggester.h"

#include "ash/clipboard/clipboard_history_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/input_method/assistive_suggester_client_filter.h"
#include "chrome/browser/ash/input_method/assistive_suggester_switch.h"
#include "chrome/browser/ash/input_method/fake_suggestion_handler.h"
#include "chrome/browser/ash/input_method/get_current_window_properties.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "components/account_id/account_id.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/geo/country_names.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/views/controls/textfield/textfield.h"

namespace ash::input_method {
namespace {

using ime::AssistiveSuggestion;
using ime::AssistiveSuggestionMode;
using ime::AssistiveSuggestionType;
using ime::SuggestionsTextContext;
using EnabledSuggestions = AssistiveSuggesterSwitch::EnabledSuggestions;

const char kUsEnglishEngineId[] = "xkb:us::eng";
const char kSpainSpanishEngineId[] = "xkb:es::spa";
const char kEmojiData[] = "arrow,←;↑;→";
const TextInputMethod::InputContext empty_context(ui::TEXT_INPUT_TYPE_NONE);
constexpr size_t kTakeLastNChars = 100;

ui::KeyEvent GenerateKeyEvent(const ui::DomCode& code,
                              const ui::EventType& event_type,
                              int flags) {
  return ui::KeyEvent(event_type, ui::VKEY_UNKNOWN, code, flags,
                      ui::DomKey::NONE, ui::EventTimeForNow());
}

ui::KeyEvent ReleaseKey(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyReleased, ui::EF_NONE);
}

ui::KeyEvent PressKey(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyPressed, ui::EF_NONE);
}

ui::KeyEvent PressKeyWithAlt(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyPressed, ui::EF_ALT_DOWN);
}

ui::KeyEvent PressKeyWithCtrl(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyPressed,
                          ui::EF_CONTROL_DOWN);
}

ui::KeyEvent PressKeyWithShift(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyPressed, ui::EF_SHIFT_DOWN);
}

ui::KeyEvent CreateRepeatKeyEvent(const ui::DomCode& code) {
  return GenerateKeyEvent(code, ui::EventType::kKeyPressed, ui::EF_IS_REPEAT);
}

void SetInputMethodOptions(Profile& profile,
                           bool predictive_writing_enabled,
                           bool diacritics_on_longpress_enabled) {
  base::Value::Dict input_method_setting;
  input_method_setting.SetByDottedPath(
      std::string(kUsEnglishEngineId) +
          ".physicalKeyboardEnablePredictiveWriting",
      predictive_writing_enabled);
  profile.GetPrefs()->Set(::prefs::kLanguageInputMethodSpecificSettings,
                          base::Value(std::move(input_method_setting)));
  profile.GetPrefs()->Set(::ash::prefs::kLongPressDiacriticsEnabled,
                          base::Value(diacritics_on_longpress_enabled));
}

SuggestionsTextContext TextContext(const std::string& surrounding_text) {
  const size_t text_length = surrounding_text.length();
  const size_t trim_from =
      text_length > kTakeLastNChars ? text_length - kTakeLastNChars : 0;
  return SuggestionsTextContext{
      .last_n_chars = surrounding_text.substr(trim_from),
      .surrounding_text_length = text_length};
}

}  // namespace

class FakeSuggesterSwitch : public AssistiveSuggesterSwitch {
 public:
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  explicit FakeSuggesterSwitch(EnabledSuggestions enabled_suggestions)
      : enabled_suggestions_(enabled_suggestions) {}
  ~FakeSuggesterSwitch() override = default;

  // AssistiveSuggesterSwitch overrides
  void FetchEnabledSuggestionsThen(
      FetchEnabledSuggestionsCallback callback,
      const TextInputMethod::InputContext& context) override {
    std::move(callback).Run(enabled_suggestions_);
  }

 private:
  EnabledSuggestions enabled_suggestions_;
};

class AssistiveSuggesterTest : public testing::Test {
 protected:
  AssistiveSuggesterTest() { profile_ = std::make_unique<TestingProfile>(); }

  void SetUp() override {
    suggestion_handler_ = std::make_unique<FakeSuggestionHandler>();
    // TODO(b/242472734): Allow enabled suggestions passed without replace.
    assistive_suggester_ = std::make_unique<AssistiveSuggester>(
        suggestion_handler_.get(), profile_.get(),
        std::make_unique<AssistiveSuggesterClientFilter>(
            base::BindRepeating(&GetFocusedTabUrl),
            base::BindRepeating(&GetFocusedWindowProperties)));

    histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.UserPref.Emoji",
                                         true, 1);
    // Emoji is default to true now, so need to set emoji pref false to test
    // IsAssistiveFeatureEnabled correctly.
    profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);
  }

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<AssistiveSuggester> assistive_suggester_;
  std::unique_ptr<FakeSuggestionHandler> suggestion_handler_;
  base::HistogramTester histogram_tester_;
};

TEST_F(AssistiveSuggesterTest, EmojiSuggestion_UserPrefEnabledFalse) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   true);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest, EmojiSuggestion_EnterprisePrefEnabledFalse) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   false);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, true);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest, EmojiSuggestion_BothPrefsEnabledTrue) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   true);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, true);

  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest, EmojiSuggestion_BothPrefsEnabledFalse) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   false);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       EnhancedEmojiSuggestDisabledWhenStandardEmojiDisabledAndPrefsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistEmojiEnhanced},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   false);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       EnhancedEmojiSuggestEnabledWhenStandardEmojiEnabledAndPrefsEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistEmojiEnhanced},
      /*disabled_features=*/{features::kAssistMultiWord});
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   true);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, true);

  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       MultiWordEnabledWhenFeatureFlagEnabledAndPrefEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       MultiWordDisabledWhenFeatureFlagEnabledAndPrefDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       MultiWordDisabledWhenFeatureFlagDisabledAndPrefDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       AssistiveDiacriticsLongpressFlagAndPrefEnabled_AssistiveFeatureEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kDiacriticsOnPhysicalKeyboardLongpress);
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       AssistiveDiacriticsLongpressFlagDisabled_AssistiveFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kDiacriticsOnPhysicalKeyboardLongpress);
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       AssistiveDiacriticsLongpressPrefDisabled_AssistiveFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kDiacriticsOnPhysicalKeyboardLongpress);
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       OnlyAssistiveControlVLongpressFlagEnabled_AssistiveFeatureEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kClipboardHistoryLongpress},
      /*disabled_features=*/{features::kAssistMultiWord,
                             features::kAssistEmojiEnhanced,
                             features::kDiacriticsOnPhysicalKeyboardLongpress});
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest,
       AssistiveControlVLongpressFlagDisabled_AssistiveFeatureDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{}, /*disabled_features=*/{
          features::kClipboardHistoryLongpress, features::kAssistMultiWord,
          features::kAssistEmojiEnhanced,
          features::kDiacriticsOnPhysicalKeyboardLongpress});
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
}

TEST_F(AssistiveSuggesterTest, RecordPKDiacriticsPrefEnabledOnActivate) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kDiacriticsOnPhysicalKeyboardLongpress);

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.UserPref.PhysicalKeyboardDiacriticsOnLongpress",
      true, 1);
}

TEST_F(AssistiveSuggesterTest, RecordPKDiacriticsPrefDisabledOnActivate) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(
      features::kDiacriticsOnPhysicalKeyboardLongpress);

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.UserPref.PhysicalKeyboardDiacriticsOnLongpress",
      false, 1);
}

TEST_F(AssistiveSuggesterTest, RecordPredictiveWritingPrefOnActivate) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.UserPref.MultiWord", true, 1);
}

TEST_F(AssistiveSuggesterTest, RecordsMultiWordTextInputAsNotAllowed) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  histogram_tester_.ExpectTotalCount(
      "InputMethod.Assistive.MultiWord.InputState", 1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.MultiWord.InputState",
      AssistiveTextInputState::kFeatureBlockedByDenylist, 1);
}

TEST_F(AssistiveSuggesterTest, RecordsMultiWordTextInputAsDisabledByUser) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.multi_word_suggestions = true}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  histogram_tester_.ExpectTotalCount(
      "InputMethod.Assistive.MultiWord.InputState", 1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.MultiWord.InputState",
      AssistiveTextInputState::kFeatureBlockedByPreference, 1);
}

TEST_F(AssistiveSuggesterTest, RecordsMultiWordTextInputAsEnabledByLacros) {
  base::test::ScopedFeatureList feature_list;
  std::vector<base::test::FeatureRef> enabled =
      ash::standalone_browser::GetFeatureRefs();
  enabled.push_back(features::kAssistMultiWord);
  feature_list.InitWithFeatures(enabled, {});
  base::test::ScopedCommandLine scoped_command_line;
  scoped_command_line.GetProcessCommandLine()->AppendSwitch(
      ash::switches::kEnableLacrosForTesting);

  // Set up a user, necessary for Lacros.
  auto fake_user_manager = std::make_unique<user_manager::FakeUserManager>();
  auto* primary_user =
      fake_user_manager->AddUser(AccountId::FromUserEmail("test@test"));
  fake_user_manager->UserLoggedIn(primary_user->GetAccountId(),
                                  primary_user->username_hash(),
                                  /*browser_restart=*/false,
                                  /*is_child=*/false);
  auto scoped_user_manager = std::make_unique<user_manager::ScopedUserManager>(
      std::move(fake_user_manager));
  ASSERT_TRUE(crosapi::browser_util::IsLacrosEnabled());

  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.multi_word_suggestions = true}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  histogram_tester_.ExpectTotalCount(
      "InputMethod.Assistive.MultiWord.InputState", 1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.MultiWord.InputState",
      AssistiveTextInputState::kFeatureEnabled, 1);
}

TEST_F(AssistiveSuggesterTest,
       RecordMultiWordTextInputAsDisabledByUnsupportedLang) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.multi_word_suggestions = true}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kSpainSpanishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  histogram_tester_.ExpectTotalCount(
      "InputMethod.Assistive.MultiWord.InputState", 1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.MultiWord.InputState",
      AssistiveTextInputState::kUnsupportedLanguage, 1);
}

TEST_F(AssistiveSuggesterTest, RecordsMultiWordTextInputAsEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kAssistMultiWord},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.multi_word_suggestions = true}));

  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                        /*diacritics_on_longpress_enabled=*/false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  histogram_tester_.ExpectTotalCount(
      "InputMethod.Assistive.MultiWord.InputState", 1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.MultiWord.InputState",
      AssistiveTextInputState::kFeatureEnabled, 1);
}

TEST_F(AssistiveSuggesterTest,
       DiacriticsSuggestionNotTriggeredIfShiftDownAndShiftUp) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(PressKeyWithShift(ui::DomCode::US_A)),
      AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  EXPECT_EQ(assistive_suggester_->OnKeyEvent(ReleaseKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterTest,
       DiacriticsSuggestionOnKeyDownLongpressForUSEnglish) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_->OnSurroundingTextChanged(u"a", gfx::Range(1));
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"à;á;â;ä;æ;ã;å;ā");
}

TEST_F(
    AssistiveSuggesterTest,
    DiacriticsSuggestionDisabledOnKeyDownLongpressForLastSurroundingTextEmpty) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(
    AssistiveSuggesterTest,
    DiacriticsSuggestionDisabledOnKeyDownLongpressForLastSurroundingTextBeforeCursorNotMatch) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"xyz", gfx::Range(1));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(
    AssistiveSuggesterTest,
    DiacriticsSuggestionDisabledOnKeyDownLongpressForLastSurroundingTextCursorPosTooLarge) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"xyz", gfx::Range(10));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(
    AssistiveSuggesterTest,
    DiacriticsSuggestionDisabledOnKeyDownLongpressForLastSurroundingTextCursorPosZero) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"xyz", gfx::Range(0));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterTest, DiacriticsSuggestionOnKeyDownRecordsSuccess) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_->OnSurroundingTextChanged(u"a", gfx::Range(1));
  task_environment_.FastForwardBy(base::Seconds(1));
  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::DIGIT1)),
            AssistiveSuggesterKeyResult::kHandled);

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Success", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Success",
                                       AssistiveType::kLongpressDiacritics, 1);
}

TEST_F(AssistiveSuggesterTest,
       NoDiacriticsSuggestionOnKeyDownLongpressForUSEnglishOnPrefDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterTest,
       NoDiacriticsSuggestionOnKeyDownLongpressForNonUSEnglish) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kSpainSpanishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(base::Seconds(1));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterTest,
       DiacriticsSuggestionOnKeyDownLongpressNotInterruptedByOtherKeys) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_->OnSurroundingTextChanged(u"a", gfx::Range(1));
  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::SHIFT_LEFT)),
            AssistiveSuggesterKeyResult::kNotHandled);
  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(ReleaseKey(ui::DomCode::SHIFT_LEFT)),
      AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(base::Seconds(1));
  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"à;á;â;ä;æ;ã;å;ā");
}

TEST_F(AssistiveSuggesterTest,
       DiacriticsSuggestionWithoutContextIgnoresOnKeyDownLongpress) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  assistive_suggester_->OnActivate(kUsEnglishEngineId);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(base::Seconds(1));
  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"");
}

TEST_F(AssistiveSuggesterTest, DiacriticsSuggestionInterruptedDoesNotSuggest) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  task_environment_.FastForwardBy(
      base::Milliseconds(100));  // Not long enough to trigger longpress.
  EXPECT_EQ(assistive_suggester_->OnKeyEvent(ReleaseKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"");
}

TEST_F(AssistiveSuggesterTest,
       DoNotPropagateAlphaRepeatKeyIfDiacriticsOnLongpressEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = true}));
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(CreateRepeatKeyEvent(ui::DomCode::US_A)),
      AssistiveSuggesterKeyResult::kHandled);
  task_environment_.FastForwardBy(
      base::Seconds(1));  // Long enough to trigger longpress.
  EXPECT_EQ(assistive_suggester_->OnKeyEvent(ReleaseKey(ui::DomCode::US_A)),
            AssistiveSuggesterKeyResult::kNotHandled);
  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"");
}

TEST_F(AssistiveSuggesterTest,
       PropagateAlphaRepeatKeyIfDiacriticsOnLongpressDisabledViaSettings) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = false}));
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(CreateRepeatKeyEvent(ui::DomCode::US_A)),
      AssistiveSuggesterKeyResult::kNotHandled);
}

TEST_F(AssistiveSuggesterTest,
       PropagateAlphaRepeatKeyIfDiacriticsOnLongpressDisabledDenylist) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/false);
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(
          EnabledSuggestions{.diacritic_suggestions = false}));
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(CreateRepeatKeyEvent(ui::DomCode::US_A)),
      AssistiveSuggesterKeyResult::kNotHandled);
}

TEST_F(AssistiveSuggesterTest,
       IgnoreAndPropagateNonAlphaRepeatKeyIfDiacriticsOnLongpressEnabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(
                CreateRepeatKeyEvent(ui::DomCode::ARROW_DOWN)),
            AssistiveSuggesterKeyResult::kNotHandled);
}

TEST_F(AssistiveSuggesterTest, StoreLastEnabledSuggestionOnFocus) {
  EnabledSuggestions enabled_suggestions = EnabledSuggestions{
      .emoji_suggestions = true, .diacritic_suggestions = true};
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(enabled_suggestions));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  EXPECT_TRUE(assistive_suggester_
                  ->get_enabled_suggestion_from_last_onfocus_for_testing()
                  .has_value());
  EXPECT_EQ(*assistive_suggester_
                 ->get_enabled_suggestion_from_last_onfocus_for_testing(),
            enabled_suggestions);
}

TEST_F(AssistiveSuggesterTest, ClearLastEnabledSuggestionOnBlur) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitWithFeatures(
      /*enabled_features=*/{features::kDiacriticsOnPhysicalKeyboardLongpress},
      /*disabled_features=*/{});
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
          .emoji_suggestions = true, .diacritic_suggestions = true}));
  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
                        /*diacritics_on_longpress_enabled=*/true);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnBlur();

  EXPECT_FALSE(assistive_suggester_
                   ->get_enabled_suggestion_from_last_onfocus_for_testing()
                   .has_value());
}

class AssistiveSuggesterMultiWordTest : public testing::Test {
 protected:
  AssistiveSuggesterMultiWordTest() {
    profile_ = std::make_unique<TestingProfile>();
  }

  void SetUp() override {
    suggestion_handler_ = std::make_unique<FakeSuggestionHandler>();
    // TODO(b/242472734): Allow enabled suggestions passed without replace.
    assistive_suggester_ = std::make_unique<AssistiveSuggester>(
        suggestion_handler_.get(), profile_.get(),
        std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
            .multi_word_suggestions = true,
        }));

    feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kAssistMultiWord},
        /*disabled_features=*/{});

    SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/true,
                          /*diacritics_on_longpress_enabled=*/false);
  }

  content::BrowserTaskEnvironment task_environment_;
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<AssistiveSuggester> assistive_suggester_;
  std::unique_ptr<FakeSuggestionHandler> suggestion_handler_;
  base::HistogramTester histogram_tester_;
};

TEST_F(AssistiveSuggesterMultiWordTest,
       MatchMetricNotRecordedWhenZeroSuggestions) {
  assistive_suggester_->OnExternalSuggestionsUpdated({}, TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Match", 0);
}

TEST_F(AssistiveSuggesterMultiWordTest, OnSuggestionExistShowSuggestion) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"hello there");
}

TEST_F(AssistiveSuggesterMultiWordTest, OnDisabledFlagShouldNotShowSuggestion) {
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterMultiWordTest, ShouldNotSuggestWhenSwitchDisabled) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
          .multi_word_suggestions = false,
      }));
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));

  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterMultiWordTest,
       MatchMetricRecordedWhenOneOrMoreSuggestions) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Match", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Match",
                                       AssistiveType::kMultiWordPrediction, 1);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       MatchMetricNotRecordedWhenMultiWordFlagDisabled) {
  feature_list_.Reset();
  feature_list_.InitWithFeatures(
      /*enabled_features=*/{},
      /*disabled_features=*/{features::kAssistMultiWord});
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Match", 0);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       DisableMetricNotRecordedWhenNoSuggestionAndMultiWordBlocked) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{}));

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated({}, TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Disabled.MultiWord",
                                     0);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       DisableMetricRecordedWhenGivenSuggestionAndMultiWordBlocked) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{}));
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Disabled.MultiWord",
                                     1);
  histogram_tester_.ExpectUniqueSample(
      "InputMethod.Assistive.Disabled.MultiWord",
      DisabledReason::kUrlOrAppNotAllowed, 1);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       CoverageMetricNotRecordedWhenNoSuggestionGiven) {
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated({}, TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Coverage", 0);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       CoverageMetricRecordedWhenSuggestionShown) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Coverage", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Coverage",
                                       AssistiveType::kMultiWordPrediction, 1);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       CoverageMetricRecordedOnceWhenSuggestionShownAndTracked) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext(""));
  assistive_suggester_->OnSurroundingTextChanged(u"h", gfx::Range(1));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("h"));
  assistive_suggester_->OnSurroundingTextChanged(u"he", gfx::Range(2));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("he"));
  assistive_suggester_->OnSurroundingTextChanged(u"hel", gfx::Range(3));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("hel"));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Coverage", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Coverage",
                                       AssistiveType::kMultiWordPrediction, 1);
}

TEST_F(AssistiveSuggesterMultiWordTest,
       CoverageMetricRecordedForEverySuggestionShown) {
  std::vector<AssistiveSuggestion> first_suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "hello there"}};
  std::vector<AssistiveSuggestion> second_suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kPrediction,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "was"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"", gfx::Range(0));
  assistive_suggester_->OnExternalSuggestionsUpdated(first_suggestions,
                                                     TextContext(""));
  assistive_suggester_->OnSurroundingTextChanged(u"h", gfx::Range(1));
  assistive_suggester_->OnExternalSuggestionsUpdated(first_suggestions,
                                                     TextContext("h"));
  assistive_suggester_->OnSurroundingTextChanged(u"he", gfx::Range(2));
  assistive_suggester_->OnExternalSuggestionsUpdated(first_suggestions,
                                                     TextContext("he"));
  assistive_suggester_->OnSurroundingTextChanged(u"he ", gfx::Range(3));
  assistive_suggester_->OnExternalSuggestionsUpdated(second_suggestions,
                                                     TextContext("he "));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Coverage", 2);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Coverage",
                                       AssistiveType::kMultiWordPrediction, 2);
}

TEST_F(AssistiveSuggesterMultiWordTest, PressingTabShouldAcceptSuggestion) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kCompletion,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "aren\'t you"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"why ar", gfx::Range(6));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("why ar"));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKey(ui::DomCode::TAB)),
            AssistiveSuggesterKeyResult::kHandled);
}

TEST_F(AssistiveSuggesterMultiWordTest, AltPlusTabShouldNotAcceptSuggestion) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kCompletion,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "aren\'t you"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"why ar", gfx::Range(6));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("why ar"));

  EXPECT_EQ(assistive_suggester_->OnKeyEvent(PressKeyWithAlt(ui::DomCode::TAB)),
            AssistiveSuggesterKeyResult::kNotHandled);
}

TEST_F(AssistiveSuggesterMultiWordTest, CtrlPlusTabShouldNotAcceptSuggestion) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kCompletion,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "aren\'t you"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"why ar", gfx::Range(6));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("why ar"));

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(PressKeyWithCtrl(ui::DomCode::TAB)),
      AssistiveSuggesterKeyResult::kNotHandled);
}

TEST_F(AssistiveSuggesterMultiWordTest, ShiftPlusTabShouldNotAcceptSuggestion) {
  std::vector<AssistiveSuggestion> suggestions = {
      AssistiveSuggestion{.mode = AssistiveSuggestionMode::kCompletion,
                          .type = AssistiveSuggestionType::kMultiWord,
                          .text = "aren\'t you"}};

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"why ar", gfx::Range(6));
  assistive_suggester_->OnExternalSuggestionsUpdated(suggestions,
                                                     TextContext("why ar"));

  EXPECT_EQ(
      assistive_suggester_->OnKeyEvent(PressKeyWithShift(ui::DomCode::TAB)),
      AssistiveSuggesterKeyResult::kNotHandled);
}

class AssistiveSuggesterEmojiTest : public testing::Test {
 protected:
  AssistiveSuggesterEmojiTest() {
    profile_ = std::make_unique<TestingProfile>();
  }

  void SetUp() override {
    suggestion_handler_ = std::make_unique<FakeSuggestionHandler>();
    // TODO(b/242472734): Allow enabled suggestions passed without replace.
    assistive_suggester_ = std::make_unique<AssistiveSuggester>(
        suggestion_handler_.get(), profile_.get(),
        std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
            .emoji_suggestions = true,
        }));
    assistive_suggester_->get_emoji_suggester_for_testing()
        ->LoadEmojiMapForTesting(kEmojiData);

    // Needed to ensure globals accessed by EmojiSuggester are available.
    chrome_keyboard_controller_client_ =
        ChromeKeyboardControllerClient::CreateForTest();
    chrome_keyboard_controller_client_->set_keyboard_visible_for_test(false);

    profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                     true);
    profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, true);
  }

  content::BrowserTaskEnvironment task_environment_;
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<TestingProfile> profile_;
  std::unique_ptr<AssistiveSuggester> assistive_suggester_;
  std::unique_ptr<FakeSuggestionHandler> suggestion_handler_;
  base::HistogramTester histogram_tester_;

  // Needs to outlive the emoji_suggester under test.
  std::unique_ptr<ChromeKeyboardControllerClient>
      chrome_keyboard_controller_client_;
};

TEST_F(AssistiveSuggesterEmojiTest, ShouldNotSuggestWhenEmojiDisabled) {
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   false);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterEmojiTest, ShouldRecordDisabledWhenEmojiDisabled) {
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnterpriseAllowed,
                                   false);
  profile_->GetPrefs()->SetBoolean(prefs::kEmojiSuggestionEnabled, false);

  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Disabled", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Disabled",
                                       AssistiveType::kEmoji, 1);
}

TEST_F(AssistiveSuggesterEmojiTest, ShouldNotSuggestWhenSwitchDisabled) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
          .emoji_suggestions = false,
      }));
  assistive_suggester_->get_emoji_suggester_for_testing()
      ->LoadEmojiMapForTesting(kEmojiData);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  EXPECT_FALSE(suggestion_handler_->GetShowingSuggestion());
}

TEST_F(AssistiveSuggesterEmojiTest, ShouldRecordNotAllowedWhenSwitchDisabled) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
          .emoji_suggestions = false,
      }));
  assistive_suggester_->get_emoji_suggester_for_testing()
      ->LoadEmojiMapForTesting(kEmojiData);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.NotAllowed", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.NotAllowed",
                                       AssistiveType::kEmoji, 1);
}

TEST_F(AssistiveSuggesterEmojiTest,
       ShouldRecordDisabledReasonWhenSwitchDisabled) {
  // TODO(b/242472734): Allow enabled suggestions passed without replace.
  assistive_suggester_ = std::make_unique<AssistiveSuggester>(
      suggestion_handler_.get(), profile_.get(),
      std::make_unique<FakeSuggesterSwitch>(EnabledSuggestions{
          .emoji_suggestions = false,
      }));
  assistive_suggester_->get_emoji_suggester_for_testing()
      ->LoadEmojiMapForTesting(kEmojiData);
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);

  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Disabled.Emoji", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Disabled.Emoji",
                                       DisabledReason::kUrlOrAppNotAllowed, 1);
}

TEST_F(AssistiveSuggesterEmojiTest, ShouldReturnPrefixBasedEmojiSuggestions) {
  assistive_suggester_->OnActivate(kUsEnglishEngineId);
  assistive_suggester_->OnFocus(5, empty_context);
  assistive_suggester_->OnSurroundingTextChanged(u"arrow ", gfx::Range(6));

  EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
  EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"←;↑;→");
}

class AssistiveSuggesterControlVLongpressTest : public AshTestBase {
 protected:
  AssistiveSuggesterControlVLongpressTest()
      : AshTestBase(std::unique_ptr<base::test::TaskEnvironment>(
            std::make_unique<content::BrowserTaskEnvironment>(
                base::test::TaskEnvironment::TimeSource::MOCK_TIME))),
        assistive_suggester_(
            &suggestion_handler_,
            &profile_,
            std::make_unique<AssistiveSuggesterClientFilter>(
                base::BindRepeating(&GetFocusedTabUrl),
                base::BindRepeating(&GetFocusedWindowProperties))) {
    feature_list_.InitAndEnableFeature(features::kClipboardHistoryLongpress);
  }

  void SetUp() override {
    AshTestBase::SetUp();

    Shell::Get()
        ->clipboard_history_controller()
        ->set_confirmed_operation_callback_for_test(
            operation_confirmed_future_.GetRepeatingCallback());

    // Write content to the clipboard so that the clipboard history menu can
    // appear.
    SetClipboardText("B");
    SetClipboardText("A");

    // Create a textfield for the clipboard history controller to recognize as a
    // paste target.
    textfield_widget_ = CreateFramelessTestWidget();
    textfield_widget_->SetBounds(gfx::Rect(100, 100, 100, 100));
    textfield_ = textfield_widget_->SetContentsView(
        std::make_unique<views::Textfield>());

    // Set the textfield as the text input client so that its caret position can
    // be queried.
    IMEBridge::Get()
        ->GetInputContextHandler()
        ->GetInputMethod()
        ->SetFocusedTextInputClient(textfield_);
  }

  void SetClipboardText(const std::string& text) {
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
        .WriteText(base::UTF8ToUTF16(text));

    // Clipboard history will post a task to process clipboard data in order to
    // debounce multiple clipboard writes occurring in sequence. Here we give
    // clipboard history the chance to run its posted tasks before proceeding.
    EXPECT_TRUE(operation_confirmed_future_.Take());
  }

  ui::KeyEvent CreateControlVEvent(int extra_flags = ui::EF_NONE) {
    return ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_V,
                        ui::EF_CONTROL_DOWN | extra_flags);
  }

  base::test::ScopedFeatureList feature_list_;
  TestingProfile profile_;
  FakeSuggestionHandler suggestion_handler_;
  AssistiveSuggester assistive_suggester_;
  base::test::TestFuture<bool> operation_confirmed_future_;
  std::unique_ptr<views::Widget> textfield_widget_;
  raw_ptr<views::Textfield> textfield_;
  base::HistogramTester histogram_tester_;
};

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ClipboardHistoryTriggeredOnControlVLongpress) {
  assistive_suggester_.OnFocus(5, empty_context);
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(base::Seconds(1));

  auto* const controller = Shell::Get()->clipboard_history_controller();
  EXPECT_TRUE(controller->IsMenuShowing());
  // Precise anchoring logic may change as time goes on, so this test only
  // assumes that the clipboard history menu should be left-aligned with the
  // input field's caret.
  EXPECT_EQ(controller->GetMenuBoundsInScreenForTest().x(),
            textfield_->GetCaretBounds().x());
  histogram_tester_.ExpectUniqueSample(
      "Ash.ClipboardHistory.ContextMenu.ShowMenu",
      crosapi::mojom::ClipboardHistoryControllerShowSource::kControlVLongpress,
      1);
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ControlVLongpressPasteSuccessRecorded) {
  assistive_suggester_.OnFocus(5, empty_context);
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());

  // Paste an item from the clipboard history menu.
  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN);
  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Success", 1);
  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Success",
                                       AssistiveType::kLongpressControlV, 1);
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ClipboardHistoryDismissedNoSuccessRecorded) {
  assistive_suggester_.OnFocus(5, empty_context);
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(base::Seconds(1));

  EXPECT_TRUE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());

  // Dismiss the clipboard history menu without pasting.
  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Success", 0);
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ClipboardHistoryNotTriggeredIfShiftDown) {
  assistive_suggester_.OnFocus(5, empty_context);
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(
                CreateControlVEvent(/*extra_flags=*/ui::EF_SHIFT_DOWN)),
            AssistiveSuggesterKeyResult::kNotHandled);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(base::Seconds(1));

  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ClipboardHistoryNotTriggeredIfNoContextForControlVLongpress) {
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandled);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(base::Seconds(1));

  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       ClipboardHistoryNotTriggeredIfControlVLongpressInterrupted) {
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandled);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
  task_environment()->FastForwardBy(
      base::Milliseconds(100));  // Not long enough to trigger longpress.

  EXPECT_EQ(assistive_suggester_.OnKeyEvent(ui::KeyEvent(
                ui::EventType::kKeyReleased, ui::VKEY_V, ui::EF_CONTROL_DOWN)),
            AssistiveSuggesterKeyResult::kNotHandled);
  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
}

TEST_F(AssistiveSuggesterControlVLongpressTest,
       RepeatedControlVNotPropagatedIfControlVLongpressEnabled) {
  assistive_suggester_.OnFocus(5, empty_context);
  EXPECT_EQ(assistive_suggester_.OnKeyEvent(CreateControlVEvent()),
            AssistiveSuggesterKeyResult::kNotHandledSuppressAutoRepeat);
  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));

  EXPECT_EQ(assistive_suggester_.OnKeyEvent(
                CreateControlVEvent(/*extra_flags=*/ui::EF_IS_REPEAT)),
            AssistiveSuggesterKeyResult::kHandled);
}
}  // namespace ash::input_method