bookmark_menu_bridge_unittest.mm revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2011 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 <AppKit/AppKit.h>
6
7#include "base/strings/string16.h"
8#include "base/strings/string_util.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/app/chrome_command_ids.h"
11#include "chrome/browser/bookmarks/bookmark_model.h"
12#include "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
13#include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
14#include "grit/generated_resources.h"
15#include "testing/gtest/include/gtest/gtest.h"
16#import "testing/gtest_mac.h"
17#include "testing/platform_test.h"
18#include "ui/base/l10n/l10n_util.h"
19
20class TestBookmarkMenuBridge : public BookmarkMenuBridge {
21 public:
22  TestBookmarkMenuBridge(Profile* profile, NSMenu *menu)
23      : BookmarkMenuBridge(profile, menu),
24        menu_(menu) {
25  }
26  virtual ~TestBookmarkMenuBridge() {
27    [menu_ autorelease];
28  }
29
30  NSMenu* menu_;
31
32 protected:
33  // Overridden from BookmarkMenuBridge.
34  virtual NSMenu* BookmarkMenu() OVERRIDE {
35    return menu_;
36  }
37};
38
39// TODO(jrg): see refactor comment in bookmark_bar_state_controller_unittest.mm
40class BookmarkMenuBridgeTest : public CocoaProfileTest {
41 public:
42
43   virtual void SetUp() {
44     CocoaProfileTest::SetUp();
45     ASSERT_TRUE(profile());
46
47     NSMenu* menu = [[NSMenu alloc] initWithTitle:@"test"];
48     bridge_.reset(new TestBookmarkMenuBridge(profile(), menu));
49     EXPECT_TRUE(bridge_.get());
50   }
51
52  // We are a friend of BookmarkMenuBridge (and have access to
53  // protected methods), but none of the classes generated by TEST_F()
54  // are.  This (and AddNodeToMenu()) are simple wrappers to let
55  // derived test classes have access to protected methods.
56  void ClearBookmarkMenu(BookmarkMenuBridge* bridge, NSMenu* menu) {
57    bridge->ClearBookmarkMenu(menu);
58  }
59
60  void InvalidateMenu()  { bridge_->InvalidateMenu(); }
61  bool menu_is_valid()  { return bridge_->menuIsValid_; }
62
63  void AddNodeToMenu(BookmarkMenuBridge* bridge,
64                     const BookmarkNode* root,
65                     NSMenu* menu) {
66    bridge->AddNodeToMenu(root, menu, true);
67  }
68
69  void AddItemToMenu(BookmarkMenuBridge* bridge,
70                     int command_id,
71                     int message_id,
72                     const BookmarkNode* node,
73                     NSMenu* menu,
74                     bool enable) {
75    bridge->AddItemToMenu(command_id, message_id, node, menu, enable);
76  }
77
78  NSMenuItem* MenuItemForNode(BookmarkMenuBridge* bridge,
79                              const BookmarkNode* node) {
80    return bridge->MenuItemForNode(node);
81  }
82
83  NSMenuItem* AddTestMenuItem(NSMenu *menu, NSString *title, SEL selector) {
84    NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:title action:NULL
85                                            keyEquivalent:@""] autorelease];
86    if (selector)
87      [item setAction:selector];
88    [menu addItem:item];
89    return item;
90  }
91  scoped_ptr<TestBookmarkMenuBridge> bridge_;
92};
93
94TEST_F(BookmarkMenuBridgeTest, TestBookmarkMenuAutoSeparator) {
95  BookmarkModel* model = bridge_->GetBookmarkModel();
96  bridge_->Loaded(model, false);
97  NSMenu* menu = bridge_->menu_;
98  bridge_->UpdateMenu(menu);
99  // The bare menu after loading used to have a separator and an
100  // "Other Bookmarks" submenu, but we no longer show those items if the
101  // "Other Bookmarks" submenu would be empty.
102  EXPECT_EQ(0, [menu numberOfItems]);
103  // Add a bookmark and reload and there should be 8 items: the previous
104  // menu contents plus two new separator, the new bookmark and three
105  // versions of 'Open All Bookmarks' menu items.
106  const BookmarkNode* parent = model->bookmark_bar_node();
107  const char* url = "http://www.zim-bop-a-dee.com/";
108  model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
109  bridge_->UpdateMenu(menu);
110  EXPECT_EQ(6, [menu numberOfItems]);
111  // Remove the new bookmark and reload and we should have 2 items again
112  // because the separator should have been removed as well.
113  model->Remove(parent, 0);
114  bridge_->UpdateMenu(menu);
115  EXPECT_EQ(0, [menu numberOfItems]);
116}
117
118// Test that ClearBookmarkMenu() removes all bookmark menus.
119TEST_F(BookmarkMenuBridgeTest, TestClearBookmarkMenu) {
120  NSMenu* menu = bridge_->menu_;
121
122  AddTestMenuItem(menu, @"hi mom", nil);
123  AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
124  NSMenuItem* item = AddTestMenuItem(menu, @"hi mom", nil);
125  [item setSubmenu:[[[NSMenu alloc] initWithTitle:@"bar"] autorelease]];
126  AddTestMenuItem(menu, @"not", @selector(openBookmarkMenuItem:));
127  AddTestMenuItem(menu, @"zippy", @selector(length));
128  [menu addItem:[NSMenuItem separatorItem]];
129
130  ClearBookmarkMenu(bridge_.get(), menu);
131
132  // Make sure all bookmark items are removed, all items with
133  // submenus removed, and all separator items are gone.
134  EXPECT_EQ(2, [menu numberOfItems]);
135  for (NSMenuItem *item in [menu itemArray]) {
136    EXPECT_NSNE(@"not", [item title]);
137  }
138}
139
140// Test invalidation
141TEST_F(BookmarkMenuBridgeTest, TestInvalidation) {
142  BookmarkModel* model = bridge_->GetBookmarkModel();
143  bridge_->Loaded(model, false);
144
145  EXPECT_FALSE(menu_is_valid());
146  bridge_->UpdateMenu(bridge_->menu_);
147  EXPECT_TRUE(menu_is_valid());
148
149  InvalidateMenu();
150  EXPECT_FALSE(menu_is_valid());
151  InvalidateMenu();
152  EXPECT_FALSE(menu_is_valid());
153  bridge_->UpdateMenu(bridge_->menu_);
154  EXPECT_TRUE(menu_is_valid());
155  bridge_->UpdateMenu(bridge_->menu_);
156  EXPECT_TRUE(menu_is_valid());
157
158  const BookmarkNode* parent = model->bookmark_bar_node();
159  const char* url = "http://www.zim-bop-a-dee.com/";
160  model->AddURL(parent, 0, ASCIIToUTF16("Bookmark"), GURL(url));
161
162  EXPECT_FALSE(menu_is_valid());
163  bridge_->UpdateMenu(bridge_->menu_);
164  EXPECT_TRUE(menu_is_valid());
165}
166
167// Test that AddNodeToMenu() properly adds bookmark nodes as menus,
168// including the recursive case.
169TEST_F(BookmarkMenuBridgeTest, TestAddNodeToMenu) {
170  string16 empty;
171  NSMenu* menu = bridge_->menu_;
172
173  BookmarkModel* model = bridge_->GetBookmarkModel();
174  const BookmarkNode* root = model->bookmark_bar_node();
175  EXPECT_TRUE(model && root);
176
177  const char* short_url = "http://foo/";
178  const char* long_url = "http://super-duper-long-url--."
179    "that.cannot.possibly.fit.even-in-80-columns"
180    "or.be.reasonably-displayed-in-a-menu"
181    "without.looking-ridiculous.com/"; // 140 chars total
182
183  // 3 nodes; middle one has a child, last one has a HUGE URL
184  // Set their titles to be the same as the URLs
185  const BookmarkNode* node = NULL;
186  model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
187  bridge_->UpdateMenu(menu);
188  int prev_count = [menu numberOfItems] - 1; // "extras" added at this point
189  node = model->AddFolder(root, 1, empty);
190  model->AddURL(root, 2, ASCIIToUTF16(long_url), GURL(long_url));
191
192  // And the submenu fo the middle one
193  model->AddURL(node, 0, empty, GURL("http://sub"));
194  bridge_->UpdateMenu(menu);
195
196  EXPECT_EQ((NSInteger)(prev_count+3), [menu numberOfItems]);
197
198  // Verify the 1st one is there with the right action.
199  NSMenuItem* item = [menu itemWithTitle:[NSString
200                                           stringWithUTF8String:short_url]];
201  EXPECT_TRUE(item);
202  EXPECT_EQ(@selector(openBookmarkMenuItem:), [item action]);
203  EXPECT_EQ(NO, [item hasSubmenu]);
204  NSMenuItem* short_item = item;
205  NSMenuItem* long_item = nil;
206
207  // Now confirm we have 1 submenu (the one we added, and not "other")
208  int subs = 0;
209  for (item in [menu itemArray]) {
210    if ([item hasSubmenu])
211      subs++;
212  }
213  EXPECT_EQ(1, subs);
214
215  for (item in [menu itemArray]) {
216    if ([[item title] hasPrefix:@"http://super-duper"]) {
217      long_item = item;
218      break;
219    }
220  }
221  EXPECT_TRUE(long_item);
222
223  // Make sure a short title looks fine
224  NSString* s = [short_item title];
225  EXPECT_NSEQ([NSString stringWithUTF8String:short_url], s);
226
227  // Make sure a super-long title gets trimmed
228  s = [long_item title];
229  EXPECT_TRUE([s length] < strlen(long_url));
230
231  // Confirm tooltips and confirm they are not trimmed (like the item
232  // name might be).  Add tolerance for URL fixer-upping;
233  // e.g. http://foo becomes http://foo/)
234  EXPECT_GE([[short_item toolTip] length], strlen(short_url) - 3);
235  EXPECT_GE([[long_item toolTip] length], strlen(long_url) - 3);
236
237  // Make sure the favicon is non-nil (should be either the default site
238  // icon or a favicon, if present).
239  EXPECT_TRUE([short_item image]);
240  EXPECT_TRUE([long_item image]);
241}
242
243// Test that AddItemToMenu() properly added versions of
244// 'Open All Bookmarks' as menu items.
245TEST_F(BookmarkMenuBridgeTest, TestAddItemToMenu) {
246  NSString* title;
247  NSMenuItem* item;
248  NSMenu* menu = bridge_->menu_;
249
250  BookmarkModel* model = bridge_->GetBookmarkModel();
251  const BookmarkNode* root = model->bookmark_bar_node();
252  EXPECT_TRUE(model && root);
253  EXPECT_EQ(0, [menu numberOfItems]);
254
255  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
256                IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, true);
257  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
258                IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, true);
259  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
260                IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, true);
261  EXPECT_EQ(3, [menu numberOfItems]);
262
263  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
264  item = [menu itemWithTitle:title];
265  EXPECT_TRUE(item);
266  EXPECT_EQ(@selector(openAllBookmarks:), [item action]);
267  EXPECT_TRUE([item isEnabled]);
268
269  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
270  item = [menu itemWithTitle:title];
271  EXPECT_TRUE(item);
272  EXPECT_EQ(@selector(openAllBookmarksNewWindow:), [item action]);
273  EXPECT_TRUE([item isEnabled]);
274
275  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
276  item = [menu itemWithTitle:title];
277  EXPECT_TRUE(item);
278  EXPECT_EQ(@selector(openAllBookmarksIncognitoWindow:), [item action]);
279  EXPECT_TRUE([item isEnabled]);
280
281  ClearBookmarkMenu(bridge_.get(), menu);
282  EXPECT_EQ(0, [menu numberOfItems]);
283
284  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL,
285                IDS_BOOKMARK_BAR_OPEN_ALL, root, menu, false);
286  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW,
287                IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW, root, menu, false);
288  AddItemToMenu(bridge_.get(), IDC_BOOKMARK_BAR_OPEN_ALL_INCOGNITO,
289                IDS_BOOKMARK_BAR_OPEN_INCOGNITO, root, menu, false);
290  EXPECT_EQ(3, [menu numberOfItems]);
291
292  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL);
293  item = [menu itemWithTitle:title];
294  EXPECT_TRUE(item);
295  EXPECT_EQ(nil, [item action]);
296  EXPECT_FALSE([item isEnabled]);
297
298  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_ALL_NEW_WINDOW);
299  item = [menu itemWithTitle:title];
300  EXPECT_TRUE(item);
301  EXPECT_EQ(nil, [item action]);
302  EXPECT_FALSE([item isEnabled]);
303
304  title = l10n_util::GetNSStringWithFixup(IDS_BOOKMARK_BAR_OPEN_INCOGNITO);
305  item = [menu itemWithTitle:title];
306  EXPECT_TRUE(item);
307  EXPECT_EQ(nil, [item action]);
308  EXPECT_FALSE([item isEnabled]);
309}
310
311// Makes sure our internal map of BookmarkNode to NSMenuItem works.
312TEST_F(BookmarkMenuBridgeTest, TestGetMenuItemForNode) {
313  string16 empty;
314  NSMenu* menu = bridge_->menu_;
315
316  BookmarkModel* model = bridge_->GetBookmarkModel();
317  const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
318  const BookmarkNode* root = model->AddFolder(bookmark_bar, 0, empty);
319  EXPECT_TRUE(model && root);
320
321  model->AddURL(root, 0, ASCIIToUTF16("Test Item"), GURL("http://test"));
322  AddNodeToMenu(bridge_.get(), root, menu);
323  EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
324
325  model->AddURL(root, 1, ASCIIToUTF16("Test 2"), GURL("http://second-test"));
326  AddNodeToMenu(bridge_.get(), root, menu);
327  EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
328  EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(1)));
329
330  const BookmarkNode* removed_node = root->GetChild(0);
331  EXPECT_EQ(2, root->child_count());
332  model->Remove(root, 0);
333  EXPECT_EQ(1, root->child_count());
334  bridge_->UpdateMenu(menu);
335  EXPECT_FALSE(MenuItemForNode(bridge_.get(), removed_node));
336  EXPECT_TRUE(MenuItemForNode(bridge_.get(), root->GetChild(0)));
337
338  const BookmarkNode empty_node(GURL("http://no-where/"));
339  EXPECT_FALSE(MenuItemForNode(bridge_.get(), &empty_node));
340  EXPECT_FALSE(MenuItemForNode(bridge_.get(), NULL));
341}
342
343// Test that Loaded() adds both the bookmark bar nodes and the "other" nodes.
344TEST_F(BookmarkMenuBridgeTest, TestAddNodeToOther) {
345  NSMenu* menu = bridge_->menu_;
346
347  BookmarkModel* model = bridge_->GetBookmarkModel();
348  const BookmarkNode* root = model->other_node();
349  EXPECT_TRUE(model && root);
350
351  const char* short_url = "http://foo/";
352  model->AddURL(root, 0, ASCIIToUTF16(short_url), GURL(short_url));
353
354  bridge_->UpdateMenu(menu);
355  ASSERT_GT([menu numberOfItems], 0);
356  NSMenuItem* other = [menu itemAtIndex:([menu numberOfItems]-1)];
357  EXPECT_TRUE(other);
358  EXPECT_TRUE([other hasSubmenu]);
359  ASSERT_GT([[other submenu] numberOfItems], 0);
360  EXPECT_NSEQ(@"http://foo/", [[[other submenu] itemAtIndex:0] title]);
361}
362
363TEST_F(BookmarkMenuBridgeTest, TestFaviconLoading) {
364  NSMenu* menu = bridge_->menu_;
365
366  BookmarkModel* model = bridge_->GetBookmarkModel();
367  const BookmarkNode* root = model->bookmark_bar_node();
368  EXPECT_TRUE(model && root);
369
370  const BookmarkNode* node =
371      model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
372                    GURL("http://favicon-test"));
373  bridge_->UpdateMenu(menu);
374  NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
375  EXPECT_TRUE([item image]);
376  [item setImage:nil];
377  bridge_->BookmarkNodeFaviconChanged(model, node);
378  EXPECT_TRUE([item image]);
379}
380
381TEST_F(BookmarkMenuBridgeTest, TestChangeTitle) {
382  NSMenu* menu = bridge_->menu_;
383  BookmarkModel* model = bridge_->GetBookmarkModel();
384  const BookmarkNode* root = model->bookmark_bar_node();
385  EXPECT_TRUE(model && root);
386
387  const BookmarkNode* node =
388      model->AddURL(root, 0, ASCIIToUTF16("Test Item"),
389                    GURL("http://title-test"));
390  bridge_->UpdateMenu(menu);
391  NSMenuItem* item = [menu itemWithTitle:@"Test Item"];
392  EXPECT_TRUE([item image]);
393
394  model->SetTitle(node, ASCIIToUTF16("New Title"));
395
396  item = [menu itemWithTitle:@"Test Item"];
397  EXPECT_FALSE(item);
398  item = [menu itemWithTitle:@"New Title"];
399  EXPECT_TRUE(item);
400}
401
402