• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
55namespace {
56
57class 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
101int BookmarkBubbleControllerTest::edits_;
102
103// Confirm basics about the bubble window (e.g. that it is inside the
104// parent window)
105TEST_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
120TEST_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
136TEST_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.
177TEST_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.
216TEST_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.
234TEST_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
255TEST_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.
284TEST_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
316TEST_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
341TEST_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.
358TEST_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.
377TEST_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.
399TEST_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
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->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.
448TEST_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