chromium/ash/picker/picker_controller_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/picker/picker_controller.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "ash/accessibility/accessibility_controller.h"
#include "ash/clipboard/clipboard_history_controller_impl.h"
#include "ash/clipboard/clipboard_history_item.h"
#include "ash/clipboard/test_support/mock_clipboard_history_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/picker/metrics/picker_session_metrics.h"
#include "ash/picker/model/picker_action_type.h"
#include "ash/picker/model/picker_caps_lock_position.h"
#include "ash/picker/model/picker_model.h"
#include "ash/picker/model/picker_search_results_section.h"
#include "ash/picker/picker_test_util.h"
#include "ash/picker/views/picker_feature_tour.h"
#include "ash/picker/views/picker_search_bar_textfield.h"
#include "ash/picker/views/picker_search_field_view.h"
#include "ash/picker/views/picker_view.h"
#include "ash/public/cpp/clipboard_history_controller.h"
#include "ash/public/cpp/picker/mock_picker_client.h"
#include "ash/public/cpp/system/toast_manager.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/test_widget_builder.h"
#include "ash/test/view_drawn_waiter.h"
#include "base/memory/scoped_refptr.h"
#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "components/metrics/structured/structured_events.h"
#include "components/metrics/structured/test/test_structured_metrics_recorder.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/fake_text_input_client.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_type.h"
#include "ui/base/models/image_model.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/focus_controller.h"

namespace ash {
namespace {

using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Property;
using ::testing::Return;
using ::testing::VariantWith;

namespace cros_events = metrics::structured::events::v2::cr_os_events;

bool CopyTextToClipboard() {
  base::test::TestFuture<bool> copy_confirmed_future;
  Shell::Get()
      ->clipboard_history_controller()
      ->set_confirmed_operation_callback_for_test(
          copy_confirmed_future.GetRepeatingCallback());
  {
    ui::ScopedClipboardWriter writer(ui::ClipboardBuffer::kCopyPaste);
    writer.WriteText(u"test");
  }
  return copy_confirmed_future.Take();
}

std::optional<base::UnguessableToken> GetFirstClipboardItemId() {
  base::test::TestFuture<std::vector<ClipboardHistoryItem>> future;
  auto* controller = ClipboardHistoryController::Get();
  controller->GetHistoryValues(future.GetCallback());

  std::vector<ClipboardHistoryItem> items = future.Take();
  return items.empty() ? std::nullopt : std::make_optional(items.front().id());
}

class ClipboardPasteWaiter : public ClipboardHistoryController::Observer {
 public:
  ClipboardPasteWaiter() {
    observation_.Observe(ClipboardHistoryController::Get());
  }

  void Wait() {
    if (observation_.IsObserving()) {
      run_loop_.Run();
    }
  }

  // ClipboardHistoryController::Observer:
  void OnClipboardHistoryPasted() override {
    observation_.Reset();
    run_loop_.Quit();
  }

 private:
  base::RunLoop run_loop_;
  base::ScopedObservation<ClipboardHistoryController,
                          ClipboardHistoryController::Observer>
      observation_{this};
};

input_method::ImeKeyboard* GetImeKeyboard() {
  auto* input_method_manager = input_method::InputMethodManager::Get();
  return input_method_manager ? input_method_manager->GetImeKeyboard()
                              : nullptr;
}

class MockNewWindowDelegate : public TestNewWindowDelegate {
 public:
  MOCK_METHOD(void,
              OpenUrl,
              (const GURL& url, OpenUrlFrom from, Disposition disposition),
              (override));
  MOCK_METHOD(void, OpenFile, (const base::FilePath& file_path), (override));
};

// A PickerClient implementation used for testing.
// Automatically sets itself as the client when it's created, and unsets itself
// when it's destroyed.
class TestPickerClient : public MockPickerClient {
 public:
  TestPickerClient(PickerController* controller,
                   sync_preferences::TestingPrefServiceSyncable* prefs)
      : controller_(controller), prefs_(prefs) {
    controller_->SetClient(this);
    // Set default behaviours. These can be overridden with `WillOnce` and
    // `WillRepeatedly`.
    ON_CALL(*this, IsFeatureAllowedForDogfood).WillByDefault(Return(true));
    ON_CALL(*this, GetPrefs).WillByDefault(Return(prefs_));
  }
  ~TestPickerClient() override { controller_->SetClient(nullptr); }

  PrefRegistrySimple* registry() { return prefs_->registry(); }

 private:
  raw_ptr<PickerController> controller_ = nullptr;
  raw_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_ = nullptr;
};

class PickerControllerTestBase : public AshTestBase {
 public:
  PickerControllerTestBase()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
    auto delegate = std::make_unique<MockNewWindowDelegate>();
    new_window_delegate_ = delegate.get();
    delegate_provider_ =
        std::make_unique<TestNewWindowDelegateProvider>(std::move(delegate));
  }

  void SetUp() override {
    AshTestBase::SetUp();
    controller_ = std::make_unique<PickerController>();
    client_ = std::make_unique<NiceMock<TestPickerClient>>(controller_.get(),
                                                           &prefs_);
    prefs_.registry()->RegisterDictionaryPref(prefs::kEmojiPickerHistory);
    PickerSessionMetrics::RegisterProfilePrefs(prefs_.registry());
    metrics_recorder_ =
        std::make_unique<metrics::structured::TestStructuredMetricsRecorder>();
    metrics_recorder_->Initialize();
  }

  void TearDown() override {
    client_.reset();
    controller_.reset();
    metrics_recorder_.reset();
    AshTestBase::TearDown();
  }

  MockNewWindowDelegate& mock_new_window_delegate() {
    return *new_window_delegate_;
  }

  PickerController& controller() { return *controller_; }

  NiceMock<TestPickerClient>& client() { return *client_; }

  sync_preferences::TestingPrefServiceSyncable& prefs() { return prefs_; }

  metrics::structured::TestStructuredMetricsRecorder& metrics_recorder() {
    return *metrics_recorder_;
  }

 private:
  std::unique_ptr<TestNewWindowDelegateProvider> delegate_provider_;
  // Holds a raw ptr to the `MockNewWindowDelegate` owned by
  // `delegate_provider_`.
  raw_ptr<MockNewWindowDelegate> new_window_delegate_;
  sync_preferences::TestingPrefServiceSyncable prefs_;
  std::unique_ptr<PickerController> controller_;
  std::unique_ptr<NiceMock<TestPickerClient>> client_;
  std::unique_ptr<metrics::structured::TestStructuredMetricsRecorder>
      metrics_recorder_;
};

class PickerControllerTest : public PickerControllerTestBase {
  void SetUp() override {
    PickerControllerTestBase::SetUp();
    PickerController::DisableFeatureKeyCheck();
  }
};

TEST_F(PickerControllerTest, ToggleWidgetShowsWidgetIfClosed) {
  controller().ToggleWidget();

  EXPECT_TRUE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest,
       ToggleWidgetInPasswordFieldTogglesCapslockAndShowsBubbleForAShortTime) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();

  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_PASSWORD});
  input_method->SetFocusedTextInputClient(&input_field);

  controller().ToggleWidget();
  input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
  ASSERT_TRUE(ime_keyboard);

  EXPECT_FALSE(controller().widget_for_testing());
  EXPECT_TRUE(controller()
                  .caps_lock_bubble_controller_for_testing()
                  .bubble_view_for_testing());
  EXPECT_TRUE(ime_keyboard->IsCapsLockEnabled());

  task_environment()->FastForwardBy(base::Seconds(4));
  EXPECT_FALSE(controller()
                   .caps_lock_bubble_controller_for_testing()
                   .bubble_view_for_testing());
}

TEST_F(PickerControllerTest, TogglingWidgetRecordsStartSessionMetrics) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();

  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.SetTextAndSelection(u"abcd", gfx::Range(1, 4));
  input_method->SetFocusedTextInputClient(&input_field);

  controller().ToggleWidget();

  cros_events::Picker_StartSession expected_event;
  expected_event
      .SetInputFieldType(cros_events::PickerInputFieldType::PLAIN_TEXT)
      .SetSelectionLength(3);
  EXPECT_THAT(
      metrics_recorder().GetEvents(),
      ElementsAre(AllOf(
          Property("event name", &metrics::structured::Event::event_name,
                   Eq(expected_event.event_name())),
          Property("metric values", &metrics::structured::Event::metric_values,
                   Eq(std::ref(expected_event.metric_values()))))));
}

TEST_F(PickerControllerTest, ToggleWidgetClosesWidgetIfOpen) {
  controller().ToggleWidget();
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());

  controller().ToggleWidget();

  widget_destroyed_waiter.Wait();
  EXPECT_FALSE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest, ToggleWidgetShowsWidgetIfOpenedThenClosed) {
  controller().ToggleWidget();
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());
  controller().ToggleWidget();
  widget_destroyed_waiter.Wait();

  controller().ToggleWidget();

  EXPECT_TRUE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest, ToggleWidgetShowsFeatureTourForFirstTime) {
  PickerFeatureTour::RegisterProfilePrefs(client().registry());
  controller().ToggleWidget();

  EXPECT_TRUE(controller().feature_tour_for_testing().widget_for_testing());
  EXPECT_FALSE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest,
       ToggleWidgetShowsWidgetAfterCompletingFeatureTourWithNoWindows) {
  wm::FocusController* focus_controller = Shell::Get()->focus_controller();
  ASSERT_EQ(focus_controller->GetActiveWindow(), nullptr);
  ASSERT_EQ(focus_controller->GetFocusedWindow(), nullptr);

  // Show the feature tour.
  PickerFeatureTour::RegisterProfilePrefs(client().registry());
  controller().ToggleWidget();
  auto& feature_tour = controller().feature_tour_for_testing();
  views::test::WidgetVisibleWaiter(feature_tour.widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());

  // Complete the feature tour.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(feature_tour.widget_for_testing()).Wait();
  ASSERT_NE(controller().widget_for_testing(), nullptr);
  views::test::WidgetVisibleWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  auto* view = views::AsViewClass<PickerView>(
      controller().widget_for_testing()->widget_delegate()->GetContentsView());
  ASSERT_NE(view, nullptr);
  EXPECT_TRUE(
      view->search_field_view_for_testing().textfield_for_testing().HasFocus());

  // Dismiss Picker.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(), nullptr);
  EXPECT_EQ(focus_controller->GetFocusedWindow(), nullptr);
}

TEST_F(PickerControllerTest,
       ToggleWidgetShowsWidgetAfterCompletingFeatureTourWithoutFocus) {
  std::unique_ptr<views::Widget> test_widget =
      ash::TestWidgetBuilder()
          .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS)
          .SetShow(true)
          .BuildClientOwnsWidget();
  wm::FocusController* focus_controller = Shell::Get()->focus_controller();
  ASSERT_EQ(focus_controller->GetActiveWindow(),
            test_widget->GetNativeWindow());
  ASSERT_EQ(focus_controller->GetFocusedWindow(),
            test_widget->GetNativeWindow());

  // Show the feature tour.
  PickerFeatureTour::RegisterProfilePrefs(client().registry());
  controller().ToggleWidget();
  auto& feature_tour = controller().feature_tour_for_testing();
  views::test::WidgetVisibleWaiter(feature_tour.widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());

  // Complete the feature tour.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(feature_tour.widget_for_testing()).Wait();
  ASSERT_NE(controller().widget_for_testing(), nullptr);
  views::test::WidgetVisibleWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  auto* view = views::AsViewClass<PickerView>(
      controller().widget_for_testing()->widget_delegate()->GetContentsView());
  ASSERT_NE(view, nullptr);
  EXPECT_TRUE(
      view->search_field_view_for_testing().textfield_for_testing().HasFocus());

  // Dismiss Picker.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            test_widget->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            test_widget->GetNativeWindow());
}

TEST_F(PickerControllerTest,
       ToggleWidgetShowsWidgetAfterCompletingFeatureTourWithFocus) {
  std::unique_ptr<views::Widget> textfield_widget =
      ash::TestWidgetBuilder()
          .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS)
          .SetShow(true)
          .BuildClientOwnsWidget();
  auto* textfield =
      textfield_widget->SetContentsView(std::make_unique<views::Textfield>());
  textfield->GetViewAccessibility().SetName(u"textfield");
  textfield->RequestFocus();
  wm::FocusController* focus_controller = Shell::Get()->focus_controller();
  ASSERT_EQ(focus_controller->GetActiveWindow(),
            textfield_widget->GetNativeWindow());
  ASSERT_EQ(focus_controller->GetFocusedWindow(),
            textfield_widget->GetNativeWindow());
  ASSERT_TRUE(textfield->HasFocus());

  // Show the feature tour.
  PickerFeatureTour::RegisterProfilePrefs(client().registry());
  controller().ToggleWidget();
  auto& feature_tour = controller().feature_tour_for_testing();
  views::test::WidgetVisibleWaiter(feature_tour.widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            feature_tour.widget_for_testing()->GetNativeWindow());
  EXPECT_FALSE(textfield->HasFocus());

  // Complete the feature tour.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(feature_tour.widget_for_testing()).Wait();
  ASSERT_NE(controller().widget_for_testing(), nullptr);
  views::test::WidgetVisibleWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            controller().widget_for_testing()->GetNativeWindow());
  EXPECT_FALSE(textfield->HasFocus());
  auto* view = views::AsViewClass<PickerView>(
      controller().widget_for_testing()->widget_delegate()->GetContentsView());
  ASSERT_NE(view, nullptr);
  EXPECT_TRUE(
      view->search_field_view_for_testing().textfield_for_testing().HasFocus());

  // Dismiss Picker.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
  views::test::WidgetDestroyedWaiter(controller().widget_for_testing()).Wait();
  EXPECT_EQ(focus_controller->GetActiveWindow(),
            textfield_widget->GetNativeWindow());
  EXPECT_EQ(focus_controller->GetFocusedWindow(),
            textfield_widget->GetNativeWindow());
  EXPECT_TRUE(textfield->HasFocus());
}

TEST_F(PickerControllerTest, ToggleWidgetOpensUrlAfterLearnMore) {
  PickerFeatureTour::RegisterProfilePrefs(client().registry());
  controller().ToggleWidget();
  auto& feature_tour = controller().feature_tour_for_testing();
  views::test::WidgetVisibleWaiter(feature_tour.widget_for_testing()).Wait();

  EXPECT_CALL(
      mock_new_window_delegate(),
      OpenUrl(Property("host", &GURL::host_piece, "support.google.com"), _, _))
      .Times(1);

  const views::Button* button = feature_tour.learn_more_button_for_testing();
  ASSERT_NE(button, nullptr);
  LeftClickOn(button);
  views::test::WidgetDestroyedWaiter(feature_tour.widget_for_testing()).Wait();

  EXPECT_FALSE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest, SetClientToNullKeepsWidget) {
  controller().ToggleWidget();

  controller().SetClient(nullptr);

  EXPECT_TRUE(controller().widget_for_testing());
}

TEST_F(PickerControllerTest, ShowWidgetRecordsInputReadyLatency) {
  base::HistogramTester histogram;

  controller().ToggleWidget(base::TimeTicks::Now());
  views::test::WidgetVisibleWaiter widget_visible_waiter(
      controller().widget_for_testing());
  widget_visible_waiter.Wait();

  histogram.ExpectTotalCount("Ash.Picker.Session.InputReadyLatency", 1);
}

TEST_F(PickerControllerTest, InsertResultDoesNothingWhenWidgetIsClosed) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();

  controller().CloseWidgetThenInsertResultOnNextFocus(PickerTextResult(u"abc"));
  ui::FakeTextInputClient input_field(ui::TEXT_INPUT_TYPE_TEXT);
  input_method->SetFocusedTextInputClient(&input_field);
  absl::Cleanup focused_input_field_reset = [input_method] {
    // Reset the input field since it will be destroyed before `input_method`.
    input_method->SetFocusedTextInputClient(nullptr);
  };

  EXPECT_EQ(input_field.text(), u"");
}

TEST_F(PickerControllerTest, InsertTextResultInsertsIntoInputFieldAfterFocus) {
  controller().ToggleWidget();
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();

  controller().CloseWidgetThenInsertResultOnNextFocus(PickerTextResult(u"abc"));
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());
  ui::FakeTextInputClient input_field(ui::TEXT_INPUT_TYPE_TEXT);
  input_method->SetFocusedTextInputClient(&input_field);
  absl::Cleanup focused_input_field_reset = [input_method] {
    // Reset the input field since it will be destroyed before `input_method`.
    input_method->SetFocusedTextInputClient(nullptr);
  };

  EXPECT_EQ(input_field.text(), u"abc");
}

TEST_F(PickerControllerTest,
       InsertClipboardResultPastesIntoInputFieldAfterFocus) {
  controller().ToggleWidget();
  ASSERT_TRUE(CopyTextToClipboard());
  std::optional<base::UnguessableToken> clipboard_item_id =
      GetFirstClipboardItemId();
  ASSERT_TRUE(clipboard_item_id.has_value());

  controller().CloseWidgetThenInsertResultOnNextFocus(PickerClipboardResult(
      *clipboard_item_id, PickerClipboardResult::DisplayFormat::kText,
      /*file_count=*/0,
      /*display_text=*/u"", /*display_image=*/{}, /*is_recent=*/false));
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());
  ClipboardPasteWaiter waiter;
  // Create a new to focus on.
  auto new_widget = CreateFramelessTestWidget();

  waiter.Wait();
}

TEST_F(PickerControllerTest,
       InsertBrowsingHistoryResultInsertsIntoInputFieldAfterFocus) {
  controller().ToggleWidget();
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();

  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerBrowsingHistoryResult(GURL("http://foo.com"), u"Foo",
                                  ui::ImageModel{}));
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);

  EXPECT_EQ(input_field.text(), u"http://foo.com/");
}

TEST_F(PickerControllerTest, InsertResultClosesWidgetImmediately) {
  controller().ToggleWidget();

  controller().CloseWidgetThenInsertResultOnNextFocus(PickerTextResult(u"abc"));

  EXPECT_TRUE(controller().widget_for_testing()->IsClosed());
}

TEST_F(PickerControllerTest, InsertResultDelaysWidgetCloseForAccessibility) {
  controller().ToggleWidget();
  Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
      true, A11Y_NOTIFICATION_NONE);

  controller().CloseWidgetThenInsertResultOnNextFocus(PickerTextResult(u"abc"));

  EXPECT_FALSE(controller().widget_for_testing()->IsClosed());
  views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
      controller().widget_for_testing());
}

TEST_F(PickerControllerTest, OpenBrowsingHistoryResult) {
  controller().ToggleWidget();

  EXPECT_CALL(mock_new_window_delegate(), OpenUrl(GURL("http://foo.com"), _, _))
      .Times(1);

  controller().OpenResult(PickerBrowsingHistoryResult(
      GURL("http://foo.com"), u"Foo", ui::ImageModel{}));
}

TEST_F(PickerControllerTest, OpenDriveFileResult) {
  controller().ToggleWidget();

  EXPECT_CALL(mock_new_window_delegate(), OpenUrl(GURL("http://foo.com"), _, _))
      .Times(1);

  controller().OpenResult(PickerDriveFileResult(
      /*id=*/std::nullopt, u"title", GURL("http://foo.com"), base::FilePath()));
}

TEST_F(PickerControllerTest, OpenLocalFileResult) {
  controller().ToggleWidget();

  EXPECT_CALL(mock_new_window_delegate(), OpenFile(base::FilePath("abc.png")))
      .Times(1);

  controller().OpenResult(
      PickerLocalFileResult(u"title", base::FilePath("abc.png")));
}

TEST_F(PickerControllerTest, OpenNewGoogleDocOpensGoogleDocs) {
  controller().ToggleWidget();

  EXPECT_CALL(mock_new_window_delegate(),
              OpenUrl(GURL("https://docs.new"), _, _))
      .Times(1);

  controller().OpenResult(
      PickerNewWindowResult(PickerNewWindowResult::Type::kDoc));
}

TEST_F(PickerControllerTest, OpenCapsLockResultTurnsOnCapsLock) {
  controller().ToggleWidget();

  controller().OpenResult(PickerCapsLockResult(
      /*enabled=*/true, PickerCapsLockResult::Shortcut::kAltSearch));

  input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
  ASSERT_TRUE(ime_keyboard);
  EXPECT_TRUE(ime_keyboard->IsCapsLockEnabled());
}

TEST_F(PickerControllerTest, OpenCapsLockResultTurnsOffCapsLock) {
  controller().ToggleWidget();

  controller().OpenResult(PickerCapsLockResult(
      /*enabled=*/false, PickerCapsLockResult::Shortcut::kAltSearch));

  input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
  ASSERT_TRUE(ime_keyboard);
  EXPECT_FALSE(ime_keyboard->IsCapsLockEnabled());
}

TEST_F(PickerControllerTest, OpenUpperCaseResultCommitsUpperCase) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  input_field.SetTextAndSelection(u"aBc DeF", gfx::Range(0, 7));

  controller().ToggleWidget();
  controller().OpenResult(
      PickerCaseTransformResult(PickerCaseTransformResult::Type::kUpperCase));
  input_method->SetFocusedTextInputClient(&input_field);

  EXPECT_EQ(input_field.text(), u"ABC DEF");
}

TEST_F(PickerControllerTest, OpenLowerCaseResultCommitsLowerCase) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  input_field.SetTextAndSelection(u"aBc DeF", gfx::Range(0, 7));

  controller().ToggleWidget();
  controller().OpenResult(
      PickerCaseTransformResult(PickerCaseTransformResult::Type::kLowerCase));
  input_method->SetFocusedTextInputClient(&input_field);

  EXPECT_EQ(input_field.text(), u"abc def");
}

TEST_F(PickerControllerTest, OpenTitleCaseResultCommitsTitleCase) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  input_field.SetTextAndSelection(u"aBc DeF", gfx::Range(0, 7));

  controller().ToggleWidget();
  controller().OpenResult(
      PickerCaseTransformResult(PickerCaseTransformResult::Type::kTitleCase));
  input_method->SetFocusedTextInputClient(&input_field);

  EXPECT_EQ(input_field.text(), u"Abc Def");
}

TEST_F(PickerControllerTest, ShowEmojiPickerCallsEmojiPanelCallback) {
  controller().ToggleWidget();
  base::test::TestFuture<ui::EmojiPickerCategory, ui::EmojiPickerFocusBehavior,
                         const std::string&>
      future;
  ui::SetShowEmojiKeyboardCallback(future.GetRepeatingCallback());

  controller().ShowEmojiPicker(ui::EmojiPickerCategory::kSymbols, u"abc");

  const auto& [category, focus_behavior, initial_query] = future.Get();
  EXPECT_EQ(category, ui::EmojiPickerCategory::kSymbols);
  EXPECT_EQ(focus_behavior, ui::EmojiPickerFocusBehavior::kAlwaysShow);
  EXPECT_EQ(initial_query, "abc");
}

TEST_F(PickerControllerTest, ShowingAndClosingWidgetRecordsUsageMetrics) {
  base::HistogramTester histogram_tester;

  // Show the widget twice.
  controller().ToggleWidget();
  task_environment()->FastForwardBy(base::Seconds(1));
  controller().widget_for_testing()->CloseNow();
  task_environment()->FastForwardBy(base::Seconds(2));
  controller().ToggleWidget();
  task_environment()->FastForwardBy(base::Seconds(3));
  controller().widget_for_testing()->CloseNow();
  task_environment()->FastForwardBy(base::Seconds(4));

  histogram_tester.ExpectBucketCount(
      "ChromeOS.FeatureUsage.Picker",
      static_cast<int>(
          feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess),
      2);
  histogram_tester.ExpectBucketCount(
      "ChromeOS.FeatureUsage.Picker",
      static_cast<int>(
          feature_usage::FeatureUsageMetrics::Event::kUsedWithFailure),
      0);
  histogram_tester.ExpectTimeBucketCount("ChromeOS.FeatureUsage.Picker.Usetime",
                                         base::Seconds(1), 1);
  histogram_tester.ExpectTimeBucketCount("ChromeOS.FeatureUsage.Picker.Usetime",
                                         base::Seconds(3), 1);
}

TEST_F(PickerControllerTest, ShowEditorCallsCallbackFromClient) {
  base::test::TestFuture<std::optional<std::string>, std::optional<std::string>>
      show_editor_future;
  EXPECT_CALL(client(), CacheEditorContext)
      .WillOnce(Return(show_editor_future.GetCallback()));

  controller().ToggleWidget();
  controller().ShowEditor(/*preset_query_id=*/"preset",
                          /*freeform_text=*/"freeform");

  EXPECT_THAT(show_editor_future.Get(), FieldsAre("preset", "freeform"));
}

TEST_F(PickerControllerTest, GetResultsForCategoryReturnsEmptyForEmptyResults) {
  base::test::TestFuture<std::vector<PickerSearchResultsSection>> future;
  EXPECT_CALL(client(), GetSuggestedLinkResults)
      .WillRepeatedly([](size_t max_results,
                         TestPickerClient::SuggestedLinksCallback callback) {
        std::move(callback).Run({});
      });

  controller().ToggleWidget();
  controller().GetResultsForCategory(PickerCategory::kLinks,
                                     future.GetRepeatingCallback());

  EXPECT_THAT(future.Take(), IsEmpty());
}

TEST_F(PickerControllerTest, AvailableCategoriesContainsEditorWhenEnabled) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.Focus();

  EXPECT_CALL(client(), CacheEditorContext).WillOnce(Return(base::DoNothing()));

  controller().ToggleWidget();

  EXPECT_THAT(controller().GetAvailableCategories(),
              Contains(PickerCategory::kEditorWrite));
}

TEST_F(PickerControllerTest,
       AvailableCategoriesDoesNotContainEditorWhenDisabled) {
  EXPECT_CALL(client(), CacheEditorContext)
      .WillOnce(Return(base::NullCallback()));

  controller().ToggleWidget();

  EXPECT_THAT(controller().GetAvailableCategories(),
              Not(Contains(PickerCategory::kEditorWrite)));
}

TEST_F(PickerControllerTest, SuggestedEmojiReturnsDefaultEmojisWhenEmpty) {
  controller().ToggleWidget();

  EXPECT_THAT(
      controller().GetSuggestedEmoji(),
      ElementsAre(
          PickerEmojiResult::Emoji(u"🙂"), PickerEmojiResult::Emoji(u"😂"),
          PickerEmojiResult::Emoji(u"🤔"), PickerEmojiResult::Emoji(u"😢"),
          PickerEmojiResult::Emoji(u"👏"), PickerEmojiResult::Emoji(u"👍")));
}

TEST_F(PickerControllerTest,
       SuggestedEmojiReturnsRecentEmojiFollowedByDefaultEmojis) {
  base::Value::List history_value;
  history_value.Append(base::Value::Dict().Set("text", "abc"));
  history_value.Append(base::Value::Dict().Set("text", "xyz"));
  ScopedDictPrefUpdate update(client().GetPrefs(), prefs::kEmojiPickerHistory);
  update->Set("emoji", std::move(history_value));

  controller().ToggleWidget();

  EXPECT_THAT(
      controller().GetSuggestedEmoji(),
      ElementsAre(
          PickerEmojiResult::Emoji(u"abc"), PickerEmojiResult::Emoji(u"xyz"),
          PickerEmojiResult::Emoji(u"🙂"), PickerEmojiResult::Emoji(u"😂"),
          PickerEmojiResult::Emoji(u"🤔"), PickerEmojiResult::Emoji(u"😢")));
}

TEST_F(PickerControllerTest, AddsNewRecentEmoji) {
  base::Value::List history_value;
  history_value.Append(base::Value::Dict().Set("text", "abc"));
  history_value.Append(base::Value::Dict().Set("text", "xyz"));
  ScopedDictPrefUpdate update(client().GetPrefs(), prefs::kEmojiPickerHistory);
  update->Set("emoji", std::move(history_value));

  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"def"));

  EXPECT_THAT(
      controller().GetSuggestedEmoji(),
      ElementsAre(
          PickerEmojiResult::Emoji(u"def"), PickerEmojiResult::Emoji(u"abc"),
          PickerEmojiResult::Emoji(u"xyz"), PickerEmojiResult::Emoji(u"🙂"),
          PickerEmojiResult::Emoji(u"😂"), PickerEmojiResult::Emoji(u"🤔")));
}

TEST_F(PickerControllerTest, AddsExistingRecentEmoji) {
  base::Value::List history_value;
  history_value.Append(base::Value::Dict().Set("text", "abc"));
  history_value.Append(base::Value::Dict().Set("text", "xyz"));
  ScopedDictPrefUpdate update(client().GetPrefs(), prefs::kEmojiPickerHistory);
  update->Set("emoji", std::move(history_value));

  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"xyz"));

  EXPECT_THAT(
      controller().GetSuggestedEmoji(),
      ElementsAre(
          PickerEmojiResult::Emoji(u"xyz"), PickerEmojiResult::Emoji(u"abc"),
          PickerEmojiResult::Emoji(u"🙂"), PickerEmojiResult::Emoji(u"😂"),
          PickerEmojiResult::Emoji(u"🤔"), PickerEmojiResult::Emoji(u"😢")));
}

TEST_F(PickerControllerTest, AddsRecentEmojiEmptyHistory) {
  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"abc"));

  EXPECT_THAT(
      controller().GetSuggestedEmoji(),
      ElementsAre(
          PickerEmojiResult::Emoji(u"abc"), PickerEmojiResult::Emoji(u"🙂"),
          PickerEmojiResult::Emoji(u"😂"), PickerEmojiResult::Emoji(u"🤔"),
          PickerEmojiResult::Emoji(u"😢"), PickerEmojiResult::Emoji(u"👏")));
}

TEST_F(PickerControllerTest, RecentlyAddedEmojiHasCorrectType) {
  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"abc"));

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              Contains(PickerEmojiResult::Emoji(u"abc")));
}

TEST_F(PickerControllerTest, RecentlyAddedSymbolHasCorrectType) {
  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Symbol(u"abc"));

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              Contains(PickerEmojiResult::Symbol(u"abc")));
}

TEST_F(PickerControllerTest, RecentlyAddedEmoticonHasCorrectType) {
  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoticon(u"abc"));

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              Contains(PickerEmojiResult::Emoticon(u"abc")));
}

TEST_F(PickerControllerTest, AddRecentEmojiWithFocus) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(
      input_method,
      {.type = ui::TEXT_INPUT_TYPE_TEXT, .should_do_learning = true});
  input_method->SetFocusedTextInputClient(&input_field);

  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"abc"));

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              Contains(PickerEmojiResult::Emoji(u"abc")));
}

TEST_F(PickerControllerTest, DoesNotAddRecentEmojiWithFocusIfIncognito) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(
      input_method,
      {.type = ui::TEXT_INPUT_TYPE_TEXT, .should_do_learning = false});
  input_method->SetFocusedTextInputClient(&input_field);

  controller().ToggleWidget();
  controller().CloseWidgetThenInsertResultOnNextFocus(
      PickerEmojiResult::Emoji(u"abc"));

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              Not(Contains(PickerEmojiResult::Emoji(u"abc"))));
}

TEST_F(PickerControllerTest,
       SuggestedEmojiReturnsRecentEmojiEmoticonAndSymbol) {
  base::Value::List emoji_history_value;
  emoji_history_value.Append(
      base::Value::Dict().Set("text", "emoji1").Set("timestamp", "10"));
  emoji_history_value.Append(
      base::Value::Dict().Set("text", "emoji2").Set("timestamp", "5"));
  base::Value::List emoticon_history_value;
  emoticon_history_value.Append(
      base::Value::Dict().Set("text", "emoticon1").Set("timestamp", "12"));
  emoticon_history_value.Append(
      base::Value::Dict().Set("text", "emoticon2").Set("timestamp", "2"));
  base::Value::List symbol_history_value;
  symbol_history_value.Append(
      base::Value::Dict().Set("text", "symbol1").Set("timestamp", "15"));
  symbol_history_value.Append(
      base::Value::Dict().Set("text", "symbol2").Set("timestamp", "8"));
  ScopedDictPrefUpdate update(client().GetPrefs(), prefs::kEmojiPickerHistory);
  update->Set("emoji", std::move(emoji_history_value));
  update->Set("emoticon", std::move(emoticon_history_value));
  update->Set("symbol", std::move(symbol_history_value));

  controller().ToggleWidget();

  EXPECT_THAT(controller().GetSuggestedEmoji(),
              ElementsAre(PickerEmojiResult::Symbol(u"symbol1"),
                          PickerEmojiResult::Emoticon(u"emoticon1"),
                          PickerEmojiResult::Emoji(u"emoji1"),
                          PickerEmojiResult::Symbol(u"symbol2"),
                          PickerEmojiResult::Emoji(u"emoji2"),
                          PickerEmojiResult::Emoticon(u"emoticon2")));
}

TEST_F(PickerControllerTest, SearchesCapsLockOnWhenCapsLockIsOff) {
  base::test::TestFuture<std::vector<PickerSearchResultsSection>> search_future;

  controller().ToggleWidget();
  controller().StartSearch(u"caps", /*category=*/{},
                           search_future.GetRepeatingCallback());

  EXPECT_THAT(
      search_future.Take(),
      Contains(Property(&PickerSearchResultsSection::results,
                        Contains(PickerCapsLockResult(
                            /*enabled=*/true,
                            PickerCapsLockResult::Shortcut::kAltLauncher)))));
}

TEST_F(PickerControllerTest, SearchesCapsLockOffWhenCapsLockIsOn) {
  base::test::TestFuture<std::vector<PickerSearchResultsSection>> search_future;
  GetImeKeyboard()->SetCapsLockEnabled(true);

  controller().ToggleWidget();
  controller().StartSearch(u"caps", /*category=*/{},
                           search_future.GetRepeatingCallback());

  EXPECT_THAT(
      search_future.Take(),
      Contains(Property(&PickerSearchResultsSection::results,
                        Contains(PickerCapsLockResult(
                            /*enabled=*/false,
                            PickerCapsLockResult::Shortcut::kAltLauncher)))));
}

TEST_F(PickerControllerTest, DoesNotSearchCaseTransformWhenNoSelectedText) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  base::MockCallback<PickerController::SearchResultsCallback> callback;

  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(Contains(Property(
                  &PickerSearchResultsSection::results,
                  Contains(VariantWith<PickerCaseTransformResult>(_))))))
      .Times(0);

  controller().ToggleWidget();
  controller().StartSearch(u"uppercase", /*category=*/{}, callback.Get());
}

TEST_F(PickerControllerTest, SearchesCaseTransformWhenSelectedText) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.SetTextAndSelection(u"a", gfx::Range(0, 1));
  input_method->SetFocusedTextInputClient(&input_field);
  base::MockCallback<PickerController::SearchResultsCallback> callback;

  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(Contains(Property(
                  &PickerSearchResultsSection::results,
                  Contains(VariantWith<PickerCaseTransformResult>(
                      Field(&PickerCaseTransformResult::type,
                            PickerCaseTransformResult::kUpperCase)))))))
      .Times(1);

  controller().ToggleWidget();
  controller().StartSearch(u"uppercase", /*category=*/{}, callback.Get());
}

TEST_F(PickerControllerTest, IsValidDuringWidgetClose) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  controller().ToggleWidget();
  views::test::WidgetVisibleWaiter(controller().widget_for_testing()).Wait();

  controller().ToggleWidget();
  controller().GetActionForResult(PickerTextResult(u"a"));
  controller().IsGifsEnabled();
  controller().GetAvailableCategories();
}

TEST_F(PickerControllerTest,
       ReturnsCapsLockPositionTopWhenCapsLockHasNotShownEnoughTimes) {
  prefs().SetInteger(prefs::kPickerCapsLockDislayedCountPrefName, 4);
  prefs().SetInteger(prefs::kPickerCapsLockSelectedCountPrefName, 0);
  EXPECT_EQ(controller().GetCapsLockPosition(), PickerCapsLockPosition::kTop);
}

TEST_F(PickerControllerTest,
       ReturnsCapsLockPositionTopWhenCapsLockIsAlwaysUsed) {
  prefs().SetInteger(prefs::kPickerCapsLockDislayedCountPrefName, 15);
  prefs().SetInteger(prefs::kPickerCapsLockSelectedCountPrefName, 14);
  EXPECT_EQ(controller().GetCapsLockPosition(), PickerCapsLockPosition::kTop);
}

TEST_F(PickerControllerTest,
       ReturnsCapsLockPositionMiddleWhenCapsLockIsSometimesUsed) {
  prefs().SetInteger(prefs::kPickerCapsLockDislayedCountPrefName, 15);
  prefs().SetInteger(prefs::kPickerCapsLockSelectedCountPrefName, 7);
  EXPECT_EQ(controller().GetCapsLockPosition(),
            PickerCapsLockPosition::kMiddle);
}

TEST_F(PickerControllerTest,
       ReturnsCapsLockPositionBottomWhenCapsLockIsNeverUsed) {
  prefs().SetInteger(prefs::kPickerCapsLockDislayedCountPrefName, 15);
  prefs().SetInteger(prefs::kPickerCapsLockSelectedCountPrefName, 0);
  EXPECT_EQ(controller().GetCapsLockPosition(),
            PickerCapsLockPosition::kBottom);
}

class PickerControllerKeyEnabledTest : public PickerControllerTestBase {};

TEST_F(PickerControllerKeyEnabledTest,
       ToggleWidgetShowsWidgetForDogfoodWhenClientAllowed) {
  base::test::ScopedFeatureList features(ash::features::kPickerDogfood);

  EXPECT_CALL(client(), IsFeatureAllowedForDogfood).WillOnce(Return(true));

  controller().ToggleWidget();

  EXPECT_TRUE(controller().widget_for_testing());
}

TEST_F(PickerControllerKeyEnabledTest,
       ToggleWidgetDoesNotShowWidgetWhenClientDisallowsDogfood) {
  base::test::ScopedFeatureList features(ash::features::kPickerDogfood);

  EXPECT_CALL(client(), IsFeatureAllowedForDogfood).WillOnce(Return(false));

  controller().ToggleWidget();

  EXPECT_FALSE(controller().widget_for_testing());
}

struct ActionTestCase {
  PickerSearchResult result;
  std::optional<PickerActionType> unfocused_action;
  std::optional<PickerActionType> no_selection_action;
  std::optional<PickerActionType> has_selection_action;
};

class PickerControllerActionTest
    : public PickerControllerTest,
      public testing::WithParamInterface<ActionTestCase> {};

TEST_P(PickerControllerActionTest, GetActionForResultUnfocused) {
  controller().ToggleWidget();

  if (GetParam().unfocused_action.has_value()) {
    EXPECT_EQ(controller().GetActionForResult(GetParam().result),
              GetParam().unfocused_action);
  }
}

TEST_P(PickerControllerActionTest, GetActionForResultNoSelection) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  controller().ToggleWidget();

  if (GetParam().no_selection_action.has_value()) {
    EXPECT_EQ(controller().GetActionForResult(GetParam().result),
              GetParam().no_selection_action);
  }
}

TEST_P(PickerControllerActionTest, GetActionForResultHasSelection) {
  auto* input_method =
      Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
  ui::FakeTextInputClient input_field(input_method,
                                      {.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method->SetFocusedTextInputClient(&input_field);
  input_field.SetTextAndSelection(u"a", gfx::Range(0, 1));
  controller().ToggleWidget();

  if (GetParam().has_selection_action.has_value()) {
    EXPECT_EQ(controller().GetActionForResult(GetParam().result),
              GetParam().has_selection_action);
  }
}

INSTANTIATE_TEST_SUITE_P(
    ,
    PickerControllerActionTest,
    testing::ValuesIn<ActionTestCase>({
        {
            .result = PickerTextResult(u""),
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerEmojiResult::Emoji(u""),
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerEmojiResult::Symbol(u""),
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerEmojiResult::Emoticon(u""),
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerClipboardResult(
                base::UnguessableToken::Create(),
                PickerClipboardResult::DisplayFormat::kFile,
                0,
                u"",
                {},
                false),
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerBrowsingHistoryResult({}, u"", {}),
            .unfocused_action = PickerActionType::kOpen,
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerLocalFileResult(u"", {}),
            .unfocused_action = PickerActionType::kOpen,
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerDriveFileResult(std::nullopt, u"", {}, {}),
            .unfocused_action = PickerActionType::kOpen,
            .no_selection_action = PickerActionType::kInsert,
            .has_selection_action = PickerActionType::kInsert,
        },
        {
            .result = PickerCategoryResult(PickerCategory::kEmojisGifs),
            .unfocused_action = PickerActionType::kDo,
            .no_selection_action = PickerActionType::kDo,
            .has_selection_action = PickerActionType::kDo,
        },
        {
            .result = PickerCategoryResult(PickerCategory::kEmojis),
            .unfocused_action = PickerActionType::kDo,
            .no_selection_action = PickerActionType::kDo,
            .has_selection_action = PickerActionType::kDo,
        },
        {
            .result = PickerSearchRequestResult(u"", u"", {}),
            .unfocused_action = PickerActionType::kDo,
            .no_selection_action = PickerActionType::kDo,
            .has_selection_action = PickerActionType::kDo,
        },
        {
            .result = PickerEditorResult(PickerEditorResult::Mode::kWrite,
                                         u"",
                                         {},
                                         {}),
            .unfocused_action = PickerActionType::kCreate,
            .no_selection_action = PickerActionType::kCreate,
            .has_selection_action = PickerActionType::kCreate,
        },
        {
            .result = PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
            .unfocused_action = PickerActionType::kDo,
        },
    }));

}  // namespace
}  // namespace ash