Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / ui / views / controls / menu / menu_controller_unittest.cc
blobdd7bb0b393deedbc5704b994bf4d4d2ae8a443c3
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/run_loop.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "ui/aura/scoped_window_targeter.h"
10 #include "ui/aura/window.h"
11 #include "ui/events/event_handler.h"
12 #include "ui/events/null_event_targeter.h"
13 #include "ui/events/platform/platform_event_source.h"
14 #include "ui/views/controls/menu/menu_item_view.h"
15 #include "ui/views/controls/menu/submenu_view.h"
16 #include "ui/views/test/views_test_base.h"
17 #include "ui/wm/public/dispatcher_client.h"
19 #if defined(OS_WIN)
20 #include "base/message_loop/message_pump_dispatcher.h"
21 #elif defined(USE_X11)
22 #include <X11/Xlib.h>
23 #undef Bool
24 #undef None
25 #include "ui/events/devices/x11/device_data_manager_x11.h"
26 #include "ui/events/test/events_test_utils_x11.h"
27 #elif defined(USE_OZONE)
28 #include "ui/events/event.h"
29 #endif
31 namespace views {
33 namespace {
35 class TestMenuItemView : public MenuItemView {
36 public:
37 TestMenuItemView() : MenuItemView(NULL) {}
38 ~TestMenuItemView() override {}
40 private:
41 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView);
44 class TestPlatformEventSource : public ui::PlatformEventSource {
45 public:
46 TestPlatformEventSource() {
47 #if defined(USE_X11)
48 ui::DeviceDataManagerX11::CreateInstance();
49 #endif
51 ~TestPlatformEventSource() override {}
53 uint32_t Dispatch(const ui::PlatformEvent& event) {
54 return DispatchEvent(event);
57 private:
58 DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource);
61 class TestDispatcherClient : public aura::client::DispatcherClient {
62 public:
63 TestDispatcherClient() : dispatcher_(NULL) {}
64 ~TestDispatcherClient() override {}
66 base::MessagePumpDispatcher* dispatcher() {
67 return dispatcher_;
70 // aura::client::DispatcherClient:
71 void PrepareNestedLoopClosures(base::MessagePumpDispatcher* dispatcher,
72 base::Closure* run_closure,
73 base::Closure* quit_closure) override {
74 scoped_ptr<base::RunLoop> run_loop(new base::RunLoop());
75 *quit_closure = run_loop->QuitClosure();
76 *run_closure = base::Bind(&TestDispatcherClient::RunNestedDispatcher,
77 base::Unretained(this),
78 base::Passed(&run_loop),
79 dispatcher);
82 private:
83 void RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop,
84 base::MessagePumpDispatcher* dispatcher) {
85 base::AutoReset<base::MessagePumpDispatcher*> reset_dispatcher(&dispatcher_,
86 dispatcher);
87 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
88 base::MessageLoop::ScopedNestableTaskAllower allow(loop);
89 run_loop->Run();
92 base::MessagePumpDispatcher* dispatcher_;
94 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient);
97 } // namespace
99 class MenuControllerTest : public ViewsTestBase {
100 public:
101 MenuControllerTest() : controller_(NULL) {}
102 ~MenuControllerTest() override { ResetMenuController(); }
104 // Dispatches |count| number of items, each in a separate iteration of the
105 // message-loop, by posting a task.
106 void Step3_DispatchEvents(int count) {
107 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
108 base::MessageLoop::ScopedNestableTaskAllower allow(loop);
109 controller_->exit_type_ = MenuController::EXIT_ALL;
111 DispatchEvent();
112 if (count) {
113 base::MessageLoop::current()->PostTask(
114 FROM_HERE,
115 base::Bind(&MenuControllerTest::Step3_DispatchEvents,
116 base::Unretained(this),
117 count - 1));
118 } else {
119 EXPECT_TRUE(run_loop_->running());
120 run_loop_->Quit();
124 // Runs a nested message-loop that does not involve the menu itself.
125 void Step2_RunNestedLoop() {
126 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
127 base::MessageLoop::ScopedNestableTaskAllower allow(loop);
128 base::MessageLoop::current()->PostTask(
129 FROM_HERE,
130 base::Bind(&MenuControllerTest::Step3_DispatchEvents,
131 base::Unretained(this),
132 3));
133 run_loop_.reset(new base::RunLoop());
134 run_loop_->Run();
137 void Step1_RunMenu() {
138 base::MessageLoop::current()->PostTask(
139 FROM_HERE,
140 base::Bind(&MenuControllerTest::Step2_RunNestedLoop,
141 base::Unretained(this)));
142 scoped_ptr<Widget> owner(CreateOwnerWidget());
143 RunMenu(owner.get());
146 scoped_ptr<Widget> CreateOwnerWidget() {
147 scoped_ptr<Widget> widget(new Widget);
148 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
149 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
150 widget->Init(params);
151 widget->Show();
153 aura::client::SetDispatcherClient(
154 widget->GetNativeWindow()->GetRootWindow(), &dispatcher_client_);
155 return widget.Pass();
158 const MenuItemView* pending_state_item() const {
159 return controller_->pending_state_.item;
162 void SetPendingStateItem(MenuItemView* item) {
163 controller_->pending_state_.item = item;
166 void IncrementSelection(int delta) {
167 controller_->IncrementSelection(delta);
170 MenuItemView* FindFirstSelectableMenuItem(MenuItemView* parent) {
171 return controller_->FindFirstSelectableMenuItem(parent);
174 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent,
175 int index,
176 int delta) {
177 return controller_->FindNextSelectableMenuItem(parent, index, delta);
179 void SetupMenu(views::Widget* owner, views::MenuItemView* item) {
180 ResetMenuController();
181 controller_ = new MenuController(NULL, true, NULL);
182 controller_->owner_ = owner;
183 controller_->showing_ = true;
184 controller_->SetSelection(item,
185 MenuController::SELECTION_UPDATE_IMMEDIATELY);
188 void RunMenu(views::Widget* owner) {
189 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView);
190 SetupMenu(owner, menu_item.get());
191 controller_->RunMessageLoop(false);
194 #if defined(USE_X11)
195 void DispatchEscapeAndExpect(MenuController::ExitType exit_type) {
196 ui::ScopedXI2Event key_event;
197 key_event.InitKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0);
198 event_source_.Dispatch(key_event);
199 EXPECT_EQ(exit_type, controller_->exit_type());
200 controller_->exit_type_ = MenuController::EXIT_ALL;
201 DispatchEvent();
204 void DispatchTouch(int evtype, int id) {
205 ui::ScopedXI2Event touch_event;
206 std::vector<ui::Valuator> valuators;
207 touch_event.InitTouchEvent(1, evtype, id, gfx::Point(10, 10), valuators);
208 event_source_.Dispatch(touch_event);
209 DispatchEvent();
211 #endif
213 void DispatchEvent() {
214 #if defined(USE_X11)
215 XEvent xevent;
216 memset(&xevent, 0, sizeof(xevent));
217 event_source_.Dispatch(&xevent);
218 #elif defined(OS_WIN)
219 MSG msg;
220 memset(&msg, 0, sizeof(MSG));
221 dispatcher_client_.dispatcher()->Dispatch(msg);
222 #elif defined(USE_OZONE)
223 ui::KeyEvent event(' ', ui::VKEY_SPACE, ui::EF_NONE);
224 event_source_.Dispatch(&event);
225 #else
226 #error Unsupported platform
227 #endif
230 private:
231 void ResetMenuController() {
232 if (controller_) {
233 // These properties are faked by RunMenu for the purposes of testing and
234 // need to be undone before we call the destructor.
235 controller_->owner_ = NULL;
236 controller_->showing_ = false;
237 delete controller_;
238 controller_ = NULL;
242 // A weak pointer to the MenuController owned by this class.
243 MenuController* controller_;
244 scoped_ptr<base::RunLoop> run_loop_;
245 TestPlatformEventSource event_source_;
246 TestDispatcherClient dispatcher_client_;
248 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest);
251 TEST_F(MenuControllerTest, Basic) {
252 base::MessageLoop::ScopedNestableTaskAllower allow_nested(
253 base::MessageLoop::current());
254 message_loop()->PostTask(
255 FROM_HERE,
256 base::Bind(&MenuControllerTest::Step1_RunMenu, base::Unretained(this)));
259 #if defined(OS_LINUX) && defined(USE_X11)
260 // Tests that an event targeter which blocks events will be honored by the menu
261 // event dispatcher.
262 TEST_F(MenuControllerTest, EventTargeter) {
264 // Verify that the menu handles the escape key under normal circumstances.
265 scoped_ptr<Widget> owner(CreateOwnerWidget());
266 message_loop()->PostTask(
267 FROM_HERE,
268 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect,
269 base::Unretained(this),
270 MenuController::EXIT_OUTERMOST));
271 RunMenu(owner.get());
275 // With the NULL targeter instantiated and assigned we expect the menu to
276 // not handle the key event.
277 scoped_ptr<Widget> owner(CreateOwnerWidget());
278 aura::ScopedWindowTargeter scoped_targeter(
279 owner->GetNativeWindow()->GetRootWindow(),
280 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter));
281 message_loop()->PostTask(
282 FROM_HERE,
283 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect,
284 base::Unretained(this),
285 MenuController::EXIT_NONE));
286 RunMenu(owner.get());
289 #endif
291 #if defined(USE_X11)
293 class TestEventHandler : public ui::EventHandler {
294 public:
295 TestEventHandler() : outstanding_touches_(0) {}
297 void OnTouchEvent(ui::TouchEvent* event) override {
298 switch(event->type()) {
299 case ui::ET_TOUCH_PRESSED:
300 outstanding_touches_++;
301 break;
302 case ui::ET_TOUCH_RELEASED:
303 case ui::ET_TOUCH_CANCELLED:
304 outstanding_touches_--;
305 break;
306 default:
307 break;
311 int outstanding_touches() const { return outstanding_touches_; }
313 private:
314 int outstanding_touches_;
317 // Tests that touch event ids are released correctly. See
318 // crbug.com/439051 for details. When the ids aren't managed
319 // correctly, we get stuck down touches.
320 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) {
321 scoped_ptr<Widget> owner(CreateOwnerWidget());
322 TestEventHandler test_event_handler;
323 owner->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler(
324 &test_event_handler);
326 std::vector<int> devices;
327 devices.push_back(1);
328 ui::SetUpTouchDevicesForTest(devices);
330 DispatchTouch(XI_TouchBegin, 0);
331 DispatchTouch(XI_TouchBegin, 1);
332 DispatchTouch(XI_TouchEnd, 0);
334 message_loop()->PostTask(FROM_HERE,
335 base::Bind(&MenuControllerTest::DispatchTouch,
336 base::Unretained(this), XI_TouchEnd, 1));
338 message_loop()->PostTask(
339 FROM_HERE,
340 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect,
341 base::Unretained(this), MenuController::EXIT_OUTERMOST));
343 RunMenu(owner.get());
344 EXPECT_EQ(0, test_event_handler.outstanding_touches());
346 owner->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler(
347 &test_event_handler);
349 #endif // defined(USE_X11)
351 TEST_F(MenuControllerTest, FirstSelectedItem) {
352 scoped_ptr<Widget> owner(CreateOwnerWidget());
353 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView);
354 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One"));
355 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two"));
356 // Disabling the item "One" gets it skipped when a menu is first opened.
357 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false);
359 SetupMenu(owner.get(), menu_item.get());
361 // First selectable item should be item "Two".
362 MenuItemView* first_selectable = FindFirstSelectableMenuItem(menu_item.get());
363 EXPECT_EQ(2, first_selectable->GetCommand());
365 // There should be no next or previous selectable item since there is only a
366 // single enabled item in the menu.
367 SetPendingStateItem(first_selectable);
368 EXPECT_EQ(NULL, FindNextSelectableMenuItem(menu_item.get(), 1, 1));
369 EXPECT_EQ(NULL, FindNextSelectableMenuItem(menu_item.get(), 1, -1));
372 TEST_F(MenuControllerTest, NextSelectedItem) {
373 scoped_ptr<Widget> owner(CreateOwnerWidget());
374 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView);
375 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One"));
376 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two"));
377 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three"));
378 menu_item->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four"));
379 // Disabling the item "Three" gets it skipped when using keyboard to navigate.
380 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false);
382 SetupMenu(owner.get(), menu_item.get());
384 // Fake initial hot selection.
385 SetPendingStateItem(menu_item->GetSubmenu()->GetMenuItemAt(0));
386 EXPECT_EQ(1, pending_state_item()->GetCommand());
388 // Move down in the menu.
389 // Select next item.
390 IncrementSelection(1);
391 EXPECT_EQ(2, pending_state_item()->GetCommand());
393 // Skip disabled item.
394 IncrementSelection(1);
395 EXPECT_EQ(4, pending_state_item()->GetCommand());
397 // Wrap around.
398 IncrementSelection(1);
399 EXPECT_EQ(1, pending_state_item()->GetCommand());
401 // Move up in the menu.
402 // Wrap around.
403 IncrementSelection(-1);
404 EXPECT_EQ(4, pending_state_item()->GetCommand());
406 // Skip disabled item.
407 IncrementSelection(-1);
408 EXPECT_EQ(2, pending_state_item()->GetCommand());
410 // Select previous item.
411 IncrementSelection(-1);
412 EXPECT_EQ(1, pending_state_item()->GetCommand());
415 } // namespace views