blob: 4fe64a3c51937c32b76f9a3f39504e0cd6797c9e [file] [log] [blame]
// 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/quick_insert/quick_insert_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/public/cpp/ash_prefs.h"
#include "ash/public/cpp/clipboard_history_controller.h"
#include "ash/public/cpp/system/toast_manager.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/quick_insert/metrics/quick_insert_session_metrics.h"
#include "ash/quick_insert/mock_quick_insert_client.h"
#include "ash/quick_insert/model/quick_insert_action_type.h"
#include "ash/quick_insert/model/quick_insert_caps_lock_position.h"
#include "ash/quick_insert/model/quick_insert_model.h"
#include "ash/quick_insert/model/quick_insert_search_results_section.h"
#include "ash/quick_insert/quick_insert_test_util.h"
#include "ash/quick_insert/views/quick_insert_feature_tour.h"
#include "ash/quick_insert/views/quick_insert_search_bar_textfield.h"
#include "ash/quick_insert/views/quick_insert_search_field_view.h"
#include "ash/quick_insert/views/quick_insert_view.h"
#include "ash/session/session_controller_impl.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/files/scoped_temp_dir.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/history/core/browser/history_service.h"
#include "components/history/core/test/history_service_test_util.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 QuickInsertClient implementation used for testing.
// Automatically sets itself as the client when it's created, and unsets itself
// when it's destroyed.
class TestQuickInsertClient : public MockQuickInsertClient {
public:
TestQuickInsertClient(QuickInsertController* controller, PrefService* prefs)
: controller_(controller) {
controller_->SetClient(this);
CHECK(history_dir_.CreateUniqueTempDir());
history_service_ =
history::CreateHistoryService(history_dir_.GetPath(), true);
// Set default behaviours. These can be overridden with `WillOnce` and
// `WillRepeatedly`.
ON_CALL(*this, GetSharedURLLoaderFactory)
.WillByDefault(
base::MakeRefCounted<network::TestSharedURLLoaderFactory>);
ON_CALL(*this, GetHistoryService)
.WillByDefault(Return(history_service_.get()));
}
~TestQuickInsertClient() override { controller_->SetClient(nullptr); }
private:
raw_ptr<QuickInsertController> controller_ = nullptr;
base::ScopedTempDir history_dir_;
std::unique_ptr<history::HistoryService> history_service_;
};
class QuickInsertControllerTest : public AshTestBase {
public:
QuickInsertControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
AshTestBase::SetUp();
controller_ = std::make_unique<QuickInsertController>();
client_ = std::make_unique<NiceMock<TestQuickInsertClient>>(
controller_.get(), prefs());
// Disable the feature tour by default.
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, true);
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_;
}
QuickInsertController& controller() { return *controller_; }
NiceMock<TestQuickInsertClient>& client() { return *client_; }
PrefService* prefs() {
return Shell::Get()->session_controller()->GetPrimaryUserPrefService();
}
metrics::structured::TestStructuredMetricsRecorder& metrics_recorder() {
return *metrics_recorder_;
}
private:
MockNewWindowDelegate new_window_delegate_;
std::unique_ptr<QuickInsertController> controller_;
std::unique_ptr<NiceMock<TestQuickInsertClient>> client_;
std::unique_ptr<metrics::structured::TestStructuredMetricsRecorder>
metrics_recorder_;
};
TEST_F(QuickInsertControllerTest, ToggleWidgetShowsWidgetIfClosed) {
controller().ToggleWidget();
EXPECT_TRUE(controller().widget_for_testing());
}
TEST_F(QuickInsertControllerTest,
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(QuickInsertControllerTest, 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(QuickInsertControllerTest, 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(QuickInsertControllerTest, 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(QuickInsertControllerTest, ToggleWidgetShowsFeatureTourForFirstTime) {
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, false);
controller().ToggleWidget();
EXPECT_TRUE(controller().feature_tour_for_testing().widget_for_testing());
EXPECT_FALSE(controller().widget_for_testing());
}
TEST_F(QuickInsertControllerTest,
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.
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, false);
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<QuickInsertView>(
controller().widget_for_testing()->widget_delegate()->GetContentsView());
ASSERT_NE(view, nullptr);
EXPECT_TRUE(
view->search_field_view_for_testing().textfield_for_testing().HasFocus());
// Dismiss Quick Insert.
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(QuickInsertControllerTest,
ToggleWidgetShowsWidgetAfterCompletingFeatureTourWithoutFocus) {
std::unique_ptr<views::Widget> test_widget =
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.
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, false);
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<QuickInsertView>(
controller().widget_for_testing()->widget_delegate()->GetContentsView());
ASSERT_NE(view, nullptr);
EXPECT_TRUE(
view->search_field_view_for_testing().textfield_for_testing().HasFocus());
// Dismiss Quick Insert.
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(QuickInsertControllerTest,
ToggleWidgetShowsWidgetAfterCompletingFeatureTourWithFocus) {
std::unique_ptr<views::Widget> textfield_widget =
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.
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, false);
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<QuickInsertView>(
controller().widget_for_testing()->widget_delegate()->GetContentsView());
ASSERT_NE(view, nullptr);
EXPECT_TRUE(
view->search_field_view_for_testing().textfield_for_testing().HasFocus());
// Dismiss Quick Insert.
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(QuickInsertControllerTest, ToggleWidgetOpensUrlAfterLearnMore) {
prefs()->SetBoolean(prefs::kQuickInsertFeatureTourCompletedPref, false);
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(QuickInsertControllerTest, SetClientToNullKeepsWidget) {
controller().ToggleWidget();
controller().SetClient(nullptr);
EXPECT_TRUE(controller().widget_for_testing());
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertControllerTest, InsertResultDoesNothingWhenWidgetIsClosed) {
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertTextResult(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(QuickInsertControllerTest,
InsertTextResultInsertsIntoInputFieldAfterFocus) {
controller().ToggleWidget();
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertTextResult(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(QuickInsertControllerTest,
InsertClipboardResultPastesIntoInputFieldAfterFocus) {
controller().ToggleWidget();
ASSERT_TRUE(CopyTextToClipboard());
std::optional<base::UnguessableToken> clipboard_item_id =
GetFirstClipboardItemId();
ASSERT_TRUE(clipboard_item_id.has_value());
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertClipboardResult(
*clipboard_item_id, QuickInsertClipboardResult::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(QuickInsertControllerTest,
InsertGifResultInsertsIntoInputFieldAfterFocus) {
controller().ToggleWidget();
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
controller().CloseWidgetThenInsertResultOnNextFocus(QuickInsertGifResult(
GURL("http://foo.com/fake_preview.gif"),
GURL("http://foo.com/fake_preview_image.png"), gfx::Size(),
GURL("http://foo.com/fake.gif"), gfx::Size(),
/*content_description=*/u""));
views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
controller().widget_for_testing());
ui::FakeTextInputClient input_field(
input_method,
{.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = true});
input_method->SetFocusedTextInputClient(&input_field);
EXPECT_EQ(input_field.last_inserted_image_url(),
GURL("http://foo.com/fake.gif"));
}
TEST_F(QuickInsertControllerTest,
InsertUnsupportedImageResultTimeoutCopiesToClipboard) {
controller().ToggleWidget();
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
controller().CloseWidgetThenInsertResultOnNextFocus(QuickInsertGifResult(
/*preview_url=*/GURL("http://foo.com/preview"),
/*preview_image_url=*/GURL(), gfx::Size(30, 20),
/*full_url=*/GURL("http://foo.com"), gfx::Size(60, 40),
/*content_description=*/u"a gif"));
views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
controller().widget_for_testing());
ui::FakeTextInputClient input_field(
input_method,
{.type = ui::TEXT_INPUT_TYPE_TEXT, .can_insert_image = false});
input_method->SetFocusedTextInputClient(&input_field);
task_environment()->FastForwardBy(QuickInsertController::kInsertMediaTimeout);
EXPECT_EQ(
ReadHtmlFromClipboard(ui::Clipboard::GetForCurrentThread()),
uR"html(<img src="http://foo.com/" referrerpolicy="no-referrer" alt="a gif" width="60" height="40"/>)html");
EXPECT_TRUE(
ToastManager::Get()->IsToastShown("quick_insert_copy_to_clipboard"));
}
TEST_F(QuickInsertControllerTest,
InsertBrowsingHistoryResultInsertsIntoInputFieldAfterFocus) {
controller().ToggleWidget();
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertBrowsingHistoryResult(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(QuickInsertControllerTest, InsertResultClosesWidgetImmediately) {
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertTextResult(u"abc"));
EXPECT_TRUE(controller().widget_for_testing()->IsClosed());
}
TEST_F(QuickInsertControllerTest,
InsertResultDelaysWidgetCloseForAccessibility) {
controller().ToggleWidget();
Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled(
true, A11Y_NOTIFICATION_NONE);
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertTextResult(u"abc"));
EXPECT_FALSE(controller().widget_for_testing()->IsClosed());
views::test::WidgetDestroyedWaiter widget_destroyed_waiter(
controller().widget_for_testing());
}
TEST_F(QuickInsertControllerTest, OpenBrowsingHistoryResult) {
controller().ToggleWidget();
EXPECT_CALL(mock_new_window_delegate(), OpenUrl(GURL("http://foo.com"), _, _))
.Times(1);
controller().OpenResult(QuickInsertBrowsingHistoryResult(
GURL("http://foo.com"), u"Foo", ui::ImageModel{}));
}
TEST_F(QuickInsertControllerTest, OpenDriveFileResult) {
controller().ToggleWidget();
EXPECT_CALL(mock_new_window_delegate(), OpenUrl(GURL("http://foo.com"), _, _))
.Times(1);
controller().OpenResult(QuickInsertDriveFileResult(
/*id=*/std::nullopt, u"title", GURL("http://foo.com"), base::FilePath()));
}
TEST_F(QuickInsertControllerTest, OpenLocalFileResult) {
controller().ToggleWidget();
EXPECT_CALL(mock_new_window_delegate(), OpenFile(base::FilePath("abc.png")))
.Times(1);
controller().OpenResult(
QuickInsertLocalFileResult(u"title", base::FilePath("abc.png")));
}
TEST_F(QuickInsertControllerTest, OpenNewGoogleDocOpensGoogleDocs) {
controller().ToggleWidget();
EXPECT_CALL(mock_new_window_delegate(),
OpenUrl(GURL("https://docs.new"), _, _))
.Times(1);
controller().OpenResult(
QuickInsertNewWindowResult(QuickInsertNewWindowResult::Type::kDoc));
}
TEST_F(QuickInsertControllerTest,
OpenCapsLockResultTurnsOnCapsLockOnNextFocus) {
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
ui::FakeTextInputClient input_field(input_method,
{.type = ui::TEXT_INPUT_TYPE_TEXT});
controller().ToggleWidget();
controller().OpenResult(QuickInsertCapsLockResult(
/*enabled=*/true, QuickInsertCapsLockResult::Shortcut::kAltSearch));
input_method->SetFocusedTextInputClient(&input_field);
input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
ASSERT_TRUE(ime_keyboard);
EXPECT_TRUE(ime_keyboard->IsCapsLockEnabled());
}
TEST_F(QuickInsertControllerTest,
OpenCapsLockResultTurnsOffCapsLockOnNextFocus) {
auto* input_method =
Shell::GetPrimaryRootWindow()->GetHost()->GetInputMethod();
ui::FakeTextInputClient input_field(input_method,
{.type = ui::TEXT_INPUT_TYPE_TEXT});
controller().ToggleWidget();
controller().OpenResult(QuickInsertCapsLockResult(
/*enabled=*/false, QuickInsertCapsLockResult::Shortcut::kAltSearch));
input_method->SetFocusedTextInputClient(&input_field);
input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
ASSERT_TRUE(ime_keyboard);
EXPECT_FALSE(ime_keyboard->IsCapsLockEnabled());
}
TEST_F(QuickInsertControllerTest, OpenCapsLockResultTurnsOnCapsLockOnTimeout) {
controller().ToggleWidget();
controller().OpenResult(QuickInsertCapsLockResult(
/*enabled=*/true, QuickInsertCapsLockResult::Shortcut::kAltSearch));
task_environment()->FastForwardBy(base::Seconds(1));
input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
ASSERT_TRUE(ime_keyboard);
EXPECT_TRUE(ime_keyboard->IsCapsLockEnabled());
}
TEST_F(QuickInsertControllerTest, OpenCapsLockResultTurnsOffCapsLockOnTimeout) {
controller().ToggleWidget();
controller().OpenResult(QuickInsertCapsLockResult(
/*enabled=*/false, QuickInsertCapsLockResult::Shortcut::kAltSearch));
task_environment()->FastForwardBy(base::Seconds(1));
input_method::ImeKeyboard* ime_keyboard = GetImeKeyboard();
ASSERT_TRUE(ime_keyboard);
EXPECT_FALSE(ime_keyboard->IsCapsLockEnabled());
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertCaseTransformResult(
QuickInsertCaseTransformResult::Type::kUpperCase));
input_method->SetFocusedTextInputClient(&input_field);
EXPECT_EQ(input_field.text(), u"ABC DEF");
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertCaseTransformResult(
QuickInsertCaseTransformResult::Type::kLowerCase));
input_method->SetFocusedTextInputClient(&input_field);
EXPECT_EQ(input_field.text(), u"abc def");
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertCaseTransformResult(
QuickInsertCaseTransformResult::Type::kTitleCase));
input_method->SetFocusedTextInputClient(&input_field);
EXPECT_EQ(input_field.text(), u"Abc Def");
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertControllerTest, 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(QuickInsertControllerTest, 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(QuickInsertControllerTest, ShowLobsterCallsCallbackFromClient) {
base::test::TestFuture<std::optional<std::string>> show_lobster_future;
EXPECT_CALL(client(), CacheLobsterContext)
.WillOnce(Return(show_lobster_future.GetCallback()));
controller().ToggleWidget();
controller().ShowLobster(/*freeform_text=*/"freeform");
EXPECT_THAT(show_lobster_future.Get(), "freeform");
}
TEST_F(QuickInsertControllerTest,
GetResultsForCategoryReturnsEmptyForEmptyResults) {
base::test::TestFuture<std::vector<QuickInsertSearchResultsSection>> future;
controller().ToggleWidget();
controller().GetResultsForCategory(QuickInsertCategory::kLinks,
future.GetRepeatingCallback());
EXPECT_THAT(future.Take(), IsEmpty());
}
TEST_F(QuickInsertControllerTest,
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(QuickInsertCategory::kEditorWrite));
}
TEST_F(QuickInsertControllerTest,
AvailableCategoriesDoesNotContainEditorWhenDisabled) {
EXPECT_CALL(client(), CacheEditorContext)
.WillOnce(Return(base::NullCallback()));
controller().ToggleWidget();
EXPECT_THAT(controller().GetAvailableCategories(),
Not(Contains(QuickInsertCategory::kEditorWrite)));
}
TEST_F(QuickInsertControllerTest, SuggestedEmojiReturnsDefaultEmojisWhenEmpty) {
controller().ToggleWidget();
EXPECT_THAT(controller().GetSuggestedEmoji(),
ElementsAre(QuickInsertEmojiResult::Emoji(u"🙂"),
QuickInsertEmojiResult::Emoji(u"😂"),
QuickInsertEmojiResult::Emoji(u"🤔"),
QuickInsertEmojiResult::Emoji(u"😢"),
QuickInsertEmojiResult::Emoji(u"👏"),
QuickInsertEmojiResult::Emoji(u"👍")));
}
TEST_F(QuickInsertControllerTest,
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(prefs(), prefs::kEmojiPickerHistory);
update->Set("emoji", std::move(history_value));
controller().ToggleWidget();
EXPECT_THAT(controller().GetSuggestedEmoji(),
ElementsAre(QuickInsertEmojiResult::Emoji(u"abc"),
QuickInsertEmojiResult::Emoji(u"xyz"),
QuickInsertEmojiResult::Emoji(u"🙂"),
QuickInsertEmojiResult::Emoji(u"😂"),
QuickInsertEmojiResult::Emoji(u"🤔"),
QuickInsertEmojiResult::Emoji(u"😢")));
}
TEST_F(QuickInsertControllerTest, 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(prefs(), prefs::kEmojiPickerHistory);
update->Set("emoji", std::move(history_value));
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Emoji(u"def"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
ElementsAre(QuickInsertEmojiResult::Emoji(u"def"),
QuickInsertEmojiResult::Emoji(u"abc"),
QuickInsertEmojiResult::Emoji(u"xyz"),
QuickInsertEmojiResult::Emoji(u"🙂"),
QuickInsertEmojiResult::Emoji(u"😂"),
QuickInsertEmojiResult::Emoji(u"🤔")));
}
TEST_F(QuickInsertControllerTest, 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(prefs(), prefs::kEmojiPickerHistory);
update->Set("emoji", std::move(history_value));
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Emoji(u"xyz"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
ElementsAre(QuickInsertEmojiResult::Emoji(u"xyz"),
QuickInsertEmojiResult::Emoji(u"abc"),
QuickInsertEmojiResult::Emoji(u"🙂"),
QuickInsertEmojiResult::Emoji(u"😂"),
QuickInsertEmojiResult::Emoji(u"🤔"),
QuickInsertEmojiResult::Emoji(u"😢")));
}
TEST_F(QuickInsertControllerTest, AddsRecentEmojiEmptyHistory) {
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Emoji(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
ElementsAre(QuickInsertEmojiResult::Emoji(u"abc"),
QuickInsertEmojiResult::Emoji(u"🙂"),
QuickInsertEmojiResult::Emoji(u"😂"),
QuickInsertEmojiResult::Emoji(u"🤔"),
QuickInsertEmojiResult::Emoji(u"😢"),
QuickInsertEmojiResult::Emoji(u"👏")));
}
TEST_F(QuickInsertControllerTest, RecentlyAddedEmojiHasCorrectType) {
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Emoji(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
Contains(QuickInsertEmojiResult::Emoji(u"abc")));
}
TEST_F(QuickInsertControllerTest, RecentlyAddedSymbolHasCorrectType) {
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Symbol(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
Contains(QuickInsertEmojiResult::Symbol(u"abc")));
}
TEST_F(QuickInsertControllerTest, RecentlyAddedEmoticonHasCorrectType) {
controller().ToggleWidget();
controller().CloseWidgetThenInsertResultOnNextFocus(
QuickInsertEmojiResult::Emoticon(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
Contains(QuickInsertEmojiResult::Emoticon(u"abc")));
}
TEST_F(QuickInsertControllerTest, 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(
QuickInsertEmojiResult::Emoji(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
Contains(QuickInsertEmojiResult::Emoji(u"abc")));
}
TEST_F(QuickInsertControllerTest, 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(
QuickInsertEmojiResult::Emoji(u"abc"));
EXPECT_THAT(controller().GetSuggestedEmoji(),
Not(Contains(QuickInsertEmojiResult::Emoji(u"abc"))));
}
TEST_F(QuickInsertControllerTest,
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(prefs(), 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(QuickInsertEmojiResult::Symbol(u"symbol1"),
QuickInsertEmojiResult::Emoticon(u"emoticon1"),
QuickInsertEmojiResult::Emoji(u"emoji1"),
QuickInsertEmojiResult::Symbol(u"symbol2"),
QuickInsertEmojiResult::Emoji(u"emoji2"),
QuickInsertEmojiResult::Emoticon(u"emoticon2")));
}
TEST_F(QuickInsertControllerTest, SearchesCapsLockOnWhenCapsLockIsOff) {
base::test::TestFuture<std::vector<QuickInsertSearchResultsSection>>
search_future;
controller().ToggleWidget();
controller().StartSearch(u"caps", /*category=*/{},
search_future.GetRepeatingCallback());
EXPECT_THAT(search_future.Take(),
Contains(Property(
&QuickInsertSearchResultsSection::results,
Contains(QuickInsertCapsLockResult(
/*enabled=*/true,
QuickInsertCapsLockResult::Shortcut::kAltLauncher)))));
}
TEST_F(QuickInsertControllerTest, SearchesCapsLockOffWhenCapsLockIsOn) {
base::test::TestFuture<std::vector<QuickInsertSearchResultsSection>>
search_future;
GetImeKeyboard()->SetCapsLockEnabled(true);
controller().ToggleWidget();
controller().StartSearch(u"caps", /*category=*/{},
search_future.GetRepeatingCallback());
EXPECT_THAT(search_future.Take(),
Contains(Property(
&QuickInsertSearchResultsSection::results,
Contains(QuickInsertCapsLockResult(
/*enabled=*/false,
QuickInsertCapsLockResult::Shortcut::kAltLauncher)))));
}
TEST_F(QuickInsertControllerTest,
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<QuickInsertController::SearchResultsCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
EXPECT_CALL(callback,
Run(Contains(Property(
&QuickInsertSearchResultsSection::results,
Contains(VariantWith<QuickInsertCaseTransformResult>(_))))))
.Times(0);
controller().ToggleWidget();
controller().StartSearch(u"uppercase", /*category=*/{}, callback.Get());
}
TEST_F(QuickInsertControllerTest, 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<QuickInsertController::SearchResultsCallback> callback;
EXPECT_CALL(callback, Run(_)).Times(AnyNumber());
EXPECT_CALL(callback,
Run(Contains(Property(
&QuickInsertSearchResultsSection::results,
Contains(VariantWith<QuickInsertCaseTransformResult>(
Field(&QuickInsertCaseTransformResult::type,
QuickInsertCaseTransformResult::kUpperCase)))))))
.Times(1);
controller().ToggleWidget();
controller().StartSearch(u"uppercase", /*category=*/{}, callback.Get());
}
TEST_F(QuickInsertControllerTest, 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(QuickInsertTextResult(u"a"));
controller().IsGifsEnabled();
controller().GetAvailableCategories();
}
TEST_F(QuickInsertControllerTest,
ReturnsCapsLockPositionTopWhenCapsLockHasNotShownEnoughTimes) {
prefs()->SetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName, 4);
prefs()->SetInteger(prefs::kQuickInsertLockSelectedCountPrefName, 0);
EXPECT_EQ(controller().GetCapsLockPosition(),
QuickInsertCapsLockPosition::kTop);
}
TEST_F(QuickInsertControllerTest,
ReturnsCapsLockPositionTopWhenCapsLockIsAlwaysUsed) {
prefs()->SetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName, 15);
prefs()->SetInteger(prefs::kQuickInsertLockSelectedCountPrefName, 14);
EXPECT_EQ(controller().GetCapsLockPosition(),
QuickInsertCapsLockPosition::kTop);
}
TEST_F(QuickInsertControllerTest,
ReturnsCapsLockPositionMiddleWhenCapsLockIsSometimesUsed) {
prefs()->SetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName, 15);
prefs()->SetInteger(prefs::kQuickInsertLockSelectedCountPrefName, 7);
EXPECT_EQ(controller().GetCapsLockPosition(),
QuickInsertCapsLockPosition::kMiddle);
}
TEST_F(QuickInsertControllerTest,
ReturnsCapsLockPositionBottomWhenCapsLockIsNeverUsed) {
prefs()->SetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName, 15);
prefs()->SetInteger(prefs::kQuickInsertLockSelectedCountPrefName, 0);
EXPECT_EQ(controller().GetCapsLockPosition(),
QuickInsertCapsLockPosition::kBottom);
}
TEST_F(QuickInsertControllerTest,
ReturnCapsLockPositionTopWhenCapsLockIsEnabled) {
prefs()->SetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName, 4);
prefs()->SetInteger(prefs::kQuickInsertLockSelectedCountPrefName, 0);
GetImeKeyboard()->SetCapsLockEnabled(true);
EXPECT_EQ(controller().GetCapsLockPosition(),
QuickInsertCapsLockPosition::kTop);
}
struct ActionTestCase {
QuickInsertSearchResult result;
std::optional<QuickInsertActionType> unfocused_action;
std::optional<QuickInsertActionType> no_selection_action;
std::optional<QuickInsertActionType> has_selection_action;
};
class QuickInsertControllerActionTest
: public QuickInsertControllerTest,
public testing::WithParamInterface<ActionTestCase> {};
TEST_P(QuickInsertControllerActionTest, GetActionForResultUnfocused) {
controller().ToggleWidget();
if (GetParam().unfocused_action.has_value()) {
EXPECT_EQ(controller().GetActionForResult(GetParam().result),
GetParam().unfocused_action);
}
}
TEST_P(QuickInsertControllerActionTest, 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(QuickInsertControllerActionTest, 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(
,
QuickInsertControllerActionTest,
testing::ValuesIn<ActionTestCase>({
{
.result = QuickInsertTextResult(u""),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertEmojiResult::Emoji(u""),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertEmojiResult::Symbol(u""),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertEmojiResult::Emoticon(u""),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertClipboardResult(
base::UnguessableToken::Create(),
QuickInsertClipboardResult::DisplayFormat::kFile,
0,
u"",
{},
false),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertGifResult({}, {}, {}, {}, {}, u""),
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertBrowsingHistoryResult({}, u"", {}),
.unfocused_action = QuickInsertActionType::kOpen,
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertLocalFileResult(u"", {}),
.unfocused_action = QuickInsertActionType::kOpen,
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result = QuickInsertDriveFileResult(std::nullopt, u"", {}, {}),
.unfocused_action = QuickInsertActionType::kOpen,
.no_selection_action = QuickInsertActionType::kInsert,
.has_selection_action = QuickInsertActionType::kInsert,
},
{
.result =
QuickInsertCategoryResult(QuickInsertCategory::kEmojisGifs),
.unfocused_action = QuickInsertActionType::kDo,
.no_selection_action = QuickInsertActionType::kDo,
.has_selection_action = QuickInsertActionType::kDo,
},
{
.result = QuickInsertCategoryResult(QuickInsertCategory::kEmojis),
.unfocused_action = QuickInsertActionType::kDo,
.no_selection_action = QuickInsertActionType::kDo,
.has_selection_action = QuickInsertActionType::kDo,
},
{
.result = QuickInsertSearchRequestResult(u"", u"", {}),
.unfocused_action = QuickInsertActionType::kDo,
.no_selection_action = QuickInsertActionType::kDo,
.has_selection_action = QuickInsertActionType::kDo,
},
{
.result =
QuickInsertEditorResult(QuickInsertEditorResult::Mode::kWrite,
u"",
{},
{}),
.unfocused_action = QuickInsertActionType::kCreate,
.no_selection_action = QuickInsertActionType::kCreate,
.has_selection_action = QuickInsertActionType::kCreate,
},
{
.result = QuickInsertNewWindowResult(
QuickInsertNewWindowResult::Type::kDoc),
.unfocused_action = QuickInsertActionType::kDo,
},
}));
} // namespace
} // namespace ash