blob: 3c69d0a1a9776ab271d4788dd137959b03d30799 [file] [log] [blame]
// 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