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