| // 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 |