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