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