// 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/picker/views/picker_view.h"
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "ash/picker/metrics/picker_session_metrics.h"
#include "ash/picker/mock_picker_asset_fetcher.h"
#include "ash/picker/model/picker_action_type.h"
#include "ash/picker/model/picker_caps_lock_position.h"
#include "ash/picker/model/picker_search_results_section.h"
#include "ash/picker/picker_controller.h"
#include "ash/picker/views/picker_category_type.h"
#include "ash/picker/views/picker_contents_view.h"
#include "ash/picker/views/picker_emoji_bar_view.h"
#include "ash/picker/views/picker_emoji_item_view.h"
#include "ash/picker/views/picker_item_view.h"
#include "ash/picker/views/picker_list_item_view.h"
#include "ash/picker/views/picker_preview_bubble.h"
#include "ash/picker/views/picker_preview_bubble_controller.h"
#include "ash/picker/views/picker_search_bar_textfield.h"
#include "ash/picker/views/picker_search_field_view.h"
#include "ash/picker/views/picker_search_results_view.h"
#include "ash/picker/views/picker_section_list_view.h"
#include "ash/picker/views/picker_section_view.h"
#include "ash/picker/views/picker_style.h"
#include "ash/picker/views/picker_submenu_controller.h"
#include "ash/picker/views/picker_submenu_view.h"
#include "ash/picker/views/picker_view_delegate.h"
#include "ash/picker/views/picker_widget.h"
#include "ash/picker/views/picker_zero_state_view.h"
#include "ash/public/cpp/picker/picker_category.h"
#include "ash/public/cpp/picker/picker_search_result.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/icon_button.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/view_drawn_waiter.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "build/branding_buildflags.h"
#include "components/metrics/structured/structured_events.h"
#include "components/metrics/structured/test/test_structured_metrics_recorder.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/background.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "url/gurl.h"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chromeos/ash/resources/internal/strings/grit/ash_internal_strings.h"
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
namespace ash {
namespace {
namespace cros_events = metrics::structured::events::v2::cr_os_events;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Optional;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::ResultOf;
using ::testing::Truly;
using ::testing::VariantWith;
constexpr gfx::Rect kDefaultAnchorBounds(200, 100, 0, 10);
template <class V, class Matcher>
auto AsView(Matcher matcher) {
return ResultOf(
"AsViewClass",
[](views::View* view) { return views::AsViewClass<V>(view); },
Pointee(matcher));
}
auto ContainsEvent(const metrics::structured::Event& event) {
return Contains(AllOf(
Property("event name", &metrics::structured::Event::event_name,
Eq(event.event_name())),
Property("metric values", &metrics::structured::Event::metric_values,
Eq(std::ref(event.metric_values())))));
}
class PickerPreviewBubbleVisibleWaiter
: public PickerPreviewBubbleController::Observer {
public:
void Wait(PickerPreviewBubbleController* preview_controller) {
if (!preview_controller->IsBubbleVisible()) {
preview_bubble_observation_.Observe(preview_controller);
run_loop_.Run();
}
}
void OnPreviewBubbleVisibilityChanged(bool visible) override {
if (visible) {
run_loop_.Quit();
}
}
private:
base::RunLoop run_loop_;
base::ScopedObservation<PickerPreviewBubbleController,
PickerPreviewBubbleController::Observer>
preview_bubble_observation_{this};
};
class PickerViewTest : public AshTestBase {
public:
PickerViewTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
AshTestBase::SetUp();
metrics_recorder_.Initialize();
}
protected:
metrics::structured::TestStructuredMetricsRecorder metrics_recorder_;
};
// PickerViewTest parameterized by the Emoji Category.
class PickerViewEmojiTest : public PickerViewTest,
public testing::WithParamInterface<PickerCategory> {
};
INSTANTIATE_TEST_SUITE_P(,
PickerViewEmojiTest,
testing::ValuesIn({PickerCategory::kEmojisGifs,
PickerCategory::kEmojis}));
class FakePickerViewDelegate : public PickerViewDelegate {
public:
using FakeSearchFunction =
base::RepeatingCallback<void(std::u16string_view query,
SearchResultsCallback callback)>;
using FakeCategorySearchFunction =
base::RepeatingCallback<void(SearchResultsCallback callback)>;
struct Options {
std::vector<PickerCategory> available_categories;
std::vector<PickerSearchResult> zero_state_suggested_results;
FakeSearchFunction search_function;
base::RepeatingClosure stop_search_function;
FakeCategorySearchFunction category_results_function;
PickerActionType action_type = PickerActionType::kInsert;
std::vector<PickerEmojiResult> emoji_results;
std::vector<std::string> suggested_emojis;
PickerModeType mode = PickerModeType::kNoSelection;
};
FakePickerViewDelegate() = default;
explicit FakePickerViewDelegate(Options options) : options_(options) {}
std::vector<PickerCategory> GetAvailableCategories() override {
if (options_.available_categories.empty()) {
// Use at least one category.
return {PickerCategory::kLinks};
}
return options_.available_categories;
}
void GetZeroStateSuggestedResults(
SuggestedResultsCallback callback) override {
callback.Run(options_.zero_state_suggested_results);
}
void GetResultsForCategory(PickerCategory category,
SearchResultsCallback callback) override {
if (options_.category_results_function.is_null()) {
std::move(callback).Run({});
} else {
options_.category_results_function.Run(std::move(callback));
}
}
void StartSearch(std::u16string_view query,
std::optional<PickerCategory> category,
SearchResultsCallback callback) override {
if (options_.search_function.is_null()) {
std::move(callback).Run({});
} else {
options_.search_function.Run(query, std::move(callback));
}
}
void StopSearch() override {
if (!options_.stop_search_function.is_null()) {
options_.stop_search_function.Run();
}
}
void StartEmojiSearch(std::u16string_view query,
EmojiSearchResultsCallback callback) override {
std::move(callback).Run(options_.emoji_results);
}
void CloseWidgetThenInsertResultOnNextFocus(
const PickerSearchResult& result) override {
last_inserted_result_ = result;
}
void OpenResult(const PickerSearchResult& result) override {
last_opened_result_ = result;
}
void ShowEmojiPicker(ui::EmojiPickerCategory category,
std::u16string_view query) override {
emoji_picker_category_ = category;
emoji_picker_query_ = std::u16string(query);
}
void ShowEditor(std::optional<std::string> preset_query_id,
std::optional<std::string> freeform_text) override {
showed_editor_ = true;
}
PickerAssetFetcher* GetAssetFetcher() override { return &asset_fetcher_; }
PickerSessionMetrics& GetSessionMetrics() override {
return session_metrics_;
}
PickerActionType GetActionForResult(
const PickerSearchResult& result) override {
return options_.action_type;
}
std::vector<PickerEmojiResult> GetSuggestedEmoji() override {
std::vector<PickerEmojiResult> results;
for (const std::string& emoji : options_.suggested_emojis) {
results.push_back(PickerEmojiResult::Emoji(base::UTF8ToUTF16(emoji)));
}
return results;
}
bool IsGifsEnabled() override { return true; }
PickerModeType GetMode() override { return options_.mode; }
PickerCapsLockPosition GetCapsLockPosition() override {
return PickerCapsLockPosition::kTop;
}
std::optional<PickerSearchResult> last_inserted_result() const {
return last_inserted_result_;
}
std::optional<PickerSearchResult> last_opened_result() const {
return last_opened_result_;
}
std::optional<ui::EmojiPickerCategory> emoji_picker_category() const {
return emoji_picker_category_;
}
std::optional<std::u16string> emoji_picker_query() const {
return emoji_picker_query_;
}
bool showed_editor() const { return showed_editor_; }
private:
Options options_;
MockPickerAssetFetcher asset_fetcher_;
PickerSessionMetrics session_metrics_;
std::optional<PickerSearchResult> last_inserted_result_;
std::optional<PickerSearchResult> last_opened_result_;
std::optional<ui::EmojiPickerCategory> emoji_picker_category_;
std::optional<std::u16string> emoji_picker_query_;
bool showed_editor_ = false;
};
PickerView* GetPickerViewFromWidget(views::Widget& widget) {
return views::AsViewClass<PickerView>(
widget.non_client_view()->client_view()->children().front());
}
// Gets the first category item view that can be clicked to select a category.
PickerItemView* GetFirstCategoryItemView(PickerView* picker_view) {
return picker_view->zero_state_view_for_testing()
.category_section_views_for_testing()
.begin()
->second->item_views_for_testing()[0];
}
TEST_P(PickerViewEmojiTest, SizeIsLessThanMaxWhenNoContentWithoutEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(view->size().width(), kPickerViewWidth);
EXPECT_LT(view->size().height(), 300);
}
TEST_P(PickerViewEmojiTest, SizeIsLessThanMaxWhenNoContentWithEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(view->size().width(), kPickerViewWidth);
EXPECT_LT(view->size().height(), 356);
}
TEST_F(PickerViewTest, SizeIsMaxWhenLotsOfContentWithoutEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.zero_state_suggested_results =
std::vector<PickerSearchResult>(10, PickerTextResult(u"abc")),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(view->size(), gfx::Size(kPickerViewWidth, 300));
}
TEST_P(PickerViewEmojiTest, SizeIsMaxWhenLotsOfContentWithEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
.zero_state_suggested_results =
std::vector<PickerSearchResult>(10, PickerTextResult(u"abc")),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(view->size(), gfx::Size(kPickerViewWidth, 356));
}
TEST_F(PickerViewTest, ShowsZeroStateView) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_THAT(view->search_field_view_for_testing(),
Property(&views::View::GetVisible, true));
EXPECT_THAT(view->zero_state_view_for_testing(),
Property(&views::View::GetVisible, true));
EXPECT_THAT(view->search_results_view_for_testing(),
Property(&views::View::GetVisible, false));
}
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
TEST_F(PickerViewTest, SearchPlaceholderMatchesUnfocusedMode) {
FakePickerViewDelegate delegate({
.mode = PickerModeType::kUnfocused,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_SEARCH_FIELD_NO_FOCUS_PLACEHOLDER_TEXT));
}
TEST_F(PickerViewTest, SearchPlaceholderMatchesNoSelectionModeWithEditor) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEditorWrite},
.mode = PickerModeType::kNoSelection,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(
picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_SEARCH_FIELD_NO_SELECTION_WITH_EDITOR_PLACEHOLDER_TEXT));
}
TEST_F(PickerViewTest, SearchPlaceholderMatchesNoSelectionModeWithoutEditor) {
FakePickerViewDelegate delegate({
.mode = PickerModeType::kNoSelection,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_SEARCH_FIELD_NO_SELECTION_PLACEHOLDER_TEXT));
}
TEST_F(PickerViewTest, SearchPlaceholderMatchesHasSelectionModeWithEditor) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEditorRewrite},
.mode = PickerModeType::kHasSelection,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(
picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_SEARCH_FIELD_HAS_SELECTION_WITH_EDITOR_PLACEHOLDER_TEXT));
}
TEST_F(PickerViewTest, SearchPlaceholderMatchesHasSelectionModeWithoutEditor) {
FakePickerViewDelegate delegate({
.mode = PickerModeType::kHasSelection,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
EXPECT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_SEARCH_FIELD_HAS_SELECTION_PLACEHOLDER_TEXT));
}
#endif
TEST_F(PickerViewTest, NonEmptySearchFieldContentsSwitchesToSearchResultsView) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_THAT(view->zero_state_view_for_testing(),
Property(&views::View::GetVisible, false));
EXPECT_THAT(view->search_results_view_for_testing(),
Property(&views::View::GetVisible, true));
}
TEST_F(PickerViewTest, EmptySearchFieldContentsSwitchesToZeroStateView) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_THAT(view->zero_state_view_for_testing(),
Property(&views::View::GetVisible, true));
EXPECT_THAT(view->search_results_view_for_testing(),
Property(&views::View::GetVisible, false));
}
TEST_F(PickerViewTest, LeftClickSearchResultInsertsResult) {
{
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false),
});
}),
.action_type = PickerActionType::kInsert,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ASSERT_THAT(
view->search_results_view_for_testing().section_views_for_testing(),
Not(IsEmpty()));
ASSERT_THAT(view->search_results_view_for_testing()
.section_views_for_testing()[0]
->item_views_for_testing(),
Not(IsEmpty()));
PickerItemView* result_view = view->search_results_view_for_testing()
.section_views_for_testing()[0]
->item_views_for_testing()[0];
ViewDrawnWaiter().Wait(result_view);
LeftClickOn(result_view);
EXPECT_EQ(delegate.last_opened_result(), std::nullopt);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"result")));
}
cros_events::Picker_FinishSession expected_event;
expected_event.SetOutcome(cros_events::PickerSessionOutcome::UNKNOWN)
.SetAction(cros_events::PickerAction::UNKNOWN)
.SetResultSource(cros_events::PickerResultSource::UNKNOWN)
.SetResultType(cros_events::PickerResultType::TEXT)
.SetTotalEdits(1)
.SetFinalQuerySize(1)
.SetResultIndex(0);
EXPECT_THAT(metrics_recorder_.GetEvents(), ContainsEvent(expected_event));
}
TEST_F(PickerViewTest, LeftClickZeroStateSuggestedResultInsertsResult) {
{
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.zero_state_suggested_results =
std::vector<PickerSearchResult>(10, PickerTextResult(u"abc")),
.action_type = PickerActionType::kInsert,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PickerItemView* result_view = view->zero_state_view_for_testing()
.primary_section_view_for_testing()
->item_views_for_testing()[0];
ViewDrawnWaiter().Wait(result_view);
LeftClickOn(result_view);
EXPECT_EQ(delegate.last_opened_result(), std::nullopt);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"abc")));
}
cros_events::Picker_FinishSession expected_event;
expected_event.SetOutcome(cros_events::PickerSessionOutcome::UNKNOWN)
.SetAction(cros_events::PickerAction::UNKNOWN)
.SetResultSource(cros_events::PickerResultSource::UNKNOWN)
.SetResultType(cros_events::PickerResultType::TEXT)
.SetTotalEdits(0)
.SetFinalQuerySize(0)
.SetResultIndex(-1);
EXPECT_THAT(metrics_recorder_.GetEvents(), ContainsEvent(expected_event));
}
TEST_F(PickerViewTest, LeftClickSearchResultOpensResult) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kLinks,
{PickerBrowsingHistoryResult({}, u"a", {})},
/*has_more_results=*/false),
});
}),
.action_type = PickerActionType::kOpen,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ASSERT_THAT(
view->search_results_view_for_testing().section_views_for_testing(),
Not(IsEmpty()));
ASSERT_THAT(view->search_results_view_for_testing()
.section_views_for_testing()[0]
->item_views_for_testing(),
Not(IsEmpty()));
PickerItemView* result_view = view->search_results_view_for_testing()
.section_views_for_testing()[0]
->item_views_for_testing()[0];
ViewDrawnWaiter().Wait(result_view);
LeftClickOn(result_view);
EXPECT_EQ(delegate.last_inserted_result(), std::nullopt);
EXPECT_THAT(delegate.last_opened_result(),
Optional(PickerBrowsingHistoryResult({}, u"a", {})));
}
TEST_F(PickerViewTest, SwitchesToCategoryView) {
{
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
cros_events::Picker_FinishSession expected_event;
expected_event.SetOutcome(cros_events::PickerSessionOutcome::UNKNOWN)
.SetAction(cros_events::PickerAction::OPEN_LINKS)
.SetResultSource(cros_events::PickerResultSource::UNKNOWN)
.SetResultType(cros_events::PickerResultType::UNKNOWN)
.SetTotalEdits(0)
.SetFinalQuerySize(0)
.SetResultIndex(-1);
EXPECT_THAT(metrics_recorder_.GetEvents(), ContainsEvent(expected_event));
}
TEST_F(PickerViewTest, ClickingCategoryResultsSwitchesToCategoryView) {
base::test::TestFuture<void> search_called;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_called.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerCategoryResult(PickerCategory::kLinks)}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_called.Wait());
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_result_item_view =
picker_view->search_results_view_for_testing()
.section_views_for_testing()[0]
->item_views_for_testing()[0];
ViewDrawnWaiter().Wait(category_result_item_view);
LeftClickOn(category_result_item_view);
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, SelectingCategoryUpdatesSearchFieldPlaceholderText) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetPlaceholderText(),
l10n_util::GetStringUTF16(
IDS_PICKER_LINKS_CATEGORY_SEARCH_FIELD_PLACEHOLDER_TEXT));
}
TEST_F(PickerViewTest, SelectingCategoryShowsBackButton) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_TRUE(picker_view->search_field_view_for_testing()
.back_button_for_testing()
.GetVisible());
}
TEST_F(PickerViewTest, SearchingWithCategoryKeepsShowingBackButton) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Type something into the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_TRUE(picker_view->search_field_view_for_testing()
.back_button_for_testing()
.GetVisible());
}
TEST_P(PickerViewEmojiTest, SelectingCategoryHidesEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks, GetParam()},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_FALSE(picker_view->emoji_bar_view_for_testing()->GetVisible());
}
TEST_P(PickerViewEmojiTest, ReturningToZeroStateFromCategoryPageShowsEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks, GetParam()},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_BACK, ui::EF_NONE);
EXPECT_TRUE(picker_view->emoji_bar_view_for_testing()->GetVisible());
}
TEST_F(PickerViewTest, SearchingWithCategorySwitchesToSearchResultsView) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Switch to category view.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Type something into the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_FALSE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, EmptySearchFieldSwitchesBackToCategoryView) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Switch to category view.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Type something into the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
// Clear the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, EmptySearchFieldSwitchesToCategoryViewFromSeeMore) {
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/true),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Type something into the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
// See more results.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* trailing_link = picker_view->search_results_view_for_testing()
.section_views_for_testing()[0]
->title_trailing_link_for_testing();
ViewDrawnWaiter().Wait(trailing_link);
LeftClickOn(trailing_link);
// Clear the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, CategoryViewFromSeeMoreHasResults) {
FakePickerViewDelegate delegate(
{.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/true),
});
}),
.category_results_function = base::BindLambdaForTesting(
[&](FakePickerViewDelegate::SearchResultsCallback callback) {
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks,
{
PickerTextResult(u"result"),
},
/*has_more_results=*/false),
});
})});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Type something into the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
// See more results.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* trailing_link = picker_view->search_results_view_for_testing()
.section_views_for_testing()[0]
->title_trailing_link_for_testing();
ViewDrawnWaiter().Wait(trailing_link);
LeftClickOn(trailing_link);
// Clear the search field.
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_THAT(
picker_view->category_results_view_for_testing()
.section_views_for_testing(),
ElementsAre(Pointee(Property(
"item views", &PickerSectionView::item_views_for_testing,
ElementsAre(AsView<PickerListItemView>(Property(
"primary text", &PickerListItemView::GetPrimaryTextForTesting,
u"result")))))));
}
TEST_F(PickerViewTest, SearchingSpacesFromZeroStateDoesNotStartSearch) {
FakePickerViewDelegate delegate(
{
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
ADD_FAILURE()
<< "Search function was unexpectedly called with query "
<< query;
// This should never be run - but if it is, immediately publish
// to get results.
callback.Run({{PickerSearchResultsSection(
PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
// Signals that all results are done.
callback.Run({});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_TRUE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_TRUE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, SearchTrimsLeftAndRightSpaces) {
base::test::TestFuture<std::u16string> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
// This will crash if it is run multiple times.
future.SetValue(std::u16string(query));
callback.Run({{PickerSearchResultsSection(
PickerSectionType::kClipboard, {{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
// Signals that all results are done.
callback.Run({});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
// [....|]
PressAndReleaseKey(ui::KeyboardCode::VKEY_LEFT, ui::EF_NONE);
// [...|.]
PressAndReleaseKey(ui::KeyboardCode::VKEY_LEFT, ui::EF_NONE);
// [..|..]
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
// [..a|..]
ASSERT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetText(),
u" a ");
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_EQ(future.Take(), u"a");
}
TEST_F(PickerViewTest, SearchIsNotRerunIfSpacesAreAddedToEnds) {
base::test::TestFuture<std::u16string> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
// This will crash if it is run multiple times.
future.SetValue(std::u16string(query));
callback.Run({{PickerSearchResultsSection(
PickerSectionType::kClipboard, {{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
// Signals that all results are done.
callback.Run({});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
EXPECT_EQ(future.Get(), u"a");
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
// [a..|]
PressAndReleaseKey(ui::KeyboardCode::VKEY_HOME, ui::EF_NONE);
// [|a..]
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
// [.|a..]
PressAndReleaseKey(ui::KeyboardCode::VKEY_SPACE, ui::EF_NONE);
// [..|a..]
ASSERT_EQ(picker_view->search_field_view_for_testing()
.textfield_for_testing()
.GetText(),
u" a ");
}
TEST_F(PickerViewTest,
SearchingFromZeroStateDoesNotImmediatelySwitchToResults) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback callback = future.Take();
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
callback.Run({{PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest,
SearchingFromZeroStateSwitchesToEmptyResultsAfterTimeout) {
base::test::TestFuture<void> search_called;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_called.SetValue();
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_called.Wait());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_list_view_for_testing()
->children(),
IsEmpty());
}
TEST_F(PickerViewTest, SearchingFromCategoryDoesNotImmediatelySwitchToResults) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback callback = future.Take();
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
callback.Run({{PickerSearchResultsSection(PickerSectionType::kLinks,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest,
SearchingFromCategorySwitchesToEmptyResultsAfterTimeout) {
base::test::TestFuture<void> search_called;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_called.SetValue();
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_called.Wait());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_list_view_for_testing()
->children(),
IsEmpty());
}
TEST_F(PickerViewTest, SearchingShowResultsWhenResultsArriveAsynchronously) {
base::test::TestFuture<void> search_called;
FakePickerViewDelegate::SearchResultsCallback search_callback;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_callback = std::move(callback);
search_called.SetValue();
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_called.Wait());
search_callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/false),
});
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_views_for_testing(),
ElementsAre(Pointee(
Property("title", &PickerSectionView::title_label_for_testing,
Property("text", &views::Label::GetText,
l10n_util::GetStringUTF16(
IDS_PICKER_LINKS_CATEGORY_LABEL))))));
}
TEST_F(PickerViewTest, SearchingKeepsOldResultsUntilNewResultsArrive) {
base::test::TestFuture<void> search1_called;
base::test::TestFuture<void> search2_called;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
if (!search1_called.IsReady()) {
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/false),
});
search1_called.SetValue();
} else {
search2_called.SetValue();
}
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
// Go to the results page.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search1_called.Wait());
// Start another search.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search2_called.Wait());
// Results page should keep old results until new results arrive.
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_views_for_testing(),
ElementsAre(Pointee(
Property("title", &PickerSectionView::title_label_for_testing,
Property("text", &views::Label::GetText,
l10n_util::GetStringUTF16(
IDS_PICKER_LINKS_CATEGORY_LABEL))))));
}
TEST_F(PickerViewTest, SearchingReplacesOldResultsWithNewResults) {
base::test::TestFuture<void> search1_called;
base::test::TestFuture<void> search2_called;
FakePickerViewDelegate::SearchResultsCallback search2_callback;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
if (!search1_called.IsReady()) {
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLocalFiles, {},
/*has_more_results=*/false),
});
search1_called.SetValue();
} else {
search2_callback = std::move(callback);
search2_called.SetValue();
}
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
// Go to the results page.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search1_called.Wait());
// Start another search.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search2_called.Wait());
search2_callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/false),
});
// Results page should show the new results.
EXPECT_TRUE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_views_for_testing(),
ElementsAre(Pointee(
Property("title", &PickerSectionView::title_label_for_testing,
Property("text", &views::Label::GetText,
l10n_util::GetStringUTF16(
IDS_PICKER_LINKS_CATEGORY_LABEL))))));
}
TEST_F(PickerViewTest, ShowsNoResultsBeforeTimeout) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout -
base::Milliseconds(1));
future.Take().Run({});
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.no_results_view_for_testing()
->GetVisible());
}
TEST_F(PickerViewTest, ShowsNoResultsAfterTimeout) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
future.Take().Run({});
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.no_results_view_for_testing()
->GetVisible());
}
TEST_F(PickerViewTest, ShowsNoResultsWithNoIllustration) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
future.Take().Run({});
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.no_results_view_for_testing()
->GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing()
.no_results_illustration_for_testing()
.GetVisible());
EXPECT_EQ(picker_view->search_results_view_for_testing()
.no_results_label_for_testing()
.GetText(),
l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT));
}
TEST_F(PickerViewTest, NoMainResultsAndNoEmojisIsAnnounced) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::test::AXEventCounter counter(views::AXEventManager::Get());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
future.Take().Run({});
EXPECT_EQ(picker_view->search_results_view_for_testing().GetAccessibleName(),
l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT));
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kLiveRegionChanged), 1);
}
TEST_P(PickerViewEmojiTest, NoMainResultsAndSomeEmojisIsAnnounced) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
.emoji_results = {PickerEmojiResult::Emoji(u"😊"),
PickerEmojiResult::Symbol(u"♬")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::test::AXEventCounter counter(views::AXEventManager::Get());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
future.Take().Run({});
EXPECT_EQ(picker_view->search_results_view_for_testing().GetAccessibleName(),
u"2 emojis. No other results.");
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kLiveRegionChanged), 1);
}
TEST_F(PickerViewTest, DoesNotClearResultsBeforeTimeout) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback first_callback = future.Take();
first_callback.Run({{PickerSearchResultsSection(
PickerSectionType::kClipboard, {{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
ASSERT_FALSE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
future.Clear();
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout -
base::Milliseconds(1));
EXPECT_FALSE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
}
TEST_F(PickerViewTest, ClearsResultsAfterTimeout) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback first_callback = future.Take();
first_callback.Run({{PickerSearchResultsSection(
PickerSectionType::kClipboard, {{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
ASSERT_FALSE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
future.Clear();
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
}
TEST_F(PickerViewTest, ClearsResultsWhenQueryClearedNoCategory) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback callback = future.Take();
callback.Run({{PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
ASSERT_FALSE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
}
TEST_F(PickerViewTest, ClearsResultsWhenQueryClearedWithCategory) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(std::move(callback));
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback callback = future.Take();
callback.Run({{PickerSearchResultsSection(PickerSectionType::kLinks,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false)}});
ASSERT_FALSE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.section_views_for_testing()
.empty());
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, StopsSearchWhenQueryClearedNoCategory) {
base::test::TestFuture<void> search_future;
base::test::TestFuture<void> stop_search_future;
FakePickerViewDelegate delegate(
{.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_future.SetValue();
}),
.stop_search_function = stop_search_future.GetRepeatingCallback()});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_future.Wait());
EXPECT_FALSE(stop_search_future.IsReady());
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(stop_search_future.Wait());
}
TEST_F(PickerViewTest, StopsSearchWhenQueryClearedWithCategory) {
base::test::TestFuture<void> search_future;
base::test::TestFuture<void> stop_search_future;
FakePickerViewDelegate delegate(
{.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_future.SetValue();
}),
.stop_search_function = stop_search_future.GetRepeatingCallback()});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Starting a category search - even if there is no query - stops the previous
// search.
ASSERT_TRUE(stop_search_future.WaitAndClear());
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_future.Wait());
EXPECT_FALSE(stop_search_future.IsReady());
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_TRUE(stop_search_future.Wait());
EXPECT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, StopsSearchWhenBackButtonPressed) {
base::test::TestFuture<void> search_future;
base::test::TestFuture<void> stop_search_future;
FakePickerViewDelegate delegate(
{.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_future.SetValue();
}),
.stop_search_function = stop_search_future.GetRepeatingCallback()});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Starting a category search - even if there is no query - stops the previous
// search.
ASSERT_TRUE(stop_search_future.WaitAndClear());
ASSERT_TRUE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_future.Wait());
ASSERT_FALSE(stop_search_future.IsReady());
PickerSearchFieldView& search_field_view =
picker_view->search_field_view_for_testing();
ViewDrawnWaiter().Wait(&search_field_view.back_button_for_testing());
LeftClickOn(&search_field_view.back_button_for_testing());
EXPECT_TRUE(stop_search_future.Wait());
}
TEST_F(PickerViewTest, StopsSearchWhenCategorySelectedOnZeroStateDuringSearch) {
base::test::TestFuture<void> search_future;
base::test::TestFuture<void> stop_search_future;
FakePickerViewDelegate delegate(
{.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_future.SetValue();
}),
.stop_search_function = stop_search_future.GetRepeatingCallback()});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_future.Wait());
ASSERT_FALSE(stop_search_future.IsReady());
ASSERT_FALSE(picker_view->category_results_view_for_testing().GetVisible());
ASSERT_TRUE(picker_view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_TRUE(stop_search_future.Wait());
}
TEST_F(PickerViewTest, StopsSearchWhenCategorySelectedInSearchResults) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback>
search_future;
base::test::TestFuture<void> stop_search_future;
FakePickerViewDelegate delegate(
{.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_future.SetValue(std::move(callback));
}),
.stop_search_function = stop_search_future.GetRepeatingCallback()});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback callback = search_future.Take();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerCategoryResult(PickerCategory::kLinks)}},
/*has_more_results=*/false),
});
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* category_result = view->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem();
ASSERT_TRUE(category_result);
ViewDrawnWaiter().Wait(category_result);
ASSERT_FALSE(stop_search_future.IsReady());
LeftClickOn(category_result);
ASSERT_TRUE(view->category_results_view_for_testing().GetVisible());
ASSERT_FALSE(view->zero_state_view_for_testing().GetVisible());
ASSERT_FALSE(view->search_results_view_for_testing().GetVisible());
EXPECT_TRUE(stop_search_future.Wait());
}
TEST_P(PickerViewEmojiTest, SearchingShowsExpressionResultsInEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
.emoji_results = {PickerEmojiResult::Emoji(u"😊"),
PickerEmojiResult::Symbol(u"♬")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_NE(picker_view->emoji_bar_view_for_testing(), nullptr);
EXPECT_TRUE(picker_view->emoji_bar_view_for_testing()->GetVisible());
EXPECT_THAT(picker_view->emoji_bar_view_for_testing()->GetItemsForTesting(),
ElementsAre(Truly(&views::IsViewClass<PickerEmojiItemView>),
Truly(&views::IsViewClass<PickerEmojiItemView>)));
}
TEST_P(PickerViewEmojiTest, InitiallyShowsSuggestedEmojis) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
.suggested_emojis = {"😊", "👍"},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
ASSERT_NE(picker_view->emoji_bar_view_for_testing(), nullptr);
EXPECT_TRUE(picker_view->emoji_bar_view_for_testing()->GetVisible());
EXPECT_THAT(
picker_view->emoji_bar_view_for_testing()->GetItemsForTesting(),
ElementsAre(AsView<PickerEmojiItemView>(
Property(&PickerEmojiItemView::GetTextForTesting, u"😊")),
AsView<PickerEmojiItemView>(Property(
&PickerEmojiItemView::GetTextForTesting, u"👍"))));
}
TEST_F(PickerViewTest, NoEmojiBarIfExpressionsCategoryNotAvailable) {
FakePickerViewDelegate delegate(
{.available_categories = {PickerCategory::kLinks}});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
EXPECT_EQ(GetPickerViewFromWidget(*widget)->emoji_bar_view_for_testing(),
nullptr);
}
TEST_F(PickerViewTest, ClearsResultsWhenGoingBackToZeroState) {
base::test::TestFuture<void> search_called;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
search_called.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
// Go to the results page.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(search_called.Wait());
// Go back to the zero state page.
PressAndReleaseKey(ui::KeyboardCode::VKEY_BACK, ui::EF_NONE);
EXPECT_FALSE(picker_view->search_results_view_for_testing().GetVisible());
EXPECT_THAT(picker_view->search_results_view_for_testing()
.section_list_view_for_testing()
->children(),
IsEmpty());
}
TEST_F(PickerViewTest, PressingEscClosesPickerWidget) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
EXPECT_TRUE(widget->IsClosed());
}
TEST_F(PickerViewTest, RecordsSearchLatencyAfterSearchFinished) {
base::HistogramTester histogram;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&, this](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
// The search automatically publishes results after burn-in + 50ms,
// so publish "burn in results" before that.
task_environment()->FastForwardBy(PickerController::kBurnInPeriod);
// This needs to be non-empty, or else `MarkSearchResultsUpdated`
// will be called with `kNoResultsFound` - which does not emit the
// search latency metric.
// TODO: b/349913604 - Replace the metric with a new one which
// records search latency even if "no results found" was shown.
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"result")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
histogram.ExpectUniqueTimeSample("Ash.Picker.Session.SearchLatency",
PickerController::kBurnInPeriod, 1);
}
TEST_F(PickerViewTest, RecordsSearchLatencyWhenResultsAreAutomaticallyCleared) {
base::HistogramTester histogram;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&, this](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
task_environment()->FastForwardBy(PickerView::kClearResultsTimeout);
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
histogram.ExpectUniqueTimeSample("Ash.Picker.Session.SearchLatency",
PickerView::kClearResultsTimeout, 1);
}
TEST_F(PickerViewTest, BoundsDefaultAlignedWithAnchor) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
// Should be entirely on screen.
EXPECT_TRUE(display::Screen::GetScreen()
->GetDisplayMatching(kDefaultAnchorBounds)
.work_area()
.Contains(view->GetBoundsInScreen()));
// Should be to the right of the anchor.
EXPECT_EQ(view->GetBoundsInScreen().x(), kDefaultAnchorBounds.right());
// Center of the search field should be vertically aligned with the anchor.
EXPECT_EQ(view->search_field_view_for_testing()
.GetBoundsInScreen()
.CenterPoint()
.y(),
kDefaultAnchorBounds.CenterPoint().y());
}
TEST_F(PickerViewTest, BoundsAlignedWithAnchorNearTopLeftOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.origin(), {0, 10});
anchor_bounds.Offset(80, 120);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
// Should be entirely on screen.
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
// Should be to the right of the anchor.
EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.right());
// Center of the search field should be vertically aligned with the anchor.
EXPECT_EQ(view->search_field_view_for_testing()
.GetBoundsInScreen()
.CenterPoint()
.y(),
anchor_bounds.CenterPoint().y());
}
TEST_F(PickerViewTest, BoundsAlignedWithAnchorNearBottomLeftOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.bottom_left(), {0, 10});
anchor_bounds.Offset(80, -80);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
// Should be entirely on screen.
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
// Should be to the right of the anchor.
EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.right());
// Center of the search field should be vertically aligned with the anchor.
EXPECT_EQ(view->search_field_view_for_testing()
.GetBoundsInScreen()
.CenterPoint()
.y(),
anchor_bounds.CenterPoint().y());
}
TEST_F(PickerViewTest, BoundsBelowAnchorForAnchorNearTopRightOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.top_right(), {0, 10});
anchor_bounds.Offset(-20, 20);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
const PickerView* view = GetPickerViewFromWidget(*widget);
// Should be entirely on screen.
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
// Should be below the anchor.
EXPECT_EQ(view->GetBoundsInScreen().y(), anchor_bounds.bottom());
}
TEST_F(PickerViewTest, BoundsAboveAnchorForAnchorNearBottomRightOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.bottom_right(), {0, 10});
anchor_bounds.Offset(-20, -20);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
const PickerView* view = GetPickerViewFromWidget(*widget);
// Should be entirely on screen.
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
// Should be above the anchor.
EXPECT_EQ(view->GetBoundsInScreen().bottom(), anchor_bounds.y());
}
TEST_F(PickerViewTest, BoundsLeftAlignedBelowSelectionNearTopOfScreen) {
FakePickerViewDelegate delegate({
.mode = PickerModeType::kHasSelection,
});
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
const gfx::Rect anchor_bounds(20, 20, 100, 20);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
const PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.x());
EXPECT_GE(view->GetBoundsInScreen().y(), anchor_bounds.bottom());
}
TEST_F(PickerViewTest, BoundsLeftAlignedAboveSelectionNearBottomOfScreen) {
FakePickerViewDelegate delegate({
.mode = PickerModeType::kHasSelection,
});
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
const gfx::Rect anchor_bounds(20, screen_work_area.bottom() - 30, 100, 20);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
const PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_TRUE(screen_work_area.Contains(view->GetBoundsInScreen()));
EXPECT_EQ(view->GetBoundsInScreen().x(), anchor_bounds.x());
EXPECT_LE(view->GetBoundsInScreen().bottom(), anchor_bounds.y());
}
TEST_F(PickerViewTest, BoundsOnScreenForEmptyAnchorBounds) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, gfx::Rect());
widget->Show();
const PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_TRUE(
display::Screen::GetScreen()->GetPrimaryDisplay().work_area().Contains(
view->GetBoundsInScreen()));
}
TEST_F(PickerViewTest, MainContentBelowSearchFieldNearTopOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.top_center(), {0, 10});
anchor_bounds.Offset(0, 80);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_GE(view->zero_state_view_for_testing().GetBoundsInScreen().y(),
view->search_field_view_for_testing().GetBoundsInScreen().bottom());
}
TEST_F(PickerViewTest, MainContentAboveSearchFieldNearBottomOfScreen) {
FakePickerViewDelegate delegate;
const gfx::Rect screen_work_area =
display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
gfx::Rect anchor_bounds(screen_work_area.bottom_center(), {0, 10});
anchor_bounds.Offset(0, -80);
auto widget = PickerWidget::Create(&delegate, anchor_bounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
EXPECT_LE(view->zero_state_view_for_testing().GetBoundsInScreen().bottom(),
view->search_field_view_for_testing().GetBoundsInScreen().y());
}
TEST_P(PickerViewEmojiTest, ShowsEmojiPickerWhenClickingOnExpressions) {
FakePickerViewDelegate delegate({
.available_categories = {GetParam()},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
LeftClickOn(GetFirstCategoryItemView(GetPickerViewFromWidget(*widget)));
EXPECT_TRUE(widget->IsClosed());
EXPECT_THAT(delegate.emoji_picker_query(), Optional(Eq(u"")));
}
TEST_F(PickerViewTest, ShowsEditorWhenClickingOnEditor) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEditorWrite},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
LeftClickOn(GetFirstCategoryItemView(GetPickerViewFromWidget(*widget)));
EXPECT_TRUE(widget->IsClosed());
EXPECT_TRUE(delegate.showed_editor());
}
TEST_F(PickerViewTest, PressingEnterDoesNothingOnEmptySearchResultsPage) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_TRUE(view->search_results_view_for_testing().GetVisible());
EXPECT_EQ(delegate.last_inserted_result(), std::nullopt);
}
TEST_F(PickerViewTest, PressingEnterDefaultSelectsFirstSearchResult) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result A")));
}
TEST_F(PickerViewTest, ArrowKeysNavigateEmojiBar) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEmojisGifs},
.emoji_results = {PickerEmojiResult::Emoji(u"😊"),
PickerEmojiResult::Symbol(u"♬")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->emoji_bar_view_for_testing()
->GetTopItem());
PressAndReleaseKey(ui::KeyboardCode::VKEY_UP, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerEmojiResult::Symbol(u"♬")));
}
TEST_F(PickerViewTest, CanTypeQueryWhileEmojiBarIsPseudoFocused) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEmojisGifs},
.emoji_results = {PickerEmojiResult::Emoji(u"😊"),
PickerEmojiResult::Symbol(u"♬")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->emoji_bar_view_for_testing()
->GetTopItem());
PressAndReleaseKey(ui::KeyboardCode::VKEY_UP, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_B, ui::EF_NONE);
EXPECT_EQ(GetPickerViewFromWidget(*widget)
->search_field_view_for_testing()
.textfield_for_testing()
.GetText(),
u"ab");
}
TEST_F(PickerViewTest, DownArrowKeyNavigatesSearchResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerBrowsingHistoryResult(GURL("http://foo.com"),
u"Foo", ui::ImageModel()),
PickerBrowsingHistoryResult(GURL("http://bar.com"),
u"Bar", ui::ImageModel())}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerBrowsingHistoryResult(GURL("http://bar.com"),
u"Bar", ui::ImageModel())));
}
TEST_F(PickerViewTest, RightArrowKeyShowsSubmenu) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
EXPECT_NE(GetPickerViewFromWidget(*widget)
->submenu_controller_for_testing()
.GetSubmenuView(),
nullptr);
}
TEST_F(PickerViewTest, EnterKeyShowsSubmenu) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_NE(GetPickerViewFromWidget(*widget)
->submenu_controller_for_testing()
.GetSubmenuView(),
nullptr);
}
TEST_F(PickerViewTest, LeftArrowKeyClosesSubmenu) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_LEFT, ui::EF_NONE);
PickerSubmenuController& submenu_controller =
GetPickerViewFromWidget(*widget)->submenu_controller_for_testing();
views::test::WidgetDestroyedWaiter(submenu_controller.widget_for_testing())
.Wait();
EXPECT_EQ(submenu_controller.GetSubmenuView(), nullptr);
}
TEST_F(PickerViewTest, PressingEscClosesSubmenuThenWidget) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results = {PickerNewWindowResult(
PickerNewWindowResult::Type::kDoc)},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
PickerSubmenuController& submenu_controller =
GetPickerViewFromWidget(*widget)->submenu_controller_for_testing();
views::test::WidgetDestroyedWaiter(submenu_controller.widget_for_testing())
.Wait();
EXPECT_EQ(submenu_controller.GetSubmenuView(), nullptr);
EXPECT_FALSE(widget->IsClosed());
PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
views::test::WidgetDestroyedWaiter(widget.get()).Wait();
}
TEST_F(PickerViewTest, PressingEscClosesPreviewThenWidget) {
// TODO: b/353592243 - This test breaks when the item with preview is the
// first item. Remove the extra Text result once it's fixed.
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{
PickerTextResult(u"a"),
PickerLocalFileResult(u"a", /*file_path=*/{}),
},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PickerPreviewBubbleController& preview_controller =
GetPickerViewFromWidget(*widget)->preview_controller_for_testing();
PickerPreviewBubbleVisibleWaiter().Wait(&preview_controller);
EXPECT_TRUE(preview_controller.IsBubbleVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
EXPECT_FALSE(preview_controller.IsBubbleVisible());
EXPECT_FALSE(widget->IsClosed());
PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE, ui::EF_NONE);
views::test::WidgetDestroyedWaiter(widget.get()).Wait();
}
TEST_F(PickerViewTest, TabKeyNavigatesItemWithPreview) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{
PickerTextResult(u"Result A"),
PickerLocalFileResult(u"Result B", /*file_path=*/{}),
PickerTextResult(u"Result C"),
},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Should navigate to the file result and show the preview bubble.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE);
PickerPreviewBubbleController& preview_controller =
GetPickerViewFromWidget(*widget)->preview_controller_for_testing();
PickerPreviewBubbleVisibleWaiter().Wait(&preview_controller);
EXPECT_TRUE(preview_controller.IsBubbleVisible());
// Should close the preview bubble and navigate to the next result.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE);
EXPECT_FALSE(preview_controller.IsBubbleVisible());
EXPECT_FALSE(widget->IsClosed());
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result C")));
}
TEST_F(PickerViewTest, KeyEventsNavigateWithinSubmenu) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
.action_type = PickerActionType::kOpen,
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Open submenu, navigate down to next submenu item, then select the item.
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->submenu_controller_for_testing()
.GetSubmenuView());
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(
delegate.last_opened_result(),
Optional(PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)));
}
TEST_F(PickerViewTest, LeftArrowKeyNavigatesToBackButton) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Select a category so that the back button is visible.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
category_item_view->ScrollViewToVisible();
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
// Navigate to the back button from the textfield, then select it.
PressAndReleaseKey(ui::KeyboardCode::VKEY_LEFT, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_TRUE(picker_view->zero_state_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, RightArrowKeyNavigatesToClearButton) {
FakePickerViewDelegate delegate;
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Type a query so that the clear button is visible.
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
const views::Textfield& textfield = GetPickerViewFromWidget(*widget)
->search_field_view_for_testing()
.textfield_for_testing();
EXPECT_EQ(textfield.GetText(), u"a");
// Navigate to the clear button from the textfield, then select it.
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_TRUE(textfield.GetText().empty());
}
TEST_F(PickerViewTest, TabKeyNavigatesSearchResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result B")));
}
TEST_F(PickerViewTest, ShiftTabKeyNavigatesSearchResultsWithEmojiBar) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEmojisGifs},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
.emoji_results = {PickerEmojiResult::Emoji(u"😊")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
// Navigate backward, to clear button.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate backward, to textfield.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate backward, to emoji bar.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate backward, to the last search result.
PressAndReleaseKey(ui::KeyboardCode::VKEY_UP);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result B")));
}
TEST_F(PickerViewTest, ShiftTabKeyNavigatesSearchResultsWithoutEmojiBar) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
// Navigate backward, to clear button.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate backward, to textfield.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate backward, to the last search result.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result B")));
}
TEST_F(PickerViewTest, ShiftTabNavigatesToClearButton) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
// Navigate backward, to clear button.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_EQ(GetPickerViewFromWidget(*widget)
->search_field_view_for_testing()
.textfield_for_testing()
.GetText(),
u"");
}
TEST_F(PickerViewTest, DownArrowKeyNavigatesFromClearButtonToSearchResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A"),
PickerTextResult(u"Result B")}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
// Navigate backward, to clear button.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_SHIFT_DOWN);
// Navigate downward, to the first search result.
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(delegate.last_inserted_result(),
Optional(PickerTextResult(u"Result A")));
}
TEST_F(PickerViewTest, ShowsSubmenuOnMouseHover) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
GetEventGenerator()->MoveMouseTo(picker_view->zero_state_view_for_testing()
.primary_section_view_for_testing()
->item_views_for_testing()[0]
->GetBoundsInScreen()
.CenterPoint());
PickerSubmenuController& submenu_controller =
picker_view->submenu_controller_for_testing();
views::test::WidgetVisibleWaiter(submenu_controller.widget_for_testing())
.Wait();
EXPECT_NE(submenu_controller.GetSubmenuView(), nullptr);
}
// This is an edge case where the user can open a submenu with mouse hover while
// they are using keyboard to navigate the main PickerView. Since the keyboard
// selection can be separate to the mouse hover selection, we just close the
// submenu if the user resumes keyboard navigation in the main PickerView.
TEST_F(PickerViewTest, ClosesSubmenuWhenResumingKeyboardNavigationInMainView) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kEmojisGifs},
.zero_state_suggested_results =
{PickerNewWindowResult(PickerNewWindowResult::Type::kDoc),
PickerNewWindowResult(PickerNewWindowResult::Type::kSheet)},
.emoji_results = {PickerEmojiResult::Emoji(u"😊"),
PickerEmojiResult::Symbol(u"♬")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
// Start keyboard navigation.
PressAndReleaseKey(ui::KeyboardCode::VKEY_UP, ui::EF_NONE);
// Mouse hover over an item with a submenu to show a submenu.
PickerView* picker_view = GetPickerViewFromWidget(*widget);
GetEventGenerator()->MoveMouseTo(picker_view->zero_state_view_for_testing()
.primary_section_view_for_testing()
->item_views_for_testing()[0]
->GetBoundsInScreen()
.CenterPoint());
PickerSubmenuController& submenu_controller =
picker_view->submenu_controller_for_testing();
views::test::WidgetVisibleWaiter(submenu_controller.widget_for_testing())
.Wait();
// Resume keyboard navigation.
PressAndReleaseKey(ui::KeyboardCode::VKEY_RIGHT, ui::EF_NONE);
views::test::WidgetDestroyedWaiter(submenu_controller.widget_for_testing())
.Wait();
EXPECT_EQ(submenu_controller.GetSubmenuView(), nullptr);
}
TEST_F(PickerViewTest, ClearsSearchWhenClickingOnCategoryResult) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerCategoryResult(PickerCategory::kLinks)}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* category_result = view->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem();
ASSERT_TRUE(category_result);
ViewDrawnWaiter().Wait(category_result);
LeftClickOn(category_result);
EXPECT_EQ(
view->search_field_view_for_testing().textfield_for_testing().GetText(),
u"");
}
TEST_F(PickerViewTest, PerformsCategorySearchWhenClickingOnSeeMoreResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/true),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
future.Clear();
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* trailing_link = view->search_results_view_for_testing()
.section_views_for_testing()[0]
->title_trailing_link_for_testing();
ViewDrawnWaiter().Wait(trailing_link);
LeftClickOn(trailing_link);
// Should call search a second time.
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(view->search_results_view_for_testing().GetVisible());
}
TEST_F(PickerViewTest, KeyNavigationToSeeMoreResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kClipboard,
{{PickerTextResult(u"Result A")}},
/*has_more_results=*/false),
PickerSearchResultsSection(
PickerSectionType::kLinks,
{PickerBrowsingHistoryResult({}, u"Result B", {})},
/*has_more_results=*/true),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
future.Clear();
ViewDrawnWaiter().Wait(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem());
// Navigate to see more button, then press enter.
PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB, ui::EF_NONE);
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
// Should call search a second time.
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(GetPickerViewFromWidget(*widget)
->search_results_view_for_testing()
.GetVisible());
}
TEST_P(PickerViewEmojiTest,
ClickingMoreEmojisButtonOpensEmojiPickerWithQuerySearch) {
FakePickerViewDelegate delegate({.available_categories = {GetParam()}});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
PickerEmojiBarView* emoji_bar =
GetPickerViewFromWidget(*widget)->emoji_bar_view_for_testing();
ASSERT_NE(emoji_bar, nullptr);
views::View* more_emojis_button = emoji_bar->more_emojis_button_for_testing();
ViewDrawnWaiter().Wait(more_emojis_button);
LeftClickOn(more_emojis_button);
EXPECT_TRUE(widget->IsClosed());
EXPECT_THAT(delegate.emoji_picker_category(),
Optional(Eq(ui::EmojiPickerCategory::kEmojis)));
EXPECT_THAT(delegate.emoji_picker_query(), Optional(Eq(u"a")));
}
TEST_F(PickerViewTest, ClickingGifsButtonOpensGifPickerWithQuerySearch) {
FakePickerViewDelegate delegate(
{.available_categories = {PickerCategory::kEmojisGifs}});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
PickerEmojiBarView* emoji_bar =
GetPickerViewFromWidget(*widget)->emoji_bar_view_for_testing();
ASSERT_NE(emoji_bar, nullptr);
views::View* gifs_button = emoji_bar->gifs_button_for_testing();
ViewDrawnWaiter().Wait(gifs_button);
LeftClickOn(gifs_button);
EXPECT_TRUE(widget->IsClosed());
EXPECT_THAT(delegate.emoji_picker_category(),
Optional(Eq(ui::EmojiPickerCategory::kGifs)));
EXPECT_THAT(delegate.emoji_picker_query(), Optional(Eq(u"a")));
}
TEST_F(PickerViewTest,
KeepsSearchFieldQueryTextAndFocusWhenClickingOnSeeMoreResults) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(PickerSectionType::kLinks, {},
/*has_more_results=*/true),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
future.Clear();
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* trailing_link = view->search_results_view_for_testing()
.section_views_for_testing()[0]
->title_trailing_link_for_testing();
ViewDrawnWaiter().Wait(trailing_link);
LeftClickOn(trailing_link);
EXPECT_EQ(
view->search_field_view_for_testing().textfield_for_testing().GetText(),
u"a");
EXPECT_TRUE(
view->search_field_view_for_testing().textfield_for_testing().HasFocus());
}
TEST_F(PickerViewTest, CategoryOnlySearchShowsNoResultsPageWithNoIllustration) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
EXPECT_TRUE(picker_view->search_results_view_for_testing()
.no_results_view_for_testing()
->GetVisible());
EXPECT_FALSE(picker_view->search_results_view_for_testing()
.no_results_illustration_for_testing()
.GetVisible());
EXPECT_EQ(picker_view->search_results_view_for_testing()
.no_results_label_for_testing()
.GetText(),
l10n_util::GetStringUTF16(IDS_PICKER_NO_RESULTS_TEXT));
}
TEST_F(PickerViewTest, CategoryZeroStateShowsNoResultsPageWithIllustration) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
views::View* category_item_view = GetFirstCategoryItemView(picker_view);
ViewDrawnWaiter().Wait(category_item_view);
LeftClickOn(category_item_view);
EXPECT_TRUE(picker_view->category_results_view_for_testing()
.no_results_view_for_testing()
->GetVisible());
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
EXPECT_TRUE(picker_view->category_results_view_for_testing()
.no_results_illustration_for_testing()
.GetVisible());
#endif
EXPECT_EQ(picker_view->category_results_view_for_testing()
.no_results_label_for_testing()
.GetText(),
l10n_util::GetStringUTF16(
IDS_PICKER_NO_RESULTS_FOR_BROWSING_HISTORY_LABEL_TEXT));
}
TEST_F(
PickerViewTest,
ChangingPseudoFocusOnZeroStateNotifiesInitialActiveDescendantChangeAfterDelay) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kClipboard,
PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
views::test::AXEventCounter counter(views::AXEventManager::Get());
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kActiveDescendantChanged), 0);
task_environment()->FastForwardBy(
PickerSearchFieldView::kNotifyInitialActiveDescendantA11yDelay);
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kActiveDescendantChanged), 1);
}
TEST_F(
PickerViewTest,
ChangingPseudoFocusOnZeroStateNotifiesActiveDescendantChangeImmediately) {
FakePickerViewDelegate delegate({
.available_categories = {PickerCategory::kClipboard,
PickerCategory::kLinks},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
views::test::AXEventCounter counter(views::AXEventManager::Get());
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
task_environment()->FastForwardBy(
PickerSearchFieldView::kNotifyInitialActiveDescendantA11yDelay);
PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN, ui::EF_NONE);
EXPECT_EQ(counter.GetCount(ax::mojom::Event::kActiveDescendantChanged), 2);
}
TEST_F(PickerViewTest, EnterOnZeroState) {
FakePickerViewDelegate delegate({
.zero_state_suggested_results = {PickerTextResult(u"zero state")},
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
base::span<const raw_ptr<PickerItemView>> zero_state_item_views =
picker_view->zero_state_view_for_testing()
.primary_section_view_for_testing()
->item_views_for_testing();
PickerListItemView* suggested_item_view;
ASSERT_THAT(
zero_state_item_views,
ElementsAre(ResultOf(
[&](const raw_ptr<PickerItemView> view) {
ViewDrawnWaiter().Wait(view);
suggested_item_view = views::AsViewClass<PickerListItemView>(view);
return suggested_item_view;
},
Pointee(
AllOf(Property("primary text",
&PickerListItemView::GetPrimaryTextForTesting,
u"zero state"),
Property("is visible", &views::View::GetVisible, true))))));
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(
delegate.last_inserted_result(),
Optional(VariantWith<PickerTextResult>(Field(
"primary text", &PickerTextResult::primary_text, u"zero state"))));
}
// TODO: b/351920494 - Insert the first new result instead of doing nothing.
TEST_F(PickerViewTest, EnterDuringBurnInOnZeroState) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.zero_state_suggested_results = {PickerTextResult(u"zero state")},
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
base::span<const raw_ptr<PickerItemView>> zero_state_item_views =
picker_view->zero_state_view_for_testing()
.primary_section_view_for_testing()
->item_views_for_testing();
PickerListItemView* suggested_item_view;
ASSERT_THAT(
zero_state_item_views,
ElementsAre(ResultOf(
[&](const raw_ptr<PickerItemView> view) {
ViewDrawnWaiter().Wait(view);
suggested_item_view = views::AsViewClass<PickerListItemView>(view);
return suggested_item_view;
},
Pointee(
AllOf(Property("primary text",
&PickerListItemView::GetPrimaryTextForTesting,
u"zero state"),
Property("is visible", &views::View::GetVisible, true))))));
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
// The suggested item should still be visible.
ASSERT_TRUE(suggested_item_view->GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_EQ(delegate.last_inserted_result(), std::nullopt);
}
TEST_F(PickerViewTest, EnterOnSearchResults) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(callback);
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback first_callback = future.Take();
first_callback.Run({PickerSearchResultsSection(
PickerSectionType::kClipboard, {PickerTextResult(u"first search")},
/*has_more_results=*/false)});
base::span<const raw_ptr<PickerSectionView>> section_views =
picker_view->search_results_view_for_testing()
.section_views_for_testing();
PickerListItemView* search_item_view;
ASSERT_THAT(
section_views,
ElementsAre(Pointee(Property(
"item views", &PickerSectionView::item_views_for_testing,
ElementsAre(ResultOf(
[&](const raw_ptr<PickerItemView> view) {
ViewDrawnWaiter().Wait(view);
search_item_view = views::AsViewClass<PickerListItemView>(view);
return search_item_view;
},
Pointee(
AllOf(Property("primary text",
&PickerListItemView::GetPrimaryTextForTesting,
u"first search"),
Property("is visible", &views::View::GetVisible,
true)))))))));
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_THAT(
delegate.last_inserted_result(),
Optional(VariantWith<PickerTextResult>(Field(
"primary text", &PickerTextResult::primary_text, u"first search"))));
}
// TODO: b/351920494 - Insert the first new result instead of doing nothing.
TEST_F(PickerViewTest, EnterDuringBurnInOnSearchResults) {
base::test::TestFuture<FakePickerViewDelegate::SearchResultsCallback> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue(callback);
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PickerView* picker_view = GetPickerViewFromWidget(*widget);
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback first_callback = future.Take();
first_callback.Run({PickerSearchResultsSection(
PickerSectionType::kClipboard, {PickerTextResult(u"first search")},
/*has_more_results=*/false)});
base::span<const raw_ptr<PickerSectionView>> section_views =
picker_view->search_results_view_for_testing()
.section_views_for_testing();
PickerListItemView* search_item_view;
ASSERT_THAT(
section_views,
ElementsAre(Pointee(Property(
"item views", &PickerSectionView::item_views_for_testing,
ElementsAre(ResultOf(
[&](const raw_ptr<PickerItemView> view) {
ViewDrawnWaiter().Wait(view);
search_item_view = views::AsViewClass<PickerListItemView>(view);
return search_item_view;
},
Pointee(
AllOf(Property("primary text",
&PickerListItemView::GetPrimaryTextForTesting,
u"first search"),
Property("is visible", &views::View::GetVisible,
true)))))))));
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
FakePickerViewDelegate::SearchResultsCallback second_callback = future.Take();
// The search item should still be visible.
ASSERT_TRUE(search_item_view->GetVisible());
PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN, ui::EF_NONE);
EXPECT_EQ(delegate.last_inserted_result(), std::nullopt);
}
TEST_F(PickerViewTest, ResetsToZeroStateWhenClickingOnBackButton) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerCategoryResult(PickerCategory::kLinks)}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* category_result = view->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem();
ASSERT_TRUE(category_result);
ViewDrawnWaiter().Wait(category_result);
LeftClickOn(category_result);
PickerSearchFieldView& search_field_view =
view->search_field_view_for_testing();
ViewDrawnWaiter().Wait(&search_field_view.back_button_for_testing());
LeftClickOn(&search_field_view.back_button_for_testing());
EXPECT_TRUE(view->zero_state_view_for_testing().GetVisible());
EXPECT_EQ(search_field_view.textfield_for_testing().GetText(), u"");
EXPECT_FALSE(search_field_view.clear_button_for_testing().GetVisible());
}
TEST_F(PickerViewTest, ResetsToZeroStateAfterPressingBrowserBack) {
base::test::TestFuture<void> future;
FakePickerViewDelegate delegate({
.search_function = base::BindLambdaForTesting(
[&](std::u16string_view query,
FakePickerViewDelegate::SearchResultsCallback callback) {
future.SetValue();
callback.Run({
PickerSearchResultsSection(
PickerSectionType::kNone,
{{PickerCategoryResult(PickerCategory::kLinks)}},
/*has_more_results=*/false),
});
}),
});
auto widget = PickerWidget::Create(&delegate, kDefaultAnchorBounds);
widget->Show();
PressAndReleaseKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
ASSERT_TRUE(future.Wait());
PickerView* view = GetPickerViewFromWidget(*widget);
views::View* category_result = view->search_results_view_for_testing()
.section_list_view_for_testing()
->GetTopItem();
ASSERT_TRUE(category_result);
ViewDrawnWaiter().Wait(category_result);
LeftClickOn(category_result);
PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_BACK, ui::EF_NONE);
EXPECT_TRUE(view->zero_state_view_for_testing().GetVisible());
EXPECT_EQ(
view->search_field_view_for_testing().textfield_for_testing().GetText(),
u"");
EXPECT_FALSE(view->search_field_view_for_testing()
.clear_button_for_testing()
.GetVisible());
}
} // namespace
} // namespace ash