1 // Copyright (c) 2012 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 "base/callback.h"
6 #include "base/location.h"
7 #include "base/single_thread_task_runner.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "chrome/test/base/interactive_test_utils.h"
11 #include "chrome/test/base/ui_test_utils.h"
12 #include "chrome/test/base/view_event_test_base.h"
13 #include "ui/base/models/menu_model.h"
14 #include "ui/base/test/ui_controls.h"
15 #include "ui/views/controls/button/menu_button.h"
16 #include "ui/views/controls/button/menu_button_listener.h"
17 #include "ui/views/controls/menu/menu_controller.h"
18 #include "ui/views/controls/menu/menu_item_view.h"
19 #include "ui/views/controls/menu/menu_model_adapter.h"
20 #include "ui/views/controls/menu/menu_runner.h"
21 #include "ui/views/controls/menu/submenu_view.h"
22 #include "ui/views/widget/root_view.h"
23 #include "ui/views/widget/widget.h"
27 const int kTopMenuBaseId
= 100;
28 const int kSubMenuBaseId
= 200;
30 // Implement most of the ui::MenuModel pure virtual methods for subclasses
33 // virtual int GetItemCount() const = 0;
34 // virtual ItemType GetTypeAt(int index) const = 0;
35 // virtual int GetCommandIdAt(int index) const = 0;
36 // virtual base::string16 GetLabelAt(int index) const = 0;
37 class CommonMenuModel
: public ui::MenuModel
{
42 ~CommonMenuModel() override
{}
45 // ui::MenuModel implementation.
46 bool HasIcons() const override
{ return false; }
48 bool IsItemDynamicAt(int index
) const override
{ return false; }
50 bool GetAcceleratorAt(int index
,
51 ui::Accelerator
* accelerator
) const override
{
55 ui::MenuSeparatorType
GetSeparatorTypeAt(int index
) const override
{
56 return ui::NORMAL_SEPARATOR
;
59 bool IsItemCheckedAt(int index
) const override
{ return false; }
61 int GetGroupIdAt(int index
) const override
{ return 0; }
63 bool GetIconAt(int index
, gfx::Image
* icon
) override
{ return false; }
65 ui::ButtonMenuItemModel
* GetButtonMenuItemAt(int index
) const override
{
69 bool IsEnabledAt(int index
) const override
{ return true; }
71 ui::MenuModel
* GetSubmenuModelAt(int index
) const override
{ return NULL
; }
73 void HighlightChangedTo(int index
) override
{}
75 void ActivatedAt(int index
) override
{}
77 void SetMenuModelDelegate(ui::MenuModelDelegate
* delegate
) override
{}
79 ui::MenuModelDelegate
* GetMenuModelDelegate() const override
{ return NULL
; }
82 DISALLOW_COPY_AND_ASSIGN(CommonMenuModel
);
85 class SubMenuModel
: public CommonMenuModel
{
91 ~SubMenuModel() override
{}
93 bool showing() const {
98 // ui::MenuModel implementation.
99 int GetItemCount() const override
{ return 1; }
101 ItemType
GetTypeAt(int index
) const override
{ return TYPE_COMMAND
; }
103 int GetCommandIdAt(int index
) const override
{
104 return index
+ kSubMenuBaseId
;
107 base::string16
GetLabelAt(int index
) const override
{
108 return base::ASCIIToUTF16("Item");
111 void MenuWillShow() override
{ showing_
= true; }
113 // Called when the menu has been closed.
114 void MenuClosed() override
{ showing_
= false; }
118 DISALLOW_COPY_AND_ASSIGN(SubMenuModel
);
121 class TopMenuModel
: public CommonMenuModel
{
126 ~TopMenuModel() override
{}
128 bool IsSubmenuShowing() {
129 return sub_menu_model_
.showing();
133 // ui::MenuModel implementation.
134 int GetItemCount() const override
{ return 1; }
136 ItemType
GetTypeAt(int index
) const override
{ return TYPE_SUBMENU
; }
138 int GetCommandIdAt(int index
) const override
{
139 return index
+ kTopMenuBaseId
;
142 base::string16
GetLabelAt(int index
) const override
{
143 return base::ASCIIToUTF16("submenu");
146 MenuModel
* GetSubmenuModelAt(int index
) const override
{
147 return &sub_menu_model_
;
150 mutable SubMenuModel sub_menu_model_
;
152 DISALLOW_COPY_AND_ASSIGN(TopMenuModel
);
157 class MenuModelAdapterTest
: public ViewEventTestBase
,
158 public views::MenuButtonListener
{
160 MenuModelAdapterTest()
161 : ViewEventTestBase(),
163 menu_model_adapter_(&top_menu_model_
),
167 ~MenuModelAdapterTest() override
{}
169 // ViewEventTestBase implementation.
171 void SetUp() override
{
172 button_
= new views::MenuButton(
173 NULL
, base::ASCIIToUTF16("Menu Adapter Test"), this, true);
175 menu_
= menu_model_adapter_
.CreateMenu();
177 new views::MenuRunner(menu_
, views::MenuRunner::HAS_MNEMONICS
));
179 ViewEventTestBase::SetUp();
182 void TearDown() override
{
183 menu_runner_
.reset(NULL
);
185 ViewEventTestBase::TearDown();
188 views::View
* CreateContentsView() override
{ return button_
; }
190 gfx::Size
GetPreferredSize() const override
{
191 return button_
->GetPreferredSize();
194 // views::MenuButtonListener implementation.
195 void OnMenuButtonClicked(views::View
* source
,
196 const gfx::Point
& point
) override
{
197 gfx::Point screen_location
;
198 views::View::ConvertPointToScreen(source
, &screen_location
);
199 gfx::Rect
bounds(screen_location
, source
->size());
200 ignore_result(menu_runner_
->RunMenuAt(source
->GetWidget(),
203 views::MENU_ANCHOR_TOPLEFT
,
204 ui::MENU_SOURCE_NONE
));
207 // ViewEventTestBase implementation
208 void DoTestOnMessageLoop() override
{
209 Click(button_
, CreateEventTask(this, &MenuModelAdapterTest::Step1
));
214 views::SubmenuView
* topmenu
= menu_
->GetSubmenu();
215 ASSERT_TRUE(topmenu
);
216 ASSERT_TRUE(topmenu
->IsShowing());
217 ASSERT_FALSE(top_menu_model_
.IsSubmenuShowing());
219 // Click the first item to open the submenu.
220 views::MenuItemView
* item
= topmenu
->GetMenuItemAt(0);
222 Click(item
, CreateEventTask(this, &MenuModelAdapterTest::Step2
));
225 // Rebuild the menu which should close the submenu.
227 views::SubmenuView
* topmenu
= menu_
->GetSubmenu();
228 ASSERT_TRUE(topmenu
);
229 ASSERT_TRUE(topmenu
->IsShowing());
230 ASSERT_TRUE(top_menu_model_
.IsSubmenuShowing());
232 menu_model_adapter_
.BuildMenu(menu_
);
234 base::MessageLoopForUI::current()->task_runner()->PostTask(
235 FROM_HERE
, CreateEventTask(this, &MenuModelAdapterTest::Step3
));
238 // Verify that the submenu MenuModel received the close callback
239 // and close the menu.
241 views::SubmenuView
* topmenu
= menu_
->GetSubmenu();
242 ASSERT_TRUE(topmenu
);
243 ASSERT_TRUE(topmenu
->IsShowing());
244 ASSERT_FALSE(top_menu_model_
.IsSubmenuShowing());
246 // Click the button to exit the menu.
247 Click(button_
, CreateEventTask(this, &MenuModelAdapterTest::Step4
));
252 views::SubmenuView
* topmenu
= menu_
->GetSubmenu();
253 ASSERT_TRUE(topmenu
);
254 ASSERT_FALSE(topmenu
->IsShowing());
255 ASSERT_FALSE(top_menu_model_
.IsSubmenuShowing());
261 // Generate a mouse click on the specified view and post a new task.
262 virtual void Click(views::View
* view
, const base::Closure
& next
) {
263 ui_test_utils::MoveMouseToCenterAndPress(
266 ui_controls::DOWN
| ui_controls::UP
,
270 views::MenuButton
* button_
;
271 TopMenuModel top_menu_model_
;
272 views::MenuModelAdapter menu_model_adapter_
;
273 views::MenuItemView
* menu_
;
274 scoped_ptr
<views::MenuRunner
> menu_runner_
;
278 // flaky on Windows - http://crbug.com/523255
279 #define MAYBE_RebuildMenu DISABLED_RebuildMenu
281 #define MAYBE_RebuildMenu RebuildMenu
283 VIEW_TEST(MenuModelAdapterTest
, MAYBE_RebuildMenu
)