blob: 78efd36e7df8153521281aa3b03d0f36c1132309 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/autofill/popup/popup_view_views.h"
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/containers/contains.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
#include "chrome/browser/ui/autofill/autofill_popup_view.h"
#include "chrome/browser/ui/autofill/mock_autofill_popup_controller.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_content_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_row_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_search_bar_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_separator_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_title_view.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
#include "chrome/browser/ui/views/autofill/popup/popup_view_views_test_api.h"
#include "chrome/browser/ui/views/autofill/popup/popup_warning_view.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/browser/ui/suggestion_hiding_reason.h"
#include "components/autofill/core/browser/ui/suggestion_type.h"
#include "components/autofill/core/common/aliases.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/input/native_web_keyboard_event.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/frame/frame_policy.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_keyboard_event.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/compositor/canvas_painter.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
namespace autofill {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Return;
using CellIndex = PopupViewViews::CellIndex;
using CellType = PopupRowView::CellType;
const std::vector<SuggestionType> kClickableSuggestionTypes{
SuggestionType::kAutocompleteEntry,
SuggestionType::kPasswordEntry,
SuggestionType::kClearForm,
SuggestionType::kAutofillOptions,
SuggestionType::kDatalistEntry,
SuggestionType::kScanCreditCard,
SuggestionType::kAllSavedPasswordsEntry,
SuggestionType::kPasswordAccountStorageOptIn,
SuggestionType::kPasswordAccountStorageReSignin,
SuggestionType::kPasswordAccountStorageOptInAndGenerate,
SuggestionType::kPasswordAccountStorageEmpty,
SuggestionType::kVirtualCreditCardEntry,
};
const std::vector<SuggestionType> kUnclickableSuggestionTypes{
SuggestionType::kInsecureContextPaymentDisabledMessage,
SuggestionType::kTitle,
SuggestionType::kSeparator,
};
bool IsClickable(SuggestionType id) {
DCHECK(base::Contains(kClickableSuggestionTypes, id) ^
base::Contains(kUnclickableSuggestionTypes, id));
return base::Contains(kClickableSuggestionTypes, id);
}
Suggestion CreateSuggestionWithChildren(
const SuggestionType suggestion_type,
std::vector<Suggestion> children,
const std::u16string& name = u"Suggestion") {
Suggestion parent(name);
parent.type = suggestion_type;
parent.children = std::move(children);
return parent;
}
Suggestion CreateSuggestionWithChildren(
std::vector<Suggestion> children,
const std::u16string& name = u"Suggestion") {
return CreateSuggestionWithChildren(SuggestionType::kAddressEntry,
std::move(children), name);
}
} // namespace
class PopupViewViewsTest : public ChromeViewsTestBase {
public:
PopupViewViewsTest() = default;
PopupViewViewsTest(PopupViewViewsTest&) = delete;
PopupViewViewsTest& operator=(PopupViewViewsTest&) = delete;
~PopupViewViewsTest() override = default;
void SetUp() override {
ChromeViewsTestBase::SetUp();
profile_ = std::make_unique<TestingProfile>();
web_contents_ = content::WebContentsTester::CreateTestWebContents(
profile_.get(), nullptr);
web_contents_->Resize({0, 0, 1024, 768});
// Make sure the element is inside the web contents area.
autofill_popup_controller_.set_element_bounds(
autofill_popup_controller_.element_bounds() +
web_contents_->GetContainerBounds().OffsetFromOrigin());
ON_CALL(autofill_popup_controller_, GetWebContents())
.WillByDefault(Return(web_contents_.get()));
ON_CALL(autofill_popup_controller_, OpenSubPopup)
.WillByDefault(Return(autofill_popup_sub_controller_.GetWeakPtr()));
ON_CALL(autofill_popup_controller_, GetMainFillingProduct)
.WillByDefault([&controller = autofill_popup_controller_]() {
return GetFillingProductFromSuggestionType(
controller.GetSuggestionAt(0).type);
});
}
void TearDown() override {
// Set to nullptr to avoid dangling pointers.
view_ = nullptr;
generator_.reset();
widget_.reset();
ChromeViewsTestBase::TearDown();
}
void ShowView(PopupViewViews* view, views::Widget& widget) {
widget.SetContentsView(view);
view->Show(AutoselectFirstSuggestion(false));
}
void CreateAndShowView(
std::optional<views::Widget::InitParams> widget_params = std::nullopt,
PopupViewSearchBarConfig search_bar_config = {}) {
view_ = nullptr;
generator_.reset();
widget_ = CreateTestWidget(
widget_params ? std::move(*widget_params)
: CreateParamsForTestWidget(
views::Widget::InitParams::Type::TYPE_POPUP));
generator_ = std::make_unique<ui::test::EventGenerator>(
GetRootWindow(widget_.get()));
view_ = new PopupViewViews(controller().GetWeakPtr(),
std::move(search_bar_config));
ShowView(view_, *widget_);
}
void CreateAndShowView(
const std::vector<SuggestionType>& ids,
std::optional<views::Widget::InitParams> widget_params = std::nullopt,
PopupViewSearchBarConfig search_bar_config = {}) {
controller().set_suggestions(ids);
CreateAndShowView(std::move(widget_params), std::move(search_bar_config));
}
void UpdateSuggestions(const std::vector<SuggestionType>& ids) {
controller().set_suggestions(ids);
static_cast<AutofillPopupView&>(view()).OnSuggestionsChanged();
}
void Paint() {
#if !BUILDFLAG(IS_MAC)
Paint(widget().GetRootView());
#else
// TODO(crbug.com/40190148): On Mac OS we need to trigger Paint() on the
// roots of the individual rows. The reason is that the
// views::ViewScrollView() created in PopupViewViews::CreateChildViews()
// owns a Layer. As a consequence, views::View::Paint() does not propagate
// to the rows because the recursion stops in
// views::View::RecursivePaintHelper().
for (size_t index = 0; index < GetNumberOfRows(); ++index) {
views::View* root = &GetRowViewAt(index);
while (!root->layer() && root->parent()) {
root = root->parent();
}
Paint(root);
}
#endif
}
void Paint(views::View* view) {
SkBitmap bitmap;
gfx::Size size = view->size();
ui::CanvasPainter canvas_painter(&bitmap, size, 1.f, SK_ColorTRANSPARENT,
false);
view->Paint(
views::PaintInfo::CreateRootPaintInfo(canvas_painter.context(), size));
}
gfx::Point GetCenterOfSuggestion(size_t row_index) {
return GetRowViewAt(row_index).GetBoundsInScreen().CenterPoint();
}
// Simulates the keyboard event and returns whether the event was handled.
bool SimulateKeyPress(int windows_key_code,
PopupViewViews& view,
bool shift_modifier_pressed = false,
bool non_shift_modifier_pressed = false) {
int modifiers = blink::WebInputEvent::kNoModifiers;
if (shift_modifier_pressed) {
modifiers |= blink::WebInputEvent::Modifiers::kShiftKey;
}
if (non_shift_modifier_pressed) {
modifiers |= blink::WebInputEvent::Modifiers::kAltKey;
}
content::NativeWebKeyboardEvent event(
blink::WebKeyboardEvent::Type::kRawKeyDown, modifiers,
ui::EventTimeForNow());
event.windows_key_code = windows_key_code;
return test_api(view).HandleKeyPressEvent(event);
}
bool SimulateKeyPress(int windows_key_code,
bool shift_modifier_pressed = false,
bool non_shift_modifier_pressed = false) {
return SimulateKeyPress(windows_key_code, view(), shift_modifier_pressed,
non_shift_modifier_pressed);
}
protected:
views::View& GetRowViewAt(size_t index) {
return *absl::visit([](views::View* view) { return view; },
test_api(view()).rows()[index]);
}
PopupRowView& GetPopupRowViewAt(size_t index) {
return *absl::get<PopupRowView*>(test_api(view()).rows()[index]);
}
size_t GetNumberOfRows() { return test_api(view()).rows().size(); }
MockAutofillPopupController& controller() {
return autofill_popup_controller_;
}
ui::test::EventGenerator& generator() { return *generator_; }
PopupViewViews& view() { return *view_; }
views::Widget& widget() { return *widget_; }
content::WebContents& web_contents() { return *web_contents_; }
std::pair<std::unique_ptr<NiceMock<MockAutofillPopupController>>,
PopupViewViews*>
OpenSubView(PopupViewViews& view,
const std::vector<Suggestion>& suggestions = {
Suggestion(u"Suggestion")}) {
auto sub_controller =
std::make_unique<NiceMock<MockAutofillPopupController>>();
sub_controller->set_suggestions(suggestions);
ON_CALL(*sub_controller, OpenSubPopup)
.WillByDefault(Return(autofill_popup_sub_controller_.GetWeakPtr()));
base::WeakPtr<AutofillPopupView> sub_view_ptr =
view.CreateSubPopupView(sub_controller->GetWeakPtr());
auto* sub_view = static_cast<PopupViewViews*>(sub_view_ptr.get());
sub_view->Show(AutoselectFirstSuggestion(false));
return {std::move(sub_controller), sub_view};
}
private:
content::RenderViewHostTestEnabler render_view_host_test_enabler_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<content::WebContents> web_contents_;
std::unique_ptr<views::Widget> widget_;
std::unique_ptr<ui::test::EventGenerator> generator_;
raw_ptr<PopupViewViews> view_;
NiceMock<MockAutofillPopupController> autofill_popup_controller_;
NiceMock<MockAutofillPopupController> autofill_popup_sub_controller_;
};
class PopupViewViewsTestWithAnySuggestionType
: public PopupViewViewsTest,
public ::testing::WithParamInterface<SuggestionType> {
public:
SuggestionType type() const { return GetParam(); }
};
class PopupViewViewsTestWithClickableSuggestionType
: public PopupViewViewsTest,
public ::testing::WithParamInterface<SuggestionType> {
public:
SuggestionType type() const {
DCHECK(IsClickable(GetParam()));
return GetParam();
}
};
TEST_F(PopupViewViewsTest, ShowHideTest) {
CreateAndShowView({SuggestionType::kAutocompleteEntry});
EXPECT_CALL(controller(), AcceptSuggestion).Times(0);
view().Hide();
}
TEST_F(PopupViewViewsTest, CanShowDropdownInBounds) {
CreateAndShowView({SuggestionType::kAutocompleteEntry,
SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
const int kSingleItemPopupHeight = view().GetPreferredSize().height();
const int kElementY = 10;
const int kElementHeight = 15;
controller().set_element_bounds({10, kElementY, 100, kElementHeight});
EXPECT_FALSE(test_api(view()).CanShowDropdownInBounds({0, 0, 100, 35}));
// Test a smaller than the popup height (-10px) available space.
EXPECT_FALSE(test_api(view()).CanShowDropdownInBounds(
{0, 0, 100, kElementY + kElementHeight + kSingleItemPopupHeight - 10}));
// Test a larger than the popup height (+10px) available space.
EXPECT_TRUE(test_api(view()).CanShowDropdownInBounds(
{0, 0, 100, kElementY + kElementHeight + kSingleItemPopupHeight + 10}));
view().Hide();
// Repeat the same tests as for the single-suggestion popup above,
// the list is scrollable so that the same restrictions apply.
CreateAndShowView(
{SuggestionType::kAutocompleteEntry, SuggestionType::kAutocompleteEntry,
SuggestionType::kAutocompleteEntry, SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
EXPECT_FALSE(test_api(view()).CanShowDropdownInBounds({0, 0, 100, 35}));
EXPECT_FALSE(test_api(view()).CanShowDropdownInBounds(
{0, 0, 100, kElementY + kElementHeight + kSingleItemPopupHeight - 10}));
EXPECT_TRUE(test_api(view()).CanShowDropdownInBounds(
{0, 0, 100, kElementY + kElementHeight + kSingleItemPopupHeight + 10}));
}
// This is a regression test for crbug.com/1113255.
TEST_F(PopupViewViewsTest, ShowViewWithOnlyFooterItemsShouldNotCrash) {
// Set suggestions to have only a footer item.
std::vector<SuggestionType> suggestion_ids = {SuggestionType::kClearForm};
controller().set_suggestions(suggestion_ids);
CreateAndShowView();
}
TEST_F(PopupViewViewsTest, AccessibilitySelectedEvent) {
views::test::AXEventCounter ax_counter(views::AXEventManager::Get());
CreateAndShowView({SuggestionType::kAutocompleteEntry,
SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
// Checks that a selection event is not sent when the view's |is_selected_|
// member does not change.
GetPopupRowViewAt(0).SetSelectedCell(std::nullopt);
EXPECT_EQ(0, ax_counter.GetCount(ax::mojom::Event::kSelection));
// Checks that a selection event is sent when an unselected view becomes
// selected.
GetPopupRowViewAt(0).SetSelectedCell(PopupRowView::CellType::kContent);
EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kSelection));
// Checks that a new selection event is not sent when the view's
// |is_selected_| member does not change.
GetPopupRowViewAt(0).SetSelectedCell(PopupRowView::CellType::kContent);
EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kSelection));
// Checks that a new selection event is not sent when a selected view becomes
// unselected.
GetPopupRowViewAt(0).SetSelectedCell(std::nullopt);
EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kSelection));
}
TEST_F(PopupViewViewsTest, AccessibilityTest) {
CreateAndShowView({SuggestionType::kDatalistEntry, SuggestionType::kSeparator,
SuggestionType::kAutocompleteEntry,
SuggestionType::kAutofillOptions});
// Select first item.
GetPopupRowViewAt(0).SetSelectedCell(PopupRowView::CellType::kContent);
EXPECT_EQ(GetNumberOfRows(), 4u);
// Item 0.
ui::AXNodeData node_data_0;
GetPopupRowViewAt(0)
.GetContentView()
.GetViewAccessibility()
.GetAccessibleNodeData(&node_data_0);
EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data_0.role);
EXPECT_EQ(1, node_data_0.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
EXPECT_EQ(3, node_data_0.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
EXPECT_TRUE(
node_data_0.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
// Item 1 (separator).
ui::AXNodeData node_data_1;
GetRowViewAt(1).GetViewAccessibility().GetAccessibleNodeData(&node_data_1);
EXPECT_FALSE(node_data_1.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet));
EXPECT_FALSE(node_data_1.HasIntAttribute(ax::mojom::IntAttribute::kSetSize));
EXPECT_EQ(ax::mojom::Role::kSplitter, node_data_1.role);
EXPECT_FALSE(
node_data_1.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
// Item 2.
ui::AXNodeData node_data_2;
GetPopupRowViewAt(2)
.GetContentView()
.GetViewAccessibility()
.GetAccessibleNodeData(&node_data_2);
EXPECT_EQ(2, node_data_2.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
EXPECT_EQ(3, node_data_2.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data_2.role);
EXPECT_FALSE(
node_data_2.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
// Item 3 (footer).
ui::AXNodeData node_data_3;
GetPopupRowViewAt(3)
.GetContentView()
.GetViewAccessibility()
.GetAccessibleNodeData(&node_data_3);
EXPECT_EQ(3, node_data_3.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet));
EXPECT_EQ(3, node_data_3.GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
EXPECT_EQ(ax::mojom::Role::kListBoxOption, node_data_3.role);
EXPECT_FALSE(
node_data_3.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
}
// Gestures are not supported on MacOS.
#if !BUILDFLAG(IS_MAC)
TEST_F(PopupViewViewsTest, AcceptingOnTap) {
ON_CALL(controller(), ShouldIgnoreMouseObservedOutsideItemBoundsCheck)
.WillByDefault(Return(true));
CreateAndShowView({SuggestionType::kPasswordEntry});
// Tapping will accept the selection.
EXPECT_CALL(controller(), AcceptSuggestion(0));
generator().GestureTapAt(
GetPopupRowViewAt(0).GetBoundsInScreen().CenterPoint());
}
TEST_F(PopupViewViewsTest, SelectionOnTouchAndUnselectionOnCancel) {
ON_CALL(controller(), ShouldIgnoreMouseObservedOutsideItemBoundsCheck)
.WillByDefault(Return(true));
CreateAndShowView({SuggestionType::kPasswordEntry});
// Tap down (initiated by generating a touch press) will select an element.
EXPECT_CALL(controller(), SelectSuggestion(0));
generator().PressTouch(
GetPopupRowViewAt(0).GetBoundsInScreen().CenterPoint());
// Canceling gesture clears any selection.
EXPECT_CALL(controller(), UnselectSuggestion);
generator().CancelTouch();
}
#endif // !BUILDFLAG(IS_MAC)
TEST_F(PopupViewViewsTest, ClickDisabledEntry) {
Suggestion opt_int_suggestion("dummy_main_text", "",
Suggestion::Icon::kNoIcon,
SuggestionType::kPasswordAccountStorageOptIn);
opt_int_suggestion.is_loading = Suggestion::IsLoading(true);
controller().set_suggestions({opt_int_suggestion});
CreateAndShowView();
EXPECT_CALL(controller(), AcceptSuggestion).Times(0);
gfx::Point inside_point(GetRowViewAt(0).x() + 1, GetRowViewAt(0).y() + 1);
ui::MouseEvent click_mouse_event(
ui::ET_MOUSE_PRESSED, inside_point, inside_point, ui::EventTimeForNow(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON);
widget().OnMouseEvent(&click_mouse_event);
}
TEST_F(PopupViewViewsTest, KeyboardFocusIsNotCapturedAutomaticallyForSubPopup) {
CreateAndShowView({SuggestionType::kAddressEntry});
auto [sub_controller, sub_view] = OpenSubView(view());
SimulateKeyPress(ui::VKEY_DOWN, *sub_view);
EXPECT_FALSE(sub_view->GetSelectedCell().has_value());
// VKEY_RIGHT is the focus capturing combination
SimulateKeyPress(ui::VKEY_RIGHT, *sub_view);
SimulateKeyPress(ui::VKEY_DOWN, *sub_view);
EXPECT_TRUE(sub_view->GetSelectedCell().has_value());
}
TEST_F(PopupViewViewsTest,
KeyboardFocusIsNotCapturedAutomaticallyForSubPopupRTL) {
base::i18n::SetRTLForTesting(true);
CreateAndShowView({SuggestionType::kAddressEntry});
auto [sub_controller, sub_view] = OpenSubView(view());
// VKEY_LEFT is the focus capturing combination for RTL environment.
SimulateKeyPress(ui::VKEY_LEFT, *sub_view);
SimulateKeyPress(ui::VKEY_DOWN, *sub_view);
EXPECT_TRUE(sub_view->GetSelectedCell().has_value());
base::i18n::SetRTLForTesting(false);
}
TEST_F(PopupViewViewsTest, CursorUpDownForSelectableCells) {
// Set up the popup.
CreateAndShowView(
{SuggestionType::kAutocompleteEntry, SuggestionType::kAutocompleteEntry});
// By default, no row is selected.
EXPECT_FALSE(view().GetSelectedCell().has_value());
// Test wrapping before the front.
SimulateKeyPress(ui::VKEY_UP);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
// Test wrapping after the end.
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
}
TEST_F(PopupViewViewsTest, SelectingSuggestionWithNoControlResetsToContent) {
controller().set_suggestions(
{CreateSuggestionWithChildren({Suggestion(u"Child suggestion")}),
Suggestion(u"Suggestion without control")});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kMouse);
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kMouse);
SimulateKeyPress(ui::VKEY_UP);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
}
TEST_F(PopupViewViewsTest, LeftAndRightKeyEventsAreHandled) {
// The control cell is present in suggestions with children.
controller().set_suggestions(
{CreateSuggestionWithChildren({Suggestion(u"Child #1")})});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
// Hitting right again does not do anything.
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
}
TEST_F(PopupViewViewsTest, LeftAndRightKeyEventsAreHandledForRTL) {
base::i18n::SetRTLForTesting(true);
// The control cell is present in suggestions with children.
controller().set_suggestions(
{CreateSuggestionWithChildren({Suggestion(u"Child #1")})});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kNonUserInput);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
// Hitting right again does not do anything.
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
base::i18n::SetRTLForTesting(false);
}
TEST_F(PopupViewViewsTest, LeftAndRightKeyEventsAreHandledWithoutControl) {
CreateAndShowView({SuggestionType::kAddressEntry});
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// Hitting right or left does not do anything, since there is only one cell to
// select.
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
}
TEST_F(PopupViewViewsTest, CursorLeftRightDownForAutocompleteEntries) {
// Set up the popup.
CreateAndShowView(
{SuggestionType::kAutocompleteEntry, SuggestionType::kAutocompleteEntry});
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// Pressing left or right does nothing because the autocomplete cell is
// handling it itself.
SimulateKeyPress(ui::VKEY_LEFT);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
SimulateKeyPress(ui::VKEY_RIGHT);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
// Going down selects the next cell.
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
}
TEST_F(PopupViewViewsTest, PageUpDownForSelectableCells) {
// Set up the popup.
CreateAndShowView(
{SuggestionType::kAutocompleteEntry, SuggestionType::kAutocompleteEntry,
SuggestionType::kAutocompleteEntry, SuggestionType::kAutocompleteEntry});
// Select the third row.
view().SetSelectedCell(CellIndex{2u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(2u, CellType::kContent));
// Page up selects the first line.
SimulateKeyPress(ui::VKEY_PRIOR);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
// Page down selects the last line.
SimulateKeyPress(ui::VKEY_NEXT);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(3u, CellType::kContent));
}
TEST_F(PopupViewViewsTest, MovingSelectionSkipsSeparator) {
CreateAndShowView({SuggestionType::kAddressEntry, SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
view().SetSelectedCell(CellIndex{0u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// Going one down skips the separator.
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(2u, CellType::kContent));
// And going up does, too.
SimulateKeyPress(ui::VKEY_UP);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
}
TEST_F(PopupViewViewsTest, MovingSelectionSkipsInsecureFormWarning) {
CreateAndShowView({SuggestionType::kAddressEntry, SuggestionType::kSeparator,
SuggestionType::kInsecureContextPaymentDisabledMessage});
view().SetSelectedCell(CellIndex{0u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// Cursor up skips the unselectable form warning when the last item cannot be
// selected.
SimulateKeyPress(ui::VKEY_UP);
EXPECT_FALSE(view().GetSelectedCell().has_value());
// Cursor down selects the first element.
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
// Cursor up leads to no selection because the last item cannot be selected.
SimulateKeyPress(ui::VKEY_UP);
EXPECT_FALSE(view().GetSelectedCell());
}
TEST_F(PopupViewViewsTest, EscClosesSubPopup) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell_content = CellIndex{0, CellType::kContent};
CellIndex cell_control = CellIndex{0, CellType::kControl};
view().SetSelectedCell(cell_control, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), cell_control.first);
SimulateKeyPress(ui::VKEY_ESCAPE);
EXPECT_EQ(view().GetSelectedCell(), cell_content);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
}
class PopupViewViewsTestKeyboard : public PopupViewViewsTest {
public:
void SelectItem(size_t index) {
CreateAndShowView(
{SuggestionType::kAddressEntry, SuggestionType::kAutofillOptions});
// Select the `index`th item.
view().SetSelectedCell(CellIndex{index, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(index, CellType::kContent));
}
void SelectFirstSuggestion() { SelectItem(0); }
};
// Tests that hitting enter on a suggestion autofills it.
TEST_F(PopupViewViewsTestKeyboard, FillOnEnter) {
SelectFirstSuggestion();
EXPECT_CALL(controller(), AcceptSuggestion(0));
SimulateKeyPress(ui::VKEY_RETURN);
}
// Tests that hitting tab on a suggestion autofills it.
TEST_F(PopupViewViewsTestKeyboard, FillOnTabPressed) {
SelectFirstSuggestion();
EXPECT_CALL(controller(), AcceptSuggestion(0));
SimulateKeyPress(ui::VKEY_TAB);
}
// Tests that `tab` together with a modified (other than shift) does not
// autofill a selected suggestion.
TEST_F(PopupViewViewsTestKeyboard, NoFillOnTabPressedWithModifiers) {
SelectFirstSuggestion();
EXPECT_CALL(controller(), AcceptSuggestion(0)).Times(0);
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/false,
/*non_shift_modifier_pressed=*/true);
}
// Verify that pressing the tab key while the "Manage addresses..." entry is
// selected does not trigger "accepting" the entry (which would mean opening
// a tab with the autofill settings).
TEST_F(PopupViewViewsTest, NoAutofillOptionsTriggeredOnTabPressed) {
// Set up the popup and select the options cell.
CreateAndShowView({SuggestionType::kAddressEntry, SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
view().SetSelectedCell(CellIndex{2u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(2u, CellType::kContent));
// Because the selected line is SuggestionType::kAutofillOptions, we expect
// that the tab key does not trigger anything.
EXPECT_CALL(controller(), AcceptSuggestion).Times(0);
SimulateKeyPress(ui::VKEY_TAB);
}
// This is a regression test for crbug.com/1309431 to ensure that we don't crash
// when we press tab before a line is selected.
TEST_F(PopupViewViewsTest, TabBeforeSelectingALine) {
CreateAndShowView({SuggestionType::kAddressEntry, SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
EXPECT_FALSE(view().GetSelectedCell().has_value());
// The following should not crash:
SimulateKeyPress(ui::VKEY_TAB);
}
TEST_F(PopupViewViewsTest, RemoveLine) {
CreateAndShowView({SuggestionType::kAddressEntry,
SuggestionType::kAddressEntry,
SuggestionType::kAutofillOptions});
// If no cell is selected, pressing delete has no effect.
EXPECT_FALSE(view().GetSelectedCell().has_value());
EXPECT_CALL(controller(), RemoveSuggestion).Times(0);
SimulateKeyPress(ui::VKEY_DELETE, /*shift_modifier_pressed=*/true);
Mock::VerifyAndClearExpectations(&controller());
view().SetSelectedCell(CellIndex{1u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
EXPECT_CALL(controller(), RemoveSuggestion).Times(0);
// If no shift key is pressed, no suggestion is removed.
SimulateKeyPress(ui::VKEY_DELETE, /*shift_modifier_pressed=*/false);
Mock::VerifyAndClearExpectations(&controller());
EXPECT_CALL(controller(),
RemoveSuggestion(1, AutofillMetrics::SingleEntryRemovalMethod::
kKeyboardShiftDeletePressed));
SimulateKeyPress(ui::VKEY_DELETE, /*shift_modifier_pressed=*/true);
}
TEST_F(PopupViewViewsTest, RemoveAutofillInvokesController) {
CreateAndShowView({SuggestionType::kAddressEntry,
SuggestionType::kAddressEntry,
SuggestionType::kAutofillOptions});
view().SetSelectedCell(CellIndex{1u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// No metrics are recorded if the entry is not an Autocomplete entry.
EXPECT_CALL(controller(),
RemoveSuggestion(1, AutofillMetrics::SingleEntryRemovalMethod::
kKeyboardShiftDeletePressed))
.WillOnce(Return(true));
SimulateKeyPress(ui::VKEY_DELETE, /*shift_modifier_pressed=*/true);
}
// Tests that pressing TAB selects a previously unselected Compose suggestion.
TEST_F(PopupViewViewsTest, ComposeSuggestion_TabSelects) {
CreateAndShowView({SuggestionType::kCompose});
ASSERT_FALSE(view().GetSelectedCell().has_value());
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/false);
EXPECT_TRUE(view().GetSelectedCell().has_value());
}
// Tests that pressing Shift+TAB in the presence of an unselected Compose
// suggestion does nothing.
TEST_F(PopupViewViewsTest, ComposeSuggestion_ShiftTabDoesNotAffect) {
EXPECT_CALL(controller(), Hide).Times(0);
CreateAndShowView({SuggestionType::kCompose});
ASSERT_FALSE(view().GetSelectedCell().has_value());
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/true);
EXPECT_FALSE(view().GetSelectedCell().has_value());
}
TEST_F(PopupViewViewsTest, ComposeSuggestion_LeftAndRightKeyEventsAreHandled) {
controller().set_suggestions({CreateSuggestionWithChildren(
SuggestionType::kCompose, {Suggestion(u"Child #1")})});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
// Hitting right again does not do anything.
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
}
TEST_F(PopupViewViewsTest,
ComposeSuggestion_LeftAndRightKeyEventsAreHandledForRTL) {
base::i18n::SetRTLForTesting(true);
controller().set_suggestions({CreateSuggestionWithChildren(
SuggestionType::kCompose, {Suggestion(u"Child #1")})});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kNonUserInput);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
// Hitting right again does not do anything.
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_RIGHT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kContent);
EXPECT_TRUE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
EXPECT_FALSE(SimulateKeyPress(ui::VKEY_LEFT));
EXPECT_EQ(view().GetSelectedCell()->second, CellType::kControl);
base::i18n::SetRTLForTesting(false);
}
TEST_F(
PopupViewViewsTest,
ComposeSuggestion_SuggestionAlreadySelected_CursorUpDownForSelectableCells) {
// Set up the popup.
CreateAndShowView(
// These are supopup compose suggestion types.
{SuggestionType::kComposeDisable, SuggestionType::kComposeGoToSettings});
// By default, no row is selected.
EXPECT_FALSE(view().GetSelectedCell().has_value());
// When a suggestion is not already selected, the compose popup does not
// handle up and down arrow keys. In practice they are only handled in the
// context of an open subpopup (there can only be one top level compose
// suggestion), therefore select the first cell/suggestion as if the user had
// open a subpopup.
view().SetSelectedCell(CellIndex{0u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
// Test wrapping before the front.
SimulateKeyPress(ui::VKEY_UP);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
// Test wrapping after the end.
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(0u, CellType::kContent));
SimulateKeyPress(ui::VKEY_DOWN);
EXPECT_EQ(view().GetSelectedCell(),
std::make_optional<CellIndex>(1u, CellType::kContent));
}
// Tests that pressing TAB in the presence of a selected Compose suggestion
// closes the popup.
TEST_F(PopupViewViewsTest,
ComposeSuggestion_TabWithSelectedComposeSuggestionHidesPopup) {
EXPECT_CALL(controller(), Hide(SuggestionHidingReason::kUserAborted));
CreateAndShowView({SuggestionType::kCompose});
view().SetSelectedCell(CellIndex{0u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/false);
}
// Tests that pressing Shift+TAB in the presence of a selected Compose
// suggestion without an open subpopup, unselects the suggestion, but does not
// close the popup.
TEST_F(PopupViewViewsTest, ComposeSuggestion_NoSubPopup_ShiftTabUnselects) {
EXPECT_CALL(controller(), Hide).Times(0);
CreateAndShowView({SuggestionType::kCompose});
view().SetSelectedCell(CellIndex{0u, CellType::kContent},
PopupCellSelectionSource::kNonUserInput);
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/true);
EXPECT_FALSE(view().GetSelectedCell().has_value());
}
// Tests that pressing Shift+TAB in the presence of a selected Compose
// suggestion with an open subpopup, closes the subpopup and selects the root
// suggestion's content cell.
TEST_F(
PopupViewViewsTest,
ComposeSuggestion_SubPopupOpen_ShiftTabClosesSubpopupAndSelectsContentCell) {
controller().set_suggestions({CreateSuggestionWithChildren(
SuggestionType::kCompose, {Suggestion(u"Child #1")})});
CreateAndShowView();
CellIndex cell_content = CellIndex{0, CellType::kContent};
CellIndex cell_control = CellIndex{0, CellType::kControl};
view().SetSelectedCell(cell_control, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), cell_control.first);
SimulateKeyPress(ui::VKEY_TAB, /*shift_modifier_pressed=*/true);
EXPECT_EQ(view().GetSelectedCell(), cell_content);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
}
// Tests that pressing up/down cursor keys does not select a Compose suggestion.
TEST_F(PopupViewViewsTest, ComposeSuggestion_CursorUpDownDoesNotSelect) {
CreateAndShowView({SuggestionType::kCompose});
ASSERT_FALSE(view().GetSelectedCell().has_value());
SimulateKeyPress(ui::VKEY_DOWN, /*shift_modifier_pressed=*/false);
EXPECT_FALSE(view().GetSelectedCell().has_value());
SimulateKeyPress(ui::VKEY_UP, /*shift_modifier_pressed=*/false);
EXPECT_FALSE(view().GetSelectedCell().has_value());
}
// Tests that pressing Esc closes a popup with a Compose suggestion.
TEST_F(PopupViewViewsTest, ComposeSuggestion_EscapeClosesComposePopup) {
EXPECT_CALL(controller(), Hide(SuggestionHidingReason::kUserAborted));
CreateAndShowView({SuggestionType::kCompose});
SimulateKeyPress(ui::VKEY_ESCAPE, /*shift_modifier_pressed=*/false);
}
// Ensure that the voice_over value of suggestions is presented to the
// accessibility layer.
TEST_F(PopupViewViewsTest, VoiceOverTest) {
const std::u16string voice_over_value = u"Password for user@gmail.com";
// Create a realistic suggestion for a password.
Suggestion suggestion(u"user@gmail.com");
suggestion.labels = {{Suggestion::Text(u"example.com")}};
suggestion.voice_over = voice_over_value;
suggestion.additional_label = u"\u2022\u2022\u2022\u2022";
suggestion.type = SuggestionType::kPasswordEntry;
// Create autofill menu.
controller().set_suggestions({suggestion});
CreateAndShowView();
// Verify that the accessibility layer gets the right string to read out.
ui::AXNodeData node_data;
GetPopupRowViewAt(0)
.GetContentView()
.GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(voice_over_value,
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
}
TEST_F(PopupViewViewsTest, ExpandableSuggestionA11yMessageTest) {
// Set up the popup with suggestions.
std::u16string address_line = u"Address line #1";
Suggestion suggestion(address_line, SuggestionType::kAddressEntry);
suggestion.children = {Suggestion(SuggestionType::kFillFullAddress),
Suggestion(SuggestionType::kFillFullName)};
controller().set_suggestions({suggestion});
CreateAndShowView();
// Verify that the accessibility layer gets the right string to read out.
ui::AXNodeData node_data;
GetPopupRowViewAt(0)
.GetViewAccessibility()
.GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
base::JoinString(
{address_line,
l10n_util::GetStringFUTF16(
IDS_AUTOFILL_EXPANDABLE_SUGGESTION_SUBMENU_HINT,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_EXPANDABLE_SUGGESTION_EXPAND_SHORTCUT))},
u". "));
ui::AXNodeData content_node_data;
GetPopupRowViewAt(0)
.GetContentView()
.GetViewAccessibility()
.GetAccessibleNodeData(&content_node_data);
EXPECT_EQ(
content_node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
base::JoinString(
{address_line,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_EXPANDABLE_SUGGESTION_FILL_ADDRESS_A11Y_ADDON),
l10n_util::GetStringFUTF16(
IDS_AUTOFILL_EXPANDABLE_SUGGESTION_SUBMENU_HINT,
l10n_util::GetStringUTF16(
IDS_AUTOFILL_EXPANDABLE_SUGGESTION_EXPAND_SHORTCUT))},
u". "));
}
TEST_F(PopupViewViewsTest, UpdateSuggestionsNoCrash) {
CreateAndShowView({SuggestionType::kAddressEntry, SuggestionType::kSeparator,
SuggestionType::kAutofillOptions});
UpdateSuggestions({SuggestionType::kAddressEntry});
}
TEST_F(PopupViewViewsTest, SubViewIsShownInChildWidget) {
CreateAndShowView({SuggestionType::kAddressEntry});
auto [sub_controller, sub_view] = OpenSubView(view());
views::Widget* sub_widget = sub_view->GetWidget();
EXPECT_EQ(view().GetWidget(), sub_widget->parent());
}
// Tests the event retriggering trick from `PopupBaseView::Widget`,
// see `PopupBaseView::Widget::OnMouseEvent()` for details.
TEST_F(PopupViewViewsTest, ChildWidgetRetriggersMouseMovesToParent) {
// The synthetic events further down originate directly inside the view
// bounds, this flag prevents ignoring them.
ON_CALL(controller(), ShouldIgnoreMouseObservedOutsideItemBoundsCheck)
.WillByDefault(Return(true));
CreateAndShowView({SuggestionType::kAddressEntry});
auto [sub_controller, sub_view] = OpenSubView(view());
ASSERT_EQ(view().GetSelectedCell(), std::nullopt);
PopupRowView* row = absl::get<PopupRowView*>(test_api(view()).rows()[0]);
// Mouse move inside parent, selection by MOUSE_ENTERED is expected.
generator().MoveMouseTo(row->GetBoundsInScreen().CenterPoint());
EXPECT_NE(view().GetSelectedCell(), std::nullopt);
// Mouse move outside parent, unselection by MOUSE_EXITED is expected.
generator().MoveMouseTo(row->GetBoundsInScreen().origin() -
gfx::Vector2d(100, 100));
EXPECT_EQ(view().GetSelectedCell(), std::nullopt);
}
TEST_F(PopupViewViewsTest, SubViewIsClosedWithParent) {
controller().set_suggestions({SuggestionType::kAddressEntry});
PopupViewViews view(controller().GetWeakPtr());
views::Widget* widget = CreateTestWidget().release();
ShowView(&view, *widget);
auto [sub_controller, sub_view] = OpenSubView(view);
base::WeakPtr<views::Widget> sub_widget = sub_view->GetWidget()->GetWeakPtr();
ASSERT_FALSE(sub_widget->IsClosed());
EXPECT_CALL(controller(), ViewDestroyed());
widget->CloseNow();
EXPECT_TRUE(!sub_widget || sub_widget->IsClosed())
<< "The sub-widget should be closed as its parent is closed.";
}
TEST_F(PopupViewViewsTest, CellOpensClosesSubPopupWithDelay) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell_0 = CellIndex{0, CellType::kControl};
view().SetSelectedCell(cell_0, PopupCellSelectionSource::kNonUserInput);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt)
<< "Should be no sub-popups initially.";
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), cell_0.first)
<< "Selected cell should have a sub-popup after the delay.";
view().SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), cell_0.first)
<< "The cell should have no sub-popup by unselecting it.";
}
TEST_F(PopupViewViewsTest, CellSubPopupResetAfterSuggestionsUpdates) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_NE(test_api(view()).GetOpenSubPopupRow(), std::nullopt)
<< "Openning a sub-popup should happen.";
UpdateSuggestions({SuggestionType::kAddressEntry});
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt)
<< "The cell's sub-popup should be closed.";
}
// TODO(crbug.com/41487832): Enable on ChromeOS when test setup in the death
// subprocess is fixed.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
// `PopupViewViewsTest` is not used in death tests because it sets up a complex
// environment (namely creates a `TestingProfile`) that fails to be created in
// the sub-process (see `ASSERT_DEATH` doc for details). This fail hides
// the real death reason to be tested.
using PopupViewViewsDeathTest = ChromeViewsTestBase;
TEST_F(PopupViewViewsDeathTest, OpenSubPopupWithNoChildrenCheckCrash) {
NiceMock<MockAutofillPopupController> controller;
controller.set_suggestions({
// Regular suggestion with no children,
Suggestion(u"Suggestion #1"),
Suggestion(u"Suggestion #2"),
});
std::unique_ptr<views::Widget> widget = CreateTestWidget();
std::unique_ptr<PopupViewViews> view =
std::make_unique<PopupViewViews>(controller.GetWeakPtr());
raw_ptr<PopupViewViews> view_ptr = widget->SetContentsView(std::move(view));
view_ptr->Show(AutoselectFirstSuggestion(false));
ASSERT_DEATH(
view_ptr->SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kNonUserInput),
"can_open_sub_popup");
}
#endif
TEST_F(PopupViewViewsTest, SubPopupHidingOnNoSelection) {
ui::MouseEvent fake_event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_IS_SYNTHESIZED, 0);
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell{0, CellType::kControl};
view().SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), cell.first);
auto [sub_controller, sub_view] = OpenSubView(
view(), {CreateSuggestionWithChildren({Suggestion(u"Sub Child #1")})});
view().SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
sub_view->SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(*sub_view).GetOpenSubPopupRow(), cell.first);
auto [sub_sub_controller, sub_sub_view] = OpenSubView(
*sub_view,
{CreateSuggestionWithChildren({Suggestion(u"Sub Sub Child #1")})});
sub_view->SetSelectedCell(std::nullopt,
PopupCellSelectionSource::kNonUserInput);
sub_sub_view->SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
sub_sub_view->SetSelectedCell(std::nullopt,
PopupCellSelectionSource::kNonUserInput);
sub_sub_view->OnMouseExited(fake_event);
task_environment()->FastForwardBy(
PopupViewViews::kNoSelectionHideSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
EXPECT_EQ(test_api(*sub_view).GetOpenSubPopupRow(), std::nullopt);
}
TEST_F(PopupViewViewsTest, SubPopupHidingIsCanceledOnSelection) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell{0, CellType::kControl};
view().SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), cell.first);
auto [sub_controller, sub_view] = OpenSubView(
view(), {CreateSuggestionWithChildren({Suggestion(u"Sub Child #1")})});
view().SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
// This triggers the no-selection hiding timer.
sub_view->OnMouseExited(ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(),
gfx::Point(), ui::EventTimeForNow(),
ui::EF_IS_SYNTHESIZED, 0));
// A cell is selected - the timer should be canceled.
view().SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(
PopupViewViews::kNoSelectionHideSubPopupDelay);
EXPECT_NE(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
}
TEST_F(PopupViewViewsTest, SubPopupHidingIsCanceledOnParentHiding) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell{0, CellType::kControl};
view().SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
view().Hide();
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
}
TEST_F(PopupViewViewsTest, SubPopupOwnSelectionPreventsHiding) {
ui::MouseEvent fake_event(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_IS_SYNTHESIZED, 0);
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
Suggestion(u"Suggestion #2"),
});
CreateAndShowView();
CellIndex cell{0, CellType::kControl};
view().SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(view()).GetOpenSubPopupRow(), cell.first);
auto [sub_controller, sub_view] = OpenSubView(
view(), {CreateSuggestionWithChildren({Suggestion(u"Sub Child #1")})});
view().SetSelectedCell(std::nullopt, PopupCellSelectionSource::kNonUserInput);
sub_view->SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
ASSERT_EQ(test_api(*sub_view).GetOpenSubPopupRow(), cell.first);
auto [sub_sub_controller, sub_sub_view] = OpenSubView(
*sub_view,
{CreateSuggestionWithChildren({Suggestion(u"Sub Sub Child #1")})});
sub_view->SetSelectedCell(std::nullopt,
PopupCellSelectionSource::kNonUserInput);
sub_sub_view->SetSelectedCell(cell, PopupCellSelectionSource::kNonUserInput);
sub_sub_view->SetSelectedCell(std::nullopt,
PopupCellSelectionSource::kNonUserInput);
sub_sub_view->OnMouseExited(fake_event);
// The interrupting selection in the root popup, should prevent
// its sub-popup from closing, but not the middle one's sub-popup.
task_environment()->FastForwardBy(base::Milliseconds(1));
view().OnMouseEntered(fake_event);
task_environment()->FastForwardBy(
PopupViewViews::kNoSelectionHideSubPopupDelay);
EXPECT_NE(test_api(view()).GetOpenSubPopupRow(), std::nullopt);
EXPECT_EQ(test_api(*sub_view).GetOpenSubPopupRow(), std::nullopt);
}
TEST_F(PopupViewViewsTest, SubPopupOpensWithNoAutoselectByMouse) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
});
CreateAndShowView();
EXPECT_CALL(controller(),
OpenSubPopup(_, _, AutoselectFirstSuggestion(false)));
view().SetSelectedCell(CellIndex{0, CellType::kControl},
PopupCellSelectionSource::kMouse);
task_environment()->FastForwardBy(PopupViewViews::kMouseOpenSubPopupDelay);
}
TEST_F(PopupViewViewsTest, SubPopupOpensWithAutoselectByRightKey) {
controller().set_suggestions({
CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
});
CreateAndShowView();
EXPECT_CALL(controller(),
OpenSubPopup(_, _, AutoselectFirstSuggestion(true)));
SimulateKeyPress(ui::VKEY_DOWN);
SimulateKeyPress(ui::VKEY_RIGHT);
task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
}
TEST_F(PopupViewViewsTest, SubPopupOpensForNonSelectableContentSelection) {
Suggestion suggestion = CreateSuggestionWithChildren({Suggestion(u"Child")});
suggestion.is_acceptable = false;
controller().set_suggestions({suggestion});
CreateAndShowView();
EXPECT_CALL(controller(), OpenSubPopup);
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kMouse);
task_environment()->FastForwardBy(PopupViewViews::kMouseOpenSubPopupDelay);
}
TEST_F(PopupViewViewsTest, SubPopupNotOpenForSelectableContentSelection) {
Suggestion suggestion = CreateSuggestionWithChildren({Suggestion(u"Child")});
suggestion.is_acceptable = true;
controller().set_suggestions({suggestion});
CreateAndShowView();
EXPECT_CALL(controller(), OpenSubPopup).Times(0);
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kMouse);
task_environment()->FastForwardBy(PopupViewViews::kMouseOpenSubPopupDelay);
}
TEST_F(PopupViewViewsTest,
SubPopupNotOpenForMerchantOptedOutVcnContentSelection) {
Suggestion suggestion = CreateSuggestionWithChildren({Suggestion(u"Child")});
suggestion.is_acceptable = false;
suggestion.apply_deactivated_style = true;
controller().set_suggestions({suggestion});
CreateAndShowView();
EXPECT_CALL(controller(), OpenSubPopup).Times(0);
view().SetSelectedCell(CellIndex{0, CellType::kContent},
PopupCellSelectionSource::kMouse);
task_environment()->FastForwardBy(PopupViewViews::kMouseOpenSubPopupDelay);
}
// TODO(crbug.com/40284129): Enable once the view shows itself properly.
#if !BUILDFLAG(IS_MAC)
// Tests that `GetPopupScreenLocation` returns the bounds and arrow position of
// the popup.
TEST_F(PopupViewViewsTest, GetPopupScreenLocation) {
CreateAndShowView({SuggestionType::kCompose});
using PopupScreenLocation = AutofillClient::PopupScreenLocation;
auto MatchesScreenLocation =
[](gfx::Rect bounds, PopupScreenLocation::ArrowPosition arrow_position) {
return Optional(
AllOf(Field(&PopupScreenLocation::bounds, bounds),
Field(&PopupScreenLocation::arrow_position, arrow_position)));
};
EXPECT_THAT(
view().GetPopupScreenLocation(),
MatchesScreenLocation(widget().GetWindowBoundsInScreen(),
PopupScreenLocation::ArrowPosition::kTopLeft));
}
#endif // !BUILDFLAG(IS_MAC)
// TODO(crbug.com/41496626): Rework into pixel tests and run on all available
// platforms. The test below is a temporary solution to cover positioning
// calculations in the popup. The exact numbers were obtained by observing
// a local run, manually verified and hardcoded in the test with acceptable 15px
// error, as on different machines the popup geometry/location slightly vary.
#if BUILDFLAG(IS_LINUX)
TEST_F(PopupViewViewsTest, PopupPositioning) {
constexpr gfx::Size kSmallWindow(300, 300);
constexpr gfx::Size kLargeWindow(1000, 1000);
constexpr gfx::SizeF kElementSize(100, 25);
constexpr gfx::PointF kLargeWindowTopLeftElement(0, 0);
constexpr gfx::PointF kLargeWindowBottomLeftElement(0, 975);
constexpr gfx::PointF kLargeWindowCenterElement(500, 500);
constexpr gfx::PointF kLargeWindowTopRightElement(900, 0);
constexpr gfx::PointF kLargeWindowBottomRightElement(900, 975);
constexpr gfx::PointF kSmallWindowTopLeftElement(0, 0);
constexpr gfx::PointF kSmallWindowLeftElement(0, 140);
constexpr gfx::PointF kSmallWindowTopElement(150, 0);
constexpr gfx::PointF kSmallWindowBottomElement(150, 275);
constexpr gfx::PointF kSmallWindowBottomRightElement(200, 275);
const std::vector<SuggestionType> kSmallPopupSuggestions(
2, SuggestionType::kAutocompleteEntry);
const std::vector<SuggestionType> kLargePopupSuggestions(
10, SuggestionType::kAutocompleteEntry);
struct TestCase {
const gfx::Size web_contents_bounds;
const gfx::PointF element_position;
const std::vector<SuggestionType> suggestions;
const gfx::Rect expected_popup_bounds;
} test_cases[]{
{kLargeWindow,
kLargeWindowTopLeftElement,
kSmallPopupSuggestions,
{25, 26, 164, 138}},
{kLargeWindow,
kLargeWindowBottomLeftElement,
kSmallPopupSuggestions,
{25, 840, 164, 134}},
{kLargeWindow,
kLargeWindowCenterElement,
kSmallPopupSuggestions,
{525, 526, 164, 138}},
{kLargeWindow,
kLargeWindowTopRightElement,
kSmallPopupSuggestions,
{832, 26, 164, 138}},
{kLargeWindow,
kLargeWindowBottomRightElement,
kSmallPopupSuggestions,
{832, 840, 164, 134}},
{kSmallWindow,
kSmallWindowTopLeftElement,
kSmallPopupSuggestions,
{25, 26, 164, 138}},
{kSmallWindow,
kSmallWindowTopLeftElement,
kLargePopupSuggestions,
{100, -10, 183, 308}},
{kSmallWindow,
kSmallWindowLeftElement,
kLargePopupSuggestions,
{100, -2, 183, 308}},
{kSmallWindow,
kSmallWindowTopElement,
kLargePopupSuggestions,
{117, 26, 179, 288}},
{kSmallWindow,
kSmallWindowBottomElement,
kLargePopupSuggestions,
{117, -10, 179, 284}},
{kSmallWindow,
kSmallWindowBottomRightElement,
kLargePopupSuggestions,
{17, 6, 183, 308}},
};
for (TestCase& test_case : test_cases) {
web_contents().Resize(gfx::Rect(test_case.web_contents_bounds));
controller().set_element_bounds(
gfx::RectF(test_case.element_position, kElementSize) +
web_contents().GetContainerBounds().OffsetFromOrigin());
CreateAndShowView(test_case.suggestions);
const gfx::Rect& expected = test_case.expected_popup_bounds;
const gfx::Rect& actual = widget().GetWindowBoundsInScreen();
// The exact position and size varies on different machines (e.g. because of
// different available fonts) and this comparison relaxation is to mitigate
// slightly different dimensions.
const int kPxError = 15;
EXPECT_NEAR(expected.x(), actual.x(), kPxError);
EXPECT_NEAR(expected.y(), actual.y(), kPxError);
EXPECT_NEAR(expected.width(), actual.width(), kPxError);
EXPECT_NEAR(expected.height(), actual.height(), kPxError);
}
}
#endif // BUILDFLAG(IS_LINUX)
TEST_F(PopupViewViewsTest, StandaloneCvcSuggestion_ElementId) {
Suggestion suggestion(u"dummy_main_text");
suggestion.feature_for_iph =
&feature_engagement::kIPHAutofillVirtualCardCVCSuggestionFeature;
controller().set_suggestions({suggestion});
CreateAndShowView();
EXPECT_EQ(GetPopupRowViewAt(0).GetProperty(views::kElementIdentifierKey),
kAutofillStandaloneCvcSuggestionElementId);
}
TEST_F(PopupViewViewsTest, VirtualCardSuggestion_ElementId) {
Suggestion suggestion(u"dummy_main_text");
suggestion.feature_for_iph =
&feature_engagement::kIPHAutofillVirtualCardSuggestionFeature;
controller().set_suggestions({suggestion});
CreateAndShowView();
EXPECT_EQ(GetPopupRowViewAt(0).GetProperty(views::kElementIdentifierKey),
kAutofillCreditCardSuggestionEntryElementId);
}
#if defined(MEMORY_SANITIZER) && BUILDFLAG(IS_CHROMEOS)
#define MAYBE_ShowClickTest DISABLED_ShowClickTest
#else
#define MAYBE_ShowClickTest ShowClickTest
#endif
// Tests that (only) clickable items trigger an AcceptSuggestion event.
TEST_P(PopupViewViewsTestWithAnySuggestionType, MAYBE_ShowClickTest) {
CreateAndShowView({type()});
EXPECT_CALL(controller(), AcceptSuggestion(0)).Times(IsClickable(type()));
generator().MoveMouseTo(gfx::Point(1000, 1000));
ASSERT_FALSE(view().IsMouseHovered());
Paint();
generator().MoveMouseTo(GetCenterOfSuggestion(0));
generator().ClickLeftButton();
}
// Tests that after the mouse moves into the popup after display, clicking a
// suggestion triggers an AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
AcceptSuggestionIfUnfocusedAtPaint) {
CreateAndShowView({type()});
EXPECT_CALL(controller(), AcceptSuggestion(0));
generator().MoveMouseTo(gfx::Point(1000, 1000));
ASSERT_FALSE(view().IsMouseHovered());
Paint();
generator().MoveMouseTo(GetCenterOfSuggestion(0));
generator().ClickLeftButton();
}
// Tests that after the mouse moves from one suggestion to another, clicking the
// suggestion triggers an AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
AcceptSuggestionIfMouseSelectedAnotherRow) {
CreateAndShowView({type(), type()});
EXPECT_CALL(controller(), AcceptSuggestion);
generator().MoveMouseTo(GetCenterOfSuggestion(0));
ASSERT_TRUE(view().IsMouseHovered());
Paint();
generator().MoveMouseTo(GetCenterOfSuggestion(1)); // Selects another row.
generator().ClickLeftButton();
}
// Tests that after the mouse moves from one suggestion to another and back to
// the first one, clicking the suggestion triggers an AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
AcceptSuggestionIfMouseTemporarilySelectedAnotherRow) {
CreateAndShowView({type(), type()});
EXPECT_CALL(controller(), AcceptSuggestion);
generator().MoveMouseTo(GetCenterOfSuggestion(0));
ASSERT_TRUE(view().IsMouseHovered());
Paint();
generator().MoveMouseTo(GetCenterOfSuggestion(1)); // Selects another row.
generator().MoveMouseTo(GetCenterOfSuggestion(0));
generator().ClickLeftButton();
}
// Tests that even if the mouse hovers a suggestion when the popup is displayed,
// after moving the mouse out and back in on the popup, clicking the suggestion
// triggers an AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
AcceptSuggestionIfMouseExitedPopupSincePaint) {
CreateAndShowView({type()});
EXPECT_CALL(controller(), AcceptSuggestion);
generator().MoveMouseTo(GetCenterOfSuggestion(0));
ASSERT_TRUE(view().IsMouseHovered());
Paint();
generator().MoveMouseTo(gfx::Point(1000, 1000)); // Exits the popup.
ASSERT_FALSE(view().IsMouseHovered());
generator().MoveMouseTo(GetCenterOfSuggestion(0));
generator().ClickLeftButton();
}
// Tests that if the mouse hovers a suggestion when the popup is displayed,
// clicking the suggestion triggers no AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
IgnoreClickIfFocusedAtPaintWithoutExit) {
CreateAndShowView({type()});
EXPECT_CALL(controller(), AcceptSuggestion).Times(0);
generator().MoveMouseTo(GetCenterOfSuggestion(0));
ASSERT_TRUE(view().IsMouseHovered());
Paint();
generator().ClickLeftButton();
}
// Tests that if the mouse hovers a suggestion when the popup is displayed and
// moves around on this suggestion, clicking the suggestion triggers no
// AcceptSuggestion() event.
TEST_P(PopupViewViewsTestWithClickableSuggestionType,
IgnoreClickIfFocusedAtPaintWithSlightMouseMovement) {
CreateAndShowView({type()});
EXPECT_CALL(controller(), AcceptSuggestion).Times(0);
int width = GetRowViewAt(0).width();
int height = GetRowViewAt(0).height();
for (int x : {-width / 3, width / 3}) {
for (int y : {-height / 3, height / 3}) {
generator().MoveMouseTo(GetCenterOfSuggestion(0) + gfx::Vector2d(x, y));
ASSERT_TRUE(view().IsMouseHovered());
Paint();
}
}
generator().ClickLeftButton();
}
INSTANTIATE_TEST_SUITE_P(All,
PopupViewViewsTestWithAnySuggestionType,
testing::ValuesIn([] {
std::vector<SuggestionType> all_ids;
all_ids.insert(all_ids.end(),
kClickableSuggestionTypes.begin(),
kClickableSuggestionTypes.end());
all_ids.insert(all_ids.end(),
kUnclickableSuggestionTypes.begin(),
kUnclickableSuggestionTypes.end());
return all_ids;
}()));
INSTANTIATE_TEST_SUITE_P(All,
PopupViewViewsTestWithClickableSuggestionType,
testing::ValuesIn(kClickableSuggestionTypes));
TEST_F(PopupViewViewsTest, ViewFocusOnShowDependsOnWidgetActivatability) {
views::Widget::InitParams activatable_widget_params =
CreateParamsForTestWidget(views::Widget::InitParams::Type::TYPE_POPUP);
activatable_widget_params.activatable =
views::Widget::InitParams::Activatable::kYes;
CreateAndShowView({SuggestionType::kAddressEntry},
std::move(activatable_widget_params));
EXPECT_EQ(view().HasFocus(), true);
views::Widget::InitParams non_activatable_widget_params =
CreateParamsForTestWidget(views::Widget::InitParams::Type::TYPE_POPUP);
non_activatable_widget_params.activatable =
views::Widget::InitParams::Activatable::kNo;
CreateAndShowView({SuggestionType::kAddressEntry},
std::move(non_activatable_widget_params));
EXPECT_EQ(view().HasFocus(), false);
}
TEST_F(PopupViewViewsTest, SearchBar_InputGetsFocusOnShow) {
views::Widget::InitParams widget_params =
CreateParamsForTestWidget(views::Widget::InitParams::Type::TYPE_POPUP);
widget_params.activatable = views::Widget::InitParams::Activatable::kYes;
CreateAndShowView({SuggestionType::kAddressEntry}, std::move(widget_params),
/*search_bar_config=*/{.enabled = true});
views::View* focused_field = widget().GetFocusManager()->GetFocusedView();
ASSERT_NE(focused_field, nullptr);
EXPECT_EQ(focused_field->GetProperty(views::kElementIdentifierKey),
PopupSearchBarView::kInputField);
}
TEST_F(PopupViewViewsTest, SearchBar_HidesPopupOnFocusLost) {
views::Widget::InitParams widget_params =
CreateParamsForTestWidget(views::Widget::InitParams::Type::TYPE_POPUP);
widget_params.activatable = views::Widget::InitParams::Activatable::kYes;
CreateAndShowView({SuggestionType::kAddressEntry}, std::move(widget_params),
/*search_bar_config=*/{.enabled = true});
views::View* focused_field = widget().GetFocusManager()->GetFocusedView();
ASSERT_NE(focused_field, nullptr);
EXPECT_CALL(controller(), Hide(SuggestionHidingReason::kFocusChanged));
widget().GetFocusManager()->SetFocusedView(nullptr);
Mock::VerifyAndClearExpectations(&controller());
}
TEST_F(PopupViewViewsTest, SearchBar_QueryIsSetAsFilterToController) {
CreateAndShowView({SuggestionType::kAddressEntry},
CreateParamsForTestWidget(),
/*search_bar_config=*/{.enabled = true});
MockFunction<void()> check;
{
InSequence s;
EXPECT_CALL(
controller(),
SetFilter(std::optional(
AutofillPopupController::SuggestionFilter(u"search input"))));
EXPECT_CALL(check, Call);
EXPECT_CALL(
controller(),
SetFilter(std::optional<AutofillPopupController::SuggestionFilter>()));
}
test_api(view()).SetSearchQuery(u"search input");
task_environment()->FastForwardBy(
PopupSearchBarView::kInputChangeCallbackDelay);
check.Call();
test_api(view()).SetSearchQuery(u"");
task_environment()->FastForwardBy(
PopupSearchBarView::kInputChangeCallbackDelay);
}
} // namespace autofill