1// Copyright (c) 2010 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#import <Cocoa/Cocoa.h> 6 7#include "base/sys_string_conversions.h" 8#include "base/utf_string_conversions.h" 9#include "chrome/browser/ui/cocoa/cocoa_test_helper.h" 10#include "chrome/browser/ui/cocoa/menu_controller.h" 11#include "grit/app_resources.h" 12#include "grit/generated_resources.h" 13#include "third_party/skia/include/core/SkBitmap.h" 14#include "ui/base/models/simple_menu_model.h" 15#include "ui/base/resource/resource_bundle.h" 16 17class MenuControllerTest : public CocoaTest { 18}; 19 20// A menu delegate that counts the number of times certain things are called 21// to make sure things are hooked up properly. 22class Delegate : public ui::SimpleMenuModel::Delegate { 23 public: 24 Delegate() : execute_count_(0), enable_count_(0) { } 25 26 virtual bool IsCommandIdChecked(int command_id) const { return false; } 27 virtual bool IsCommandIdEnabled(int command_id) const { 28 ++enable_count_; 29 return true; 30 } 31 virtual bool GetAcceleratorForCommandId( 32 int command_id, 33 ui::Accelerator* accelerator) { return false; } 34 virtual void ExecuteCommand(int command_id) { ++execute_count_; } 35 36 int execute_count_; 37 mutable int enable_count_; 38}; 39 40// Just like Delegate, except the items are treated as "dynamic" so updates to 41// the label/icon in the model are reflected in the menu. 42class DynamicDelegate : public Delegate { 43 public: 44 DynamicDelegate() : icon_(NULL) {} 45 virtual bool IsItemForCommandIdDynamic(int command_id) const { return true; } 46 virtual string16 GetLabelForCommandId(int command_id) const { return label_; } 47 virtual bool GetIconForCommandId(int command_id, SkBitmap* icon) const { 48 if (icon_) { 49 *icon = *icon_; 50 return true; 51 } else { 52 return false; 53 } 54 } 55 void SetDynamicLabel(string16 label) { label_ = label; } 56 void SetDynamicIcon(SkBitmap* icon) { icon_ = icon; } 57 58 private: 59 string16 label_; 60 SkBitmap* icon_; 61}; 62 63TEST_F(MenuControllerTest, EmptyMenu) { 64 Delegate delegate; 65 ui::SimpleMenuModel model(&delegate); 66 scoped_nsobject<MenuController> menu( 67 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 68 EXPECT_EQ([[menu menu] numberOfItems], 0); 69} 70 71TEST_F(MenuControllerTest, BasicCreation) { 72 Delegate delegate; 73 ui::SimpleMenuModel model(&delegate); 74 model.AddItem(1, ASCIIToUTF16("one")); 75 model.AddItem(2, ASCIIToUTF16("two")); 76 model.AddItem(3, ASCIIToUTF16("three")); 77 model.AddSeparator(); 78 model.AddItem(4, ASCIIToUTF16("four")); 79 model.AddItem(5, ASCIIToUTF16("five")); 80 81 scoped_nsobject<MenuController> menu( 82 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 83 EXPECT_EQ([[menu menu] numberOfItems], 6); 84 85 // Check the title, tag, and represented object are correct for a random 86 // element. 87 NSMenuItem* itemTwo = [[menu menu] itemAtIndex:2]; 88 NSString* title = [itemTwo title]; 89 EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title)); 90 EXPECT_EQ([itemTwo tag], 2); 91 EXPECT_EQ([[itemTwo representedObject] pointerValue], &model); 92 93 EXPECT_TRUE([[[menu menu] itemAtIndex:3] isSeparatorItem]); 94} 95 96TEST_F(MenuControllerTest, Submenus) { 97 Delegate delegate; 98 ui::SimpleMenuModel model(&delegate); 99 model.AddItem(1, ASCIIToUTF16("one")); 100 ui::SimpleMenuModel submodel(&delegate); 101 submodel.AddItem(2, ASCIIToUTF16("sub-one")); 102 submodel.AddItem(3, ASCIIToUTF16("sub-two")); 103 submodel.AddItem(4, ASCIIToUTF16("sub-three")); 104 model.AddSubMenuWithStringId(5, IDS_ZOOM_MENU, &submodel); 105 model.AddItem(6, ASCIIToUTF16("three")); 106 107 scoped_nsobject<MenuController> menu( 108 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 109 EXPECT_EQ([[menu menu] numberOfItems], 3); 110 111 // Inspect the submenu to ensure it has correct properties. 112 NSMenu* submenu = [[[menu menu] itemAtIndex:1] submenu]; 113 EXPECT_TRUE(submenu); 114 EXPECT_EQ([submenu numberOfItems], 3); 115 116 // Inspect one of the items to make sure it has the correct model as its 117 // represented object and the proper tag. 118 NSMenuItem* submenuItem = [submenu itemAtIndex:1]; 119 NSString* title = [submenuItem title]; 120 EXPECT_EQ(ASCIIToUTF16("sub-two"), base::SysNSStringToUTF16(title)); 121 EXPECT_EQ([submenuItem tag], 1); 122 EXPECT_EQ([[submenuItem representedObject] pointerValue], &submodel); 123 124 // Make sure the item after the submenu is correct and its represented 125 // object is back to the top model. 126 NSMenuItem* item = [[menu menu] itemAtIndex:2]; 127 title = [item title]; 128 EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title)); 129 EXPECT_EQ([item tag], 2); 130 EXPECT_EQ([[item representedObject] pointerValue], &model); 131} 132 133TEST_F(MenuControllerTest, EmptySubmenu) { 134 Delegate delegate; 135 ui::SimpleMenuModel model(&delegate); 136 model.AddItem(1, ASCIIToUTF16("one")); 137 ui::SimpleMenuModel submodel(&delegate); 138 model.AddSubMenuWithStringId(2, IDS_ZOOM_MENU, &submodel); 139 140 scoped_nsobject<MenuController> menu( 141 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 142 EXPECT_EQ([[menu menu] numberOfItems], 2); 143} 144 145TEST_F(MenuControllerTest, PopUpButton) { 146 Delegate delegate; 147 ui::SimpleMenuModel model(&delegate); 148 model.AddItem(1, ASCIIToUTF16("one")); 149 model.AddItem(2, ASCIIToUTF16("two")); 150 model.AddItem(3, ASCIIToUTF16("three")); 151 152 // Menu should have an extra item inserted at position 0 that has an empty 153 // title. 154 scoped_nsobject<MenuController> menu( 155 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:YES]); 156 EXPECT_EQ([[menu menu] numberOfItems], 4); 157 EXPECT_EQ(base::SysNSStringToUTF16([[[menu menu] itemAtIndex:0] title]), 158 string16()); 159 160 // Make sure the tags are still correct (the index no longer matches the tag). 161 NSMenuItem* itemTwo = [[menu menu] itemAtIndex:2]; 162 EXPECT_EQ([itemTwo tag], 1); 163} 164 165TEST_F(MenuControllerTest, Execute) { 166 Delegate delegate; 167 ui::SimpleMenuModel model(&delegate); 168 model.AddItem(1, ASCIIToUTF16("one")); 169 scoped_nsobject<MenuController> menu( 170 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 171 EXPECT_EQ([[menu menu] numberOfItems], 1); 172 173 // Fake selecting the menu item, we expect the delegate to be told to execute 174 // a command. 175 NSMenuItem* item = [[menu menu] itemAtIndex:0]; 176 [[item target] performSelector:[item action] withObject:item]; 177 EXPECT_EQ(delegate.execute_count_, 1); 178} 179 180void Validate(MenuController* controller, NSMenu* menu) { 181 for (int i = 0; i < [menu numberOfItems]; ++i) { 182 NSMenuItem* item = [menu itemAtIndex:i]; 183 [controller validateUserInterfaceItem:item]; 184 if ([item hasSubmenu]) 185 Validate(controller, [item submenu]); 186 } 187} 188 189TEST_F(MenuControllerTest, Validate) { 190 Delegate delegate; 191 ui::SimpleMenuModel model(&delegate); 192 model.AddItem(1, ASCIIToUTF16("one")); 193 model.AddItem(2, ASCIIToUTF16("two")); 194 ui::SimpleMenuModel submodel(&delegate); 195 submodel.AddItem(2, ASCIIToUTF16("sub-one")); 196 model.AddSubMenuWithStringId(3, IDS_ZOOM_MENU, &submodel); 197 198 scoped_nsobject<MenuController> menu( 199 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 200 EXPECT_EQ([[menu menu] numberOfItems], 3); 201 202 Validate(menu.get(), [menu menu]); 203} 204 205TEST_F(MenuControllerTest, DefaultInitializer) { 206 Delegate delegate; 207 ui::SimpleMenuModel model(&delegate); 208 model.AddItem(1, ASCIIToUTF16("one")); 209 model.AddItem(2, ASCIIToUTF16("two")); 210 model.AddItem(3, ASCIIToUTF16("three")); 211 212 scoped_nsobject<MenuController> menu([[MenuController alloc] init]); 213 EXPECT_FALSE([menu menu]); 214 215 [menu setModel:&model]; 216 [menu setUseWithPopUpButtonCell:NO]; 217 EXPECT_TRUE([menu menu]); 218 EXPECT_EQ(3, [[menu menu] numberOfItems]); 219 220 // Check immutability. 221 model.AddItem(4, ASCIIToUTF16("four")); 222 EXPECT_EQ(3, [[menu menu] numberOfItems]); 223} 224 225// Test that menus with dynamic labels actually get updated. 226TEST_F(MenuControllerTest, Dynamic) { 227 DynamicDelegate delegate; 228 229 // Create a menu containing a single item whose label is "initial" and who has 230 // no icon. 231 string16 initial = ASCIIToUTF16("initial"); 232 delegate.SetDynamicLabel(initial); 233 ui::SimpleMenuModel model(&delegate); 234 model.AddItem(1, ASCIIToUTF16("foo")); 235 scoped_nsobject<MenuController> menu( 236 [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]); 237 EXPECT_EQ([[menu menu] numberOfItems], 1); 238 // Validate() simulates opening the menu - the item label/icon should be 239 // initialized after this so we can validate the menu contents. 240 Validate(menu.get(), [menu menu]); 241 NSMenuItem* item = [[menu menu] itemAtIndex:0]; 242 // Item should have the "initial" label and no icon. 243 EXPECT_EQ(initial, base::SysNSStringToUTF16([item title])); 244 EXPECT_EQ(nil, [item image]); 245 246 // Now update the item to have a label of "second" and an icon. 247 string16 second = ASCIIToUTF16("second"); 248 delegate.SetDynamicLabel(second); 249 SkBitmap* bitmap = 250 ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_THROBBER); 251 delegate.SetDynamicIcon(bitmap); 252 // Simulate opening the menu and validate that the item label + icon changes. 253 Validate(menu.get(), [menu menu]); 254 EXPECT_EQ(second, base::SysNSStringToUTF16([item title])); 255 EXPECT_TRUE([item image] != nil); 256 257 // Now get rid of the icon and make sure it goes away. 258 delegate.SetDynamicIcon(NULL); 259 Validate(menu.get(), [menu menu]); 260 EXPECT_EQ(second, base::SysNSStringToUTF16([item title])); 261 EXPECT_EQ(nil, [item image]); 262} 263