#include "ui/views/controls/editable_combobox/editable_combobox.h"
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/simple_combobox_model.h"
#include "ui/base/mojom/menu_source_type.mojom-forward.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/context_menu_controller.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/combobox/combobox_util.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/test/menu_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
#if BUILDFLAG(IS_OZONE)
#include "ui/events/ozone/layout/keyboard_layout_engine_test_utils.h"
#endif
namespace views {
namespace {
using base::ASCIIToUTF16;
using views::test::WaitForMenuClosureAnimation;
class TestContextMenuController : public ContextMenuController {
public:
TestContextMenuController() = default;
TestContextMenuController(const TestContextMenuController&) = delete;
TestContextMenuController& operator=(const TestContextMenuController&) =
delete;
~TestContextMenuController() override = default;
void ShowContextMenuForViewImpl(
View* source,
const gfx::Point& point,
ui::mojom::MenuSourceType source_type) override {
opened_menu_ = true;
}
bool opened_menu() const { return opened_menu_; }
private:
bool opened_menu_ = false;
};
}
class EditableComboboxTest : public ViewsTestBase {
public:
static constexpr gfx::Rect kWidgetBounds = gfx::Rect(0, 0, 1000, 1000);
static constexpr gfx::Rect kComboboxBounds = gfx::Rect(0, 0, 500, 40);
EditableComboboxTest() { views::test::DisableMenuClosureAnimations(); }
EditableComboboxTest(const EditableComboboxTest&) = delete;
EditableComboboxTest& operator=(const EditableComboboxTest&) = delete;
void SetUp() override;
void TearDown() override;
void InitEditableCombobox(int item_count = 8,
bool filter_on_edit = false,
bool show_on_empty = true);
void InitEditableCombobox(const std::vector<std::u16string>& items,
bool filter_on_edit,
bool show_on_empty = true);
void InitEditableCombobox(
const std::vector<ui::SimpleComboboxModel::Item>& items,
bool filter_on_edit,
bool show_on_empty = true);
void InitWidget();
static size_t GetItemCount(const EditableCombobox* combobox);
protected:
enum class IconSource { kMenuModel, kComboboxModel };
size_t GetItemCount() const;
std::u16string GetItemAt(size_t index) const;
ui::ImageModel GetIconAt(size_t index, IconSource source) const;
void ClickArrow();
void ClickMenuItem(int index);
void ClickTextfield();
void FocusTextfield();
bool IsTextfieldFocused() const;
std::u16string_view GetSelectedText() const;
void SetContextMenuController(ContextMenuController* controller);
void DragMouseTo(const gfx::Point& location);
MenuRunner* GetMenuRunner();
bool IsMenuOpen();
Button* GetArrowButton();
void PerformMouseEvent(Widget* widget,
const gfx::Point& point,
ui::EventType type);
void PerformClick(Widget* widget, const gfx::Point& point);
void SendKeyEvent(ui::KeyboardCode key_code,
bool alt = false,
bool shift = false,
bool ctrl_cmd = false);
int change_count() const { return change_count_; }
void OnContentChanged() { ++change_count_; }
std::unique_ptr<Widget> widget_;
raw_ptr<EditableCombobox> combobox_ = nullptr;
raw_ptr<View> dummy_focusable_view_ = nullptr;
raw_ptr<View> parent_of_combobox_ = nullptr;
int change_count_ = 0;
std::unique_ptr<ui::test::EventGenerator> event_generator_;
};
void EditableComboboxTest::SetUp() {
ViewsTestBase::SetUp();
#if BUILDFLAG(IS_OZONE)
ui::WaitUntilLayoutEngineIsReadyForTest();
#endif
}
void EditableComboboxTest::TearDown() {
if (IsMenuOpen()) {
GetMenuRunner()->Cancel();
WaitForMenuClosureAnimation();
}
if (widget_) {
combobox_ = nullptr;
dummy_focusable_view_ = nullptr;
parent_of_combobox_ = nullptr;
widget_->Close();
}
ViewsTestBase::TearDown();
}
void EditableComboboxTest::InitEditableCombobox(const int item_count,
const bool filter_on_edit,
const bool show_on_empty) {
std::vector<ui::SimpleComboboxModel::Item> items;
for (int i = 0; i < item_count; ++i) {
items.emplace_back(ASCIIToUTF16(base::StringPrintf("item[%i]", i)));
}
InitEditableCombobox(items, filter_on_edit, show_on_empty);
}
void EditableComboboxTest::InitEditableCombobox(
const std::vector<std::u16string>& strings,
bool filter_on_edit,
bool show_on_empty) {
std::vector<ui::SimpleComboboxModel::Item> items;
for (const auto& item_str : strings) {
items.emplace_back(item_str);
}
InitEditableCombobox(items, filter_on_edit, show_on_empty);
}
void EditableComboboxTest::InitEditableCombobox(
const std::vector<ui::SimpleComboboxModel::Item>& items,
const bool filter_on_edit,
const bool show_on_empty) {
InitWidget();
View* container = widget_->SetContentsView(std::make_unique<View>());
parent_of_combobox_ = container->AddChildView(std::make_unique<View>());
parent_of_combobox_->SetBoundsRect(kComboboxBounds);
combobox_ =
parent_of_combobox_->AddChildView(std::make_unique<EditableCombobox>(
std::make_unique<ui::SimpleComboboxModel>(items), filter_on_edit,
show_on_empty));
combobox_->SetCallback(base::BindRepeating(
&EditableComboboxTest::OnContentChanged, base::Unretained(this)));
combobox_->GetViewAccessibility().SetName(u"abc");
combobox_->SetBoundsRect(kComboboxBounds);
dummy_focusable_view_ = container->AddChildView(std::make_unique<View>());
dummy_focusable_view_->SetFocusBehavior(View::FocusBehavior::ALWAYS);
}
void EditableComboboxTest::InitWidget() {
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.bounds = kWidgetBounds;
widget_->Init(std::move(params));
widget_->Show();
#if BUILDFLAG(IS_MAC)
base::RunLoop().RunUntilIdle();
#endif
event_generator_ =
std::make_unique<ui::test::EventGenerator>(GetRootWindow(widget_.get()));
event_generator_->set_target(ui::test::EventGenerator::Target::WINDOW);
}
size_t EditableComboboxTest::GetItemCount(const EditableCombobox* combobox) {
return combobox->GetMenuModelForTesting()->GetItemCount();
}
size_t EditableComboboxTest::GetItemCount() const {
return GetItemCount(combobox_);
}
std::u16string EditableComboboxTest::GetItemAt(size_t index) const {
return combobox_->GetItemTextForTesting(index);
}
ui::ImageModel EditableComboboxTest::GetIconAt(size_t index,
IconSource icon_source) const {
switch (icon_source) {
case IconSource::kMenuModel:
return combobox_->GetMenuModelForTesting()->GetIconAt(index);
case IconSource::kComboboxModel:
return combobox_->GetMenuModelForTesting()->GetIconAt(index);
}
}
void EditableComboboxTest::ClickArrow() {
ui::MouseEvent e(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0);
views::test::ButtonTestApi test_api(combobox_->GetArrowButtonForTesting());
test_api.NotifyClick(e);
}
void EditableComboboxTest::FocusTextfield() {
combobox_->textfield_->RequestFocus();
}
bool EditableComboboxTest::IsTextfieldFocused() const {
return combobox_->textfield_->HasFocus();
}
std::u16string_view EditableComboboxTest::GetSelectedText() const {
return combobox_->textfield_->GetSelectedText();
}
void EditableComboboxTest::SetContextMenuController(
ContextMenuController* controller) {
combobox_->textfield_->set_context_menu_controller(controller);
}
void EditableComboboxTest::ClickTextfield() {
const gfx::Point textfield(combobox_->x() + 1, combobox_->y() + 1);
PerformClick(widget_.get(), textfield);
}
void EditableComboboxTest::DragMouseTo(const gfx::Point& location) {
ui::MouseEvent drag(ui::EventType::kMouseDragged, location, location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0);
combobox_->textfield_->OnMouseDragged(drag);
}
MenuRunner* EditableComboboxTest::GetMenuRunner() {
return combobox_->menu_runner_.get();
}
bool EditableComboboxTest::IsMenuOpen() {
return combobox_ && GetMenuRunner() && GetMenuRunner()->IsRunning();
}
Button* EditableComboboxTest::GetArrowButton() {
return combobox_->GetArrowButtonForTesting();
}
void EditableComboboxTest::PerformMouseEvent(Widget* widget,
const gfx::Point& point,
const ui::EventType type) {
ui::MouseEvent event = ui::MouseEvent(
type, point, point, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_NUM_LOCK_ON, ui::EF_LEFT_MOUSE_BUTTON);
widget->OnMouseEvent(&event);
}
void EditableComboboxTest::PerformClick(Widget* widget,
const gfx::Point& point) {
PerformMouseEvent(widget, point, ui::EventType::kMousePressed);
PerformMouseEvent(widget, point, ui::EventType::kMouseReleased);
}
void EditableComboboxTest::SendKeyEvent(ui::KeyboardCode key_code,
const bool alt,
const bool shift,
const bool ctrl_cmd) {
#if BUILDFLAG(IS_MAC)
bool command = ctrl_cmd;
bool control = false;
#else
bool command = false;
bool control = ctrl_cmd;
#endif
int flags = (shift ? ui::EF_SHIFT_DOWN : 0) |
(control ? ui::EF_CONTROL_DOWN : 0) |
(alt ? ui::EF_ALT_DOWN : 0) | (command ? ui::EF_COMMAND_DOWN : 0);
event_generator_->PressKey(key_code, flags);
}
TEST_F(EditableComboboxTest, FocusOnTextfieldDoesntOpenMenu) {
InitEditableCombobox();
EXPECT_FALSE(IsMenuOpen());
FocusTextfield();
EXPECT_FALSE(IsMenuOpen());
}
TEST_F(EditableComboboxTest, ArrowDownOpensMenu) {
InitEditableCombobox();
EXPECT_FALSE(IsMenuOpen());
FocusTextfield();
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(IsMenuOpen());
}
TEST_F(EditableComboboxTest, TabMovesToOtherViewAndClosesMenu) {
InitEditableCombobox();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
EXPECT_TRUE(IsTextfieldFocused());
SendKeyEvent(ui::VKEY_TAB);
EXPECT_FALSE(IsTextfieldFocused());
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
}
TEST_F(EditableComboboxTest,
ClickOutsideEditableComboboxWithoutLosingFocusClosesMenu) {
InitEditableCombobox();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
EXPECT_TRUE(IsTextfieldFocused());
const gfx::Point outside_point(combobox_->x() + combobox_->width() + 1,
combobox_->y() + 1);
PerformClick(widget_.get(), outside_point);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
EXPECT_TRUE(IsTextfieldFocused());
}
TEST_F(EditableComboboxTest, ClickTextfieldDoesntCloseMenu) {
InitEditableCombobox();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
MenuRunner* menu_runner1 = GetMenuRunner();
ClickTextfield();
MenuRunner* menu_runner2 = GetMenuRunner();
EXPECT_TRUE(IsMenuOpen());
EXPECT_EQ(menu_runner1, menu_runner2);
}
TEST_F(EditableComboboxTest, RemovingControlWhileMenuOpenClosesMenu) {
InitEditableCombobox();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
auto combobox = parent_of_combobox_->RemoveChildViewT(combobox_);
EXPECT_EQ(nullptr, GetMenuRunner());
combobox_ = nullptr;
}
TEST_F(EditableComboboxTest, RemovingParentOfControlWhileMenuOpenClosesMenu) {
InitEditableCombobox();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
auto parent =
widget_->GetContentsView()->RemoveChildViewT(parent_of_combobox_);
EXPECT_EQ(nullptr, GetMenuRunner());
combobox_ = nullptr;
parent_of_combobox_ = nullptr;
}
TEST_F(EditableComboboxTest, LeftOrRightKeysMoveInTextfield) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_E);
SendKeyEvent(ui::VKEY_LEFT);
SendKeyEvent(ui::VKEY_LEFT);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_RIGHT);
SendKeyEvent(ui::VKEY_D);
EXPECT_EQ(u"abcde", combobox_->GetText());
}
#if BUILDFLAG(IS_WIN)
#define MAYBE_UpOrDownKeysMoveInMenu DISABLED_UpOrDownKeysMoveInMenu
#else
#define MAYBE_UpOrDownKeysMoveInMenu UpOrDownKeysMoveInMenu
#endif
TEST_F(EditableComboboxTest, MAYBE_UpOrDownKeysMoveInMenu) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_UP);
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_EQ(u"item[1]", combobox_->GetText());
}
TEST_F(EditableComboboxTest, EndOrHomeMovesToBeginningOrEndOfText) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_HOME);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_END);
SendKeyEvent(ui::VKEY_Y);
EXPECT_EQ(u"xabcy", combobox_->GetText());
}
#if BUILDFLAG(IS_MAC)
TEST_F(EditableComboboxTest, AltLeftOrRightMovesToNextWords) {
InitEditableCombobox();
FocusTextfield();
combobox_->SetText(u"foo bar foobar");
SendKeyEvent(ui::VKEY_LEFT, true, false,
false);
SendKeyEvent(ui::VKEY_LEFT, true, false,
false);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_RIGHT, true, false,
false);
SendKeyEvent(ui::VKEY_Y);
EXPECT_EQ(u"foo xbary foobar", combobox_->GetText());
}
TEST_F(EditableComboboxTest, CtrlLeftOrRightMovesToBeginningOrEndOfText) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_LEFT, false, false,
true);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_RIGHT, false, false,
true);
SendKeyEvent(ui::VKEY_Y);
EXPECT_EQ(u"xabcy", combobox_->GetText());
}
#else
TEST_F(EditableComboboxTest, AltLeftOrRightDoesNothing) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_LEFT, true, false,
false);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_LEFT);
SendKeyEvent(ui::VKEY_RIGHT, true, false,
false);
SendKeyEvent(ui::VKEY_Y);
EXPECT_EQ(u"abcyx", combobox_->GetText());
}
TEST_F(EditableComboboxTest, CtrlLeftOrRightMovesToNextWords) {
InitEditableCombobox();
FocusTextfield();
combobox_->SetText(u"foo bar foobar");
SendKeyEvent(ui::VKEY_LEFT, false, false,
true);
SendKeyEvent(ui::VKEY_LEFT, false, false,
true);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_RIGHT, false, false,
true);
SendKeyEvent(ui::VKEY_Y);
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(u"foo xbar yfoobar", combobox_->GetText());
#else
EXPECT_EQ(u"foo xbary foobar", combobox_->GetText());
#endif
}
#endif
TEST_F(EditableComboboxTest, ShiftLeftOrRightSelectsCharInTextfield) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_LEFT, false, true,
false);
SendKeyEvent(ui::VKEY_X);
SendKeyEvent(ui::VKEY_LEFT);
SendKeyEvent(ui::VKEY_LEFT);
SendKeyEvent(ui::VKEY_RIGHT, false, true,
false);
SendKeyEvent(ui::VKEY_Y);
EXPECT_EQ(u"ayx", combobox_->GetText());
}
TEST_F(EditableComboboxTest, EnterClosesMenuWhileSelectingHighlightedMenuItem) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(IsMenuOpen());
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
EXPECT_EQ(u"item[0]", combobox_->GetText());
}
#if BUILDFLAG(IS_WIN)
#define MAYBE_F4ClosesMenuWhileSelectingHighlightedMenuItem \
DISABLED_F4ClosesMenuWhileSelectingHighlightedMenuItem
#else
#define MAYBE_F4ClosesMenuWhileSelectingHighlightedMenuItem \
F4ClosesMenuWhileSelectingHighlightedMenuItem
#endif
TEST_F(EditableComboboxTest,
MAYBE_F4ClosesMenuWhileSelectingHighlightedMenuItem) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(IsMenuOpen());
SendKeyEvent(ui::VKEY_F4);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
EXPECT_EQ(u"item[0]", combobox_->GetText());
}
TEST_F(EditableComboboxTest, EscClosesMenuWithoutSelectingHighlightedMenuItem) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(IsMenuOpen());
SendKeyEvent(ui::VKEY_ESCAPE);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
EXPECT_EQ(u"a", combobox_->GetText());
}
TEST_F(EditableComboboxTest, TypingInTextfieldUnhighlightsMenuItem) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_B);
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_C);
SendKeyEvent(ui::VKEY_RETURN);
EXPECT_EQ(u"abc", combobox_->GetText());
}
TEST_F(EditableComboboxTest, SpaceIsReflectedInTextfield) {
InitEditableCombobox();
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_SPACE);
SendKeyEvent(ui::VKEY_SPACE);
SendKeyEvent(ui::VKEY_B);
EXPECT_EQ(u"a b", combobox_->GetText());
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
#define MAYBE_MenuCanAdaptToContentChange DISABLED_MenuCanAdaptToContentChange
#else
#define MAYBE_MenuCanAdaptToContentChange MenuCanAdaptToContentChange
#endif
TEST_F(EditableComboboxTest, MAYBE_MenuCanAdaptToContentChange) {
std::vector<std::u16string> items = {u"abc", u"abd", u"bac", u"bad"};
InitEditableCombobox(items, true);
ClickArrow();
ASSERT_TRUE(IsMenuOpen());
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_EQ(u"abc", combobox_->GetText());
SendKeyEvent(ui::VKEY_BACK);
SendKeyEvent(ui::VKEY_BACK);
SendKeyEvent(ui::VKEY_BACK);
MenuRunner* menu_runner1 = GetMenuRunner();
SendKeyEvent(ui::VKEY_B);
MenuRunner* menu_runner2 = GetMenuRunner();
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_EQ(u"bac", combobox_->GetText());
EXPECT_EQ(menu_runner1, menu_runner2);
}
#if BUILDFLAG(IS_LINUX)
#define MAYBE_RefocusingReopensMenuBasedOnLatestContent \
DISABLED_RefocusingReopensMenuBasedOnLatestContent
#else
#define MAYBE_RefocusingReopensMenuBasedOnLatestContent \
RefocusingReopensMenuBasedOnLatestContent
#endif
TEST_F(EditableComboboxTest, MAYBE_RefocusingReopensMenuBasedOnLatestContent) {
std::vector<std::u16string> items = {u"abc", u"abd", u"bac", u"bad", u"bac2"};
InitEditableCombobox(items, true);
FocusTextfield();
SendKeyEvent(ui::VKEY_B);
ASSERT_EQ(3u, GetItemCount());
SendKeyEvent(ui::VKEY_DOWN);
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
EXPECT_EQ(u"bac", combobox_->GetText());
dummy_focusable_view_->RequestFocus();
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
ASSERT_EQ(2u, GetItemCount());
}
TEST_F(EditableComboboxTest, GetItemsWithoutFiltering) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, false, true);
combobox_->SetText(u"z");
ASSERT_EQ(2u, GetItemCount());
ASSERT_EQ(u"item0", GetItemAt(0));
ASSERT_EQ(u"item1", GetItemAt(1));
}
TEST_F(EditableComboboxTest, FilteringEffectOnGetItems) {
std::vector<std::u16string> items = {u"abc", u"abd", u"bac", u"bad"};
InitEditableCombobox(items, true, true);
ASSERT_EQ(4u, GetItemCount());
ASSERT_EQ(u"abc", GetItemAt(0));
ASSERT_EQ(u"abd", GetItemAt(1));
ASSERT_EQ(u"bac", GetItemAt(2));
ASSERT_EQ(u"bad", GetItemAt(3));
combobox_->SetText(u"b");
ASSERT_EQ(2u, GetItemCount());
ASSERT_EQ(u"bac", GetItemAt(0));
ASSERT_EQ(u"bad", GetItemAt(1));
combobox_->SetText(u"bc");
ASSERT_EQ(0u, GetItemCount());
combobox_->SetText(std::u16string());
ASSERT_EQ(4u, GetItemCount());
ASSERT_EQ(u"abc", GetItemAt(0));
ASSERT_EQ(u"abd", GetItemAt(1));
ASSERT_EQ(u"bac", GetItemAt(2));
ASSERT_EQ(u"bad", GetItemAt(3));
}
TEST_F(EditableComboboxTest, FilteringEffectOnIcons) {
ui::SimpleComboboxModel::Item item1(
u"abc", std::u16string(),
ui::ImageModel::FromImage(gfx::test::CreateImage(16, 16)));
ui::SimpleComboboxModel::Item item2(
u"def", std::u16string(),
ui::ImageModel::FromImage(gfx::test::CreateImage(20, 20)));
InitEditableCombobox({item1, item2},
true,
true);
ASSERT_EQ(2u, GetItemCount());
EXPECT_EQ(16, GetIconAt(0, IconSource::kComboboxModel).Size().width());
EXPECT_EQ(20, GetIconAt(1, IconSource::kComboboxModel).Size().width());
combobox_->SetText(u"a");
ASSERT_EQ(1u, GetItemCount());
EXPECT_EQ(16, GetIconAt(0, IconSource::kMenuModel).Size().width());
combobox_->SetText(u"d");
ASSERT_EQ(1u, GetItemCount());
EXPECT_EQ(20, GetIconAt(0, IconSource::kMenuModel).Size().width());
}
TEST_F(EditableComboboxTest, FilteringWithMismatchedCase) {
std::vector<std::u16string> items = {u"AbCd", u"aBcD", u"xyz"};
InitEditableCombobox(items, true, true);
ASSERT_EQ(3u, GetItemCount());
ASSERT_EQ(u"AbCd", GetItemAt(0));
ASSERT_EQ(u"aBcD", GetItemAt(1));
ASSERT_EQ(u"xyz", GetItemAt(2));
combobox_->SetText(u"abcd");
ASSERT_EQ(2u, GetItemCount());
ASSERT_EQ(u"AbCd", GetItemAt(0));
ASSERT_EQ(u"aBcD", GetItemAt(1));
combobox_->SetText(u"ABCD");
ASSERT_EQ(2u, GetItemCount());
ASSERT_EQ(u"AbCd", GetItemAt(0));
ASSERT_EQ(u"aBcD", GetItemAt(1));
}
TEST_F(EditableComboboxTest, DontShowOnEmpty) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, false,
false);
ASSERT_EQ(0u, GetItemCount());
combobox_->SetText(u"a");
ASSERT_EQ(2u, GetItemCount());
ASSERT_EQ(u"item0", GetItemAt(0));
ASSERT_EQ(u"item1", GetItemAt(1));
}
TEST_F(EditableComboboxTest, NoFilteringNotifiesCallback) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, false, true);
ASSERT_EQ(0, change_count());
combobox_->SetText(u"a");
ASSERT_EQ(1, change_count());
combobox_->SetText(u"ab");
ASSERT_EQ(2, change_count());
}
TEST_F(EditableComboboxTest, FilteringNotifiesCallback) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, true, true);
ASSERT_EQ(0, change_count());
combobox_->SetText(u"i");
ASSERT_EQ(1, change_count());
combobox_->SetText(u"ix");
ASSERT_EQ(2, change_count());
combobox_->SetText(u"ixy");
ASSERT_EQ(3, change_count());
}
TEST_F(EditableComboboxTest, ArrowButtonOpensAndClosesMenu) {
InitEditableCombobox();
dummy_focusable_view_->RequestFocus();
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
ClickArrow();
EXPECT_TRUE(IsMenuOpen());
ClickArrow();
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
}
TEST_F(EditableComboboxTest, ShowContextMenuOnMouseRelease) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, false,
true);
EXPECT_FALSE(IsMenuOpen());
TestContextMenuController context_menu_controller;
SetContextMenuController(&context_menu_controller);
const gfx::Point textfield_point(combobox_->x() + 1, combobox_->y() + 1);
ui::MouseEvent click_mouse_event(
ui::EventType::kMousePressed, textfield_point, textfield_point,
ui::EventTimeForNow(), ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON);
widget_->OnMouseEvent(&click_mouse_event);
EXPECT_FALSE(IsMenuOpen());
ui::MouseEvent release_mouse_event(
ui::EventType::kMouseReleased, textfield_point, textfield_point,
ui::EventTimeForNow(), ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON);
widget_->OnMouseEvent(&release_mouse_event);
EXPECT_FALSE(IsMenuOpen());
EXPECT_TRUE(context_menu_controller.opened_menu());
}
TEST_F(EditableComboboxTest, DragToSelectDoesntOpenTheMenu) {
std::vector<std::u16string> items = {u"item0", u"item1"};
InitEditableCombobox(items, false,
true);
combobox_->SetText(u"abc");
dummy_focusable_view_->RequestFocus();
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
const int kCursorXStart = 0;
const int kCursorXEnd = combobox_->x() + combobox_->width();
const int kCursorY = combobox_->y() + 1;
gfx::Point start_point(kCursorXStart, kCursorY);
gfx::Point end_point(kCursorXEnd, kCursorY);
PerformMouseEvent(widget_.get(), start_point, ui::EventType::kMousePressed);
EXPECT_TRUE(GetSelectedText().empty());
DragMouseTo(end_point);
ASSERT_EQ(u"abc", GetSelectedText());
EXPECT_FALSE(IsMenuOpen());
PerformMouseEvent(widget_.get(), end_point, ui::EventType::kMouseReleased);
ASSERT_EQ(u"abc", GetSelectedText());
EXPECT_FALSE(IsMenuOpen());
}
TEST_F(EditableComboboxTest, AccessibleNameAndRole) {
InitEditableCombobox();
ui::AXNodeData data;
combobox_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kComboBoxGrouping);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
u"abc");
EXPECT_EQ(combobox_->GetViewAccessibility().GetCachedName(), u"abc");
data = ui::AXNodeData();
combobox_->GetViewAccessibility().SetName(u"New name");
combobox_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kName),
u"New name");
EXPECT_EQ(combobox_->GetViewAccessibility().GetCachedName(), u"New name");
}
TEST_F(EditableComboboxTest, AccessibleValue) {
InitEditableCombobox();
ui::AXNodeData data;
combobox_->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetString16Attribute(ax::mojom::StringAttribute::kValue), u"");
FocusTextfield();
SendKeyEvent(ui::VKEY_A);
SendKeyEvent(ui::VKEY_DOWN);
EXPECT_TRUE(IsMenuOpen());
SendKeyEvent(ui::VKEY_RETURN);
WaitForMenuClosureAnimation();
EXPECT_FALSE(IsMenuOpen());
data = ui::AXNodeData();
combobox_->GetViewAccessibility().GetAccessibleNodeData(&data);
ASSERT_TRUE(data.HasStringAttribute(ax::mojom::StringAttribute::kValue));
std::u16string val =
data.GetString16Attribute(ax::mojom::StringAttribute::kValue);
EXPECT_EQ(u"item[0]", val);
}
TEST_F(EditableComboboxTest, AccessibleArrowDefaultActionVerb) {
InitEditableCombobox();
auto* arrow_button = GetArrowButton();
ui::AXNodeData data;
arrow_button->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetDefaultActionVerb(), ax::mojom::DefaultActionVerb::kOpen);
arrow_button->SetEnabled(false);
data = ui::AXNodeData();
arrow_button->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_FALSE(
data.HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb));
arrow_button->SetEnabled(true);
data = ui::AXNodeData();
arrow_button->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.GetDefaultActionVerb(), ax::mojom::DefaultActionVerb::kOpen);
}
using EditableComboboxDefaultTest = ViewsTestBase;
class ConfigurableComboboxModel final : public ui::ComboboxModel {
public:
explicit ConfigurableComboboxModel(bool* destroyed = nullptr)
: destroyed_(destroyed) {
if (destroyed_) {
*destroyed_ = false;
}
}
ConfigurableComboboxModel(ConfigurableComboboxModel&) = delete;
ConfigurableComboboxModel& operator=(const ConfigurableComboboxModel&) =
delete;
~ConfigurableComboboxModel() override {
if (destroyed_) {
*destroyed_ = true;
}
}
size_t GetItemCount() const override { return item_count_; }
std::u16string GetItemAt(size_t index) const override {
DCHECK_LT(index, item_count_);
return base::NumberToString16(index);
}
void SetItemCount(size_t item_count) { item_count_ = item_count; }
private:
const raw_ptr<bool> destroyed_;
size_t item_count_ = 0;
};
TEST_F(EditableComboboxDefaultTest, Default) {
auto combobox = std::make_unique<EditableCombobox>();
EXPECT_EQ(0u, EditableComboboxTest::GetItemCount(combobox.get()));
}
TEST_F(EditableComboboxDefaultTest, SetModel) {
std::unique_ptr<ConfigurableComboboxModel> model =
std::make_unique<ConfigurableComboboxModel>();
model->SetItemCount(42);
auto combobox = std::make_unique<EditableCombobox>();
combobox->SetModel(std::move(model));
EXPECT_EQ(42u, EditableComboboxTest::GetItemCount(combobox.get()));
}
TEST_F(EditableComboboxDefaultTest, SetModelOverwrite) {
bool destroyed_first = false;
bool destroyed_second = false;
{
auto combobox = std::make_unique<EditableCombobox>();
combobox->SetModel(
std::make_unique<ConfigurableComboboxModel>(&destroyed_first));
ASSERT_FALSE(destroyed_first);
combobox->SetModel(
std::make_unique<ConfigurableComboboxModel>(&destroyed_second));
EXPECT_TRUE(destroyed_first);
ASSERT_FALSE(destroyed_second);
}
EXPECT_TRUE(destroyed_second);
}
}