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.
5 #import <Cocoa/Cocoa.h>
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 "chrome/test/base/testing_profile.h"
21 #include "content/public/browser/notification_service.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #import "testing/gtest_mac.h"
24 #include "testing/platform_test.h"
26 using content::WebContents;
28 // Watch for bookmark pulse notifications so we can confirm they were sent.
29 @interface BookmarkPulseObserver : NSObject {
32 @property (assign, nonatomic) int notifications;
36 @implementation BookmarkPulseObserver
38 @synthesize notifications = notifications_;
41 if ((self = [super init])) {
42 [[NSNotificationCenter defaultCenter]
44 selector:@selector(pulseBookmarkNotification:)
45 name:bookmark_button::kPulseBookmarkButtonNotification
51 - (void)pulseBookmarkNotification:(NSNotificationCenter *)notification {
56 [[NSNotificationCenter defaultCenter] removeObserver:self];
65 // URL of the test bookmark.
66 const char kTestBookmarkURL[] = "http://www.google.com";
68 class BookmarkBubbleControllerTest : public CocoaProfileTest {
71 BookmarkBubbleController* controller_;
73 BookmarkBubbleControllerTest() : controller_(nil) {
77 virtual void TearDown() OVERRIDE {
79 CocoaProfileTest::TearDown();
82 // Returns a controller but ownership not transferred.
83 // Only one of these will be valid at a time.
84 BookmarkBubbleController* ControllerForNode(const BookmarkNode* node) {
85 if (controller_ && !IsWindowClosing()) {
89 controller_ = [[BookmarkBubbleController alloc]
90 initWithParentWindow:browser()->window()->GetNativeWindow()
91 model:BookmarkModelFactory::GetForProfile(profile())
93 alreadyBookmarked:YES];
94 EXPECT_TRUE([controller_ window]);
95 // The window must be gone or we'll fail a unit test with windows left open.
96 [static_cast<InfoBubbleWindow*>([controller_ window])
97 setAllowedAnimations:info_bubble::kAnimateNone];
98 [controller_ showWindow:nil];
102 BookmarkModel* GetBookmarkModel() {
103 return BookmarkModelFactory::GetForProfile(profile());
106 const BookmarkNode* CreateTestBookmark() {
107 BookmarkModel* model = GetBookmarkModel();
108 return model->AddURL(model->bookmark_bar_node(),
110 ASCIIToUTF16("Bookie markie title"),
111 GURL(kTestBookmarkURL));
114 bool IsWindowClosing() {
115 return [static_cast<InfoBubbleWindow*>([controller_ window]) isClosing];
120 int BookmarkBubbleControllerTest::edits_;
122 // Confirm basics about the bubble window (e.g. that it is inside the
124 TEST_F(BookmarkBubbleControllerTest, TestBubbleWindow) {
125 const BookmarkNode* node = CreateTestBookmark();
126 BookmarkBubbleController* controller = ControllerForNode(node);
127 EXPECT_TRUE(controller);
128 NSWindow* window = [controller window];
130 EXPECT_TRUE(NSContainsRect([browser()->window()->GetNativeWindow() frame],
134 // Test that we can handle closing the parent window
135 TEST_F(BookmarkBubbleControllerTest, TestClosingParentWindow) {
136 const BookmarkNode* node = CreateTestBookmark();
137 BookmarkBubbleController* controller = ControllerForNode(node);
138 EXPECT_TRUE(controller);
139 NSWindow* window = [controller window];
141 base::mac::ScopedNSAutoreleasePool pool;
142 [browser()->window()->GetNativeWindow() performClose:NSApp];
146 // Confirm population of folder list
147 TEST_F(BookmarkBubbleControllerTest, TestFillInFolder) {
148 // Create some folders, including a nested folder
149 BookmarkModel* model = GetBookmarkModel();
151 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
152 EXPECT_TRUE(bookmarkBarNode);
153 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
154 ASCIIToUTF16("one"));
156 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
157 ASCIIToUTF16("two"));
159 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
160 ASCIIToUTF16("three"));
162 const BookmarkNode* node4 = model->AddFolder(node2, 0, ASCIIToUTF16("sub"));
164 const BookmarkNode* node5 = model->AddURL(node1, 0, ASCIIToUTF16("title1"),
165 GURL(kTestBookmarkURL));
167 const BookmarkNode* node6 = model->AddURL(node3, 0, ASCIIToUTF16("title2"),
168 GURL(kTestBookmarkURL));
170 const BookmarkNode* node7 = model->AddURL(
171 node4, 0, ASCIIToUTF16("title3"), GURL("http://www.google.com/reader"));
174 BookmarkBubbleController* controller = ControllerForNode(node4);
175 EXPECT_TRUE(controller);
178 [[[controller folderPopUpButton] itemArray] valueForKey:@"title"];
179 EXPECT_TRUE([titles containsObject:@"one"]);
180 EXPECT_TRUE([titles containsObject:@"two"]);
181 EXPECT_TRUE([titles containsObject:@"three"]);
182 EXPECT_TRUE([titles containsObject:@"sub"]);
183 EXPECT_FALSE([titles containsObject:@"title1"]);
184 EXPECT_FALSE([titles containsObject:@"title2"]);
187 // Verify that the top level folders are displayed correctly.
188 EXPECT_TRUE([titles containsObject:@"Other Bookmarks"]);
189 EXPECT_TRUE([titles containsObject:@"Bookmarks Bar"]);
190 if (model->mobile_node()->IsVisible()) {
191 EXPECT_TRUE([titles containsObject:@"Mobile Bookmarks"]);
193 EXPECT_FALSE([titles containsObject:@"Mobile Bookmarks"]);
197 // Confirm ability to handle folders with blank name.
198 TEST_F(BookmarkBubbleControllerTest, TestFolderWithBlankName) {
199 // Create some folders, including a nested folder
200 BookmarkModel* model = GetBookmarkModel();
202 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
203 EXPECT_TRUE(bookmarkBarNode);
204 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
205 ASCIIToUTF16("one"));
207 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
210 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
211 ASCIIToUTF16("three"));
213 const BookmarkNode* node2_1 = model->AddURL(node2, 0, ASCIIToUTF16("title1"),
214 GURL(kTestBookmarkURL));
215 EXPECT_TRUE(node2_1);
217 BookmarkBubbleController* controller = ControllerForNode(node1);
218 EXPECT_TRUE(controller);
220 // One of the items should be blank and its node should be node2.
221 NSArray* items = [[controller folderPopUpButton] itemArray];
222 EXPECT_GT([items count], 4U);
223 BOOL blankFolderFound = NO;
224 for (NSMenuItem* item in [[controller folderPopUpButton] itemArray]) {
225 if ([[item title] length] == 0 &&
226 static_cast<const BookmarkNode*>([[item representedObject]
227 pointerValue]) == node2) {
228 blankFolderFound = YES;
232 EXPECT_TRUE(blankFolderFound);
236 // Click on edit; bubble gets closed.
237 TEST_F(BookmarkBubbleControllerTest, TestEdit) {
238 const BookmarkNode* node = CreateTestBookmark();
239 BookmarkBubbleController* controller = ControllerForNode(node);
240 EXPECT_TRUE(controller);
242 EXPECT_EQ(edits_, 0);
243 EXPECT_FALSE(IsWindowClosing());
244 [controller edit:controller];
245 EXPECT_EQ(edits_, 1);
246 EXPECT_TRUE(IsWindowClosing());
249 // CallClose; bubble gets closed.
250 // Also confirm pulse notifications get sent.
251 TEST_F(BookmarkBubbleControllerTest, TestClose) {
252 const BookmarkNode* node = CreateTestBookmark();
253 EXPECT_EQ(edits_, 0);
255 base::scoped_nsobject<BookmarkPulseObserver> observer(
256 [[BookmarkPulseObserver alloc] 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);
268 // User changes title and parent folder in the UI
269 TEST_F(BookmarkBubbleControllerTest, TestUserEdit) {
270 BookmarkModel* model = GetBookmarkModel();
272 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
273 EXPECT_TRUE(bookmarkBarNode);
274 const BookmarkNode* node = model->AddURL(bookmarkBarNode,
276 ASCIIToUTF16("short-title"),
277 GURL(kTestBookmarkURL));
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);
285 BookmarkBubbleController* controller = ControllerForNode(node);
286 EXPECT_TRUE(controller);
288 // simulate a user edit
289 [controller setTitle:@"oops" parentFolder:grandma];
290 [controller edit:controller];
292 // Make sure bookmark has changed
293 EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("oops"));
294 EXPECT_EQ(node->parent()->GetTitle(), ASCIIToUTF16("grandma"));
297 // Confirm happiness with parent nodes that have the same name.
298 TEST_F(BookmarkBubbleControllerTest, TestNewParentSameName) {
299 BookmarkModel* model = GetBookmarkModel();
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,
306 ASCIIToUTF16("short-title"),
307 GURL(kTestBookmarkURL));
309 const BookmarkNode* folder = model->AddFolder(bookmarkBarNode, 0,
310 ASCIIToUTF16("NAME"));
312 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
314 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
316 BookmarkBubbleController* controller = ControllerForNode(node);
317 EXPECT_TRUE(controller);
319 // simulate a user edit
320 [controller setParentFolderSelection:bookmarkBarNode->GetChild(i)];
321 [controller edit:controller];
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));
329 // Confirm happiness with nodes with the same Name
330 TEST_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"));
337 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 0,
338 ASCIIToUTF16("NAME"));
340 BookmarkBubbleController* controller = ControllerForNode(bookmarkBarNode);
341 EXPECT_TRUE(controller);
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);
354 // Click the "remove" button
355 TEST_F(BookmarkBubbleControllerTest, TestRemove) {
356 const BookmarkNode* node = CreateTestBookmark();
357 BookmarkBubbleController* controller = ControllerForNode(node);
358 EXPECT_TRUE(controller);
360 BookmarkModel* model = GetBookmarkModel();
361 EXPECT_TRUE(model->IsBookmarked(GURL(kTestBookmarkURL)));
363 [controller remove:controller];
364 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
365 EXPECT_TRUE(IsWindowClosing());
368 // Confirm picking "choose another folder" caused edit: to be called.
369 TEST_F(BookmarkBubbleControllerTest, PopUpSelectionChanged) {
370 BookmarkModel* model = GetBookmarkModel();
371 const BookmarkNode* node = model->AddURL(model->bookmark_bar_node(),
372 0, ASCIIToUTF16("super-title"),
373 GURL(kTestBookmarkURL));
374 BookmarkBubbleController* controller = ControllerForNode(node);
375 EXPECT_TRUE(controller);
377 NSPopUpButton* button = [controller folderPopUpButton];
378 [button selectItemWithTitle:[[controller class] chooseAnotherFolderString]];
379 EXPECT_EQ(edits_, 0);
380 [button sendAction:[button action] to:[button target]];
381 EXPECT_EQ(edits_, 1);
384 // Create a controller that simulates the bookmark just now being created by
385 // the user clicking the star, then sending the "cancel" command to represent
386 // them pressing escape. The bookmark should not be there.
387 TEST_F(BookmarkBubbleControllerTest, EscapeRemovesNewBookmark) {
388 BookmarkModel* model = GetBookmarkModel();
389 const BookmarkNode* node = CreateTestBookmark();
390 BookmarkBubbleController* controller =
391 [[BookmarkBubbleController alloc]
392 initWithParentWindow:browser()->window()->GetNativeWindow()
395 alreadyBookmarked:NO]; // The last param is the key difference.
396 EXPECT_TRUE([controller window]);
397 // Calls release on controller.
398 [controller cancel:nil];
399 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
402 // Create a controller where the bookmark already existed prior to clicking
403 // the star and test that sending a cancel command doesn't change the state
405 TEST_F(BookmarkBubbleControllerTest, EscapeDoesntTouchExistingBookmark) {
406 const BookmarkNode* node = CreateTestBookmark();
407 BookmarkBubbleController* controller = ControllerForNode(node);
408 EXPECT_TRUE(controller);
410 [(id)controller cancel:nil];
411 EXPECT_TRUE(GetBookmarkModel()->IsBookmarked(GURL(kTestBookmarkURL)));
414 // Confirm indentation of items in pop-up menu
415 TEST_F(BookmarkBubbleControllerTest, TestMenuIndentation) {
416 // Create some folders, including a nested folder
417 BookmarkModel* model = GetBookmarkModel();
419 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
420 EXPECT_TRUE(bookmarkBarNode);
421 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
422 ASCIIToUTF16("one"));
424 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
425 ASCIIToUTF16("two"));
427 const BookmarkNode* node2_1 = model->AddFolder(node2, 0,
428 ASCIIToUTF16("two dot one"));
429 EXPECT_TRUE(node2_1);
430 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
431 ASCIIToUTF16("three"));
434 BookmarkBubbleController* controller = ControllerForNode(node1);
435 EXPECT_TRUE(controller);
437 // Compare the menu item indents against expectations.
438 static const int kExpectedIndent[] = {0, 1, 1, 2, 1, 0};
439 NSArray* items = [[controller folderPopUpButton] itemArray];
440 ASSERT_GE([items count], 6U);
441 for(int itemNo = 0; itemNo < 6; itemNo++) {
442 NSMenuItem* item = [items objectAtIndex:itemNo];
443 EXPECT_EQ(kExpectedIndent[itemNo], [item indentationLevel])
444 << "Unexpected indent for menu item #" << itemNo;
448 // Confirm that the sync promo is displayed when the user is not signed in.
449 TEST_F(BookmarkBubbleControllerTest, SyncPromoNotSignedIn) {
450 const BookmarkNode* node = CreateTestBookmark();
451 BookmarkBubbleController* controller = ControllerForNode(node);
453 EXPECT_EQ(1u, [[controller.syncPromoPlaceholder subviews] count]);
456 // Confirm that the sync promo is not displayed when the user is signed in.
457 TEST_F(BookmarkBubbleControllerTest, SyncPromoSignedIn) {
458 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
459 signin->SetAuthenticatedUsername("fake_username");
461 const BookmarkNode* node = CreateTestBookmark();
462 BookmarkBubbleController* controller = ControllerForNode(node);
464 EXPECT_EQ(0u, [[controller.syncPromoPlaceholder subviews] count]);
469 @implementation NSApplication (BookmarkBubbleUnitTest)
470 // Add handler for the editBookmarkNode: action to NSApp for testing purposes.
471 // Normally this would be sent up the responder tree correctly, but since
472 // tests run in the background, key window and main window are never set on
473 // NSApplication. Adding it to NSApplication directly removes the need for
474 // worrying about what the current window with focus is.
475 - (void)editBookmarkNode:(id)sender {
476 EXPECT_TRUE([sender respondsToSelector:@selector(node)]);
477 BookmarkBubbleControllerTest::edits_++;