menu_view_drag_and_drop_test.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 "base/strings/utf_string_conversions.h"
6#include "chrome/browser/ui/views/menu_test_base.h"
7#include "chrome/test/base/interactive_test_utils.h"
8#include "ui/base/dragdrop/drag_drop_types.h"
9#include "ui/base/dragdrop/os_exchange_data.h"
10#include "ui/views/controls/menu/menu_item_view.h"
11#include "ui/views/controls/menu/submenu_view.h"
12#include "ui/views/view.h"
13
14namespace {
15
16// Borrowed from chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc,
17// since these are also disabled on Linux for drag and drop.
18// TODO(erg): Fix DND tests on linux_aura. crbug.com/163931
19#if defined(OS_LINUX) && defined(USE_AURA)
20#define MAYBE(x) DISABLED_##x
21#else
22#define MAYBE(x) x
23#endif
24
25const char kTestNestedDragData[] = "test_nested_drag_data";
26const char kTestTopLevelDragData[] = "test_top_level_drag_data";
27
28// A simple view which can be dragged.
29class TestDragView : public views::View {
30 public:
31  TestDragView();
32  virtual ~TestDragView();
33
34 private:
35  // views::View:
36  virtual int GetDragOperations(const gfx::Point& point) OVERRIDE;
37  virtual void WriteDragData(const gfx::Point& point,
38                             ui::OSExchangeData* data) OVERRIDE;
39
40  DISALLOW_COPY_AND_ASSIGN(TestDragView);
41};
42
43TestDragView::TestDragView() {
44}
45
46TestDragView::~TestDragView() {
47}
48
49int TestDragView::GetDragOperations(const gfx::Point& point) {
50  return ui::DragDropTypes::DRAG_MOVE;
51}
52
53void TestDragView::WriteDragData(const gfx::Point& point,
54                                 ui::OSExchangeData* data) {
55  data->SetString(base::ASCIIToUTF16(kTestNestedDragData));
56}
57
58// A simple view to serve as a drop target.
59class TestTargetView : public views::View {
60 public:
61  TestTargetView();
62  virtual ~TestTargetView();
63
64  // Initializes this view to have the same bounds as |parent| and two draggable
65  // child views.
66  void Init(views::View* parent);
67  bool dragging() const { return dragging_; }
68  bool dropped() const { return dropped_; }
69
70 private:
71  // views::View:
72  virtual bool GetDropFormats(
73      int* formats,
74      std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
75  virtual bool AreDropTypesRequired() OVERRIDE;
76  virtual bool CanDrop(const OSExchangeData& data) OVERRIDE;
77  virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE;
78  virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE;
79  virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE;
80  virtual void OnDragExited() OVERRIDE;
81
82  // Whether or not we are currently dragging.
83  bool dragging_;
84
85  // Whether or not a drop has been performed on the view.
86  bool dropped_;
87
88  DISALLOW_COPY_AND_ASSIGN(TestTargetView);
89};
90
91TestTargetView::TestTargetView() : dragging_(false), dropped_(false) {
92}
93
94void TestTargetView::Init(views::View* parent) {
95  // First, match the parent's size.
96  SetSize(parent->size());
97
98  // Then add two draggable views, each 10x2.
99  views::View* first = new TestDragView();
100  AddChildView(first);
101  first->SetBounds(2, 2, 10, 2);
102
103  views::View* second = new TestDragView();
104  AddChildView(second);
105  second->SetBounds(15, 2, 10, 2);
106}
107
108TestTargetView::~TestTargetView() {
109}
110
111bool TestTargetView::GetDropFormats(
112    int* formats, std::set<OSExchangeData::CustomFormat>* custom_formats) {
113  *formats = ui::OSExchangeData::STRING;
114  return true;
115}
116
117bool TestTargetView::AreDropTypesRequired() {
118  return true;
119}
120
121bool TestTargetView::CanDrop(const OSExchangeData& data) {
122  base::string16 contents;
123  return data.GetString(&contents) &&
124         contents == base::ASCIIToUTF16(kTestNestedDragData);
125}
126
127void TestTargetView::OnDragEntered(const ui::DropTargetEvent& event) {
128  dragging_ = true;
129}
130
131int TestTargetView::OnDragUpdated(const ui::DropTargetEvent& event) {
132  return ui::DragDropTypes::DRAG_MOVE;
133}
134
135int TestTargetView::OnPerformDrop(const ui::DropTargetEvent& event) {
136  dragging_ = false;
137  dropped_ = true;
138  return ui::DragDropTypes::DRAG_MOVE;
139}
140
141void TestTargetView::OnDragExited() {
142  dragging_ = false;
143}
144
145}  // namespace
146
147class MenuViewDragAndDropTest : public MenuTestBase {
148 public:
149  MenuViewDragAndDropTest();
150  virtual ~MenuViewDragAndDropTest();
151
152 protected:
153  TestTargetView* target_view() { return target_view_; }
154  bool asked_to_close() const { return asked_to_close_; }
155  bool performed_in_menu_drop() const { return performed_in_menu_drop_; }
156
157 private:
158  // MenuTestBase:
159  virtual void BuildMenu(views::MenuItemView* menu) OVERRIDE;
160
161  // views::MenuDelegate:
162  virtual bool GetDropFormats(
163      views::MenuItemView* menu,
164      int* formats,
165      std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
166  virtual bool AreDropTypesRequired(views::MenuItemView* menu) OVERRIDE;
167  virtual bool CanDrop(views::MenuItemView* menu,
168                       const ui::OSExchangeData& data) OVERRIDE;
169  virtual int GetDropOperation(views::MenuItemView* item,
170                               const ui::DropTargetEvent& event,
171                               DropPosition* position) OVERRIDE;
172  virtual int OnPerformDrop(views::MenuItemView* menu,
173                            DropPosition position,
174                            const ui::DropTargetEvent& event) OVERRIDE;
175  virtual bool CanDrag(views::MenuItemView* menu) OVERRIDE;
176  virtual void WriteDragData(views::MenuItemView* sender,
177                             ui::OSExchangeData* data) OVERRIDE;
178  virtual int GetDragOperations(views::MenuItemView* sender) OVERRIDE;
179  virtual bool ShouldCloseOnDragComplete() OVERRIDE;
180
181  // The special view in the menu, which supports its own drag and drop.
182  TestTargetView* target_view_;
183
184  // Whether or not we have been asked to close on drag complete.
185  bool asked_to_close_;
186
187  // Whether or not a drop was performed in-menu (i.e., not including drops
188  // in separate child views).
189  bool performed_in_menu_drop_;
190
191  DISALLOW_COPY_AND_ASSIGN(MenuViewDragAndDropTest);
192};
193
194MenuViewDragAndDropTest::MenuViewDragAndDropTest()
195    : target_view_(NULL),
196      asked_to_close_(false),
197      performed_in_menu_drop_(false) {
198}
199
200MenuViewDragAndDropTest::~MenuViewDragAndDropTest() {
201}
202
203void MenuViewDragAndDropTest::BuildMenu(views::MenuItemView* menu) {
204  // Build a menu item that has a nested view that supports its own drag and
205  // drop...
206  views::MenuItemView* menu_item_view =
207      menu->AppendMenuItem(1,
208                           base::ASCIIToUTF16("item 1"),
209                           views::MenuItemView::NORMAL);
210  target_view_ = new TestTargetView();
211  menu_item_view->AddChildView(target_view_);
212  // ... as well as two other, normal items.
213  menu->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("item 2"));
214  menu->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("item 3"));
215}
216
217bool MenuViewDragAndDropTest::GetDropFormats(
218    views::MenuItemView* menu,
219    int* formats,
220    std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
221  *formats = ui::OSExchangeData::STRING;
222  return true;
223}
224
225bool MenuViewDragAndDropTest::AreDropTypesRequired(views::MenuItemView* menu) {
226  return true;
227}
228
229bool MenuViewDragAndDropTest::CanDrop(views::MenuItemView* menu,
230                                      const ui::OSExchangeData& data) {
231  base::string16 contents;
232  return data.GetString(&contents) &&
233         contents == base::ASCIIToUTF16(kTestTopLevelDragData);
234}
235
236int MenuViewDragAndDropTest::GetDropOperation(views::MenuItemView* item,
237                                              const ui::DropTargetEvent& event,
238                                              DropPosition* position) {
239  return ui::DragDropTypes::DRAG_MOVE;
240}
241
242
243int MenuViewDragAndDropTest::OnPerformDrop(views::MenuItemView* menu,
244                                           DropPosition position,
245                                           const ui::DropTargetEvent& event) {
246  performed_in_menu_drop_ = true;
247  return ui::DragDropTypes::DRAG_MOVE;
248}
249
250bool MenuViewDragAndDropTest::CanDrag(views::MenuItemView* menu) {
251  return true;
252}
253
254void MenuViewDragAndDropTest::WriteDragData(
255    views::MenuItemView* sender, ui::OSExchangeData* data) {
256  data->SetString(base::ASCIIToUTF16(kTestTopLevelDragData));
257}
258
259int MenuViewDragAndDropTest::GetDragOperations(views::MenuItemView* sender) {
260  return ui::DragDropTypes::DRAG_MOVE;
261}
262
263bool MenuViewDragAndDropTest::ShouldCloseOnDragComplete() {
264  asked_to_close_ = true;
265  return false;
266}
267
268class MenuViewDragAndDropTestTestInMenuDrag : public MenuViewDragAndDropTest {
269 public:
270  MenuViewDragAndDropTestTestInMenuDrag() {}
271  virtual ~MenuViewDragAndDropTestTestInMenuDrag() {}
272
273 private:
274  // MenuViewDragAndDropTest:
275  virtual void DoTestWithMenuOpen() OVERRIDE;
276
277  void Step2();
278  void Step3();
279  void Step4();
280};
281
282void MenuViewDragAndDropTestTestInMenuDrag::DoTestWithMenuOpen() {
283  // A few sanity checks to make sure the menu built correctly.
284  views::SubmenuView* submenu = menu()->GetSubmenu();
285  ASSERT_TRUE(submenu);
286  ASSERT_TRUE(submenu->IsShowing());
287  ASSERT_EQ(3, submenu->GetMenuItemCount());
288
289  // We do this here (instead of in BuildMenu()) so that the menu is already
290  // built and the bounds are correct.
291  target_view()->Init(submenu->GetMenuItemAt(0));
292
293  // We're going to drag the second menu element.
294  views::MenuItemView* drag_view = submenu->GetMenuItemAt(1);
295  ASSERT_TRUE(drag_view != NULL);
296
297  // Move mouse to center of menu and press button.
298  ui_test_utils::MoveMouseToCenterAndPress(
299      drag_view,
300      ui_controls::LEFT,
301      ui_controls::DOWN,
302      CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step2));
303}
304
305void MenuViewDragAndDropTestTestInMenuDrag::Step2() {
306  views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2);
307  gfx::Point loc(1, drop_target->height() - 1);
308  views::View::ConvertPointToScreen(drop_target, &loc);
309
310  // Start a drag.
311  ui_controls::SendMouseMoveNotifyWhenDone(
312      loc.x() + 10,
313      loc.y(),
314      CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step3));
315
316  ScheduleMouseMoveInBackground(loc.x(), loc.y());
317}
318
319void MenuViewDragAndDropTestTestInMenuDrag::Step3() {
320  // Drop the item on the target.
321  views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2);
322  gfx::Point loc(1, drop_target->height() - 2);
323  views::View::ConvertPointToScreen(drop_target, &loc);
324  ui_controls::SendMouseMove(loc.x(), loc.y());
325
326  ui_controls::SendMouseEventsNotifyWhenDone(
327      ui_controls::LEFT,
328      ui_controls::UP,
329      CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step4));
330}
331
332void MenuViewDragAndDropTestTestInMenuDrag::Step4() {
333  // Verify our state.
334  // We should have performed an in-menu drop, and the nested view should not
335  // have had a drag and drop. Since the drag happened in menu code, the
336  // delegate should not have been asked whether or not to close, and the menu
337  // should simply be closed.
338  EXPECT_TRUE(performed_in_menu_drop());
339  EXPECT_FALSE(target_view()->dropped());
340  EXPECT_FALSE(asked_to_close());
341  EXPECT_FALSE(menu()->GetSubmenu()->IsShowing());
342
343  Done();
344}
345
346VIEW_TEST(MenuViewDragAndDropTestTestInMenuDrag, MAYBE(TestInMenuDrag))
347
348class MenuViewDragAndDropTestNestedDrag : public MenuViewDragAndDropTest {
349 public:
350  MenuViewDragAndDropTestNestedDrag() {}
351  virtual ~MenuViewDragAndDropTestNestedDrag() {}
352
353 private:
354  // MenuViewDragAndDropTest:
355  virtual void DoTestWithMenuOpen() OVERRIDE;
356
357  void Step2();
358  void Step3();
359  void Step4();
360};
361
362void MenuViewDragAndDropTestNestedDrag::DoTestWithMenuOpen() {
363  // Sanity checks: We should be showing the menu, it should have three
364  // children, and the first of those children should have a nested view of the
365  // TestTargetView.
366  views::SubmenuView* submenu = menu()->GetSubmenu();
367  ASSERT_TRUE(submenu);
368  ASSERT_TRUE(submenu->IsShowing());
369  ASSERT_EQ(3, submenu->GetMenuItemCount());
370  views::View* first_view = submenu->GetMenuItemAt(0);
371  ASSERT_EQ(1, first_view->child_count());
372  views::View* child_view = first_view->child_at(0);
373  ASSERT_EQ(child_view, target_view());
374
375  // We do this here (instead of in BuildMenu()) so that the menu is already
376  // built and the bounds are correct.
377  target_view()->Init(submenu->GetMenuItemAt(0));
378
379  // The target view should now have two children.
380  ASSERT_EQ(2, target_view()->child_count());
381
382  views::View* drag_view = target_view()->child_at(0);
383  ASSERT_TRUE(drag_view != NULL);
384
385  // Move mouse to center of menu and press button.
386  ui_test_utils::MoveMouseToCenterAndPress(
387      drag_view,
388      ui_controls::LEFT,
389      ui_controls::DOWN,
390      CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step2));
391}
392
393void MenuViewDragAndDropTestNestedDrag::Step2() {
394  views::View* drop_target = target_view()->child_at(1);
395  gfx::Point loc(2, 0);
396  views::View::ConvertPointToScreen(drop_target, &loc);
397
398  // Start a drag.
399  ui_controls::SendMouseMoveNotifyWhenDone(
400      loc.x() + 3,
401      loc.y(),
402      CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step3));
403
404  ScheduleMouseMoveInBackground(loc.x(), loc.y());
405}
406
407void MenuViewDragAndDropTestNestedDrag::Step3() {
408  // The view should be dragging now.
409  EXPECT_TRUE(target_view()->dragging());
410
411  // Drop the item so that it's now the second item.
412  views::View* drop_target = target_view()->child_at(1);
413  gfx::Point loc(5, 0);
414  views::View::ConvertPointToScreen(drop_target, &loc);
415  ui_controls::SendMouseMove(loc.x(), loc.y());
416
417  ui_controls::SendMouseEventsNotifyWhenDone(
418      ui_controls::LEFT,
419      ui_controls::UP,
420      CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step4));
421}
422
423void MenuViewDragAndDropTestNestedDrag::Step4() {
424  // Check our state.
425  // The target view should have finished its drag, and should have dropped the
426  // view. The main menu should not have done any drag, and the delegate should
427  // have been asked if it wanted to close. Since the delegate did not want to
428  // close, the menu should still be open.
429  EXPECT_FALSE(target_view()->dragging());
430  EXPECT_TRUE(target_view()->dropped());
431  EXPECT_FALSE(performed_in_menu_drop());
432  EXPECT_TRUE(asked_to_close());
433  EXPECT_TRUE(menu()->GetSubmenu()->IsShowing());
434
435  // Clean up.
436  menu()->GetSubmenu()->Close();
437
438  Done();
439}
440
441VIEW_TEST(MenuViewDragAndDropTestNestedDrag,
442          MAYBE(MenuViewDragAndDropNestedDrag))
443