Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / button / menu_button_unittest.cc
blob021cb922a9b42518619da7b7e30afbf724e7d3cf
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/button/menu_button.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "ui/base/dragdrop/drag_drop_types.h"
10 #include "ui/events/test/event_generator.h"
11 #include "ui/views/controls/button/menu_button_listener.h"
12 #include "ui/views/drag_controller.h"
13 #include "ui/views/test/views_test_base.h"
15 #if defined(USE_AURA)
16 #include "ui/events/event.h"
17 #include "ui/events/event_handler.h"
18 #include "ui/wm/public/drag_drop_client.h"
19 #endif
21 using base::ASCIIToUTF16;
23 namespace views {
25 class MenuButtonTest : public ViewsTestBase {
26 public:
27 MenuButtonTest() : widget_(nullptr), button_(nullptr) {}
28 ~MenuButtonTest() override {}
30 void TearDown() override {
31 if (widget_ && !widget_->IsClosed())
32 widget_->Close();
34 ViewsTestBase::TearDown();
37 Widget* widget() { return widget_; }
38 MenuButton* button() { return button_; }
40 protected:
41 // Creates a MenuButton with no button listener.
42 void CreateMenuButtonWithNoListener() { CreateMenuButton(nullptr, nullptr); }
44 // Creates a MenuButton with a ButtonListener. In this case, the MenuButton
45 // acts like a regular button.
46 void CreateMenuButtonWithButtonListener(ButtonListener* button_listener) {
47 CreateMenuButton(button_listener, nullptr);
50 // Creates a MenuButton with a MenuButtonListener. In this case, when the
51 // MenuButton is pushed, it notifies the MenuButtonListener to open a
52 // drop-down menu.
53 void CreateMenuButtonWithMenuButtonListener(
54 MenuButtonListener* menu_button_listener) {
55 CreateMenuButton(nullptr, menu_button_listener);
58 private:
59 void CreateMenuButton(ButtonListener* button_listener,
60 MenuButtonListener* menu_button_listener) {
61 CreateWidget();
63 const base::string16 label(ASCIIToUTF16("button"));
64 button_ =
65 new MenuButton(button_listener, label, menu_button_listener, false);
66 button_->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
67 widget_->SetContentsView(button_);
69 widget_->Show();
72 void CreateWidget() {
73 DCHECK(!widget_);
75 widget_ = new Widget;
76 Widget::InitParams params =
77 CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
78 params.bounds = gfx::Rect(0, 0, 200, 200);
79 widget_->Init(params);
82 Widget* widget_;
83 MenuButton* button_;
86 class TestButtonListener : public ButtonListener {
87 public:
88 TestButtonListener()
89 : last_sender_(nullptr),
90 last_sender_state_(Button::STATE_NORMAL),
91 last_event_type_(ui::ET_UNKNOWN) {}
92 ~TestButtonListener() override {}
94 void ButtonPressed(Button* sender, const ui::Event& event) override {
95 last_sender_ = sender;
96 CustomButton* custom_button = CustomButton::AsCustomButton(sender);
97 DCHECK(custom_button);
98 last_sender_state_ = custom_button->state();
99 last_event_type_ = event.type();
102 Button* last_sender() { return last_sender_; }
103 Button::ButtonState last_sender_state() { return last_sender_state_; }
104 ui::EventType last_event_type() { return last_event_type_; }
106 private:
107 Button* last_sender_;
108 Button::ButtonState last_sender_state_;
109 ui::EventType last_event_type_;
111 DISALLOW_COPY_AND_ASSIGN(TestButtonListener);
114 class TestMenuButtonListener : public MenuButtonListener {
115 public:
116 TestMenuButtonListener()
117 : last_source_(nullptr), last_source_state_(Button::STATE_NORMAL) {}
118 ~TestMenuButtonListener() override {}
120 void OnMenuButtonClicked(View* source, const gfx::Point& /*point*/) override {
121 last_source_ = source;
122 CustomButton* custom_button = CustomButton::AsCustomButton(source);
123 DCHECK(custom_button);
124 last_source_state_ = custom_button->state();
127 View* last_source() { return last_source_; }
128 Button::ButtonState last_source_state() { return last_source_state_; }
130 private:
131 View* last_source_;
132 Button::ButtonState last_source_state_;
135 // Basic implementation of a DragController, to test input behaviour for
136 // MenuButtons that can be dragged.
137 class TestDragController : public DragController {
138 public:
139 TestDragController() {}
140 ~TestDragController() override {}
142 void WriteDragDataForView(View* sender,
143 const gfx::Point& press_pt,
144 ui::OSExchangeData* data) override {}
146 int GetDragOperationsForView(View* sender, const gfx::Point& p) override {
147 return ui::DragDropTypes::DRAG_MOVE;
150 bool CanStartDragForView(View* sender,
151 const gfx::Point& press_pt,
152 const gfx::Point& p) override {
153 return true;
156 private:
157 DISALLOW_COPY_AND_ASSIGN(TestDragController);
160 #if defined(USE_AURA)
161 // Basic implementation of a DragDropClient, tracking the state of the drag
162 // operation. While dragging addition mouse events are consumed, preventing the
163 // target view from receiving them.
164 class TestDragDropClient : public aura::client::DragDropClient,
165 public ui::EventHandler {
166 public:
167 TestDragDropClient();
168 ~TestDragDropClient() override;
170 // aura::client::DragDropClient:
171 int StartDragAndDrop(const ui::OSExchangeData& data,
172 aura::Window* root_window,
173 aura::Window* source_window,
174 const gfx::Point& screen_location,
175 int operation,
176 ui::DragDropTypes::DragEventSource source) override;
177 void DragUpdate(aura::Window* target, const ui::LocatedEvent& event) override;
178 void Drop(aura::Window* target, const ui::LocatedEvent& event) override;
179 void DragCancel() override;
180 bool IsDragDropInProgress() override;
182 // ui::EventHandler:
183 void OnMouseEvent(ui::MouseEvent* event) override;
185 private:
186 // True while receiving ui::LocatedEvents for drag operations.
187 bool drag_in_progress_;
189 // Target window where drag operations are occuring.
190 aura::Window* target_;
192 DISALLOW_COPY_AND_ASSIGN(TestDragDropClient);
195 TestDragDropClient::TestDragDropClient()
196 : drag_in_progress_(false), target_(nullptr) {
199 TestDragDropClient::~TestDragDropClient() {
202 int TestDragDropClient::StartDragAndDrop(
203 const ui::OSExchangeData& data,
204 aura::Window* root_window,
205 aura::Window* source_window,
206 const gfx::Point& screen_location,
207 int operation,
208 ui::DragDropTypes::DragEventSource source) {
209 if (IsDragDropInProgress())
210 return ui::DragDropTypes::DRAG_NONE;
211 drag_in_progress_ = true;
212 target_ = root_window;
213 return operation;
216 void TestDragDropClient::DragUpdate(aura::Window* target,
217 const ui::LocatedEvent& event) {
220 void TestDragDropClient::Drop(aura::Window* target,
221 const ui::LocatedEvent& event) {
222 drag_in_progress_ = false;
225 void TestDragDropClient::DragCancel() {
226 drag_in_progress_ = false;
229 bool TestDragDropClient::IsDragDropInProgress() {
230 return drag_in_progress_;
233 void TestDragDropClient::OnMouseEvent(ui::MouseEvent* event) {
234 if (!IsDragDropInProgress())
235 return;
236 switch (event->type()) {
237 case ui::ET_MOUSE_DRAGGED:
238 DragUpdate(target_, *event);
239 event->StopPropagation();
240 break;
241 case ui::ET_MOUSE_RELEASED:
242 Drop(target_, *event);
243 event->StopPropagation();
244 break;
245 default:
246 break;
249 #endif // defined(USE_AURA)
251 // Tests if the listener is notified correctly, when a mouse click happens on a
252 // MenuButton that has a regular ButtonListener.
253 TEST_F(MenuButtonTest, ActivateNonDropDownOnMouseClick) {
254 TestButtonListener button_listener;
255 CreateMenuButtonWithButtonListener(&button_listener);
257 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
259 generator.set_current_location(gfx::Point(10, 10));
260 generator.ClickLeftButton();
262 // Check that MenuButton has notified the listener on mouse-released event,
263 // while it was in hovered state.
264 EXPECT_EQ(button(), button_listener.last_sender());
265 EXPECT_EQ(ui::ET_MOUSE_RELEASED, button_listener.last_event_type());
266 EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
269 // Tests if the listener is notified correctly when a mouse click happens on a
270 // MenuButton that has a MenuButtonListener.
271 TEST_F(MenuButtonTest, ActivateDropDownOnMouseClick) {
272 TestMenuButtonListener menu_button_listener;
273 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
275 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
277 generator.set_current_location(gfx::Point(10, 10));
278 generator.ClickLeftButton();
280 // Check that MenuButton has notified the listener, while it was in pressed
281 // state.
282 EXPECT_EQ(button(), menu_button_listener.last_source());
283 EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener.last_source_state());
286 // Test that the MenuButton stays pressed while there are any PressedLocks.
287 TEST_F(MenuButtonTest, MenuButtonPressedLock) {
288 CreateMenuButtonWithNoListener();
290 // Move the mouse over the button; the button should be in a hovered state.
291 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
292 generator.MoveMouseTo(gfx::Point(10, 10));
293 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
295 // Introduce a PressedLock, which should make the button pressed.
296 scoped_ptr<MenuButton::PressedLock> pressed_lock1(
297 new MenuButton::PressedLock(button()));
298 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
300 // Even if we move the mouse outside of the button, it should remain pressed.
301 generator.MoveMouseTo(gfx::Point(300, 10));
302 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
304 // Creating a new lock should obviously keep the button pressed.
305 scoped_ptr<MenuButton::PressedLock> pressed_lock2(
306 new MenuButton::PressedLock(button()));
307 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
309 // The button should remain pressed while any locks are active.
310 pressed_lock1.reset();
311 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
313 // Reseting the final lock should return the button's state to normal...
314 pressed_lock2.reset();
315 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
317 // ...And it should respond to mouse movement again.
318 generator.MoveMouseTo(gfx::Point(10, 10));
319 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
321 // Test that the button returns to the appropriate state after the press; if
322 // the mouse ends over the button, the button should be hovered.
323 pressed_lock1.reset(new MenuButton::PressedLock(button()));
324 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
325 pressed_lock1.reset();
326 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
328 // If the button is disabled before the pressed lock, it should be disabled
329 // after the pressed lock.
330 button()->SetState(Button::STATE_DISABLED);
331 pressed_lock1.reset(new MenuButton::PressedLock(button()));
332 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
333 pressed_lock1.reset();
334 EXPECT_EQ(Button::STATE_DISABLED, button()->state());
336 generator.MoveMouseTo(gfx::Point(300, 10));
338 // Edge case: the button is disabled, a pressed lock is added, and then the
339 // button is re-enabled. It should be enabled after the lock is removed.
340 pressed_lock1.reset(new MenuButton::PressedLock(button()));
341 EXPECT_EQ(Button::STATE_PRESSED, button()->state());
342 button()->SetState(Button::STATE_NORMAL);
343 pressed_lock1.reset();
344 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
347 // Test that the MenuButton does not become pressed if it can be dragged, until
348 // a release occurs.
349 TEST_F(MenuButtonTest, DraggableMenuButtonActivatesOnRelease) {
350 TestMenuButtonListener menu_button_listener;
351 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
352 TestDragController drag_controller;
353 button()->set_drag_controller(&drag_controller);
355 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
357 generator.set_current_location(gfx::Point(10, 10));
358 generator.PressLeftButton();
359 EXPECT_EQ(nullptr, menu_button_listener.last_source());
361 generator.ReleaseLeftButton();
362 EXPECT_EQ(button(), menu_button_listener.last_source());
363 EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener.last_source_state());
366 #if defined(USE_AURA)
368 // Tests that the MenuButton does not become pressed if it can be dragged, and a
369 // DragDropClient is processing the events.
370 TEST_F(MenuButtonTest, DraggableMenuButtonDoesNotActivateOnDrag) {
371 TestMenuButtonListener menu_button_listener;
372 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
373 TestDragController drag_controller;
374 button()->set_drag_controller(&drag_controller);
376 TestDragDropClient drag_client;
377 SetDragDropClient(GetContext(), &drag_client);
378 button()->PrependPreTargetHandler(&drag_client);
380 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
381 generator.set_current_location(gfx::Point(10, 10));
382 generator.DragMouseBy(10, 0);
383 EXPECT_EQ(nullptr, menu_button_listener.last_source());
384 EXPECT_EQ(Button::STATE_NORMAL, menu_button_listener.last_source_state());
387 #endif // USE_AURA
389 // No touch on desktop Mac. Tracked in http://crbug.com/445520.
390 #if !defined(OS_MACOSX) || defined(USE_AURA)
392 // Tests if the listener is notified correctly when a gesture tap happens on a
393 // MenuButton that has a regular ButtonListener.
394 TEST_F(MenuButtonTest, ActivateNonDropDownOnGestureTap) {
395 TestButtonListener button_listener;
396 CreateMenuButtonWithButtonListener(&button_listener);
398 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
399 generator.GestureTapAt(gfx::Point(10, 10));
401 // Check that MenuButton has notified the listener on gesture tap event, while
402 // it was in hovered state.
403 EXPECT_EQ(button(), button_listener.last_sender());
404 EXPECT_EQ(ui::ET_GESTURE_TAP, button_listener.last_event_type());
405 EXPECT_EQ(Button::STATE_HOVERED, button_listener.last_sender_state());
408 // Tests if the listener is notified correctly when a gesture tap happens on a
409 // MenuButton that has a MenuButtonListener.
410 TEST_F(MenuButtonTest, ActivateDropDownOnGestureTap) {
411 TestMenuButtonListener menu_button_listener;
412 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
414 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
415 generator.GestureTapAt(gfx::Point(10, 10));
417 // Check that MenuButton has notified the listener, while it was in pressed
418 // state.
419 EXPECT_EQ(button(), menu_button_listener.last_source());
420 EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener.last_source_state());
423 // Tests that the button enters a hovered state upon a tap down, before becoming
424 // pressed at activation.
425 TEST_F(MenuButtonTest, TouchFeedbackDuringTap) {
426 TestMenuButtonListener menu_button_listener;
427 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
428 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
429 generator.set_current_location(gfx::Point(10, 10));
430 generator.PressTouch();
431 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
433 generator.ReleaseTouch();
434 EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener.last_source_state());
437 // Tests that a move event that exits the button returns it to the normal state,
438 // and that the button did not activate the listener.
439 TEST_F(MenuButtonTest, TouchFeedbackDuringTapCancel) {
440 TestMenuButtonListener menu_button_listener;
441 CreateMenuButtonWithMenuButtonListener(&menu_button_listener);
442 ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
443 generator.set_current_location(gfx::Point(10, 10));
444 generator.PressTouch();
445 EXPECT_EQ(Button::STATE_HOVERED, button()->state());
447 generator.MoveTouch(gfx::Point(10, 30));
448 generator.ReleaseTouch();
449 EXPECT_EQ(Button::STATE_NORMAL, button()->state());
450 EXPECT_EQ(nullptr, menu_button_listener.last_source());
453 #endif // !defined(OS_MACOSX) || defined(USE_AURA)
455 } // namespace views