Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / menu / menu_controller_unittest.cc
blob9aa008f83f93c6dbe77eec08f7b9cc2d03bdc19f
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/menu/menu_controller.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "ui/aura/scoped_window_targeter.h"
9 #include "ui/aura/window.h"
10 #include "ui/events/event_handler.h"
11 #include "ui/events/null_event_targeter.h"
12 #include "ui/events/test/event_generator.h"
13 #include "ui/views/controls/menu/menu_item_view.h"
14 #include "ui/views/controls/menu/submenu_view.h"
15 #include "ui/views/test/views_test_base.h"
17 #if defined(USE_AURA)
18 #include "ui/aura/scoped_window_targeter.h"
19 #include "ui/aura/window.h"
20 #endif
22 #if defined(USE_X11)
23 #include <X11/Xlib.h>
24 #undef Bool
25 #undef None
26 #include "ui/events/test/events_test_utils_x11.h"
27 #endif
29 namespace views {
30 namespace test {
32 namespace {
34 class SubmenuViewShown : public SubmenuView {
35 public:
36 SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {}
37 ~SubmenuViewShown() override {}
38 bool IsShowing() override { return true; }
40 private:
41 DISALLOW_COPY_AND_ASSIGN(SubmenuViewShown);
44 class TestEventHandler : public ui::EventHandler {
45 public:
46 TestEventHandler() : outstanding_touches_(0) {}
48 void OnTouchEvent(ui::TouchEvent* event) override {
49 switch(event->type()) {
50 case ui::ET_TOUCH_PRESSED:
51 outstanding_touches_++;
52 break;
53 case ui::ET_TOUCH_RELEASED:
54 case ui::ET_TOUCH_CANCELLED:
55 outstanding_touches_--;
56 break;
57 default:
58 break;
62 int outstanding_touches() const { return outstanding_touches_; }
64 private:
65 int outstanding_touches_;
66 DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
69 } // namespace
71 class TestMenuItemViewShown : public MenuItemView {
72 public:
73 TestMenuItemViewShown() : MenuItemView(nullptr) {
74 submenu_ = new SubmenuViewShown(this);
76 ~TestMenuItemViewShown() override {}
78 private:
79 DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown);
82 class MenuControllerTest : public ViewsTestBase {
83 public:
84 MenuControllerTest() : menu_controller_(nullptr) {
87 ~MenuControllerTest() override {}
89 // ViewsTestBase:
90 void SetUp() override {
91 ViewsTestBase::SetUp();
92 Init();
95 void TearDown() override {
96 owner_->CloseNow();
98 menu_controller_->showing_ = false;
99 menu_controller_->owner_ = nullptr;
100 delete menu_controller_;
101 menu_controller_ = nullptr;
103 ViewsTestBase::TearDown();
106 void ReleaseTouchId(int id) {
107 event_generator_->ReleaseTouchId(id);
110 void PressKey(ui::KeyboardCode key_code) {
111 event_generator_->PressKey(key_code, 0);
114 #if defined(OS_LINUX) && defined(USE_X11)
115 void TestEventTargeter() {
117 // With the |ui::NullEventTargeter| instantiated and assigned we expect
118 // the menu to not handle the key event.
119 aura::ScopedWindowTargeter scoped_targeter(
120 owner()->GetNativeWindow()->GetRootWindow(),
121 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter));
122 event_generator_->PressKey(ui::VKEY_ESCAPE, 0);
123 EXPECT_EQ(MenuController::EXIT_NONE, menu_exit_type());
125 // Now that the targeter has been destroyed, expect to exit the menu
126 // normally when hitting escape.
127 event_generator_->PressKey(ui::VKEY_ESCAPE, 0);
128 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type());
130 #endif // defined(OS_LINUX) && defined(USE_X11)
132 protected:
133 void SetPendingStateItem(MenuItemView* item) {
134 menu_controller_->pending_state_.item = item;
135 menu_controller_->pending_state_.submenu_open = true;
138 void ResetSelection() {
139 menu_controller_->SetSelection(
140 nullptr,
141 MenuController::SELECTION_EXIT |
142 MenuController::SELECTION_UPDATE_IMMEDIATELY);
145 void IncrementSelection() {
146 menu_controller_->IncrementSelection(
147 MenuController::INCREMENT_SELECTION_DOWN);
150 void DecrementSelection() {
151 menu_controller_->IncrementSelection(
152 MenuController::INCREMENT_SELECTION_UP);
155 MenuItemView* FindInitialSelectableMenuItemDown(MenuItemView* parent) {
156 return menu_controller_->FindInitialSelectableMenuItem(
157 parent, MenuController::INCREMENT_SELECTION_DOWN);
160 MenuItemView* FindInitialSelectableMenuItemUp(MenuItemView* parent) {
161 return menu_controller_->FindInitialSelectableMenuItem(
162 parent, MenuController::INCREMENT_SELECTION_UP);
165 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
166 int index) {
168 return menu_controller_->FindNextSelectableMenuItem(
169 parent, index, MenuController::INCREMENT_SELECTION_DOWN);
172 MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent,
173 int index) {
174 return menu_controller_->FindNextSelectableMenuItem(
175 parent, index, MenuController::INCREMENT_SELECTION_UP);
178 void RunMenu() {
179 menu_controller_->RunMessageLoop(false);
182 Widget* owner() { return owner_.get(); }
183 ui::test::EventGenerator* event_generator() { return event_generator_.get(); }
184 TestMenuItemViewShown* menu_item() { return menu_item_.get(); }
185 MenuController* menu_controller() { return menu_controller_; }
186 const MenuItemView* pending_state_item() const {
187 return menu_controller_->pending_state_.item;
189 MenuController::ExitType menu_exit_type() const {
190 return menu_controller_->exit_type_;
193 private:
194 void Init() {
195 owner_.reset(new Widget);
196 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
197 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
198 owner_->Init(params);
199 event_generator_.reset(
200 new ui::test::EventGenerator(GetContext(), owner_->GetNativeWindow()));
201 owner_->Show();
203 SetupMenuItem();
205 SetupMenuController();
208 void SetupMenuItem() {
209 menu_item_.reset(new TestMenuItemViewShown);
210 menu_item_->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One"));
211 menu_item_->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two"));
212 menu_item_->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three"));
213 menu_item_->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four"));
216 void SetupMenuController() {
217 menu_controller_= new MenuController(nullptr, true, nullptr);
218 menu_controller_->owner_ = owner_.get();
219 menu_controller_->showing_ = true;
220 menu_controller_->SetSelection(
221 menu_item_.get(), MenuController::SELECTION_UPDATE_IMMEDIATELY);
224 scoped_ptr<Widget> owner_;
225 scoped_ptr<ui::test::EventGenerator> event_generator_;
226 scoped_ptr<TestMenuItemViewShown> menu_item_;
227 MenuController* menu_controller_;
229 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest);
232 #if defined(OS_LINUX) && defined(USE_X11)
233 // Tests that an event targeter which blocks events will be honored by the menu
234 // event dispatcher.
235 TEST_F(MenuControllerTest, EventTargeter) {
236 base::MessageLoopForUI::current()->PostTask(
237 FROM_HERE,
238 base::Bind(&MenuControllerTest::TestEventTargeter,
239 base::Unretained(this)));
240 RunMenu();
242 #endif // defined(OS_LINUX) && defined(USE_X11)
244 #if defined(USE_X11)
245 // Tests that touch event ids are released correctly. See crbug.com/439051 for
246 // details. When the ids aren't managed correctly, we get stuck down touches.
247 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) {
248 TestEventHandler test_event_handler;
249 owner()->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler(
250 &test_event_handler);
252 std::vector<int> devices;
253 devices.push_back(1);
254 ui::SetUpTouchDevicesForTest(devices);
256 event_generator()->PressTouchId(0);
257 event_generator()->PressTouchId(1);
258 event_generator()->ReleaseTouchId(0);
260 base::MessageLoopForUI::current()->PostTask(
261 FROM_HERE,
262 base::Bind(&MenuControllerTest::ReleaseTouchId,
263 base::Unretained(this),
264 1));
266 base::MessageLoopForUI::current()->PostTask(
267 FROM_HERE,
268 base::Bind(&MenuControllerTest::PressKey,
269 base::Unretained(this),
270 ui::VKEY_ESCAPE));
272 RunMenu();
274 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type());
275 EXPECT_EQ(0, test_event_handler.outstanding_touches());
277 owner()->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler(
278 &test_event_handler);
280 #endif // defined(USE_X11)
282 // Tests that initial selected menu items are correct when items are enabled or
283 // disabled.
284 TEST_F(MenuControllerTest, InitialSelectedItem) {
285 // Leave items "Two", "Three", and "Four" enabled.
286 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false);
287 // The first selectable item should be item "Two".
288 MenuItemView* first_selectable =
289 FindInitialSelectableMenuItemDown(menu_item());
290 ASSERT_NE(nullptr, first_selectable);
291 EXPECT_EQ(2, first_selectable->GetCommand());
292 // The last selectable item should be item "Four".
293 MenuItemView* last_selectable =
294 FindInitialSelectableMenuItemUp(menu_item());
295 ASSERT_NE(nullptr, last_selectable);
296 EXPECT_EQ(4, last_selectable->GetCommand());
298 // Leave items "One" and "Two" enabled.
299 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true);
300 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true);
301 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
302 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
303 // The first selectable item should be item "One".
304 first_selectable = FindInitialSelectableMenuItemDown(menu_item());
305 ASSERT_NE(nullptr, first_selectable);
306 EXPECT_EQ(1, first_selectable->GetCommand());
307 // The last selectable item should be item "Two".
308 last_selectable = FindInitialSelectableMenuItemUp(menu_item());
309 ASSERT_NE(nullptr, last_selectable);
310 EXPECT_EQ(2, last_selectable->GetCommand());
312 // Leave only a single item "One" enabled.
313 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true);
314 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false);
315 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
316 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
317 // The first selectable item should be item "One".
318 first_selectable = FindInitialSelectableMenuItemDown(menu_item());
319 ASSERT_NE(nullptr, first_selectable);
320 EXPECT_EQ(1, first_selectable->GetCommand());
321 // The last selectable item should be item "One".
322 last_selectable = FindInitialSelectableMenuItemUp(menu_item());
323 ASSERT_NE(nullptr, last_selectable);
324 EXPECT_EQ(1, last_selectable->GetCommand());
326 // Leave only a single item "Three" enabled.
327 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false);
328 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false);
329 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(true);
330 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
331 // The first selectable item should be item "Three".
332 first_selectable = FindInitialSelectableMenuItemDown(menu_item());
333 ASSERT_NE(nullptr, first_selectable);
334 EXPECT_EQ(3, first_selectable->GetCommand());
335 // The last selectable item should be item "Three".
336 last_selectable = FindInitialSelectableMenuItemUp(menu_item());
337 ASSERT_NE(nullptr, last_selectable);
338 EXPECT_EQ(3, last_selectable->GetCommand());
340 // Leave only a single item ("Two") selected. It should be the first and the
341 // last selectable item.
342 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false);
343 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true);
344 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
345 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
346 first_selectable = FindInitialSelectableMenuItemDown(menu_item());
347 ASSERT_NE(nullptr, first_selectable);
348 EXPECT_EQ(2, first_selectable->GetCommand());
349 last_selectable = FindInitialSelectableMenuItemUp(menu_item());
350 ASSERT_NE(nullptr, last_selectable);
351 EXPECT_EQ(2, last_selectable->GetCommand());
353 // There should be no next or previous selectable item since there is only a
354 // single enabled item in the menu.
355 EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item(), 1));
356 EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item(), 1));
358 // Clear references in menu controller to the menu item that is going away.
359 ResetSelection();
362 // Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled
363 // items.
364 TEST_F(MenuControllerTest, NextSelectedItem) {
365 // Disabling the item "Three" gets it skipped when using keyboard to navigate.
366 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
368 // Fake initial hot selection.
369 SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0));
370 EXPECT_EQ(1, pending_state_item()->GetCommand());
372 // Move down in the menu.
373 // Select next item.
374 IncrementSelection();
375 EXPECT_EQ(2, pending_state_item()->GetCommand());
377 // Skip disabled item.
378 IncrementSelection();
379 EXPECT_EQ(4, pending_state_item()->GetCommand());
381 // Wrap around.
382 IncrementSelection();
383 EXPECT_EQ(1, pending_state_item()->GetCommand());
385 // Move up in the menu.
386 // Wrap around.
387 DecrementSelection();
388 EXPECT_EQ(4, pending_state_item()->GetCommand());
390 // Skip disabled item.
391 DecrementSelection();
392 EXPECT_EQ(2, pending_state_item()->GetCommand());
394 // Select previous item.
395 DecrementSelection();
396 EXPECT_EQ(1, pending_state_item()->GetCommand());
398 // Clear references in menu controller to the menu item that is going away.
399 ResetSelection();
402 // Tests that opening menu and pressing 'Up' selects the last enabled menu item.
403 TEST_F(MenuControllerTest, PreviousSelectedItem) {
404 // Disabling the item "Four" gets it skipped when using keyboard to navigate.
405 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false);
407 // Fake initial root item selection and submenu showing.
408 SetPendingStateItem(menu_item());
409 EXPECT_EQ(0, pending_state_item()->GetCommand());
411 // Move up and select a previous (in our case the last enabled) item.
412 DecrementSelection();
413 EXPECT_EQ(3, pending_state_item()->GetCommand());
415 // Clear references in menu controller to the menu item that is going away.
416 ResetSelection();
419 } // namespace test
420 } // namespace views