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