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. 4 5 #include "base/callback.h" 6 #include "base/strings/utf_string_conversions.h" 7 #include "chrome/test/base/interactive_test_utils.h" 8 #include "chrome/test/base/ui_test_utils.h" 9 #include "chrome/test/base/view_event_test_base.h" 10 #include "ui/base/models/menu_model.h" 11 #include "ui/base/test/ui_controls.h" 12 #include "ui/views/controls/button/menu_button.h" 13 #include "ui/views/controls/button/menu_button_listener.h" 14 #include "ui/views/controls/menu/menu_controller.h" 15 #include "ui/views/controls/menu/menu_item_view.h" 16 #include "ui/views/controls/menu/menu_model_adapter.h" 17 #include "ui/views/controls/menu/menu_runner.h" 18 #include "ui/views/controls/menu/submenu_view.h" 19 #include "ui/views/widget/root_view.h" 20 #include "ui/views/widget/widget.h" 21 22 namespace { 23 24 const int kTopMenuBaseId = 100; 25 const int kSubMenuBaseId = 200; 26 27 // Implement most of the ui::MenuModel pure virtual methods for subclasses 28 // 29 // Exceptions: 30 // virtual int GetItemCount() const = 0; 31 // virtual ItemType GetTypeAt(int index) const = 0; 32 // virtual int GetCommandIdAt(int index) const = 0; 33 // virtual base::string16 GetLabelAt(int index) const = 0; 34 class CommonMenuModel : public ui::MenuModel { 35 public: CommonMenuModel()36 CommonMenuModel() { 37 } 38 ~CommonMenuModel()39 virtual ~CommonMenuModel() { 40 } 41 42 protected: 43 // ui::MenuModel implementation. HasIcons() const44 virtual bool HasIcons() const OVERRIDE { 45 return false; 46 } 47 IsItemDynamicAt(int index) const48 virtual bool IsItemDynamicAt(int index) const OVERRIDE { 49 return false; 50 } 51 GetAcceleratorAt(int index,ui::Accelerator * accelerator) const52 virtual bool GetAcceleratorAt(int index, 53 ui::Accelerator* accelerator) const OVERRIDE { 54 return false; 55 } 56 GetSeparatorTypeAt(int index) const57 virtual ui::MenuSeparatorType GetSeparatorTypeAt(int index) const OVERRIDE { 58 return ui::NORMAL_SEPARATOR; 59 } 60 IsItemCheckedAt(int index) const61 virtual bool IsItemCheckedAt(int index) const OVERRIDE { 62 return false; 63 } 64 GetGroupIdAt(int index) const65 virtual int GetGroupIdAt(int index) const OVERRIDE { 66 return 0; 67 } 68 GetIconAt(int index,gfx::Image * icon)69 virtual bool GetIconAt(int index, gfx::Image* icon) OVERRIDE { 70 return false; 71 } 72 GetButtonMenuItemAt(int index) const73 virtual ui::ButtonMenuItemModel* GetButtonMenuItemAt( 74 int index) const OVERRIDE { 75 return NULL; 76 } 77 IsEnabledAt(int index) const78 virtual bool IsEnabledAt(int index) const OVERRIDE { 79 return true; 80 } 81 GetSubmenuModelAt(int index) const82 virtual ui::MenuModel* GetSubmenuModelAt(int index) const OVERRIDE { 83 return NULL; 84 } 85 HighlightChangedTo(int index)86 virtual void HighlightChangedTo(int index) OVERRIDE { 87 } 88 ActivatedAt(int index)89 virtual void ActivatedAt(int index) OVERRIDE { 90 } 91 SetMenuModelDelegate(ui::MenuModelDelegate * delegate)92 virtual void SetMenuModelDelegate(ui::MenuModelDelegate* delegate) OVERRIDE { 93 } 94 GetMenuModelDelegate() const95 virtual ui::MenuModelDelegate* GetMenuModelDelegate() const OVERRIDE { 96 return NULL; 97 } 98 99 private: 100 DISALLOW_COPY_AND_ASSIGN(CommonMenuModel); 101 }; 102 103 class SubMenuModel : public CommonMenuModel { 104 public: SubMenuModel()105 SubMenuModel() 106 : showing_(false) { 107 } 108 ~SubMenuModel()109 virtual ~SubMenuModel() { 110 } 111 showing() const112 bool showing() const { 113 return showing_; 114 } 115 116 private: 117 // ui::MenuModel implementation. GetItemCount() const118 virtual int GetItemCount() const OVERRIDE { 119 return 1; 120 } 121 GetTypeAt(int index) const122 virtual ItemType GetTypeAt(int index) const OVERRIDE { 123 return TYPE_COMMAND; 124 } 125 GetCommandIdAt(int index) const126 virtual int GetCommandIdAt(int index) const OVERRIDE { 127 return index + kSubMenuBaseId; 128 } 129 GetLabelAt(int index) const130 virtual base::string16 GetLabelAt(int index) const OVERRIDE { 131 return base::ASCIIToUTF16("Item"); 132 } 133 MenuWillShow()134 virtual void MenuWillShow() OVERRIDE { 135 showing_ = true; 136 } 137 138 // Called when the menu has been closed. MenuClosed()139 virtual void MenuClosed() OVERRIDE { 140 showing_ = false; 141 } 142 143 bool showing_; 144 145 DISALLOW_COPY_AND_ASSIGN(SubMenuModel); 146 }; 147 148 class TopMenuModel : public CommonMenuModel { 149 public: TopMenuModel()150 TopMenuModel() { 151 } 152 ~TopMenuModel()153 virtual ~TopMenuModel() { 154 } 155 IsSubmenuShowing()156 bool IsSubmenuShowing() { 157 return sub_menu_model_.showing(); 158 } 159 160 private: 161 // ui::MenuModel implementation. GetItemCount() const162 virtual int GetItemCount() const OVERRIDE { 163 return 1; 164 } 165 GetTypeAt(int index) const166 virtual ItemType GetTypeAt(int index) const OVERRIDE { 167 return TYPE_SUBMENU; 168 } 169 GetCommandIdAt(int index) const170 virtual int GetCommandIdAt(int index) const OVERRIDE { 171 return index + kTopMenuBaseId; 172 } 173 GetLabelAt(int index) const174 virtual base::string16 GetLabelAt(int index) const OVERRIDE { 175 return base::ASCIIToUTF16("submenu"); 176 } 177 GetSubmenuModelAt(int index) const178 virtual MenuModel* GetSubmenuModelAt(int index) const OVERRIDE { 179 return &sub_menu_model_; 180 } 181 182 mutable SubMenuModel sub_menu_model_; 183 184 DISALLOW_COPY_AND_ASSIGN(TopMenuModel); 185 }; 186 187 } // namespace 188 189 class MenuModelAdapterTest : public ViewEventTestBase, 190 public views::MenuButtonListener { 191 public: MenuModelAdapterTest()192 MenuModelAdapterTest() 193 : ViewEventTestBase(), 194 button_(NULL), 195 menu_model_adapter_(&top_menu_model_), 196 menu_(NULL) { 197 } 198 ~MenuModelAdapterTest()199 virtual ~MenuModelAdapterTest() { 200 } 201 202 // ViewEventTestBase implementation. 203 SetUp()204 virtual void SetUp() OVERRIDE { 205 button_ = new views::MenuButton( 206 NULL, base::ASCIIToUTF16("Menu Adapter Test"), this, true); 207 208 menu_ = menu_model_adapter_.CreateMenu(); 209 menu_runner_.reset(new views::MenuRunner(menu_)); 210 211 ViewEventTestBase::SetUp(); 212 } 213 TearDown()214 virtual void TearDown() OVERRIDE { 215 menu_runner_.reset(NULL); 216 menu_ = NULL; 217 ViewEventTestBase::TearDown(); 218 } 219 CreateContentsView()220 virtual views::View* CreateContentsView() OVERRIDE { 221 return button_; 222 } 223 GetPreferredSize() const224 virtual gfx::Size GetPreferredSize() const OVERRIDE { 225 return button_->GetPreferredSize(); 226 } 227 228 // views::MenuButtonListener implementation. OnMenuButtonClicked(views::View * source,const gfx::Point & point)229 virtual void OnMenuButtonClicked(views::View* source, 230 const gfx::Point& point) OVERRIDE { 231 gfx::Point screen_location; 232 views::View::ConvertPointToScreen(source, &screen_location); 233 gfx::Rect bounds(screen_location, source->size()); 234 ignore_result(menu_runner_->RunMenuAt(source->GetWidget(), 235 button_, 236 bounds, 237 views::MENU_ANCHOR_TOPLEFT, 238 ui::MENU_SOURCE_NONE, 239 views::MenuRunner::HAS_MNEMONICS)); 240 } 241 242 // ViewEventTestBase implementation DoTestOnMessageLoop()243 virtual void DoTestOnMessageLoop() OVERRIDE { 244 Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step1)); 245 } 246 247 // Open the submenu. Step1()248 void Step1() { 249 views::SubmenuView* topmenu = menu_->GetSubmenu(); 250 ASSERT_TRUE(topmenu); 251 ASSERT_TRUE(topmenu->IsShowing()); 252 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 253 254 // Click the first item to open the submenu. 255 views::MenuItemView* item = topmenu->GetMenuItemAt(0); 256 ASSERT_TRUE(item); 257 Click(item, CreateEventTask(this, &MenuModelAdapterTest::Step2)); 258 } 259 260 // Rebuild the menu which should close the submenu. Step2()261 void Step2() { 262 views::SubmenuView* topmenu = menu_->GetSubmenu(); 263 ASSERT_TRUE(topmenu); 264 ASSERT_TRUE(topmenu->IsShowing()); 265 ASSERT_TRUE(top_menu_model_.IsSubmenuShowing()); 266 267 menu_model_adapter_.BuildMenu(menu_); 268 269 base::MessageLoopForUI::current()->PostTask( 270 FROM_HERE, CreateEventTask(this, &MenuModelAdapterTest::Step3)); 271 } 272 273 // Verify that the submenu MenuModel received the close callback 274 // and close the menu. Step3()275 void Step3() { 276 views::SubmenuView* topmenu = menu_->GetSubmenu(); 277 ASSERT_TRUE(topmenu); 278 ASSERT_TRUE(topmenu->IsShowing()); 279 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 280 281 // Click the button to exit the menu. 282 Click(button_, CreateEventTask(this, &MenuModelAdapterTest::Step4)); 283 } 284 285 // All done. Step4()286 void Step4() { 287 views::SubmenuView* topmenu = menu_->GetSubmenu(); 288 ASSERT_TRUE(topmenu); 289 ASSERT_FALSE(topmenu->IsShowing()); 290 ASSERT_FALSE(top_menu_model_.IsSubmenuShowing()); 291 292 Done(); 293 } 294 295 private: 296 // Generate a mouse click on the specified view and post a new task. Click(views::View * view,const base::Closure & next)297 virtual void Click(views::View* view, const base::Closure& next) { 298 ui_test_utils::MoveMouseToCenterAndPress( 299 view, 300 ui_controls::LEFT, 301 ui_controls::DOWN | ui_controls::UP, 302 next); 303 } 304 305 views::MenuButton* button_; 306 TopMenuModel top_menu_model_; 307 views::MenuModelAdapter menu_model_adapter_; 308 views::MenuItemView* menu_; 309 scoped_ptr<views::MenuRunner> menu_runner_; 310 }; 311 312 VIEW_TEST(MenuModelAdapterTest, RebuildMenu) 313