910e62b5创建于 1月15日历史提交
// 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 "ash/quick_insert/quick_insert_suggestions_controller.h"

#include "ash/clipboard/clipboard_history_item.h"
#include "ash/clipboard/test_support/clipboard_history_item_builder.h"
#include "ash/clipboard/test_support/mock_clipboard_history_controller.h"
#include "ash/quick_insert/mock_quick_insert_client.h"
#include "ash/quick_insert/model/quick_insert_model.h"
#include "ash/test/ash_test_base.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/test/history_service_test_util.h"
#include "ui/base/ime/ash/fake_ime_keyboard.h"
#include "ui/base/ime/fake_text_input_client.h"

namespace ash {
namespace {

using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::Contains;
using ::testing::Each;
using ::testing::Field;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Property;
using ::testing::Return;
using ::testing::VariantWith;
using ::testing::WithArg;

class QuickInsertSuggestionsControllerTest : public testing::Test {
 public:
  QuickInsertSuggestionsControllerTest() {
    CHECK(history_dir_.CreateUniqueTempDir());
    history_service_ =
        history::CreateHistoryService(history_dir_.GetPath(), true);
  }

  history::HistoryService* GetHistoryService() {
    return history_service_.get();
  }

 private:
  base::test::TaskEnvironment task_environment_;
  base::ScopedTempDir history_dir_;
  std::unique_ptr<history::HistoryService> history_service_;
};

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWhenUnfocusedReturnsNewWindowResults) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/nullptr,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(Contains(VariantWith<QuickInsertNewWindowResult>(_))))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWhenSelectedTextReturnsEditorRewriteResults) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetSuggestedEditorResults)
      .WillOnce(RunOnceCallback<0>(std::vector<QuickInsertSearchResult>{
          QuickInsertEditorResult(QuickInsertEditorResult::Mode::kRewrite, u"",
                                  {}, {}),
      }));
  QuickInsertSuggestionsController controller;
  ui::FakeTextInputClient input_field({.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.SetTextAndSelection(u"a", gfx::Range(0, 1));
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/&input_field,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(AllOf(Not(IsEmpty()),
                        Each(VariantWith<QuickInsertEditorResult>(
                            Field(&QuickInsertEditorResult::mode,
                                  QuickInsertEditorResult::Mode::kRewrite))))))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWithSelectionReturnsLobsterResult) {
  NiceMock<MockQuickInsertClient> client;
  QuickInsertSuggestionsController controller;
  ui::FakeTextInputClient input_field({.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.SetTextAndSelection(u"a", gfx::Range(0, 1));
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, &input_field, &keyboard,
                         QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(IsSupersetOf({
                  QuickInsertLobsterResult(
                      QuickInsertLobsterResult::Mode::kWithSelection, u""),
              })))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWhenFocusedDoesNotReturnNewWindowResults) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  ui::FakeTextInputClient input_field({.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/&input_field,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback,
              Run(Contains(VariantWith<QuickInsertNewWindowResult>(_))))
      .Times(0);
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWhenCapsOffReturnsCapsOn) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  input_method::FakeImeKeyboard keyboard;
  keyboard.SetCapsLockEnabled(false);
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/nullptr,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(
      callback,
      Run(Contains(QuickInsertCapsLockResult(
          /*enabled=*/true, QuickInsertCapsLockResult::Shortcut::kAltSearch))))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWhenCapsOnReturnsCapsOff) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  input_method::FakeImeKeyboard keyboard;
  keyboard.SetCapsLockEnabled(true);
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/nullptr,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(
      callback,
      Run(Contains(QuickInsertCapsLockResult(
          /*enabled=*/false, QuickInsertCapsLockResult::Shortcut::kAltSearch))))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWithSelectionReturnsCaseTransforms) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  ui::FakeTextInputClient input_field({.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_field.SetTextAndSelection(u"a", gfx::Range(0, 1));
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, &input_field, &keyboard,
                         QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback,
              Run(IsSupersetOf({
                  QuickInsertCaseTransformResult(
                      QuickInsertCaseTransformResult::Type::kUpperCase),
                  QuickInsertCaseTransformResult(
                      QuickInsertCaseTransformResult::Type::kLowerCase),
                  QuickInsertCaseTransformResult(
                      QuickInsertCaseTransformResult::Type::kTitleCase),
              })))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsWithNoSelectionDoesNotReturnCaseTransforms) {
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(GetHistoryService()));
  QuickInsertSuggestionsController controller;
  ui::FakeTextInputClient input_field({.type = ui::TEXT_INPUT_TYPE_TEXT});
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, &input_field, &keyboard,
                         QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
  EXPECT_CALL(callback, Run(Contains(QuickInsertCaseTransformResult(
                            QuickInsertCaseTransformResult::Type::kUpperCase))))
      .Times(0);
  EXPECT_CALL(callback, Run(Contains(QuickInsertCaseTransformResult(
                            QuickInsertCaseTransformResult::Type::kLowerCase))))
      .Times(0);
  EXPECT_CALL(callback, Run(Contains(QuickInsertCaseTransformResult(
                            QuickInsertCaseTransformResult::Type::kTitleCase))))
      .Times(0);

  controller.GetSuggestions(client, model, callback.Get());
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsRequestsAndReturnsSuggestionsPerCategory) {
  const base::Time now = base::Time::Now();
  auto* history_service = GetHistoryService();
  history_service->AddPageWithDetails(
      GURL("https://a.com"), /*title=*/u"", /*visit_count=*/1,
      /*typed_count=*/1,
      /*last_visit=*/now,
      /*hidden=*/false, history::SOURCE_BROWSED);
  history_service->AddPageWithDetails(
      GURL("https://b.com"), /*title=*/u"", /*visit_count=*/1,
      /*typed_count=*/1,
      /*last_visit=*/now - base::Seconds(1),
      /*hidden=*/false, history::SOURCE_BROWSED);
  history::BlockUntilHistoryProcessesPendingRequests(history_service);
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(history_service));
  EXPECT_CALL(client, GetRecentDriveFileResults(5, _))
      .WillOnce(RunOnceCallback<1>(std::vector<QuickInsertSearchResult>{
          QuickInsertDriveFileResult(/*id=*/{}, u"a", GURL("a.com"),
                                     /*file_path=*/{}),
          QuickInsertDriveFileResult(/*id=*/{}, u"b", GURL("b.com"),
                                     /*file_path=*/{}),
      }));
  EXPECT_CALL(client, GetRecentLocalFileResults(3, _, _))
      .WillOnce(RunOnceCallback<2>(std::vector<QuickInsertSearchResult>{
          QuickInsertLocalFileResult(u"a", /*file_path=*/{}),
          QuickInsertLocalFileResult(u"b", /*file_path=*/{}),
          QuickInsertLocalFileResult(u"c", /*file_path=*/{}),
          QuickInsertLocalFileResult(u"d", /*file_path=*/{}),
      }));
  QuickInsertSuggestionsController controller;
  input_method::FakeImeKeyboard keyboard;
  QuickInsertModel model(/*prefs=*/nullptr, /*focused_client=*/nullptr,
                         &keyboard, QuickInsertModel::EditorStatus::kEnabled,
                         QuickInsertModel::LobsterStatus::kEnabled);

  base::test::TestFuture<void> history_future;
  base::MockCallback<QuickInsertSuggestionsController::SuggestionsCallback>
      callback;
  EXPECT_CALL(callback, Run).Times(AnyNumber());
  EXPECT_CALL(
      callback,
      Run(ElementsAre(VariantWith<QuickInsertBrowsingHistoryResult>(_))))
      .WillOnce([&]() { history_future.SetValue(); });
  EXPECT_CALL(callback,
              Run(ElementsAre(VariantWith<QuickInsertDriveFileResult>(_))))
      .Times(1);
  EXPECT_CALL(callback,
              Run(ElementsAre(VariantWith<QuickInsertLocalFileResult>(_),
                              VariantWith<QuickInsertLocalFileResult>(_),
                              VariantWith<QuickInsertLocalFileResult>(_))))
      .Times(1);

  controller.GetSuggestions(client, model, callback.Get());
  ASSERT_TRUE(history_future.Wait());
}

TEST_F(QuickInsertSuggestionsControllerTest, GetSuggestionsForLinkCategory) {
  const base::Time now = base::Time::Now();
  auto* history_service = GetHistoryService();
  history_service->AddPageWithDetails(
      GURL("https://a.com"), /*title=*/u"a", /*visit_count=*/1,
      /*typed_count=*/1,
      /*last_visit=*/now,
      /*hidden=*/false, history::SOURCE_BROWSED);
  history_service->AddPageWithDetails(
      GURL("https://b.com"), /*title=*/u"b", /*visit_count=*/1,
      /*typed_count=*/1,
      /*last_visit=*/now - base::Seconds(1),
      /*hidden=*/false, history::SOURCE_BROWSED);
  history::BlockUntilHistoryProcessesPendingRequests(history_service);
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetHistoryService)
      .WillRepeatedly(Return(history_service));
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kLinks,
                                       future.GetRepeatingCallback());

  EXPECT_THAT(future.Take(),
              ElementsAre(VariantWith<QuickInsertBrowsingHistoryResult>(
                              FieldsAre(GURL("https://a.com"), u"a", _, _)),
                          VariantWith<QuickInsertBrowsingHistoryResult>(
                              FieldsAre(GURL("https://b.com"), u"b", _, _))));
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsForDriveFileCategory) {
  const std::vector<QuickInsertSearchResult> suggested_files = {
      QuickInsertDriveFileResult(/*id=*/{}, u"a", GURL("a.com"),
                                 /*file_path=*/{}),
      QuickInsertDriveFileResult(/*id=*/{}, u"b", GURL("b.com"),
                                 /*file_path=*/{}),
  };
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetRecentDriveFileResults)
      .WillOnce(RunOnceCallback<1>(suggested_files));
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kDriveFiles,
                                       future.GetRepeatingCallback());

  EXPECT_EQ(future.Take(), suggested_files);
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsForLocalFileCategory) {
  const std::vector<QuickInsertSearchResult> suggested_files = {
      QuickInsertLocalFileResult(u"a", /*file_path=*/{}),
      QuickInsertLocalFileResult(u"b", /*file_path=*/{}),
  };
  NiceMock<MockQuickInsertClient> client;
  EXPECT_CALL(client, GetRecentLocalFileResults)
      .WillOnce(RunOnceCallback<2>(suggested_files));
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kLocalFiles,
                                       future.GetRepeatingCallback());

  EXPECT_EQ(future.Take(), suggested_files);
}

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsForDatesCategoryReturnsSomeResults) {
  NiceMock<MockQuickInsertClient> client;
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kDatesTimes,
                                       future.GetRepeatingCallback());

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

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsForMathsCategoryReturnsSomeResults) {
  NiceMock<MockQuickInsertClient> client;
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kUnitsMaths,
                                       future.GetRepeatingCallback());

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

TEST_F(QuickInsertSuggestionsControllerTest,
       GetSuggestionsForClipboardCategory) {
  ClipboardHistoryItem clipboard_item =
      ClipboardHistoryItemBuilder()
          .SetFormat(ui::ClipboardInternalFormat::kText)
          .SetText("abc")
          .Build();
  MockClipboardHistoryController mock_clipboard;
  EXPECT_CALL(mock_clipboard, GetHistoryValues)
      .WillOnce(RunOnceCallback<0>(
          std::vector<ClipboardHistoryItem>{clipboard_item}));
  NiceMock<MockQuickInsertClient> client;
  QuickInsertSuggestionsController controller;

  base::test::TestFuture<std::vector<QuickInsertSearchResult>> future;
  controller.GetSuggestionsForCategory(client, QuickInsertCategory::kClipboard,
                                       future.GetRepeatingCallback());

  EXPECT_THAT(future.Take(),
              ElementsAre(VariantWith<QuickInsertClipboardResult>(
                  FieldsAre(_, QuickInsertClipboardResult::DisplayFormat::kText,
                            _, u"abc", _, _))));
}

}  // namespace
}  // namespace ash