OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/views/controls/menu/menu_controller.h" | 5 #include "ui/views/controls/menu/menu_controller.h" |
6 | 6 |
7 #include "base/run_loop.h" | |
8 #include "base/strings/utf_string_conversions.h" | 7 #include "base/strings/utf_string_conversions.h" |
8 #include "ui/aura/scoped_window_targeter.h" | |
9 #include "ui/aura/window.h" | |
9 #include "ui/events/event_handler.h" | 10 #include "ui/events/event_handler.h" |
10 #include "ui/events/null_event_targeter.h" | 11 #include "ui/events/null_event_targeter.h" |
11 #include "ui/events/platform/platform_event_source.h" | 12 #include "ui/events/test/event_generator.h" |
12 #include "ui/events/test/platform_event_source_test_api.h" | |
13 #include "ui/views/controls/menu/menu_item_view.h" | 13 #include "ui/views/controls/menu/menu_item_view.h" |
14 #include "ui/views/controls/menu/submenu_view.h" | 14 #include "ui/views/controls/menu/submenu_view.h" |
15 #include "ui/views/test/views_test_base.h" | 15 #include "ui/views/test/views_test_base.h" |
16 | 16 |
17 #if defined(USE_AURA) | 17 #if defined(USE_AURA) |
18 #include "ui/aura/env.h" | |
19 #include "ui/aura/scoped_window_targeter.h" | 18 #include "ui/aura/scoped_window_targeter.h" |
20 #include "ui/aura/window.h" | 19 #include "ui/aura/window.h" |
21 #include "ui/wm/public/dispatcher_client.h" | |
22 #endif | 20 #endif |
23 | 21 |
24 #if defined(OS_WIN) | 22 #if defined(USE_X11) |
25 #include "base/message_loop/message_pump_dispatcher.h" | |
26 #elif defined(USE_X11) | |
27 #include <X11/Xlib.h> | 23 #include <X11/Xlib.h> |
28 #undef Bool | 24 #undef Bool |
29 #undef None | 25 #undef None |
30 #include "ui/events/test/events_test_utils_x11.h" | 26 #include "ui/events/test/events_test_utils_x11.h" |
31 #elif defined(USE_OZONE) | |
32 #include "ui/events/event.h" | |
33 #elif defined(OS_MACOSX) | |
34 #include "ui/events/test/event_generator.h" | |
35 #endif | 27 #endif |
36 | 28 |
37 namespace views { | 29 namespace views { |
38 namespace test { | 30 namespace test { |
39 | 31 |
40 namespace { | 32 namespace { |
41 | 33 |
42 class TestMenuItemView : public MenuItemView { | |
43 public: | |
44 TestMenuItemView() : MenuItemView(nullptr) {} | |
45 ~TestMenuItemView() override {} | |
46 | |
47 private: | |
48 DISALLOW_COPY_AND_ASSIGN(TestMenuItemView); | |
49 }; | |
50 | |
51 class SubmenuViewShown : public SubmenuView { | 34 class SubmenuViewShown : public SubmenuView { |
52 public: | 35 public: |
53 SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} | 36 SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} |
54 ~SubmenuViewShown() override {} | 37 ~SubmenuViewShown() override {} |
55 bool IsShowing() override { return true; } | 38 bool IsShowing() override { return true; } |
56 | 39 |
57 private: | 40 private: |
58 DISALLOW_COPY_AND_ASSIGN(SubmenuViewShown); | 41 DISALLOW_COPY_AND_ASSIGN(SubmenuViewShown); |
59 }; | 42 }; |
60 | 43 |
61 #if defined(USE_AURA) | 44 class TestEventHandler : public ui::EventHandler { |
62 class TestDispatcherClient : public aura::client::DispatcherClient { | |
63 public: | 45 public: |
64 TestDispatcherClient() : dispatcher_(nullptr) {} | 46 TestEventHandler() : outstanding_touches_(0) {} |
65 ~TestDispatcherClient() override {} | |
66 | 47 |
67 base::MessagePumpDispatcher* dispatcher() { | 48 void OnTouchEvent(ui::TouchEvent* event) override { |
68 return dispatcher_; | 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; | |
59 } | |
69 } | 60 } |
70 | 61 |
71 // aura::client::DispatcherClient: | 62 int outstanding_touches() const { return outstanding_touches_; } |
72 void PrepareNestedLoopClosures(base::MessagePumpDispatcher* dispatcher, | |
73 base::Closure* run_closure, | |
74 base::Closure* quit_closure) override { | |
75 scoped_ptr<base::RunLoop> run_loop(new base::RunLoop()); | |
76 *quit_closure = run_loop->QuitClosure(); | |
77 *run_closure = base::Bind(&TestDispatcherClient::RunNestedDispatcher, | |
78 base::Unretained(this), | |
79 base::Passed(&run_loop), | |
80 dispatcher); | |
81 } | |
82 | 63 |
83 private: | 64 private: |
84 void RunNestedDispatcher(scoped_ptr<base::RunLoop> run_loop, | 65 int outstanding_touches_; |
oshima
2015/09/02 17:08:21
DISALLOW_COPY_AND_ASSIGN
afakhry
2015/09/02 18:18:38
Done.
| |
85 base::MessagePumpDispatcher* dispatcher) { | |
86 base::AutoReset<base::MessagePumpDispatcher*> reset_dispatcher(&dispatcher_, | |
87 dispatcher); | |
88 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | |
89 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | |
90 run_loop->Run(); | |
91 } | |
92 | |
93 base::MessagePumpDispatcher* dispatcher_; | |
94 | |
95 DISALLOW_COPY_AND_ASSIGN(TestDispatcherClient); | |
96 }; | 66 }; |
97 #endif // USE_AURA | |
98 | 67 |
99 } // namespace | 68 } // namespace |
100 | 69 |
70 // This a deleter to enable us to use a scoped_ptr of a MenuController, since | |
71 // otherwise MenuController's destructor is private and cannot be called by the | |
72 // scoped_ptr's DefaulDeleter. | |
73 class MenuControllerTestDeleter { | |
74 public: | |
75 void operator()(MenuController* ptr) const { | |
76 DCHECK(ptr); | |
77 // Must clear the following value before deleting the MenuController, or | |
78 // otherwise the destructor will crash on a DCHECK(); | |
79 ptr->showing_ = false; | |
80 ptr->owner_ = nullptr; | |
81 delete ptr; | |
82 } | |
83 }; | |
84 | |
101 class TestMenuItemViewShown : public MenuItemView { | 85 class TestMenuItemViewShown : public MenuItemView { |
102 public: | 86 public: |
103 TestMenuItemViewShown() : MenuItemView(nullptr) { | 87 TestMenuItemViewShown() : MenuItemView(nullptr) { |
104 submenu_ = new SubmenuViewShown(this); | 88 submenu_ = new SubmenuViewShown(this); |
105 } | 89 } |
106 ~TestMenuItemViewShown() override {} | 90 ~TestMenuItemViewShown() override {} |
107 | 91 |
108 private: | 92 private: |
109 DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); | 93 DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); |
110 }; | 94 }; |
111 | 95 |
112 class MenuControllerTest : public ViewsTestBase { | 96 class MenuControllerTest : public ViewsTestBase { |
113 public: | 97 public: |
114 MenuControllerTest() | 98 MenuControllerTest() : menu_controller_(nullptr, controller_deleter_) { |
115 : controller_(nullptr), | 99 } |
116 event_source_(ui::PlatformEventSource::GetInstance()) {} | 100 |
117 ~MenuControllerTest() override { ResetMenuController(); } | 101 ~MenuControllerTest() override {} |
118 | 102 |
oshima
2015/09/02 17:08:21
// VIewsTestBase:
afakhry
2015/09/02 18:18:38
Done.
| |
119 // Dispatches |count| number of items, each in a separate iteration of the | 103 void SetUp() override { |
120 // message-loop, by posting a task. | 104 ViewsTestBase::SetUp(); |
121 void Step3_DispatchEvents(int count) { | 105 Init(); |
122 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 106 } |
123 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | 107 |
124 controller_->exit_type_ = MenuController::EXIT_ALL; | 108 void TearDown() override { |
125 | 109 owner_->CloseNow(); |
126 DispatchEvent(); | 110 ViewsTestBase::TearDown(); |
127 if (count) { | 111 } |
128 base::MessageLoop::current()->PostTask( | 112 |
129 FROM_HERE, | 113 void ReleaseTouchId(int id) { |
130 base::Bind(&MenuControllerTest::Step3_DispatchEvents, | 114 event_generator_->ReleaseTouchId(id); |
131 base::Unretained(this), | 115 } |
132 count - 1)); | 116 |
133 } else { | 117 void PressKey(ui::KeyboardCode key_code) { |
134 EXPECT_TRUE(run_loop_->running()); | 118 event_generator_->PressKey(key_code, 0); |
135 run_loop_->Quit(); | 119 } |
136 } | 120 |
137 } | 121 #if defined(OS_LINUX) && defined(USE_X11) |
138 | 122 void TestEventTargeter() { |
139 // Runs a nested message-loop that does not involve the menu itself. | 123 // With the |ui::NullEventTargeter| instantiated and assigned we expect the |
140 void Step2_RunNestedLoop() { | 124 // menu to not handle the key event. |
141 base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); | 125 scoped_ptr<aura::ScopedWindowTargeter> scoped_targeter( |
oshima
2015/09/02 17:08:21
do you need to allocate this in heap? can't you ju
afakhry
2015/09/02 18:18:38
It's easier this way because I want to delete it i
oshima
2015/09/02 20:58:54
You can do that in a block, and I think the code w
afakhry
2015/09/03 16:41:42
Done.
| |
142 base::MessageLoop::ScopedNestableTaskAllower allow(loop); | 126 new aura::ScopedWindowTargeter( |
143 base::MessageLoop::current()->PostTask( | 127 owner()->GetNativeWindow()->GetRootWindow(), |
144 FROM_HERE, | 128 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter))); |
145 base::Bind(&MenuControllerTest::Step3_DispatchEvents, | 129 event_generator_->PressKey(ui::VKEY_ESCAPE, 0); |
146 base::Unretained(this), | 130 EXPECT_EQ(MenuController::EXIT_NONE, menu_exit_type()); |
147 3)); | 131 |
148 run_loop_.reset(new base::RunLoop()); | 132 // Now remove the targeter and expect to exit the menu normally. |
149 run_loop_->Run(); | 133 scoped_targeter.reset(); |
150 } | 134 event_generator_->PressKey(ui::VKEY_ESCAPE, 0); |
151 | 135 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type()); |
152 void Step1_RunMenu() { | 136 } |
153 base::MessageLoop::current()->PostTask( | 137 #endif // defined(OS_LINUX) && defined(USE_X11) |
154 FROM_HERE, | 138 |
155 base::Bind(&MenuControllerTest::Step2_RunNestedLoop, | 139 protected: |
156 base::Unretained(this))); | 140 void SetPendingStateItem(MenuItemView* item) { |
157 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 141 menu_controller_->pending_state_.item = item; |
158 RunMenu(owner.get()); | 142 menu_controller_->pending_state_.submenu_open = true; |
159 } | 143 } |
160 | 144 |
161 scoped_ptr<Widget> CreateOwnerWidget() { | 145 void ResetSelection() { |
162 scoped_ptr<Widget> widget(new Widget); | 146 menu_controller_->SetSelection( |
147 nullptr, | |
148 MenuController::SELECTION_EXIT | | |
149 MenuController::SELECTION_UPDATE_IMMEDIATELY); | |
150 } | |
151 | |
152 void IncrementSelection() { | |
153 menu_controller_->IncrementSelection( | |
154 MenuController::INCREMENT_SELECTION_DOWN); | |
155 } | |
156 | |
157 void DecrementSelection() { | |
158 menu_controller_->IncrementSelection( | |
159 MenuController::INCREMENT_SELECTION_UP); | |
160 } | |
161 | |
162 MenuItemView* FindInitialSelectableMenuItemDown(MenuItemView* parent) { | |
163 return menu_controller_->FindInitialSelectableMenuItem( | |
164 parent, MenuController::INCREMENT_SELECTION_DOWN); | |
165 } | |
166 | |
167 MenuItemView* FindInitialSelectableMenuItemUp(MenuItemView* parent) { | |
168 return menu_controller_->FindInitialSelectableMenuItem( | |
169 parent, MenuController::INCREMENT_SELECTION_UP); | |
170 } | |
171 | |
172 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, | |
173 int index) { | |
174 | |
175 return menu_controller_->FindNextSelectableMenuItem( | |
176 parent, index, MenuController::INCREMENT_SELECTION_DOWN); | |
177 } | |
178 | |
179 MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, | |
180 int index) { | |
181 return menu_controller_->FindNextSelectableMenuItem( | |
182 parent, index, MenuController::INCREMENT_SELECTION_UP); | |
183 } | |
184 | |
185 void RunMenu() { | |
186 menu_controller_->RunMessageLoop(false); | |
187 } | |
188 | |
189 Widget* owner() { return owner_.get(); } | |
190 ui::test::EventGenerator* event_generator() { return event_generator_.get(); } | |
191 TestMenuItemViewShown* menu_item() { return menu_item_.get(); } | |
192 MenuController* menu_controller() { return menu_controller_.get(); } | |
193 const MenuItemView* pending_state_item() const { | |
194 return menu_controller_->pending_state_.item; | |
195 } | |
196 MenuController::ExitType menu_exit_type() const { | |
197 return menu_controller_->exit_type_; | |
198 } | |
199 | |
200 private: | |
201 void Init() { | |
202 owner_.reset(new Widget); | |
163 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); | 203 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); |
164 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 204 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
165 widget->Init(params); | 205 owner_->Init(params); |
166 widget->Show(); | 206 event_generator_.reset( |
167 | 207 new ui::test::EventGenerator(GetContext(), owner_->GetNativeWindow())); |
168 #if defined(USE_AURA) | 208 owner_->Show(); |
169 aura::client::SetDispatcherClient( | 209 |
170 widget->GetNativeWindow()->GetRootWindow(), &dispatcher_client_); | 210 SetupMenuItem(); |
171 #endif | 211 |
172 return widget.Pass(); | 212 SetupMenuController(); |
173 } | 213 } |
174 | 214 |
175 const MenuItemView* pending_state_item() const { | 215 void SetupMenuItem() { |
176 return controller_->pending_state_.item; | 216 menu_item_.reset(new TestMenuItemViewShown); |
177 } | 217 menu_item_->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); |
178 | 218 menu_item_->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); |
179 void SetPendingStateItem(MenuItemView* item) { | 219 menu_item_->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); |
180 controller_->pending_state_.item = item; | 220 menu_item_->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); |
181 controller_->pending_state_.submenu_open = true; | 221 } |
182 } | 222 |
183 | 223 void SetupMenuController() { |
184 void ResetSelection() { | 224 menu_controller_.reset(new MenuController(nullptr, true, nullptr)); |
185 controller_->SetSelection(nullptr, | 225 menu_controller_->owner_ = owner_.get(); |
186 MenuController::SELECTION_EXIT | | 226 menu_controller_->showing_ = true; |
187 MenuController::SELECTION_UPDATE_IMMEDIATELY); | 227 menu_controller_->SetSelection( |
188 } | 228 menu_item_.get(), MenuController::SELECTION_UPDATE_IMMEDIATELY); |
189 | 229 } |
190 void IncrementSelection() { | 230 |
191 controller_->IncrementSelection(MenuController::INCREMENT_SELECTION_DOWN); | 231 scoped_ptr<Widget> owner_; |
192 } | 232 scoped_ptr<ui::test::EventGenerator> event_generator_; |
193 | 233 scoped_ptr<TestMenuItemViewShown> menu_item_; |
194 void DecrementSelection() { | 234 MenuControllerTestDeleter controller_deleter_; |
oshima
2015/09/02 17:08:21
Don't you need this, do you?
afakhry
2015/09/02 18:18:38
What do you mean? I need this deleter to make sure
oshima
2015/09/02 20:58:54
You already specified the deleter class below, so
afakhry
2015/09/03 16:41:42
Yes you are right. The deleter is not stateful, so
| |
195 controller_->IncrementSelection(MenuController::INCREMENT_SELECTION_UP); | 235 scoped_ptr<MenuController, MenuControllerTestDeleter> menu_controller_; |
196 } | |
197 | |
198 MenuItemView* FindInitialSelectableMenuItemDown(MenuItemView* parent) { | |
199 return controller_->FindInitialSelectableMenuItem( | |
200 parent, MenuController::INCREMENT_SELECTION_DOWN); | |
201 } | |
202 | |
203 MenuItemView* FindInitialSelectableMenuItemUp(MenuItemView* parent) { | |
204 return controller_->FindInitialSelectableMenuItem( | |
205 parent, MenuController::INCREMENT_SELECTION_UP); | |
206 } | |
207 | |
208 MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, | |
209 int index) { | |
210 return controller_->FindNextSelectableMenuItem( | |
211 parent, index, MenuController::INCREMENT_SELECTION_DOWN); | |
212 } | |
213 | |
214 MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, | |
215 int index) { | |
216 return controller_->FindNextSelectableMenuItem( | |
217 parent, index, MenuController::INCREMENT_SELECTION_UP); | |
218 } | |
219 | |
220 void SetupMenu(views::Widget* owner, views::MenuItemView* item) { | |
221 ResetMenuController(); | |
222 controller_ = new MenuController(nullptr, true, nullptr); | |
223 controller_->owner_ = owner; | |
224 controller_->showing_ = true; | |
225 controller_->SetSelection(item, | |
226 MenuController::SELECTION_UPDATE_IMMEDIATELY); | |
227 } | |
228 | |
229 void RunMenu(views::Widget* owner) { | |
230 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | |
231 SetupMenu(owner, menu_item.get()); | |
232 controller_->RunMessageLoop(false); | |
233 } | |
234 | |
235 #if defined(USE_X11) | |
236 void DispatchEscapeAndExpect(MenuController::ExitType exit_type) { | |
237 ui::ScopedXI2Event key_event; | |
238 key_event.InitKeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, 0); | |
239 event_source_.Dispatch(key_event); | |
240 EXPECT_EQ(exit_type, controller_->exit_type()); | |
241 controller_->exit_type_ = MenuController::EXIT_ALL; | |
242 DispatchEvent(); | |
243 } | |
244 | |
245 void DispatchTouch(int evtype, int id) { | |
246 ui::ScopedXI2Event touch_event; | |
247 std::vector<ui::Valuator> valuators; | |
248 touch_event.InitTouchEvent(1, evtype, id, gfx::Point(10, 10), valuators); | |
249 event_source_.Dispatch(touch_event); | |
250 DispatchEvent(); | |
251 } | |
252 #endif | |
253 | |
254 void DispatchEvent() { | |
255 #if defined(USE_X11) | |
256 XEvent xevent; | |
257 memset(&xevent, 0, sizeof(xevent)); | |
258 event_source_.Dispatch(&xevent); | |
259 #elif defined(OS_WIN) | |
260 MSG msg; | |
261 memset(&msg, 0, sizeof(MSG)); | |
262 dispatcher_client_.dispatcher()->Dispatch(msg); | |
263 #elif defined(USE_OZONE) | |
264 ui::KeyEvent event(' ', ui::VKEY_SPACE, ui::EF_NONE); | |
265 event_source_.Dispatch(&event); | |
266 #elif defined(OS_MACOSX) && !defined(USE_AURA) | |
267 // Since this is not an interactive test, on Mac there will be no key | |
268 // window. Any system event will just get ignored, so use the EventGenerator | |
269 // to generate a dummy event. Without Aura, these will be native events. | |
270 gfx::NativeWindow window = controller_->owner()->GetNativeWindow(); | |
271 ui::test::EventGenerator generator(window, window); | |
272 // Send "up", since this will not activate a menu item. But note that the | |
273 // test has already set exit_type_ = EXIT_ALL just before the first call | |
274 // to this function. | |
275 generator.PressKey(ui::VKEY_UP, 0); | |
276 #else | |
277 #error Unsupported platform | |
278 #endif | |
279 } | |
280 | |
281 private: | |
282 void ResetMenuController() { | |
283 if (controller_) { | |
284 // These properties are faked by RunMenu for the purposes of testing and | |
285 // need to be undone before we call the destructor. | |
286 controller_->owner_ = nullptr; | |
287 controller_->showing_ = false; | |
288 delete controller_; | |
289 controller_ = nullptr; | |
290 } | |
291 } | |
292 | |
293 // A weak pointer to the MenuController owned by this class. | |
294 MenuController* controller_; | |
295 scoped_ptr<base::RunLoop> run_loop_; | |
296 ui::test::PlatformEventSourceTestAPI event_source_; | |
297 #if defined(USE_AURA) | |
298 TestDispatcherClient dispatcher_client_; | |
299 #endif | |
300 | 236 |
301 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); | 237 DISALLOW_COPY_AND_ASSIGN(MenuControllerTest); |
302 }; | 238 }; |
303 | 239 |
304 TEST_F(MenuControllerTest, Basic) { | |
305 base::MessageLoop::ScopedNestableTaskAllower allow_nested( | |
306 base::MessageLoop::current()); | |
307 message_loop()->PostTask( | |
308 FROM_HERE, | |
309 base::Bind(&MenuControllerTest::Step1_RunMenu, base::Unretained(this))); | |
310 } | |
311 | 240 |
312 #if defined(OS_LINUX) && defined(USE_X11) | 241 #if defined(OS_LINUX) && defined(USE_X11) |
313 // Tests that an event targeter which blocks events will be honored by the menu | 242 // Tests that an event targeter which blocks events will be honored by the menu |
314 // event dispatcher. | 243 // event dispatcher. |
315 TEST_F(MenuControllerTest, EventTargeter) { | 244 TEST_F(MenuControllerTest, EventTargeter) { |
316 { | 245 base::MessageLoopForUI::current()->PostTask( |
317 // Verify that the menu handles the escape key under normal circumstances. | 246 FROM_HERE, |
oshima
2015/09/02 17:08:21
indent
afakhry
2015/09/02 18:18:38
Done.
| |
318 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 247 base::Bind(&MenuControllerTest::TestEventTargeter, |
319 message_loop()->PostTask( | 248 base::Unretained(this))); |
320 FROM_HERE, | 249 RunMenu(); |
321 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | |
322 base::Unretained(this), | |
323 MenuController::EXIT_OUTERMOST)); | |
324 RunMenu(owner.get()); | |
325 } | |
326 | |
327 { | |
328 // With the NULL targeter instantiated and assigned we expect the menu to | |
329 // not handle the key event. | |
330 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
331 aura::ScopedWindowTargeter scoped_targeter( | |
332 owner->GetNativeWindow()->GetRootWindow(), | |
333 scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter)); | |
334 message_loop()->PostTask( | |
335 FROM_HERE, | |
336 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | |
337 base::Unretained(this), | |
338 MenuController::EXIT_NONE)); | |
339 RunMenu(owner.get()); | |
340 } | |
341 } | 250 } |
342 #endif | 251 #endif // defined(OS_LINUX) && defined(USE_X11) |
343 | 252 |
344 #if defined(USE_X11) | 253 #if defined(USE_X11) |
345 | 254 |
346 class TestEventHandler : public ui::EventHandler { | |
347 public: | |
348 TestEventHandler() : outstanding_touches_(0) {} | |
349 | |
350 void OnTouchEvent(ui::TouchEvent* event) override { | |
351 switch(event->type()) { | |
352 case ui::ET_TOUCH_PRESSED: | |
353 outstanding_touches_++; | |
354 break; | |
355 case ui::ET_TOUCH_RELEASED: | |
356 case ui::ET_TOUCH_CANCELLED: | |
357 outstanding_touches_--; | |
358 break; | |
359 default: | |
360 break; | |
361 } | |
362 } | |
363 | |
364 int outstanding_touches() const { return outstanding_touches_; } | |
365 | |
366 private: | |
367 int outstanding_touches_; | |
368 }; | |
369 | |
370 // Tests that touch event ids are released correctly. See crbug.com/439051 for | 255 // Tests that touch event ids are released correctly. See crbug.com/439051 for |
371 // details. When the ids aren't managed correctly, we get stuck down touches. | 256 // details. When the ids aren't managed correctly, we get stuck down touches. |
372 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { | 257 TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { |
373 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
374 TestEventHandler test_event_handler; | 258 TestEventHandler test_event_handler; |
375 owner->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler( | 259 owner()->GetNativeWindow()->GetRootWindow()->AddPreTargetHandler( |
376 &test_event_handler); | 260 &test_event_handler); |
377 | 261 |
378 std::vector<int> devices; | 262 std::vector<int> devices; |
379 devices.push_back(1); | 263 devices.push_back(1); |
380 ui::SetUpTouchDevicesForTest(devices); | 264 ui::SetUpTouchDevicesForTest(devices); |
381 | 265 |
382 DispatchTouch(XI_TouchBegin, 0); | 266 event_generator()->PressTouchId(0); |
383 DispatchTouch(XI_TouchBegin, 1); | 267 event_generator()->PressTouchId(1); |
384 DispatchTouch(XI_TouchEnd, 0); | 268 event_generator()->ReleaseTouchId(0); |
385 | 269 |
386 message_loop()->PostTask(FROM_HERE, | 270 base::MessageLoopForUI::current()->PostTask( |
387 base::Bind(&MenuControllerTest::DispatchTouch, | |
388 base::Unretained(this), XI_TouchEnd, 1)); | |
389 | |
390 message_loop()->PostTask( | |
391 FROM_HERE, | 271 FROM_HERE, |
392 base::Bind(&MenuControllerTest::DispatchEscapeAndExpect, | 272 base::Bind(&MenuControllerTest::ReleaseTouchId, |
393 base::Unretained(this), MenuController::EXIT_OUTERMOST)); | 273 base::Unretained(this), |
394 | 274 1)); |
395 RunMenu(owner.get()); | 275 |
276 base::MessageLoopForUI::current()->PostTask( | |
277 FROM_HERE, | |
278 base::Bind(&MenuControllerTest::PressKey, | |
279 base::Unretained(this), | |
280 ui::VKEY_ESCAPE)); | |
281 | |
282 RunMenu(); | |
283 | |
284 EXPECT_EQ(MenuController::EXIT_OUTERMOST, menu_exit_type()); | |
396 EXPECT_EQ(0, test_event_handler.outstanding_touches()); | 285 EXPECT_EQ(0, test_event_handler.outstanding_touches()); |
397 | 286 |
398 owner->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler( | 287 owner()->GetNativeWindow()->GetRootWindow()->RemovePreTargetHandler( |
399 &test_event_handler); | 288 &test_event_handler); |
400 } | 289 } |
401 #endif // defined(USE_X11) | 290 |
291 #endif // defined(USE_X11) | |
402 | 292 |
403 // Tests that initial selected menu items are correct when items are enabled or | 293 // Tests that initial selected menu items are correct when items are enabled or |
404 // disabled. | 294 // disabled. |
405 TEST_F(MenuControllerTest, InitialSelectedItem) { | 295 TEST_F(MenuControllerTest, InitialSelectedItem) { |
406 scoped_ptr<Widget> owner(CreateOwnerWidget()); | 296 // Leave items "Two", "Three", and "Four" enabled. |
407 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | 297 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
408 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
409 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
410 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
411 SetupMenu(owner.get(), menu_item.get()); | |
412 | |
413 // Leave items "Two" and "Three" enabled. | |
414 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | |
415 // The first selectable item should be item "Two". | 298 // The first selectable item should be item "Two". |
416 MenuItemView* first_selectable = | 299 MenuItemView* first_selectable = |
417 FindInitialSelectableMenuItemDown(menu_item.get()); | 300 FindInitialSelectableMenuItemDown(menu_item()); |
418 ASSERT_NE(nullptr, first_selectable); | 301 ASSERT_NE(nullptr, first_selectable); |
419 EXPECT_EQ(2, first_selectable->GetCommand()); | 302 EXPECT_EQ(2, first_selectable->GetCommand()); |
420 // The last selectable item should be item "Three". | 303 // The last selectable item should be item "Four". |
421 MenuItemView* last_selectable = | 304 MenuItemView* last_selectable = |
422 FindInitialSelectableMenuItemUp(menu_item.get()); | 305 FindInitialSelectableMenuItemUp(menu_item()); |
423 ASSERT_NE(nullptr, last_selectable); | 306 ASSERT_NE(nullptr, last_selectable); |
424 EXPECT_EQ(3, last_selectable->GetCommand()); | 307 EXPECT_EQ(4, last_selectable->GetCommand()); |
425 | 308 |
426 // Leave items "One" and "Two" enabled. | 309 // Leave items "One" and "Two" enabled. |
427 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); | 310 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); |
428 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); | 311 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); |
429 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 312 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
313 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); | |
430 // The first selectable item should be item "One". | 314 // The first selectable item should be item "One". |
431 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 315 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
432 ASSERT_NE(nullptr, first_selectable); | 316 ASSERT_NE(nullptr, first_selectable); |
433 EXPECT_EQ(1, first_selectable->GetCommand()); | 317 EXPECT_EQ(1, first_selectable->GetCommand()); |
434 // The last selectable item should be item "Two". | 318 // The last selectable item should be item "Two". |
435 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 319 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
436 ASSERT_NE(nullptr, last_selectable); | 320 ASSERT_NE(nullptr, last_selectable); |
437 EXPECT_EQ(2, last_selectable->GetCommand()); | 321 EXPECT_EQ(2, last_selectable->GetCommand()); |
438 | 322 |
439 // Leave only a single item "One" enabled. | 323 // Leave only a single item "One" enabled. |
440 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); | 324 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); |
441 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); | 325 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); |
442 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 326 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
327 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); | |
443 // The first selectable item should be item "One". | 328 // The first selectable item should be item "One". |
444 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 329 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
445 ASSERT_NE(nullptr, first_selectable); | 330 ASSERT_NE(nullptr, first_selectable); |
446 EXPECT_EQ(1, first_selectable->GetCommand()); | 331 EXPECT_EQ(1, first_selectable->GetCommand()); |
447 // The last selectable item should be item "One". | 332 // The last selectable item should be item "One". |
448 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 333 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
449 ASSERT_NE(nullptr, last_selectable); | 334 ASSERT_NE(nullptr, last_selectable); |
450 EXPECT_EQ(1, last_selectable->GetCommand()); | 335 EXPECT_EQ(1, last_selectable->GetCommand()); |
451 | 336 |
452 // Leave only a single item "Three" enabled. | 337 // Leave only a single item "Three" enabled. |
453 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | 338 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
454 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); | 339 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(false); |
455 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(true); | 340 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(true); |
341 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); | |
456 // The first selectable item should be item "Three". | 342 // The first selectable item should be item "Three". |
457 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 343 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); |
458 ASSERT_NE(nullptr, first_selectable); | 344 ASSERT_NE(nullptr, first_selectable); |
459 EXPECT_EQ(3, first_selectable->GetCommand()); | 345 EXPECT_EQ(3, first_selectable->GetCommand()); |
460 // The last selectable item should be item "Three". | 346 // The last selectable item should be item "Three". |
461 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 347 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
462 ASSERT_NE(nullptr, last_selectable); | 348 ASSERT_NE(nullptr, last_selectable); |
463 EXPECT_EQ(3, last_selectable->GetCommand()); | 349 EXPECT_EQ(3, last_selectable->GetCommand()); |
464 | 350 |
465 // Leave only a single item ("Two") selected. It should be the first and the | 351 // Leave only a single item ("Two") selected. It should be the first and the |
466 // last selectable item. | 352 // last selectable item. |
467 menu_item->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); | 353 menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); |
468 menu_item->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); | 354 menu_item()->GetSubmenu()->GetMenuItemAt(1)->SetEnabled(true); |
469 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 355 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
470 first_selectable = FindInitialSelectableMenuItemDown(menu_item.get()); | 356 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
357 first_selectable = FindInitialSelectableMenuItemDown(menu_item()); | |
471 ASSERT_NE(nullptr, first_selectable); | 358 ASSERT_NE(nullptr, first_selectable); |
472 EXPECT_EQ(2, first_selectable->GetCommand()); | 359 EXPECT_EQ(2, first_selectable->GetCommand()); |
473 last_selectable = FindInitialSelectableMenuItemUp(menu_item.get()); | 360 last_selectable = FindInitialSelectableMenuItemUp(menu_item()); |
474 ASSERT_NE(nullptr, last_selectable); | 361 ASSERT_NE(nullptr, last_selectable); |
475 EXPECT_EQ(2, last_selectable->GetCommand()); | 362 EXPECT_EQ(2, last_selectable->GetCommand()); |
476 | 363 |
477 // There should be no next or previous selectable item since there is only a | 364 // There should be no next or previous selectable item since there is only a |
478 // single enabled item in the menu. | 365 // single enabled item in the menu. |
479 EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item.get(), 1)); | 366 EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item(), 1)); |
480 EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item.get(), 1)); | 367 EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item(), 1)); |
481 | 368 |
482 // Clear references in menu controller to the menu item that is going away. | 369 // Clear references in menu controller to the menu item that is going away. |
483 ResetSelection(); | 370 ResetSelection(); |
484 } | 371 } |
485 | 372 |
486 // Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled | 373 // Tests that opening menu and pressing 'Down' and 'Up' iterates over enabled |
487 // items. | 374 // items. |
488 TEST_F(MenuControllerTest, NextSelectedItem) { | 375 TEST_F(MenuControllerTest, NextSelectedItem) { |
489 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
490 scoped_ptr<TestMenuItemView> menu_item(new TestMenuItemView); | |
491 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
492 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
493 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
494 menu_item->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); | |
495 // Disabling the item "Three" gets it skipped when using keyboard to navigate. | 376 // Disabling the item "Three" gets it skipped when using keyboard to navigate. |
496 menu_item->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); | 377 menu_item()->GetSubmenu()->GetMenuItemAt(2)->SetEnabled(false); |
497 | |
498 SetupMenu(owner.get(), menu_item.get()); | |
499 | 378 |
500 // Fake initial hot selection. | 379 // Fake initial hot selection. |
501 SetPendingStateItem(menu_item->GetSubmenu()->GetMenuItemAt(0)); | 380 SetPendingStateItem(menu_item()->GetSubmenu()->GetMenuItemAt(0)); |
502 EXPECT_EQ(1, pending_state_item()->GetCommand()); | 381 EXPECT_EQ(1, pending_state_item()->GetCommand()); |
503 | 382 |
504 // Move down in the menu. | 383 // Move down in the menu. |
505 // Select next item. | 384 // Select next item. |
506 IncrementSelection(); | 385 IncrementSelection(); |
507 EXPECT_EQ(2, pending_state_item()->GetCommand()); | 386 EXPECT_EQ(2, pending_state_item()->GetCommand()); |
508 | 387 |
509 // Skip disabled item. | 388 // Skip disabled item. |
510 IncrementSelection(); | 389 IncrementSelection(); |
511 EXPECT_EQ(4, pending_state_item()->GetCommand()); | 390 EXPECT_EQ(4, pending_state_item()->GetCommand()); |
(...skipping 14 matching lines...) Expand all Loading... | |
526 // Select previous item. | 405 // Select previous item. |
527 DecrementSelection(); | 406 DecrementSelection(); |
528 EXPECT_EQ(1, pending_state_item()->GetCommand()); | 407 EXPECT_EQ(1, pending_state_item()->GetCommand()); |
529 | 408 |
530 // Clear references in menu controller to the menu item that is going away. | 409 // Clear references in menu controller to the menu item that is going away. |
531 ResetSelection(); | 410 ResetSelection(); |
532 } | 411 } |
533 | 412 |
534 // Tests that opening menu and pressing 'Up' selects the last enabled menu item. | 413 // Tests that opening menu and pressing 'Up' selects the last enabled menu item. |
535 TEST_F(MenuControllerTest, PreviousSelectedItem) { | 414 TEST_F(MenuControllerTest, PreviousSelectedItem) { |
536 scoped_ptr<Widget> owner(CreateOwnerWidget()); | |
537 scoped_ptr<TestMenuItemViewShown> menu_item(new TestMenuItemViewShown); | |
538 menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); | |
539 menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); | |
540 menu_item->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("Three")); | |
541 menu_item->AppendMenuItemWithLabel(4, base::ASCIIToUTF16("Four")); | |
542 // Disabling the item "Four" gets it skipped when using keyboard to navigate. | 415 // Disabling the item "Four" gets it skipped when using keyboard to navigate. |
543 menu_item->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); | 416 menu_item()->GetSubmenu()->GetMenuItemAt(3)->SetEnabled(false); |
544 | |
545 SetupMenu(owner.get(), menu_item.get()); | |
546 | 417 |
547 // Fake initial root item selection and submenu showing. | 418 // Fake initial root item selection and submenu showing. |
548 SetPendingStateItem(menu_item.get()); | 419 SetPendingStateItem(menu_item()); |
549 EXPECT_EQ(0, pending_state_item()->GetCommand()); | 420 EXPECT_EQ(0, pending_state_item()->GetCommand()); |
550 | 421 |
551 // Move up and select a previous (in our case the last enabled) item. | 422 // Move up and select a previous (in our case the last enabled) item. |
552 DecrementSelection(); | 423 DecrementSelection(); |
553 EXPECT_EQ(3, pending_state_item()->GetCommand()); | 424 EXPECT_EQ(3, pending_state_item()->GetCommand()); |
554 | 425 |
555 // Clear references in menu controller to the menu item that is going away. | 426 // Clear references in menu controller to the menu item that is going away. |
556 ResetSelection(); | 427 ResetSelection(); |
557 } | 428 } |
558 | 429 |
559 } // namespace test | 430 } // namespace test |
560 } // namespace views | 431 } // namespace views |
OLD | NEW |