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