chromium/chrome/browser/ui/ash/picker/picker_client_impl_unittest.cc

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

#include "chrome/browser/ui/ash/picker/picker_client_impl.h"

#include <functional>
#include <memory>
#include <string>
#include <utility>

#include "ash/picker/picker_controller.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/app_list/search/test/test_ranker_manager.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/fileapi/recent_model.h"
#include "chrome/browser/ash/fileapi/recent_model_factory.h"
#include "chrome/browser/ash/fileapi/test/fake_recent_source.h"
#include "chrome/browser/ash/input_method/editor_mediator_factory.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/webui/ash/mako/mako_bubble_coordinator.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
#include "chromeos/ash/components/drivefs/fake_drivefs.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/user_manager/fake_user_manager.h"
#include "content/public/test/test_utils.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/input_method_ash.h"
#include "ui/base/ime/fake_text_input_client.h"
#include "ui/display/screen.h"
#include "ui/display/test/test_screen.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/test/ax_event_counter.h"

namespace {

using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
using ::testing::VariantWith;

using MockSearchResultsCallback =
    testing::MockFunction<PickerClientImpl::CrosSearchResultsCallback>;

namespace fmp = extensions::api::file_manager_private;

class TestFaviconService : public favicon::MockFaviconService {
 public:
  TestFaviconService() = default;
  TestFaviconService(const TestFaviconService&) = delete;
  TestFaviconService& operator=(const TestFaviconService&) = delete;
  ~TestFaviconService() override = default;

  // favicon::FaviconService:
  base::CancelableTaskTracker::TaskId GetFaviconImageForPageURL(
      const GURL& page_url,
      favicon_base::FaviconImageCallback callback,
      base::CancelableTaskTracker* tracker) override {
    page_url_ = page_url;
    std::move(callback).Run(favicon_base::FaviconImageResult());
    return {};
  }

  GURL page_url_;
};

bool CreateTestFile(const base::FilePath& path) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  if (!base::WriteFile(path, "test_file")) {
    return false;
  }
  return true;
}

std::unique_ptr<KeyedService> BuildTestHistoryService(
    base::FilePath profile_path,
    content::BrowserContext* context) {
  auto service = std::make_unique<history::HistoryService>();
  service->Init(history::TestHistoryDatabaseParamsForPath(profile_path));
  return std::move(service);
}

struct Volume {
  fmp::VolumeType type;
  std::vector<ash::RecentFile> files;
};

std::unique_ptr<KeyedService> BuildTestRecentModelFactory(
    std::vector<Volume> volumes,
    content::BrowserContext* /*context*/) {
  std::vector<std::unique_ptr<ash::RecentSource>> sources;
  for (Volume& volume : volumes) {
    auto source = std::make_unique<ash::FakeRecentSource>(volume.type);
    source->AddProducer(std::make_unique<ash::FileProducer>(
        /*lag=*/base::Milliseconds(0), std::move(volume.files)));
    sources.push_back(std::move(source));
  }
  return ash::RecentModel::CreateForTest(std::move(sources));
}

std::unique_ptr<KeyedService> BuildTestDriveIntegrationService(
    const base::FilePath& profile_path,
    std::unique_ptr<drive::FakeDriveFsHelper>& fake_drivefs_helper,
    content::BrowserContext* context) {
  Profile* profile = Profile::FromBrowserContext(context);

  base::ScopedAllowBlockingForTesting allow_blocking;
  base::FilePath mount_path = profile_path.Append("drivefs");
  static_cast<ash::disks::FakeDiskMountManager*>(
      ash::disks::DiskMountManager::GetInstance())
      ->RegisterMountPointForNetworkStorageScheme("drivefs",
                                                  mount_path.value());
  fake_drivefs_helper =
      std::make_unique<drive::FakeDriveFsHelper>(profile, mount_path);
  auto service = std::make_unique<drive::DriveIntegrationService>(
      profile, "drivefs", mount_path,
      fake_drivefs_helper->CreateFakeDriveFsListenerFactory());

  // Wait until the DriveIntegrationService is initialized.
  while (!service->IsMounted() || !service->GetDriveFsInterface()) {
    base::RunLoop().RunUntilIdle();
  }
  return service;
}

void AddSearchToHistory(TestingProfile* profile,
                        GURL url,
                        base::Time last_visit = base::Time::Now()) {
  history::HistoryService* history = HistoryServiceFactory::GetForProfile(
      profile, ServiceAccessType::EXPLICIT_ACCESS);
  history->AddPageWithDetails(url, /*title=*/u"", /*visit_count=*/1,
                              /*typed_count=*/1,
                              /*last_visit=*/last_visit,
                              /*hidden=*/false, history::SOURCE_BROWSED);
  profile->BlockUntilHistoryProcessesPendingRequests();
}

void AddBookmarks(TestingProfile* profile,
                  std::u16string_view title,
                  GURL url) {
  auto* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile);
  bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);

  bookmark_model->AddURL(bookmark_model->bookmark_bar_node(), 0,
                         std::u16string(title), url);
}

ash::RecentFile CreateRecentFile(const base::FilePath& file_path,
                                 storage::FileSystemType type,
                                 base::Time last_modified = base::Time::Now()) {
  CreateTestFile(file_path);
  return ash::RecentFile(storage::FileSystemURL::CreateForTest(
                             blink::StorageKey(), type, file_path),
                         last_modified);
}

void SetRecentFiles(TestingProfile* profile, std::vector<Volume> volumes) {
  ash::RecentModelFactory::GetInstance()->SetTestingFactoryAndUse(
      profile,
      base::BindRepeating(BuildTestRecentModelFactory, std::move(volumes)));
}

class PickerClientImplTest : public BrowserWithTestWindowTest {
 public:
  PickerClientImplTest() = default;

  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    ash::CrosDisksClient::InitializeFake();
    ash::disks::DiskMountManager::InitializeForTesting(
        new ash::disks::FakeDiskMountManager());

    BrowserWithTestWindowTest::SetUp();
  }
  void TearDown() override {
    BrowserWithTestWindowTest::TearDown();

    ash::disks::DiskMountManager::Shutdown();
    ash::CrosDisksClient::Shutdown();
  }

  scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory() {
    return test_shared_url_loader_factory_;
  }

  drivefs::FakeDriveFs& GetFakeDriveFs() {
    return fake_drivefs_helper_->fake_drivefs();
  }

  TestingProfile* CreateProfile(const std::string& profile_name) override {
    auto* profile = profile_manager()->CreateTestingProfile(
        profile_name, GetTestingFactories(), /*is_main_profile=*/false,
        test_shared_url_loader_factory_);
    OnUserProfileCreated(profile_name, profile);
    return profile;
  }

  TestingProfile::TestingFactories GetTestingFactories() override {
    return {
        TestingProfile::TestingFactory{
            HistoryServiceFactory::GetInstance(),
            base::BindRepeating(&BuildTestHistoryService, temp_dir_.GetPath())},
        TestingProfile::TestingFactory{
            BookmarkModelFactory::GetInstance(),
            BookmarkModelFactory::GetDefaultFactory()},
        TestingProfile::TestingFactory{
            TemplateURLServiceFactory::GetInstance(),
            base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)},
        TestingProfile::TestingFactory{
            ash::RecentModelFactory::GetInstance(),
            base::BindRepeating(&BuildTestRecentModelFactory,
                                std::vector<Volume>{})},
        TestingProfile::TestingFactory{
            drive::DriveIntegrationServiceFactory::GetInstance(),
            base::BindRepeating(&BuildTestDriveIntegrationService,
                                temp_dir_.GetPath(),
                                std::ref(fake_drivefs_helper_))},
        TestingProfile::TestingFactory{
            ash::input_method::EditorMediatorFactory::GetInstance(),
            base::BindRepeating(
                &ash::input_method::EditorMediatorFactory::BuildInstanceFor)}};
  }

  void LogIn(const std::string& email) override {
    // DriveFS needs the account to have an ID.
    const AccountId account_id =
        AccountId::FromUserEmailGaiaId(email, "test gaia");
    user_manager()->AddUser(account_id);
    ash_test_helper()->test_session_controller_client()->AddUserSession(email);
    user_manager()->UserLoggedIn(
        account_id,
        user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
        /*browser_restart=*/false,
        /*is_child=*/false);
  }

 private:
  base::ScopedTempDir temp_dir_;
  scoped_refptr<network::SharedURLLoaderFactory>
      test_shared_url_loader_factory_;
  std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
};

TEST_F(PickerClientImplTest, StartCrosSearch) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  AddSearchToHistory(profile(), GURL("http://foo.com/history"));
  AddBookmarks(profile(), u"Foobaz", GURL("http://foo.com/bookmarks"));
  AddTab(browser(), GURL("http://foo.com/tab"));
  base::test::TestFuture<void> test_done;

  auto ranker_manager =
      std::make_unique<app_list::TestRankerManager>(profile());
  ranker_manager->SetBestMatchString(u"tab");
  client.set_ranker_manager_for_test(std::move(ranker_manager));

  NiceMock<MockSearchResultsCallback> mock_search_callback;
  EXPECT_CALL(mock_search_callback, Call(_, _)).Times(AnyNumber());
  EXPECT_CALL(
      mock_search_callback,
      Call(
          ash::AppListSearchResultType::kOmnibox,
          IsSupersetOf({
              VariantWith<ash::PickerBrowsingHistoryResult>(AllOf(
                  Field("url", &ash::PickerBrowsingHistoryResult::url,
                        GURL("http://foo.com/history")),
                  Field("best_match",
                        &ash::PickerBrowsingHistoryResult::best_match, false))),
              VariantWith<ash::PickerBrowsingHistoryResult>(AllOf(
                  Field("url", &ash::PickerBrowsingHistoryResult::url,
                        GURL("http://foo.com/tab")),
                  Field("best_match",
                        &ash::PickerBrowsingHistoryResult::best_match, true))),

              VariantWith<ash::PickerBrowsingHistoryResult>(AllOf(
                  Field("title", &ash::PickerBrowsingHistoryResult::title,
                        u"Foobaz"),
                  Field("url", &ash::PickerBrowsingHistoryResult::url,
                        GURL("http://foo.com/bookmarks")),
                  Field("best_match",
                        &ash::PickerBrowsingHistoryResult::best_match, false))),
          })))
      .WillOnce([&]() { test_done.SetValue(); });

  client.StartCrosSearch(
      u"foo", /*category=*/std::nullopt,
      base::BindRepeating(&MockSearchResultsCallback::Call,
                          base::Unretained(&mock_search_callback)));

  ASSERT_TRUE(test_done.Wait());
}

TEST_F(PickerClientImplTest, IgnoresWhatYouTypedResults) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<void> test_done;

  NiceMock<MockSearchResultsCallback> mock_search_callback;
  EXPECT_CALL(mock_search_callback, Call(_, _)).Times(AnyNumber());
  EXPECT_CALL(mock_search_callback,
              Call(ash::AppListSearchResultType::kOmnibox, IsEmpty()))
      .WillOnce([&]() { test_done.SetValue(); });

  client.StartCrosSearch(
      u"a.com", /*category=*/std::nullopt,
      base::BindRepeating(&MockSearchResultsCallback::Call,
                          base::Unretained(&mock_search_callback)));

  ASSERT_TRUE(test_done.Wait());
}

TEST_F(PickerClientImplTest, GetRecentLocalFilesWithNoFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;

  client.GetRecentLocalFileResults(/*max_files=*/100, future.GetCallback());

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

TEST_F(PickerClientImplTest, GetRecentLocalFilesReturnsOnlyLocalFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  const base::FilePath mount_path = GetFakeDriveFs().mount_path();
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDownloads,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("local.png"),
                                       storage::kFileSystemTypeLocal),
                  },
          },
          Volume{
              .type = fmp::VolumeType::kDrive,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("drive.png"),
                                       storage::kFileSystemTypeDriveFs),
                  },
          },
      });

  client.GetRecentLocalFileResults(/*max_files=*/100, future.GetCallback());

  EXPECT_THAT(
      future.Get(),
      UnorderedElementsAre(VariantWith<ash::PickerLocalFileResult>(
          Field("title", &ash::PickerLocalFileResult::title, u"local.png"))));
}

TEST_F(PickerClientImplTest, GetRecentLocalFilesDoesNotReturnOldFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDownloads,
              .files =
                  {
                      CreateRecentFile(
                          GetFakeDriveFs().mount_path().AppendASCII("old.png"),
                          storage::kFileSystemTypeLocal,
                          base::Time::Now() - base::Days(31)),
                  },
          },
      });

  client.GetRecentLocalFileResults(/*max_files=*/100, future.GetCallback());

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

TEST_F(PickerClientImplTest, GetRecentDriveFilesWithNoFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;

  client.GetRecentDriveFileResults(/*max_files=*/100, future.GetCallback());

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

TEST_F(PickerClientImplTest, GetRecentDriveFilesReturnsOnlyDriveFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  const base::FilePath mount_path = GetFakeDriveFs().mount_path();
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDownloads,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("local.png"),
                                       storage::kFileSystemTypeLocal),
                  },
          },
          Volume{
              .type = fmp::VolumeType::kDrive,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("drive.png"),
                                       storage::kFileSystemTypeDriveFs),
                  },
          },
      });

  client.GetRecentDriveFileResults(/*max_files=*/100, future.GetCallback());

  EXPECT_THAT(
      future.Get(),
      UnorderedElementsAre(VariantWith<ash::PickerDriveFileResult>(AllOf(
          Field("title", &ash::PickerDriveFileResult::title, u"drive.png"),
          Field("url", &ash::PickerDriveFileResult::url,
                GURL("https://file_alternate_link/drive.png"))))));
}

TEST_F(PickerClientImplTest, GetRecentDriveFilesDoesNotReturnOldFiles) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDrive,
              .files =
                  {
                      CreateRecentFile(
                          GetFakeDriveFs().mount_path().AppendASCII("old.png"),
                          storage::kFileSystemTypeDriveFs,
                          base::Time::Now() - base::Days(31)),
                  },
          },
      });

  client.GetRecentLocalFileResults(/*max_files=*/100, future.GetCallback());

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

TEST_F(PickerClientImplTest, GetRecentLocalFilesTruncates) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  const base::FilePath mount_path = GetFakeDriveFs().mount_path();
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDownloads,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("1.jpg"),
                                       storage::kFileSystemTypeLocal),
                      CreateRecentFile(mount_path.AppendASCII("2.jpg"),
                                       storage::kFileSystemTypeLocal),
                  },
          },
      });

  client.GetRecentLocalFileResults(/*max_files=*/1, future.GetCallback());

  EXPECT_THAT(future.Get(), SizeIs(1));
}

TEST_F(PickerClientImplTest, GetRecentDriveFilesTruncates) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  const base::FilePath mount_path = GetFakeDriveFs().mount_path();
  SetRecentFiles(
      profile(),
      {
          Volume{
              .type = fmp::VolumeType::kDrive,
              .files =
                  {
                      CreateRecentFile(mount_path.AppendASCII("1"),
                                       storage::kFileSystemTypeDriveFs),
                      CreateRecentFile(mount_path.AppendASCII("2"),
                                       storage::kFileSystemTypeDriveFs),
                  },
          },
      });

  client.GetRecentDriveFileResults(/*max_files=*/1, future.GetCallback());

  EXPECT_THAT(future.Get(), SizeIs(1));
}

TEST_F(PickerClientImplTest, GetSuggestedLinkResultsReturnsLinks) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  const base::Time now = base::Time::Now();
  AddSearchToHistory(profile(), GURL("http://a.com/history"),
                     now - base::Seconds(1));
  AddSearchToHistory(profile(), GURL("http://b.com/history"), now);
  TestFaviconService favicon_service;
  client.get_link_suggester_for_test()->set_favicon_service_for_test(
      &favicon_service);

  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  client.GetSuggestedLinkResults(100u, future.GetRepeatingCallback());

  EXPECT_THAT(future.Get(),
              ElementsAre(VariantWith<ash::PickerBrowsingHistoryResult>(Field(
                              "url", &ash::PickerBrowsingHistoryResult::url,
                              GURL("http://b.com/history"))),
                          VariantWith<ash::PickerBrowsingHistoryResult>(Field(
                              "url", &ash::PickerBrowsingHistoryResult::url,
                              GURL("http://a.com/history")))));
  EXPECT_EQ(favicon_service.page_url_, GURL("http://a.com/history"));
}

TEST_F(PickerClientImplTest, GetSuggestedLinkResultsAreTruncatedToMostRecent) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  const base::Time now = base::Time::Now();
  AddSearchToHistory(profile(), GURL("http://a.com/history"),
                     now - base::Seconds(1));
  AddSearchToHistory(profile(), GURL("http://b.com/history"), now);
  TestFaviconService favicon_service;
  client.get_link_suggester_for_test()->set_favicon_service_for_test(
      &favicon_service);

  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  client.GetSuggestedLinkResults(1u, future.GetRepeatingCallback());

  EXPECT_THAT(future.Get(),
              ElementsAre(VariantWith<ash::PickerBrowsingHistoryResult>(
                  Field("url", &ash::PickerBrowsingHistoryResult::url,
                        GURL("http://b.com/history")))));
  EXPECT_EQ(favicon_service.page_url_, GURL("http://b.com/history"));
}

class PickerClientImplEditorTest : public PickerClientImplTest {
 public:
  ash::input_method::EditorMediator& GetEditorMediator(Profile* profile) {
    return *ash::input_method::EditorMediatorFactory::GetForProfile(profile);
  }

  ui::InputMethod& ime() { return ime_; }

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

    ash::IMEBridge::Get()->SetInputContextHandler(&ime_);
  }

  void TearDown() override {
    PickerClientImplTest::TearDown();

    ash::IMEBridge::Get()->SetInputContextHandler(nullptr);
  }

 private:
  ash::InputMethodAsh ime_{nullptr};
};

TEST_F(PickerClientImplEditorTest,
       IsEligibleForEditorReturnsFalseIfEditorDisabled) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kHardBlocked);

  EXPECT_FALSE(client.IsEligibleForEditor());
}

TEST_F(PickerClientImplEditorTest,
       IsEligibleForEditorReturnsFalseIfHardBlocked) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kHardBlocked);

  EXPECT_FALSE(client.IsEligibleForEditor());
}

TEST_F(PickerClientImplEditorTest,
       IsEligibleForEditorReturnsTrueIfSoftBlocked) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kSoftBlocked);

  EXPECT_TRUE(client.IsEligibleForEditor());
}

TEST_F(PickerClientImplEditorTest,
       CacheEditorContextReturnsNullCallbackWhenEditorFlagDisabled) {
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kHardBlocked);

  EXPECT_TRUE(client.CacheEditorContext().is_null());
}

TEST_F(PickerClientImplEditorTest,
       CacheEditorContextReturnsNullCallbackWhenBlocked) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kSoftBlocked);

  EXPECT_TRUE(client.CacheEditorContext().is_null());
}

TEST_F(PickerClientImplEditorTest,
       CacheEditorContextReturnsCallbackWhenNotBlocked) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kConsentNeeded);

  EXPECT_FALSE(client.CacheEditorContext().is_null());
}

TEST_F(PickerClientImplEditorTest, CacheEditorContextCachesCaretBounds) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kConsentNeeded);
  ui::FakeTextInputClient text_input_client(
      &ime(), {
                  .type = ui::TEXT_INPUT_TYPE_TEXT,
                  .caret_bounds = gfx::Rect(1, 2, 3, 4),
              });
  text_input_client.Focus();

  client.CacheEditorContext();

  EXPECT_EQ(GetEditorMediator(profile())
                .mako_bubble_coordinator_for_testing()
                .context_caret_bounds_for_testing(),
            gfx::Rect(1, 2, 3, 4));
}

TEST_F(PickerClientImplEditorTest, GetSuggestedEditorResults) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kRewrite);
  ui::FakeTextInputClient text_input_client(&ime(),
                                            {.type = ui::TEXT_INPUT_TYPE_TEXT});
  text_input_client.Focus();

  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  client.GetSuggestedEditorResults(future.GetCallback());

  EXPECT_TRUE(future.Wait());
  // TODO: b/331286774 - Add expectation for the suggested editor results once
  // EditorServiceConnector is injectable.
}

TEST_F(PickerClientImplEditorTest,
       GetSuggestedEditorResultsReturnsNothingWhenBlocked) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  GetEditorMediator(profile()).OverrideEditorModeForTesting(
      ash::input_method::EditorMode::kSoftBlocked);
  ui::FakeTextInputClient text_input_client(&ime(),
                                            {.type = ui::TEXT_INPUT_TYPE_TEXT});
  text_input_client.Focus();

  base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
  client.GetSuggestedEditorResults(future.GetCallback());

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

TEST_F(PickerClientImplEditorTest, AnnounceSendsLiveRegionChanges) {
  base::test::ScopedFeatureList features(chromeos::features::kOrcaDogfood);
  ash::PickerController controller;
  PickerClientImpl client(&controller, user_manager());
  views::test::AXEventCounter counter(views::AXEventManager::Get());

  client.Announce(u"hello");

  counter.WaitForEvent(ax::mojom::Event::kLiveRegionChanged);
}

// TODO: b/325540366 - Add PickerClientImpl tests.

}  // namespace