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