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"
16 #include "ui/events/event.h"
17 #include "ui/events/event_handler.h"
18 #include "ui/wm/public/drag_drop_client.h"
21 using base::ASCIIToUTF16
;
25 class MenuButtonTest
: public ViewsTestBase
{
27 MenuButtonTest() : widget_(nullptr), button_(nullptr) {}
28 ~MenuButtonTest() override
{}
30 void TearDown() override
{
31 if (widget_
&& !widget_
->IsClosed())
34 ViewsTestBase::TearDown();
37 Widget
* widget() { return widget_
; }
38 MenuButton
* button() { return button_
; }
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
53 void CreateMenuButtonWithMenuButtonListener(
54 MenuButtonListener
* menu_button_listener
) {
55 CreateMenuButton(nullptr, menu_button_listener
);
59 void CreateMenuButton(ButtonListener
* button_listener
,
60 MenuButtonListener
* menu_button_listener
) {
63 const base::string16
label(ASCIIToUTF16("button"));
65 new MenuButton(button_listener
, label
, menu_button_listener
, false);
66 button_
->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
67 widget_
->SetContentsView(button_
);
76 Widget::InitParams params
=
77 CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS
);
78 params
.bounds
= gfx::Rect(0, 0, 200, 200);
79 widget_
->Init(params
);
86 class TestButtonListener
: public ButtonListener
{
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_
; }
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
{
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_
; }
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
{
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
{
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
{
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
,
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
;
183 void OnMouseEvent(ui::MouseEvent
* event
) override
;
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
,
208 ui::DragDropTypes::DragEventSource source
) {
209 if (IsDragDropInProgress())
210 return ui::DragDropTypes::DRAG_NONE
;
211 drag_in_progress_
= true;
212 target_
= root_window
;
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())
236 switch (event
->type()) {
237 case ui::ET_MOUSE_DRAGGED
:
238 DragUpdate(target_
, *event
);
239 event
->StopPropagation();
241 case ui::ET_MOUSE_RELEASED
:
242 Drop(target_
, *event
);
243 event
->StopPropagation();
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
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
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());
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
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)