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_factory.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_controller.h"
16 #include "chrome/browser/ui/cocoa/browser_window_controller.h"
17 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
18 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/signin/core/browser/signin_manager.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 base::ASCIIToUTF16;
27 using content::WebContents;
29 // Watch for bookmark pulse notifications so we can confirm they were sent.
30 @interface BookmarkPulseObserver : NSObject {
33 @property (assign, nonatomic) int notifications;
37 @implementation BookmarkPulseObserver
39 @synthesize notifications = notifications_;
42 if ((self = [super init])) {
43 [[NSNotificationCenter defaultCenter]
45 selector:@selector(pulseBookmarkNotification:)
46 name:bookmark_button::kPulseBookmarkButtonNotification
52 - (void)pulseBookmarkNotification:(NSNotificationCenter *)notification {
57 [[NSNotificationCenter defaultCenter] removeObserver:self];
66 // URL of the test bookmark.
67 const char kTestBookmarkURL[] = "http://www.google.com";
69 class BookmarkBubbleControllerTest : public CocoaProfileTest {
72 BookmarkBubbleController* controller_;
74 BookmarkBubbleControllerTest() : controller_(nil) {
78 virtual void TearDown() OVERRIDE {
80 CocoaProfileTest::TearDown();
83 // Returns a controller but ownership not transferred.
84 // Only one of these will be valid at a time.
85 BookmarkBubbleController* ControllerForNode(const BookmarkNode* node) {
86 if (controller_ && !IsWindowClosing()) {
90 controller_ = [[BookmarkBubbleController alloc]
91 initWithParentWindow:browser()->window()->GetNativeWindow()
92 model:BookmarkModelFactory::GetForProfile(profile())
94 alreadyBookmarked:YES];
95 EXPECT_TRUE([controller_ window]);
96 // The window must be gone or we'll fail a unit test with windows left open.
97 [static_cast<InfoBubbleWindow*>([controller_ window])
98 setAllowedAnimations:info_bubble::kAnimateNone];
99 [controller_ showWindow:nil];
103 BookmarkModel* GetBookmarkModel() {
104 return BookmarkModelFactory::GetForProfile(profile());
107 const BookmarkNode* CreateTestBookmark() {
108 BookmarkModel* model = GetBookmarkModel();
109 return model->AddURL(model->bookmark_bar_node(),
111 ASCIIToUTF16("Bookie markie title"),
112 GURL(kTestBookmarkURL));
115 bool IsWindowClosing() {
116 return [static_cast<InfoBubbleWindow*>([controller_ window]) isClosing];
121 int BookmarkBubbleControllerTest::edits_;
123 // Confirm basics about the bubble window (e.g. that it is inside the
125 TEST_F(BookmarkBubbleControllerTest, TestBubbleWindow) {
126 const BookmarkNode* node = CreateTestBookmark();
127 BookmarkBubbleController* controller = ControllerForNode(node);
128 EXPECT_TRUE(controller);
129 NSWindow* window = [controller window];
131 EXPECT_TRUE(NSContainsRect([browser()->window()->GetNativeWindow() frame],
135 // Test that we can handle closing the parent window
136 TEST_F(BookmarkBubbleControllerTest, TestClosingParentWindow) {
137 const BookmarkNode* node = CreateTestBookmark();
138 BookmarkBubbleController* controller = ControllerForNode(node);
139 EXPECT_TRUE(controller);
140 NSWindow* window = [controller window];
142 base::mac::ScopedNSAutoreleasePool pool;
143 [browser()->window()->GetNativeWindow() performClose:NSApp];
147 // Confirm population of folder list
148 TEST_F(BookmarkBubbleControllerTest, TestFillInFolder) {
149 // Create some folders, including a nested folder
150 BookmarkModel* model = GetBookmarkModel();
152 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
153 EXPECT_TRUE(bookmarkBarNode);
154 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
155 ASCIIToUTF16("one"));
157 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
158 ASCIIToUTF16("two"));
160 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
161 ASCIIToUTF16("three"));
163 const BookmarkNode* node4 = model->AddFolder(node2, 0, ASCIIToUTF16("sub"));
165 const BookmarkNode* node5 = model->AddURL(node1, 0, ASCIIToUTF16("title1"),
166 GURL(kTestBookmarkURL));
168 const BookmarkNode* node6 = model->AddURL(node3, 0, ASCIIToUTF16("title2"),
169 GURL(kTestBookmarkURL));
171 const BookmarkNode* node7 = model->AddURL(
172 node4, 0, ASCIIToUTF16("title3"), GURL("http://www.google.com/reader"));
175 BookmarkBubbleController* controller = ControllerForNode(node4);
176 EXPECT_TRUE(controller);
179 [[[controller folderPopUpButton] itemArray] valueForKey:@"title"];
180 EXPECT_TRUE([titles containsObject:@"one"]);
181 EXPECT_TRUE([titles containsObject:@"two"]);
182 EXPECT_TRUE([titles containsObject:@"three"]);
183 EXPECT_TRUE([titles containsObject:@"sub"]);
184 EXPECT_FALSE([titles containsObject:@"title1"]);
185 EXPECT_FALSE([titles containsObject:@"title2"]);
188 // Verify that the top level folders are displayed correctly.
189 EXPECT_TRUE([titles containsObject:@"Other Bookmarks"]);
190 EXPECT_TRUE([titles containsObject:@"Bookmarks Bar"]);
191 if (model->mobile_node()->IsVisible()) {
192 EXPECT_TRUE([titles containsObject:@"Mobile Bookmarks"]);
194 EXPECT_FALSE([titles containsObject:@"Mobile Bookmarks"]);
198 // Confirm ability to handle folders with blank name.
199 TEST_F(BookmarkBubbleControllerTest, TestFolderWithBlankName) {
200 // Create some folders, including a nested folder
201 BookmarkModel* model = GetBookmarkModel();
203 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
204 EXPECT_TRUE(bookmarkBarNode);
205 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
206 ASCIIToUTF16("one"));
208 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
211 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
212 ASCIIToUTF16("three"));
214 const BookmarkNode* node2_1 = model->AddURL(node2, 0, ASCIIToUTF16("title1"),
215 GURL(kTestBookmarkURL));
216 EXPECT_TRUE(node2_1);
218 BookmarkBubbleController* controller = ControllerForNode(node1);
219 EXPECT_TRUE(controller);
221 // One of the items should be blank and its node should be node2.
222 NSArray* items = [[controller folderPopUpButton] itemArray];
223 EXPECT_GT([items count], 4U);
224 BOOL blankFolderFound = NO;
225 for (NSMenuItem* item in [[controller folderPopUpButton] itemArray]) {
226 if ([[item title] length] == 0 &&
227 static_cast<const BookmarkNode*>([[item representedObject]
228 pointerValue]) == node2) {
229 blankFolderFound = YES;
233 EXPECT_TRUE(blankFolderFound);
237 // Click on edit; bubble gets closed.
238 TEST_F(BookmarkBubbleControllerTest, TestEdit) {
239 const BookmarkNode* node = CreateTestBookmark();
240 BookmarkBubbleController* controller = ControllerForNode(node);
241 EXPECT_TRUE(controller);
243 EXPECT_EQ(edits_, 0);
244 EXPECT_FALSE(IsWindowClosing());
245 [controller edit:controller];
246 EXPECT_EQ(edits_, 1);
247 EXPECT_TRUE(IsWindowClosing());
250 // CallClose; bubble gets closed.
251 // Also confirm pulse notifications get sent.
252 TEST_F(BookmarkBubbleControllerTest, TestClose) {
253 const BookmarkNode* node = CreateTestBookmark();
254 EXPECT_EQ(edits_, 0);
256 base::scoped_nsobject<BookmarkPulseObserver> observer(
257 [[BookmarkPulseObserver alloc] init]);
258 EXPECT_EQ([observer notifications], 0);
259 BookmarkBubbleController* controller = ControllerForNode(node);
260 EXPECT_TRUE(controller);
261 EXPECT_FALSE(IsWindowClosing());
262 EXPECT_EQ([observer notifications], 1);
263 [controller ok:controller];
264 EXPECT_EQ(edits_, 0);
265 EXPECT_TRUE(IsWindowClosing());
266 EXPECT_EQ([observer notifications], 2);
269 // User changes title and parent folder in the UI
270 TEST_F(BookmarkBubbleControllerTest, TestUserEdit) {
271 BookmarkModel* model = GetBookmarkModel();
273 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
274 EXPECT_TRUE(bookmarkBarNode);
275 const BookmarkNode* node = model->AddURL(bookmarkBarNode,
277 ASCIIToUTF16("short-title"),
278 GURL(kTestBookmarkURL));
279 const BookmarkNode* grandma = model->AddFolder(bookmarkBarNode, 0,
280 ASCIIToUTF16("grandma"));
281 EXPECT_TRUE(grandma);
282 const BookmarkNode* grandpa = model->AddFolder(bookmarkBarNode, 0,
283 ASCIIToUTF16("grandpa"));
284 EXPECT_TRUE(grandpa);
286 BookmarkBubbleController* controller = ControllerForNode(node);
287 EXPECT_TRUE(controller);
289 // simulate a user edit
290 [controller setTitle:@"oops" parentFolder:grandma];
291 [controller edit:controller];
293 // Make sure bookmark has changed
294 EXPECT_EQ(node->GetTitle(), ASCIIToUTF16("oops"));
295 EXPECT_EQ(node->parent()->GetTitle(), ASCIIToUTF16("grandma"));
298 // Confirm happiness with parent nodes that have the same name.
299 TEST_F(BookmarkBubbleControllerTest, TestNewParentSameName) {
300 BookmarkModel* model = GetBookmarkModel();
302 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
303 EXPECT_TRUE(bookmarkBarNode);
304 for (int i=0; i<2; i++) {
305 const BookmarkNode* node = model->AddURL(bookmarkBarNode,
307 ASCIIToUTF16("short-title"),
308 GURL(kTestBookmarkURL));
310 const BookmarkNode* folder = model->AddFolder(bookmarkBarNode, 0,
311 ASCIIToUTF16("NAME"));
313 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
315 folder = model->AddFolder(bookmarkBarNode, 0, ASCIIToUTF16("NAME"));
317 BookmarkBubbleController* controller = ControllerForNode(node);
318 EXPECT_TRUE(controller);
320 // simulate a user edit
321 [controller setParentFolderSelection:bookmarkBarNode->GetChild(i)];
322 [controller edit:controller];
324 // Make sure bookmark has changed, and that the parent is what we
325 // expect. This proves nobody did searching based on name.
326 EXPECT_EQ(node->parent(), bookmarkBarNode->GetChild(i));
330 // Confirm happiness with nodes with the same Name
331 TEST_F(BookmarkBubbleControllerTest, TestDuplicateNodeNames) {
332 BookmarkModel* model = GetBookmarkModel();
333 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
334 EXPECT_TRUE(bookmarkBarNode);
335 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
336 ASCIIToUTF16("NAME"));
338 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 0,
339 ASCIIToUTF16("NAME"));
341 BookmarkBubbleController* controller = ControllerForNode(bookmarkBarNode);
342 EXPECT_TRUE(controller);
344 NSPopUpButton* button = [controller folderPopUpButton];
345 [controller setParentFolderSelection:node1];
346 NSMenuItem* item = [button selectedItem];
347 id itemObject = [item representedObject];
348 EXPECT_NSEQ([NSValue valueWithPointer:node1], itemObject);
349 [controller setParentFolderSelection:node2];
350 item = [button selectedItem];
351 itemObject = [item representedObject];
352 EXPECT_NSEQ([NSValue valueWithPointer:node2], itemObject);
355 // Click the "remove" button
356 TEST_F(BookmarkBubbleControllerTest, TestRemove) {
357 const BookmarkNode* node = CreateTestBookmark();
358 BookmarkBubbleController* controller = ControllerForNode(node);
359 EXPECT_TRUE(controller);
361 BookmarkModel* model = GetBookmarkModel();
362 EXPECT_TRUE(model->IsBookmarked(GURL(kTestBookmarkURL)));
364 [controller remove:controller];
365 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
366 EXPECT_TRUE(IsWindowClosing());
369 // Confirm picking "choose another folder" caused edit: to be called.
370 TEST_F(BookmarkBubbleControllerTest, PopUpSelectionChanged) {
371 BookmarkModel* model = GetBookmarkModel();
372 const BookmarkNode* node = model->AddURL(model->bookmark_bar_node(),
373 0, ASCIIToUTF16("super-title"),
374 GURL(kTestBookmarkURL));
375 BookmarkBubbleController* controller = ControllerForNode(node);
376 EXPECT_TRUE(controller);
378 NSPopUpButton* button = [controller folderPopUpButton];
379 [button selectItemWithTitle:[[controller class] chooseAnotherFolderString]];
380 EXPECT_EQ(edits_, 0);
381 [button sendAction:[button action] to:[button target]];
382 EXPECT_EQ(edits_, 1);
385 // Create a controller that simulates the bookmark just now being created by
386 // the user clicking the star, then sending the "cancel" command to represent
387 // them pressing escape. The bookmark should not be there.
388 TEST_F(BookmarkBubbleControllerTest, EscapeRemovesNewBookmark) {
389 BookmarkModel* model = GetBookmarkModel();
390 const BookmarkNode* node = CreateTestBookmark();
391 BookmarkBubbleController* controller =
392 [[BookmarkBubbleController alloc]
393 initWithParentWindow:browser()->window()->GetNativeWindow()
396 alreadyBookmarked:NO]; // The last param is the key difference.
397 EXPECT_TRUE([controller window]);
398 // Calls release on controller.
399 [controller cancel:nil];
400 EXPECT_FALSE(model->IsBookmarked(GURL(kTestBookmarkURL)));
403 // Create a controller where the bookmark already existed prior to clicking
404 // the star and test that sending a cancel command doesn't change the state
406 TEST_F(BookmarkBubbleControllerTest, EscapeDoesntTouchExistingBookmark) {
407 const BookmarkNode* node = CreateTestBookmark();
408 BookmarkBubbleController* controller = ControllerForNode(node);
409 EXPECT_TRUE(controller);
411 [(id)controller cancel:nil];
412 EXPECT_TRUE(GetBookmarkModel()->IsBookmarked(GURL(kTestBookmarkURL)));
415 // Confirm indentation of items in pop-up menu
416 TEST_F(BookmarkBubbleControllerTest, TestMenuIndentation) {
417 // Create some folders, including a nested folder
418 BookmarkModel* model = GetBookmarkModel();
420 const BookmarkNode* bookmarkBarNode = model->bookmark_bar_node();
421 EXPECT_TRUE(bookmarkBarNode);
422 const BookmarkNode* node1 = model->AddFolder(bookmarkBarNode, 0,
423 ASCIIToUTF16("one"));
425 const BookmarkNode* node2 = model->AddFolder(bookmarkBarNode, 1,
426 ASCIIToUTF16("two"));
428 const BookmarkNode* node2_1 = model->AddFolder(node2, 0,
429 ASCIIToUTF16("two dot one"));
430 EXPECT_TRUE(node2_1);
431 const BookmarkNode* node3 = model->AddFolder(bookmarkBarNode, 2,
432 ASCIIToUTF16("three"));
435 BookmarkBubbleController* controller = ControllerForNode(node1);
436 EXPECT_TRUE(controller);
438 // Compare the menu item indents against expectations.
439 static const int kExpectedIndent[] = {0, 1, 1, 2, 1, 0};
440 NSArray* items = [[controller folderPopUpButton] itemArray];
441 ASSERT_GE([items count], 6U);
442 for(int itemNo = 0; itemNo < 6; itemNo++) {
443 NSMenuItem* item = [items objectAtIndex:itemNo];
444 EXPECT_EQ(kExpectedIndent[itemNo], [item indentationLevel])
445 << "Unexpected indent for menu item #" << itemNo;
449 // Confirm that the sync promo is displayed when the user is not signed in.
450 TEST_F(BookmarkBubbleControllerTest, SyncPromoNotSignedIn) {
451 const BookmarkNode* node = CreateTestBookmark();
452 BookmarkBubbleController* controller = ControllerForNode(node);
454 EXPECT_EQ(1u, [[controller.syncPromoPlaceholder subviews] count]);
457 // Confirm that the sync promo is not displayed when the user is signed in.
458 TEST_F(BookmarkBubbleControllerTest, SyncPromoSignedIn) {
459 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
460 signin->SetAuthenticatedUsername("fake_username");
462 const BookmarkNode* node = CreateTestBookmark();
463 BookmarkBubbleController* controller = ControllerForNode(node);
465 EXPECT_EQ(0u, [[controller.syncPromoPlaceholder subviews] count]);
470 @implementation NSApplication (BookmarkBubbleUnitTest)
471 // Add handler for the editBookmarkNode: action to NSApp for testing purposes.
472 // Normally this would be sent up the responder tree correctly, but since
473 // tests run in the background, key window and main window are never set on
474 // NSApplication. Adding it to NSApplication directly removes the need for
475 // worrying about what the current window with focus is.
476 - (void)editBookmarkNode:(id)sender {
477 EXPECT_TRUE([sender respondsToSelector:@selector(node)]);
478 BookmarkBubbleControllerTest::edits_++;