Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / base / cocoa / menu_controller_unittest.mm
blob36b0e1308d76c9acfd3d864ffdd42952e8205c40
1 // Copyright 2013 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 #import <Cocoa/Cocoa.h>
7 #include "base/message_loop/message_loop.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "third_party/skia/include/core/SkBitmap.h"
11 #import "ui/base/cocoa/menu_controller.h"
12 #include "ui/base/models/simple_menu_model.h"
13 #include "ui/base/resource/resource_bundle.h"
14 #include "ui/gfx/image/image.h"
15 #import "ui/gfx/test/ui_cocoa_test_helper.h"
16 #include "ui/resources/grit/ui_resources.h"
17 #include "ui/strings/grit/ui_strings.h"
19 using base::ASCIIToUTF16;
21 namespace ui {
23 namespace {
25 const int kTestLabelResourceId = IDS_APP_SCROLLBAR_CXMENU_SCROLLHERE;
27 class MenuControllerTest : public CocoaTest {
30 // A menu delegate that counts the number of times certain things are called
31 // to make sure things are hooked up properly.
32 class Delegate : public SimpleMenuModel::Delegate {
33  public:
34   Delegate()
35       : execute_count_(0),
36         enable_count_(0),
37         menu_to_close_(nil),
38         did_show_(false),
39         did_close_(false) {
40   }
42   bool IsCommandIdChecked(int command_id) const override { return false; }
43   bool IsCommandIdEnabled(int command_id) const override {
44     ++enable_count_;
45     return true;
46   }
47   bool GetAcceleratorForCommandId(int command_id,
48                                   Accelerator* accelerator) override {
49     return false;
50   }
51   void ExecuteCommand(int command_id, int event_flags) override {
52     ++execute_count_;
53   }
55   void MenuWillShow(SimpleMenuModel* /*source*/) override {
56     EXPECT_FALSE(did_show_);
57     EXPECT_FALSE(did_close_);
58     did_show_ = true;
59     NSArray* modes = [NSArray arrayWithObjects:NSEventTrackingRunLoopMode,
60                                                NSDefaultRunLoopMode,
61                                                nil];
62     [menu_to_close_ performSelector:@selector(cancelTracking)
63                          withObject:nil
64                          afterDelay:0.1
65                             inModes:modes];
66   }
68   void MenuClosed(SimpleMenuModel* /*source*/) override {
69     EXPECT_TRUE(did_show_);
70     EXPECT_FALSE(did_close_);
71     did_close_ = true;
72   }
74   int execute_count_;
75   mutable int enable_count_;
76   // The menu on which to call |-cancelTracking| after a short delay in
77   // MenuWillShow.
78   NSMenu* menu_to_close_;
79   bool did_show_;
80   bool did_close_;
83 // Just like Delegate, except the items are treated as "dynamic" so updates to
84 // the label/icon in the model are reflected in the menu.
85 class DynamicDelegate : public Delegate {
86  public:
87   DynamicDelegate() {}
88   bool IsItemForCommandIdDynamic(int command_id) const override { return true; }
89   base::string16 GetLabelForCommandId(int command_id) const override {
90     return label_;
91   }
92   bool GetIconForCommandId(int command_id, gfx::Image* icon) const override {
93     if (icon_.IsEmpty()) {
94       return false;
95     } else {
96       *icon = icon_;
97       return true;
98     }
99   }
100   void SetDynamicLabel(base::string16 label) { label_ = label; }
101   void SetDynamicIcon(const gfx::Image& icon) { icon_ = icon; }
103  private:
104   base::string16 label_;
105   gfx::Image icon_;
108 // Menu model that returns a gfx::FontList object for one of the items in the
109 // menu.
110 class FontListMenuModel : public SimpleMenuModel {
111  public:
112   FontListMenuModel(SimpleMenuModel::Delegate* delegate,
113                     const gfx::FontList* font_list, int index)
114       : SimpleMenuModel(delegate),
115         font_list_(font_list),
116         index_(index) {
117   }
118   ~FontListMenuModel() override {}
119   const gfx::FontList* GetLabelFontListAt(int index) const override {
120     return (index == index_) ? font_list_ : NULL;
121   }
123  private:
124   const gfx::FontList* font_list_;
125   const int index_;
128 TEST_F(MenuControllerTest, EmptyMenu) {
129   Delegate delegate;
130   SimpleMenuModel model(&delegate);
131   base::scoped_nsobject<MenuController> menu(
132       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
133   EXPECT_EQ([[menu menu] numberOfItems], 0);
136 TEST_F(MenuControllerTest, BasicCreation) {
137   Delegate delegate;
138   SimpleMenuModel model(&delegate);
139   model.AddItem(1, ASCIIToUTF16("one"));
140   model.AddItem(2, ASCIIToUTF16("two"));
141   model.AddItem(3, ASCIIToUTF16("three"));
142   model.AddSeparator(NORMAL_SEPARATOR);
143   model.AddItem(4, ASCIIToUTF16("four"));
144   model.AddItem(5, ASCIIToUTF16("five"));
146   base::scoped_nsobject<MenuController> menu(
147       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
148   EXPECT_EQ([[menu menu] numberOfItems], 6);
150   // Check the title, tag, and represented object are correct for a random
151   // element.
152   NSMenuItem* itemTwo = [[menu menu] itemAtIndex:2];
153   NSString* title = [itemTwo title];
154   EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title));
155   EXPECT_EQ([itemTwo tag], 2);
156   EXPECT_EQ([[itemTwo representedObject] pointerValue], &model);
158   EXPECT_TRUE([[[menu menu] itemAtIndex:3] isSeparatorItem]);
161 TEST_F(MenuControllerTest, Submenus) {
162   Delegate delegate;
163   SimpleMenuModel model(&delegate);
164   model.AddItem(1, ASCIIToUTF16("one"));
165   SimpleMenuModel submodel(&delegate);
166   submodel.AddItem(2, ASCIIToUTF16("sub-one"));
167   submodel.AddItem(3, ASCIIToUTF16("sub-two"));
168   submodel.AddItem(4, ASCIIToUTF16("sub-three"));
169   model.AddSubMenuWithStringId(5, kTestLabelResourceId, &submodel);
170   model.AddItem(6, ASCIIToUTF16("three"));
172   base::scoped_nsobject<MenuController> menu(
173       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
174   EXPECT_EQ([[menu menu] numberOfItems], 3);
176   // Inspect the submenu to ensure it has correct properties.
177   NSMenu* submenu = [[[menu menu] itemAtIndex:1] submenu];
178   EXPECT_TRUE(submenu);
179   EXPECT_EQ([submenu numberOfItems], 3);
181   // Inspect one of the items to make sure it has the correct model as its
182   // represented object and the proper tag.
183   NSMenuItem* submenuItem = [submenu itemAtIndex:1];
184   NSString* title = [submenuItem title];
185   EXPECT_EQ(ASCIIToUTF16("sub-two"), base::SysNSStringToUTF16(title));
186   EXPECT_EQ([submenuItem tag], 1);
187   EXPECT_EQ([[submenuItem representedObject] pointerValue], &submodel);
189   // Make sure the item after the submenu is correct and its represented
190   // object is back to the top model.
191   NSMenuItem* item = [[menu menu] itemAtIndex:2];
192   title = [item title];
193   EXPECT_EQ(ASCIIToUTF16("three"), base::SysNSStringToUTF16(title));
194   EXPECT_EQ([item tag], 2);
195   EXPECT_EQ([[item representedObject] pointerValue], &model);
198 TEST_F(MenuControllerTest, EmptySubmenu) {
199   Delegate delegate;
200   SimpleMenuModel model(&delegate);
201   model.AddItem(1, ASCIIToUTF16("one"));
202   SimpleMenuModel submodel(&delegate);
203   model.AddSubMenuWithStringId(2, kTestLabelResourceId, &submodel);
205   base::scoped_nsobject<MenuController> menu(
206       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
207   EXPECT_EQ([[menu menu] numberOfItems], 2);
210 TEST_F(MenuControllerTest, PopUpButton) {
211   Delegate delegate;
212   SimpleMenuModel model(&delegate);
213   model.AddItem(1, ASCIIToUTF16("one"));
214   model.AddItem(2, ASCIIToUTF16("two"));
215   model.AddItem(3, ASCIIToUTF16("three"));
217   // Menu should have an extra item inserted at position 0 that has an empty
218   // title.
219   base::scoped_nsobject<MenuController> menu(
220       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:YES]);
221   EXPECT_EQ([[menu menu] numberOfItems], 4);
222   EXPECT_EQ(base::SysNSStringToUTF16([[[menu menu] itemAtIndex:0] title]),
223             base::string16());
225   // Make sure the tags are still correct (the index no longer matches the tag).
226   NSMenuItem* itemTwo = [[menu menu] itemAtIndex:2];
227   EXPECT_EQ([itemTwo tag], 1);
230 TEST_F(MenuControllerTest, Execute) {
231   Delegate delegate;
232   SimpleMenuModel model(&delegate);
233   model.AddItem(1, ASCIIToUTF16("one"));
234   base::scoped_nsobject<MenuController> menu(
235       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
236   EXPECT_EQ([[menu menu] numberOfItems], 1);
238   // Fake selecting the menu item, we expect the delegate to be told to execute
239   // a command.
240   NSMenuItem* item = [[menu menu] itemAtIndex:0];
241   [[item target] performSelector:[item action] withObject:item];
242   EXPECT_EQ(delegate.execute_count_, 1);
245 void Validate(MenuController* controller, NSMenu* menu) {
246   for (int i = 0; i < [menu numberOfItems]; ++i) {
247     NSMenuItem* item = [menu itemAtIndex:i];
248     [controller validateUserInterfaceItem:item];
249     if ([item hasSubmenu])
250       Validate(controller, [item submenu]);
251   }
254 TEST_F(MenuControllerTest, Validate) {
255   Delegate delegate;
256   SimpleMenuModel model(&delegate);
257   model.AddItem(1, ASCIIToUTF16("one"));
258   model.AddItem(2, ASCIIToUTF16("two"));
259   SimpleMenuModel submodel(&delegate);
260   submodel.AddItem(2, ASCIIToUTF16("sub-one"));
261   model.AddSubMenuWithStringId(3, kTestLabelResourceId, &submodel);
263   base::scoped_nsobject<MenuController> menu(
264       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
265   EXPECT_EQ([[menu menu] numberOfItems], 3);
267   Validate(menu.get(), [menu menu]);
270 // Tests that items which have a font set actually use that font.
271 TEST_F(MenuControllerTest, LabelFontList) {
272   Delegate delegate;
273   const gfx::FontList& bold = ResourceBundle::GetSharedInstance().GetFontList(
274       ResourceBundle::BoldFont);
275   FontListMenuModel model(&delegate, &bold, 0);
276   model.AddItem(1, ASCIIToUTF16("one"));
277   model.AddItem(2, ASCIIToUTF16("two"));
279   base::scoped_nsobject<MenuController> menu(
280       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
281   EXPECT_EQ([[menu menu] numberOfItems], 2);
283   Validate(menu.get(), [menu menu]);
285   EXPECT_TRUE([[[menu menu] itemAtIndex:0] attributedTitle] != nil);
286   EXPECT_TRUE([[[menu menu] itemAtIndex:1] attributedTitle] == nil);
289 TEST_F(MenuControllerTest, DefaultInitializer) {
290   Delegate delegate;
291   SimpleMenuModel model(&delegate);
292   model.AddItem(1, ASCIIToUTF16("one"));
293   model.AddItem(2, ASCIIToUTF16("two"));
294   model.AddItem(3, ASCIIToUTF16("three"));
296   base::scoped_nsobject<MenuController> menu([[MenuController alloc] init]);
297   EXPECT_FALSE([menu menu]);
299   [menu setModel:&model];
300   [menu setUseWithPopUpButtonCell:NO];
301   EXPECT_TRUE([menu menu]);
302   EXPECT_EQ(3, [[menu menu] numberOfItems]);
304   // Check immutability.
305   model.AddItem(4, ASCIIToUTF16("four"));
306   EXPECT_EQ(3, [[menu menu] numberOfItems]);
309 // Test that menus with dynamic labels actually get updated.
310 TEST_F(MenuControllerTest, Dynamic) {
311   DynamicDelegate delegate;
313   // Create a menu containing a single item whose label is "initial" and who has
314   // no icon.
315   base::string16 initial = ASCIIToUTF16("initial");
316   delegate.SetDynamicLabel(initial);
317   SimpleMenuModel model(&delegate);
318   model.AddItem(1, ASCIIToUTF16("foo"));
319   base::scoped_nsobject<MenuController> menu(
320       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
321   EXPECT_EQ([[menu menu] numberOfItems], 1);
322   // Validate() simulates opening the menu - the item label/icon should be
323   // initialized after this so we can validate the menu contents.
324   Validate(menu.get(), [menu menu]);
325   NSMenuItem* item = [[menu menu] itemAtIndex:0];
326   // Item should have the "initial" label and no icon.
327   EXPECT_EQ(initial, base::SysNSStringToUTF16([item title]));
328   EXPECT_EQ(nil, [item image]);
330   // Now update the item to have a label of "second" and an icon.
331   base::string16 second = ASCIIToUTF16("second");
332   delegate.SetDynamicLabel(second);
333   const gfx::Image& icon =
334       ResourceBundle::GetSharedInstance().GetNativeImageNamed(IDR_THROBBER);
335   delegate.SetDynamicIcon(icon);
336   // Simulate opening the menu and validate that the item label + icon changes.
337   Validate(menu.get(), [menu menu]);
338   EXPECT_EQ(second, base::SysNSStringToUTF16([item title]));
339   EXPECT_TRUE([item image] != nil);
341   // Now get rid of the icon and make sure it goes away.
342   delegate.SetDynamicIcon(gfx::Image());
343   Validate(menu.get(), [menu menu]);
344   EXPECT_EQ(second, base::SysNSStringToUTF16([item title]));
345   EXPECT_EQ(nil, [item image]);
348 TEST_F(MenuControllerTest, OpenClose) {
349   // SimpleMenuModel posts a task that calls Delegate::MenuClosed. Create
350   // a MessageLoop for that purpose.
351   base::MessageLoopForUI message_loop;
353   // Create the model.
354   Delegate delegate;
355   SimpleMenuModel model(&delegate);
356   model.AddItem(1, ASCIIToUTF16("allays"));
357   model.AddItem(2, ASCIIToUTF16("i"));
358   model.AddItem(3, ASCIIToUTF16("bf"));
360   // Create the controller.
361   base::scoped_nsobject<MenuController> menu(
362       [[MenuController alloc] initWithModel:&model useWithPopUpButtonCell:NO]);
363   delegate.menu_to_close_ = [menu menu];
365   EXPECT_FALSE([menu isMenuOpen]);
367   // In the event tracking run loop mode of the menu, verify that the controller
368   // resports the menu as open.
369   CFRunLoopPerformBlock(CFRunLoopGetCurrent(), NSEventTrackingRunLoopMode, ^{
370       EXPECT_TRUE([menu isMenuOpen]);
371   });
373   // Pop open the menu, which will spin an event-tracking run loop.
374   [NSMenu popUpContextMenu:[menu menu]
375                  withEvent:nil
376                    forView:[test_window() contentView]];
378   EXPECT_FALSE([menu isMenuOpen]);
380   // When control returns back to here, the menu will have finished running its
381   // loop and will have closed itself (see Delegate::MenuWillShow).
382   EXPECT_TRUE(delegate.did_show_);
384   // When the menu tells the Model it closed, the Model posts a task to notify
385   // the delegate. But since this is a test and there's no running MessageLoop,
386   // |did_close_| will remain false until we pump the task manually.
387   EXPECT_FALSE(delegate.did_close_);
389   // Pump the task that notifies the delegate.
390   message_loop.RunUntilIdle();
392   // Expect that the delegate got notified properly.
393   EXPECT_TRUE(delegate.did_close_);
396 }  // namespace
398 }  // namespace ui