Home | History | Annotate | Download | only in cocoa
      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 
     17 class 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.
     22 class 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.
     42 class 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 
     63 TEST_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 
     71 TEST_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 
     96 TEST_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 
    133 TEST_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 
    145 TEST_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 
    165 TEST_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 
    180 void 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 
    189 TEST_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 
    205 TEST_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.
    226 TEST_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