bookmark_bubble_controller_unittest.mm revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1// Copyright (c) 2012 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/basictypes.h" 8#include "base/mac/scoped_nsobject.h" 9#include "base/strings/string16.h" 10#include "base/strings/utf_string_conversions.h" 11#include "chrome/browser/bookmarks/bookmark_model_factory.h" 12#include "chrome/browser/signin/signin_manager.h" 13#include "chrome/browser/signin/signin_manager_factory.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/browser/ui/browser_window.h" 16#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_controller.h" 17#include "chrome/browser/ui/cocoa/browser_window_controller.h" 18#include "chrome/browser/ui/cocoa/cocoa_profile_test.h" 19#import "chrome/browser/ui/cocoa/info_bubble_window.h" 20#include "content/public/browser/notification_service.h" 21#include "testing/gtest/include/gtest/gtest.h" 22#import "testing/gtest_mac.h" 23#include "testing/platform_test.h" 24 25using content::WebContents; 26 27// Watch for bookmark pulse notifications so we can confirm they were sent. 28@interface BookmarkPulseObserver : NSObject { 29 int notifications_; 30} 31@property (assign, nonatomic) int notifications; 32@end 33 34 35@implementation BookmarkPulseObserver 36 37@synthesize notifications = notifications_; 38 39- (id)init { 40 if ((self = [super init])) { 41 [[NSNotificationCenter defaultCenter] 42 addObserver:self 43 selector:@selector(pulseBookmarkNotification:) 44 name:bookmark_button::kPulseBookmarkButtonNotification 45 object:nil]; 46 } 47 return self; 48} 49 50- (void)pulseBookmarkNotification:(NSNotificationCenter *)notification { 51 notifications_++; 52} 53 54- (void)dealloc { 55 [[NSNotificationCenter defaultCenter] removeObserver:self]; 56 [super dealloc]; 57} 58 59@end 60 61 62namespace { 63 64// URL of the test bookmark. 65const char kTestBookmarkURL[] = "http://www.google.com"; 66 67class BookmarkBubbleControllerTest : public CocoaProfileTest { 68 public: 69 static int edits_; 70 BookmarkBubbleController* controller_; 71 72 BookmarkBubbleControllerTest() : controller_(nil) { 73 edits_ = 0; 74 } 75 76 virtual void TearDown() OVERRIDE { 77 [controller_ close]; 78 CocoaProfileTest::TearDown(); 79 } 80 81 // Returns a controller but ownership not transferred. 82 // Only one of these will be valid at a time. 83 BookmarkBubbleController* ControllerForNode(const BookmarkNode* node) { 84 if (controller_ && !IsWindowClosing()) { 85 [controller_ close]; 86 controller_ = nil; 87 } 88 controller_ = [[BookmarkBubbleController alloc] 89 initWithParentWindow:browser()->window()->GetNativeWindow() 90 model:BookmarkModelFactory::GetForProfile(profile()) 91 node:node 92 alreadyBookmarked:YES]; 93 EXPECT_TRUE([controller_ window]); 94 // The window must be gone or we'll fail a unit test with windows left open. 95 [static_cast<InfoBubbleWindow*>([controller_ window]) 96 setAllowedAnimations:info_bubble::kAnimateNone]; 97 [controller_ showWindow:nil]; 98 return controller_; 99 } 100 101 BookmarkModel* GetBookmarkModel() { 102 return BookmarkModelFactory::GetForProfile(profile()); 103 } 104 105 const BookmarkNode* CreateTestBookmark() { 106 BookmarkModel* model = GetBookmarkModel(); 107 return model->AddURL(model->bookmark_bar_node(), 108 0, 109 ASCIIToUTF16("Bookie markie title"), 110 GURL(kTestBookmarkURL)); 111 } 112 113 bool IsWindowClosing() { 114 return [static_cast<InfoBubbleWindow*>([controller_ window]) isClosing]; 115 } 116}; 117 118// static 119int BookmarkBubbleControllerTest::edits_; 120 121// Confirm basics about the bubble window (e.g. that it is inside the 122// parent window) 123TEST_F(BookmarkBubbleControllerTest, TestBubbleWindow) { 124 const BookmarkNode* node = CreateTestBookmark(); 125 BookmarkBubbleController* controller = ControllerForNode(node); 126 EXPECT_TRUE(controller); 127 NSWindow* window = [controller window]; 128 EXPECT_TRUE(window); 129 EXPECT_TRUE(NSContainsRect([browser()->window()->GetNativeWindow() frame], 130 [window frame])); 131} 132 133// Test that we can handle closing the parent window 134TEST_F(BookmarkBubbleControllerTest, TestClosingParentWindow) { 135 const BookmarkNode* node = CreateTestBookmark(); 136 BookmarkBubbleController* controller = ControllerForNode(node); 137 EXPECT_TRUE(controller); 138 NSWindow* window = [controller window]; 139 EXPECT_TRUE(window); 140 base::mac::ScopedNSAutoreleasePool pool; 141 [browser()->window()->GetNativeWindow() performClose:NSApp]; 142} 143 144 145// Confirm population of folder list 146TEST_F(BookmarkBubbleControllerTest, TestFillInFolder) { 147 // Create some folders, including a nested folder 148 BookmarkModel* model = GetBookmarkModel(); 149 EXPECT_TRUE(model); 150 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 151 EXPECT_TRUE(bookmarkBarNode); 152 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0, 153 ASCIIToUTF16("one")); 154 EXPECT_TRUE(node1); 155 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1, 156 ASCIIToUTF16("two")); 157 EXPECT_TRUE(node2); 158 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2, 159 ASCIIToUTF16("three")); 160 EXPECT_TRUE(node3); 161 const BookmarkNode* node4 = model->AddFolder(node2, 0, ASCIIToUTF16("sub")); 162 EXPECT_TRUE(node4); 163 const BookmarkNode* node5 = model->AddURL(node1, 0, ASCIIToUTF16("title1"), 164 GURL(kTestBookmarkURL)); 165 EXPECT_TRUE(node5); 166 const BookmarkNode* node6 = model->AddURL(node3, 0, ASCIIToUTF16("title2"), 167 GURL(kTestBookmarkURL)); 168 EXPECT_TRUE(node6); 169 const BookmarkNode* node7 = model->AddURL( 170 node4, 0, ASCIIToUTF16("title3"), GURL("http://www.google.com/reader")); 171 EXPECT_TRUE(node7); 172 173 BookmarkBubbleController* controller = ControllerForNode(node4); 174 EXPECT_TRUE(controller); 175 176 NSArray* titles = 177 [[[controller folderPopUpButton] itemArray] valueForKey:@"title"]; 178 EXPECT_TRUE([titles containsObject:@"one"]); 179 EXPECT_TRUE([titles containsObject:@"two"]); 180 EXPECT_TRUE([titles containsObject:@"three"]); 181 EXPECT_TRUE([titles containsObject:@"sub"]); 182 EXPECT_FALSE([titles containsObject:@"title1"]); 183 EXPECT_FALSE([titles containsObject:@"title2"]); 184 185 186 // Verify that the top level folders are displayed correctly. 187 EXPECT_TRUE([titles containsObject:@"Other Bookmarks"]); 188 EXPECT_TRUE([titles containsObject:@"Bookmarks Bar"]); 189 if (model->mobile_node()->IsVisible()) { 190 EXPECT_TRUE([titles containsObject:@"Mobile Bookmarks"]); 191 } else { 192 EXPECT_FALSE([titles containsObject:@"Mobile Bookmarks"]); 193 } 194} 195 196// Confirm ability to handle folders with blank name. 197TEST_F(BookmarkBubbleControllerTest, TestFolderWithBlankName) { 198 // Create some folders, including a nested folder 199 BookmarkModel* model = GetBookmarkModel(); 200 EXPECT_TRUE(model); 201 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 202 EXPECT_TRUE(bookmarkBarNode); 203 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0, 204 ASCIIToUTF16("one")); 205 EXPECT_TRUE(node1); 206 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1, 207 string16()); 208 EXPECT_TRUE(node2); 209 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2, 210 ASCIIToUTF16("three")); 211 EXPECT_TRUE(node3); 212 const BookmarkNode* node2_1 = model->AddURL(node2, 0, ASCIIToUTF16("title1"), 213 GURL(kTestBookmarkURL)); 214 EXPECT_TRUE(node2_1); 215 216 BookmarkBubbleController* controller = ControllerForNode(node1); 217 EXPECT_TRUE(controller); 218 219 // One of the items should be blank and its node should be node2. 220 NSArray* items = [[controller folderPopUpButton] itemArray]; 221 EXPECT_GT([items count], 4U); 222 BOOL blankFolderFound = NO; 223 for (NSMenuItem* item in [[controller folderPopUpButton] itemArray]) { 224 if ([[item title] length] == 0 && 225 static_cast<const BookmarkNode*>([[item representedObject] 226 pointerValue]) == node2) { 227 blankFolderFound = YES; 228 break; 229 } 230 } 231 EXPECT_TRUE(blankFolderFound); 232} 233 234 235// Click on edit; bubble gets closed. 236TEST_F(BookmarkBubbleControllerTest, TestEdit) { 237 const BookmarkNode* node = CreateTestBookmark(); 238 BookmarkBubbleController* controller = ControllerForNode(node); 239 EXPECT_TRUE(controller); 240 241 EXPECT_EQ(edits_, 0); 242 EXPECT_FALSE(IsWindowClosing()); 243 [controller edit:controller]; 244 EXPECT_EQ(edits_, 1); 245 EXPECT_TRUE(IsWindowClosing()); 246} 247 248// CallClose; bubble gets closed. 249// Also confirm pulse notifications get sent. 250TEST_F(BookmarkBubbleControllerTest, TestClose) { 251 const BookmarkNode* node = CreateTestBookmark(); 252 EXPECT_EQ(edits_, 0); 253 254 base::scoped_nsobject<BookmarkPulseObserver> observer( 255 [[BookmarkPulseObserver alloc] init]); 256 EXPECT_EQ([observer notifications], 0); 257 BookmarkBubbleController* controller = ControllerForNode(node); 258 EXPECT_TRUE(controller); 259 EXPECT_FALSE(IsWindowClosing()); 260 EXPECT_EQ([observer notifications], 1); 261 [controller ok:controller]; 262 EXPECT_EQ(edits_, 0); 263 EXPECT_TRUE(IsWindowClosing()); 264 EXPECT_EQ([observer notifications], 2); 265} 266 267// User changes title and parent folder in the UI 268TEST_F(BookmarkBubbleControllerTest, TestUserEdit) { 269 BookmarkModel* model = GetBookmarkModel(); 270 EXPECT_TRUE(model); 271 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 272 EXPECT_TRUE(bookmarkBarNode); 273 const BookmarkNode* node = model->AddURL(bookmarkBarNode, 274 0, 275 ASCIIToUTF16("short-title"), 276 GURL(kTestBookmarkURL)); 277 const BookmarkNode* grandma = model->AddFolder(bookmarkBarNode, 0, 278 ASCIIToUTF16("grandma")); 279 EXPECT_TRUE(grandma); 280 const BookmarkNode* grandpa = model->AddFolder(bookmarkBarNode, 0, 281 ASCIIToUTF16("grandpa")); 282 EXPECT_TRUE(grandpa); 283 284 BookmarkBubbleController* controller = ControllerForNode(node); 285 EXPECT_TRUE(controller); 286 287 // simulate a user edit 288 [controller setTitle:@"oops" parentFolder:grandma]; 289 [controller edit:controller]; 290 291 // Make sure bookmark has changed 292 EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("oops")); 293 EXPECT_EQ(node->parent()->GetTitle(), ASCIIToUTF16("grandma")); 294} 295 296// Confirm happiness with parent nodes that have the same name. 297TEST_F(BookmarkBubbleControllerTest, TestNewParentSameName) { 298 BookmarkModel* model = GetBookmarkModel(); 299 EXPECT_TRUE(model); 300 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 301 EXPECT_TRUE(bookmarkBarNode); 302 for (int i=0; i<2; i++) { 303 const BookmarkNode* node = model->AddURL(bookmarkBarNode, 304 0, 305 ASCIIToUTF16("short-title"), 306 GURL(kTestBookmarkURL)); 307 EXPECT_TRUE(node); 308 const BookmarkNode* folder = model->AddFolder(bookmarkBarNode, 0, 309 ASCIIToUTF16("NAME")); 310 EXPECT_TRUE(folder); 311 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME")); 312 EXPECT_TRUE(folder); 313 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME")); 314 EXPECT_TRUE(folder); 315 BookmarkBubbleController* controller = ControllerForNode(node); 316 EXPECT_TRUE(controller); 317 318 // simulate a user edit 319 [controller setParentFolderSelection:bookmarkBarNode->GetChild(i)]; 320 [controller edit:controller]; 321 322 // Make sure bookmark has changed, and that the parent is what we 323 // expect. This proves nobody did searching based on name. 324 EXPECT_EQ(node->parent(), bookmarkBarNode->GetChild(i)); 325 } 326} 327 328// Confirm happiness with nodes with the same Name 329TEST_F(BookmarkBubbleControllerTest, TestDuplicateNodeNames) { 330 BookmarkModel* model = GetBookmarkModel(); 331 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 332 EXPECT_TRUE(bookmarkBarNode); 333 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0, 334 ASCIIToUTF16("NAME")); 335 EXPECT_TRUE(node1); 336 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 0, 337 ASCIIToUTF16("NAME")); 338 EXPECT_TRUE(node2); 339 BookmarkBubbleController* controller = ControllerForNode(bookmarkBarNode); 340 EXPECT_TRUE(controller); 341 342 NSPopUpButton* button = [controller folderPopUpButton]; 343 [controller setParentFolderSelection:node1]; 344 NSMenuItem* item = [button selectedItem]; 345 id itemObject = [item representedObject]; 346 EXPECT_NSEQ([NSValue valueWithPointer:node1], itemObject); 347 [controller setParentFolderSelection:node2]; 348 item = [button selectedItem]; 349 itemObject = [item representedObject]; 350 EXPECT_NSEQ([NSValue valueWithPointer:node2], itemObject); 351} 352 353// Click the "remove" button 354TEST_F(BookmarkBubbleControllerTest, TestRemove) { 355 const BookmarkNode* node = CreateTestBookmark(); 356 BookmarkBubbleController* controller = ControllerForNode(node); 357 EXPECT_TRUE(controller); 358 359 BookmarkModel* model = GetBookmarkModel(); 360 EXPECT_TRUE(model->IsBookmarked(GURL(kTestBookmarkURL))); 361 362 [controller remove:controller]; 363 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL))); 364 EXPECT_TRUE(IsWindowClosing()); 365} 366 367// Confirm picking "choose another folder" caused edit: to be called. 368TEST_F(BookmarkBubbleControllerTest, PopUpSelectionChanged) { 369 BookmarkModel* model = GetBookmarkModel(); 370 const BookmarkNode* node = model->AddURL(model->bookmark_bar_node(), 371 0, ASCIIToUTF16("super-title"), 372 GURL(kTestBookmarkURL)); 373 BookmarkBubbleController* controller = ControllerForNode(node); 374 EXPECT_TRUE(controller); 375 376 NSPopUpButton* button = [controller folderPopUpButton]; 377 [button selectItemWithTitle:[[controller class] chooseAnotherFolderString]]; 378 EXPECT_EQ(edits_, 0); 379 [button sendAction:[button action] to:[button target]]; 380 EXPECT_EQ(edits_, 1); 381} 382 383// Create a controller that simulates the bookmark just now being created by 384// the user clicking the star, then sending the "cancel" command to represent 385// them pressing escape. The bookmark should not be there. 386TEST_F(BookmarkBubbleControllerTest, EscapeRemovesNewBookmark) { 387 BookmarkModel* model = GetBookmarkModel(); 388 const BookmarkNode* node = CreateTestBookmark(); 389 BookmarkBubbleController* controller = 390 [[BookmarkBubbleController alloc] 391 initWithParentWindow:browser()->window()->GetNativeWindow() 392 model:model 393 node:node 394 alreadyBookmarked:NO]; // The last param is the key difference. 395 EXPECT_TRUE([controller window]); 396 // Calls release on controller. 397 [controller cancel:nil]; 398 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL))); 399} 400 401// Create a controller where the bookmark already existed prior to clicking 402// the star and test that sending a cancel command doesn't change the state 403// of the bookmark. 404TEST_F(BookmarkBubbleControllerTest, EscapeDoesntTouchExistingBookmark) { 405 const BookmarkNode* node = CreateTestBookmark(); 406 BookmarkBubbleController* controller = ControllerForNode(node); 407 EXPECT_TRUE(controller); 408 409 [(id)controller cancel:nil]; 410 EXPECT_TRUE(GetBookmarkModel()->IsBookmarked(GURL(kTestBookmarkURL))); 411} 412 413// Confirm indentation of items in pop-up menu 414TEST_F(BookmarkBubbleControllerTest, TestMenuIndentation) { 415 // Create some folders, including a nested folder 416 BookmarkModel* model = GetBookmarkModel(); 417 EXPECT_TRUE(model); 418 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node(); 419 EXPECT_TRUE(bookmarkBarNode); 420 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0, 421 ASCIIToUTF16("one")); 422 EXPECT_TRUE(node1); 423 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1, 424 ASCIIToUTF16("two")); 425 EXPECT_TRUE(node2); 426 const BookmarkNode* node2_1 = model->AddFolder(node2, 0, 427 ASCIIToUTF16("two dot one")); 428 EXPECT_TRUE(node2_1); 429 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2, 430 ASCIIToUTF16("three")); 431 EXPECT_TRUE(node3); 432 433 BookmarkBubbleController* controller = ControllerForNode(node1); 434 EXPECT_TRUE(controller); 435 436 // Compare the menu item indents against expectations. 437 static const int kExpectedIndent[] = {0, 1, 1, 2, 1, 0}; 438 NSArray* items = [[controller folderPopUpButton] itemArray]; 439 ASSERT_GE([items count], 6U); 440 for(int itemNo = 0; itemNo < 6; itemNo++) { 441 NSMenuItem* item = [items objectAtIndex:itemNo]; 442 EXPECT_EQ(kExpectedIndent[itemNo], [item indentationLevel]) 443 << "Unexpected indent for menu item #" << itemNo; 444 } 445} 446 447// Confirm that the sync promo is displayed when the user is not signed in. 448TEST_F(BookmarkBubbleControllerTest, SyncPromoNotSignedIn) { 449 const BookmarkNode* node = CreateTestBookmark(); 450 BookmarkBubbleController* controller = ControllerForNode(node); 451 452 EXPECT_EQ(1u, [[controller.syncPromoPlaceholder subviews] count]); 453} 454 455// Confirm that the sync promo is not displayed when the user is signed in. 456TEST_F(BookmarkBubbleControllerTest, SyncPromoSignedIn) { 457 SigninManager* signin = SigninManagerFactory::GetForProfile(profile()); 458 signin->SetAuthenticatedUsername("fake_username"); 459 460 const BookmarkNode* node = CreateTestBookmark(); 461 BookmarkBubbleController* controller = ControllerForNode(node); 462 463 EXPECT_EQ(0u, [[controller.syncPromoPlaceholder subviews] count]); 464} 465 466} // namespace 467 468@implementation NSApplication (BookmarkBubbleUnitTest) 469// Add handler for the editBookmarkNode: action to NSApp for testing purposes. 470// Normally this would be sent up the responder tree correctly, but since 471// tests run in the background, key window and main window are never set on 472// NSApplication. Adding it to NSApplication directly removes the need for 473// worrying about what the current window with focus is. 474- (void)editBookmarkNode:(id)sender { 475 EXPECT_TRUE([sender respondsToSelector:@selector(node)]); 476 BookmarkBubbleControllerTest::edits_++; 477} 478 479@end 480