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 gesture tap happens on a
270 // MenuButton that has a regular ButtonListener.
271 TEST_F(MenuButtonTest
, ActivateNonDropDownOnGestureTap
) {
272 TestButtonListener button_listener
;
273 CreateMenuButtonWithButtonListener(&button_listener
);
275 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
276 generator
.GestureTapAt(gfx::Point(10, 10));
278 // Check that MenuButton has notified the listener on gesture tap event, while
279 // it was in hovered state.
280 EXPECT_EQ(button(), button_listener
.last_sender());
281 EXPECT_EQ(ui::ET_GESTURE_TAP
, button_listener
.last_event_type());
282 EXPECT_EQ(Button::STATE_HOVERED
, button_listener
.last_sender_state());
285 // Tests if the listener is notified correctly when a mouse click happens on a
286 // MenuButton that has a MenuButtonListener.
287 TEST_F(MenuButtonTest
, ActivateDropDownOnMouseClick
) {
288 TestMenuButtonListener menu_button_listener
;
289 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
291 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
293 generator
.set_current_location(gfx::Point(10, 10));
294 generator
.ClickLeftButton();
296 // Check that MenuButton has notified the listener, while it was in pressed
298 EXPECT_EQ(button(), menu_button_listener
.last_source());
299 EXPECT_EQ(Button::STATE_PRESSED
, menu_button_listener
.last_source_state());
302 // Tests if the listener is notified correctly when a gesture tap happens on a
303 // MenuButton that has a MenuButtonListener.
304 TEST_F(MenuButtonTest
, ActivateDropDownOnGestureTap
) {
305 TestMenuButtonListener menu_button_listener
;
306 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
308 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
309 generator
.GestureTapAt(gfx::Point(10, 10));
311 // Check that MenuButton has notified the listener, while it was in pressed
313 EXPECT_EQ(button(), menu_button_listener
.last_source());
314 EXPECT_EQ(Button::STATE_PRESSED
, menu_button_listener
.last_source_state());
317 // Test that the MenuButton stays pressed while there are any PressedLocks.
318 TEST_F(MenuButtonTest
, MenuButtonPressedLock
) {
319 CreateMenuButtonWithNoListener();
321 // Move the mouse over the button; the button should be in a hovered state.
322 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
323 generator
.MoveMouseTo(gfx::Point(10, 10));
324 EXPECT_EQ(Button::STATE_HOVERED
, button()->state());
326 // Introduce a PressedLock, which should make the button pressed.
327 scoped_ptr
<MenuButton::PressedLock
> pressed_lock1(
328 new MenuButton::PressedLock(button()));
329 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
331 // Even if we move the mouse outside of the button, it should remain pressed.
332 generator
.MoveMouseTo(gfx::Point(300, 10));
333 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
335 // Creating a new lock should obviously keep the button pressed.
336 scoped_ptr
<MenuButton::PressedLock
> pressed_lock2(
337 new MenuButton::PressedLock(button()));
338 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
340 // The button should remain pressed while any locks are active.
341 pressed_lock1
.reset();
342 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
344 // Reseting the final lock should return the button's state to normal...
345 pressed_lock2
.reset();
346 EXPECT_EQ(Button::STATE_NORMAL
, button()->state());
348 // ...And it should respond to mouse movement again.
349 generator
.MoveMouseTo(gfx::Point(10, 10));
350 EXPECT_EQ(Button::STATE_HOVERED
, button()->state());
352 // Test that the button returns to the appropriate state after the press; if
353 // the mouse ends over the button, the button should be hovered.
354 pressed_lock1
.reset(new MenuButton::PressedLock(button()));
355 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
356 pressed_lock1
.reset();
357 EXPECT_EQ(Button::STATE_HOVERED
, button()->state());
359 // If the button is disabled before the pressed lock, it should be disabled
360 // after the pressed lock.
361 button()->SetState(Button::STATE_DISABLED
);
362 pressed_lock1
.reset(new MenuButton::PressedLock(button()));
363 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
364 pressed_lock1
.reset();
365 EXPECT_EQ(Button::STATE_DISABLED
, button()->state());
367 generator
.MoveMouseTo(gfx::Point(300, 10));
369 // Edge case: the button is disabled, a pressed lock is added, and then the
370 // button is re-enabled. It should be enabled after the lock is removed.
371 pressed_lock1
.reset(new MenuButton::PressedLock(button()));
372 EXPECT_EQ(Button::STATE_PRESSED
, button()->state());
373 button()->SetState(Button::STATE_NORMAL
);
374 pressed_lock1
.reset();
375 EXPECT_EQ(Button::STATE_NORMAL
, button()->state());
378 // Test that the MenuButton does not become pressed if it can be dragged, until
380 TEST_F(MenuButtonTest
, DraggableMenuButtonActivatesOnRelease
) {
381 TestMenuButtonListener menu_button_listener
;
382 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
383 TestDragController drag_controller
;
384 button()->set_drag_controller(&drag_controller
);
386 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
388 generator
.set_current_location(gfx::Point(10, 10));
389 generator
.PressLeftButton();
390 EXPECT_EQ(nullptr, menu_button_listener
.last_source());
392 generator
.ReleaseLeftButton();
393 EXPECT_EQ(button(), menu_button_listener
.last_source());
394 EXPECT_EQ(Button::STATE_PRESSED
, menu_button_listener
.last_source_state());
397 #if defined(USE_AURA)
398 // Tests that the MenuButton does not become pressed if it can be dragged, and a
399 // DragDropClient is processing the events.
400 TEST_F(MenuButtonTest
, DraggableMenuButtonDoesNotActivateOnDrag
) {
401 TestMenuButtonListener menu_button_listener
;
402 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
403 TestDragController drag_controller
;
404 button()->set_drag_controller(&drag_controller
);
406 TestDragDropClient drag_client
;
407 SetDragDropClient(GetContext(), &drag_client
);
408 button()->PrependPreTargetHandler(&drag_client
);
410 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
411 generator
.set_current_location(gfx::Point(10, 10));
412 generator
.DragMouseBy(10, 0);
413 EXPECT_EQ(nullptr, menu_button_listener
.last_source());
414 EXPECT_EQ(Button::STATE_NORMAL
, menu_button_listener
.last_source_state());
418 // Tests that the button enters a hovered state upon a tap down, before becoming
419 // pressed at activation.
420 TEST_F(MenuButtonTest
, TouchFeedbackDuringTap
) {
421 TestMenuButtonListener menu_button_listener
;
422 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
423 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
424 generator
.set_current_location(gfx::Point(10, 10));
425 generator
.PressTouch();
426 EXPECT_EQ(Button::STATE_HOVERED
, button()->state());
428 generator
.ReleaseTouch();
429 EXPECT_EQ(Button::STATE_PRESSED
, menu_button_listener
.last_source_state());
432 // Tests that a move event that exits the button returns it to the normal state,
433 // and that the button did not activate the listener.
434 TEST_F(MenuButtonTest
, TouchFeedbackDuringTapCancel
) {
435 TestMenuButtonListener menu_button_listener
;
436 CreateMenuButtonWithMenuButtonListener(&menu_button_listener
);
437 ui::test::EventGenerator
generator(GetContext(), widget()->GetNativeWindow());
438 generator
.set_current_location(gfx::Point(10, 10));
439 generator
.PressTouch();
440 EXPECT_EQ(Button::STATE_HOVERED
, button()->state());
442 generator
.MoveTouch(gfx::Point(10, 30));
443 generator
.ReleaseTouch();
444 EXPECT_EQ(Button::STATE_NORMAL
, button()->state());
445 EXPECT_EQ(nullptr, menu_button_listener
.last_source());