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.
4
5#include "ui/views/controls/button/menu_button.h"
6
7#include "base/memory/scoped_ptr.h"
8#include "base/strings/utf_string_conversions.h"
9#include "ui/events/test/event_generator.h"
10#include "ui/views/controls/button/menu_button_listener.h"
11#include "ui/views/test/views_test_base.h"
12
13using base::ASCIIToUTF16;
14
15namespace views {
16
17class MenuButtonTest : public ViewsTestBase {
18 public:
19  MenuButtonTest() : widget_(NULL), button_(NULL) {}
20  virtual ~MenuButtonTest() {}
21
22  virtual void TearDown() OVERRIDE {
23    if (widget_ && !widget_->IsClosed())
24      widget_->Close();
25
26    ViewsTestBase::TearDown();
27  }
28
29  Widget* widget() { return widget_; }
30  MenuButton* button() { return button_; }
31
32 protected:
33  // Creates a MenuButton with no button listener.
34  void CreateMenuButtonWithNoListener() {
35    CreateMenuButton(NULL, NULL);
36  }
37
38  // Creates a MenuButton with a ButtonListener. In this case, the MenuButton
39  // acts like a regular button.
40  void CreateMenuButtonWithButtonListener(ButtonListener* button_listener) {
41    CreateMenuButton(button_listener, NULL);
42  }
43
44  // Creates a MenuButton with a MenuButtonListener. In this case, when the
45  // MenuButton is pushed, it notifies the MenuButtonListener to open a
46  // drop-down menu.
47  void CreateMenuButtonWithMenuButtonListener(
48      MenuButtonListener* menu_button_listener) {
49    CreateMenuButton(NULL, menu_button_listener);
50  }
51
52 private:
53  void CreateMenuButton(ButtonListener* button_listener,
54                        MenuButtonListener* menu_button_listener) {
55    CreateWidget();
56
57    const base::string16 label(ASCIIToUTF16("button"));
58    button_ =
59        new MenuButton(button_listener, label, menu_button_listener, false);
60    button_->SetBoundsRect(gfx::Rect(0, 0, 200, 20));
61    widget_->SetContentsView(button_);
62
63    widget_->Show();
64  }
65
66  void CreateWidget() {
67    DCHECK(!widget_);
68
69    widget_ = new Widget;
70    Widget::InitParams params =
71        CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
72    params.bounds = gfx::Rect(0, 0, 200, 200);
73    widget_->Init(params);
74  }
75
76  Widget* widget_;
77  MenuButton* button_;
78};
79
80class TestButtonListener : public ButtonListener {
81 public:
82  TestButtonListener()
83      : last_sender_(NULL),
84        last_sender_state_(Button::STATE_NORMAL),
85        last_event_type_(ui::ET_UNKNOWN) {}
86  virtual ~TestButtonListener() {}
87
88  virtual void ButtonPressed(Button* sender, const ui::Event& event) OVERRIDE {
89    last_sender_ = sender;
90    CustomButton* custom_button = CustomButton::AsCustomButton(sender);
91    DCHECK(custom_button);
92    last_sender_state_ = custom_button->state();
93    last_event_type_ = event.type();
94  }
95
96  Button* last_sender() { return last_sender_; }
97  Button::ButtonState last_sender_state() { return last_sender_state_; }
98  ui::EventType last_event_type() { return last_event_type_; }
99
100 private:
101  Button* last_sender_;
102  Button::ButtonState last_sender_state_;
103  ui::EventType last_event_type_;
104
105  DISALLOW_COPY_AND_ASSIGN(TestButtonListener);
106};
107
108class TestMenuButtonListener : public MenuButtonListener {
109 public:
110  TestMenuButtonListener() {}
111  virtual ~TestMenuButtonListener() {}
112
113  virtual void OnMenuButtonClicked(View* source,
114                                   const gfx::Point& /*point*/) OVERRIDE {
115    last_source_ = source;
116    CustomButton* custom_button = CustomButton::AsCustomButton(source);
117    DCHECK(custom_button);
118    last_source_state_ = custom_button->state();
119  }
120
121  View* last_source() { return last_source_; }
122  Button::ButtonState last_source_state() { return last_source_state_; }
123
124 private:
125  View* last_source_;
126  Button::ButtonState last_source_state_;
127};
128
129// Tests if the listener is notified correctly, when a mouse click happens on a
130// MenuButton that has a regular ButtonListener.
131TEST_F(MenuButtonTest, ActivateNonDropDownOnMouseClick) {
132  scoped_ptr<TestButtonListener> button_listener(new TestButtonListener);
133  CreateMenuButtonWithButtonListener(button_listener.get());
134
135  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
136
137  generator.set_current_location(gfx::Point(10, 10));
138  generator.ClickLeftButton();
139
140  // Check that MenuButton has notified the listener on mouse-released event,
141  // while it was in hovered state.
142  EXPECT_EQ(button(), button_listener->last_sender());
143  EXPECT_EQ(ui::ET_MOUSE_RELEASED, button_listener->last_event_type());
144  EXPECT_EQ(Button::STATE_HOVERED, button_listener->last_sender_state());
145}
146
147// Tests if the listener is notified correctly when a gesture tap happens on a
148// MenuButton that has a regular ButtonListener.
149TEST_F(MenuButtonTest, ActivateNonDropDownOnGestureTap) {
150  scoped_ptr<TestButtonListener> button_listener(new TestButtonListener);
151  CreateMenuButtonWithButtonListener(button_listener.get());
152
153  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
154  generator.GestureTapAt(gfx::Point(10, 10));
155
156  // Check that MenuButton has notified the listener on gesture tap event, while
157  // it was in hovered state.
158  EXPECT_EQ(button(), button_listener->last_sender());
159  EXPECT_EQ(ui::ET_GESTURE_TAP, button_listener->last_event_type());
160  EXPECT_EQ(Button::STATE_HOVERED, button_listener->last_sender_state());
161}
162
163// Tests if the listener is notified correctly when a mouse click happens on a
164// MenuButton that has a MenuButtonListener.
165TEST_F(MenuButtonTest, ActivateDropDownOnMouseClick) {
166  scoped_ptr<TestMenuButtonListener> menu_button_listener(
167      new TestMenuButtonListener);
168  CreateMenuButtonWithMenuButtonListener(menu_button_listener.get());
169
170  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
171
172  generator.set_current_location(gfx::Point(10, 10));
173  generator.ClickLeftButton();
174
175  // Check that MenuButton has notified the listener, while it was in pressed
176  // state.
177  EXPECT_EQ(button(), menu_button_listener->last_source());
178  EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener->last_source_state());
179}
180
181// Tests if the listener is notified correctly when a gesture tap happens on a
182// MenuButton that has a MenuButtonListener.
183TEST_F(MenuButtonTest, ActivateDropDownOnGestureTap) {
184  scoped_ptr<TestMenuButtonListener> menu_button_listener(
185      new TestMenuButtonListener);
186  CreateMenuButtonWithMenuButtonListener(menu_button_listener.get());
187
188  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
189  generator.GestureTapAt(gfx::Point(10, 10));
190
191  // Check that MenuButton has notified the listener, while it was in pressed
192  // state.
193  EXPECT_EQ(button(), menu_button_listener->last_source());
194  EXPECT_EQ(Button::STATE_PRESSED, menu_button_listener->last_source_state());
195}
196
197// Test that the MenuButton stays pressed while there are any PressedLocks.
198TEST_F(MenuButtonTest, MenuButtonPressedLock) {
199  CreateMenuButtonWithNoListener();
200
201  // Move the mouse over the button; the button should be in a hovered state.
202  ui::test::EventGenerator generator(GetContext(), widget()->GetNativeWindow());
203  generator.MoveMouseTo(gfx::Point(10, 10));
204  EXPECT_EQ(Button::STATE_HOVERED, button()->state());
205
206  // Introduce a PressedLock, which should make the button pressed.
207  scoped_ptr<MenuButton::PressedLock> pressed_lock1(
208      new MenuButton::PressedLock(button()));
209  EXPECT_EQ(Button::STATE_PRESSED, button()->state());
210
211  // Even if we move the mouse outside of the button, it should remain pressed.
212  generator.MoveMouseTo(gfx::Point(300, 10));
213  EXPECT_EQ(Button::STATE_PRESSED, button()->state());
214
215  // Creating a new lock should obviously keep the button pressed.
216  scoped_ptr<MenuButton::PressedLock> pressed_lock2(
217      new MenuButton::PressedLock(button()));
218  EXPECT_EQ(Button::STATE_PRESSED, button()->state());
219
220  // The button should remain pressed while any locks are active.
221  pressed_lock1.reset();
222  EXPECT_EQ(Button::STATE_PRESSED, button()->state());
223
224  // Reseting the final lock should return the button's state to normal...
225  pressed_lock2.reset();
226  EXPECT_EQ(Button::STATE_NORMAL, button()->state());
227
228  // ...And it should respond to mouse movement again.
229  generator.MoveMouseTo(gfx::Point(10, 10));
230  EXPECT_EQ(Button::STATE_HOVERED, button()->state());
231}
232
233}  // namespace views
234