• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#include "base/basictypes.h"
6#include "base/mac/scoped_nsobject.h"
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/bookmarks/bookmark_model_factory.h"
9#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
10#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
11#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_button_cell.h"
12#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_controller.h"
13#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_unittest_helper.h"
14#include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
15#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
16#import "chrome/browser/ui/cocoa/view_resizer_pong.h"
17#include "chrome/test/base/testing_profile.h"
18#include "components/bookmarks/browser/bookmark_model.h"
19#include "components/bookmarks/test/bookmark_test_helpers.h"
20#include "testing/gtest/include/gtest/gtest.h"
21#import "testing/gtest_mac.h"
22#include "testing/platform_test.h"
23#include "ui/base/cocoa/animation_utils.h"
24
25#include <cmath>
26
27using base::ASCIIToUTF16;
28
29namespace {
30
31const int kLotsOfNodesCount = 150;
32
33// Deletes the bookmark corresponding to |button|.
34void DeleteBookmark(BookmarkButton* button, Profile* profile) {
35  const BookmarkNode* node = [button bookmarkNode];
36  if (node) {
37    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile);
38    model->Remove(node->parent(), node->parent()->GetIndexOf(node));
39  }
40}
41
42}  // namespace
43
44// Add a redirect to make testing easier.
45@interface BookmarkBarFolderController(MakeTestingEasier)
46- (void)validateMenuSpacing;
47@end
48
49@implementation BookmarkBarFolderController(MakeTestingEasier)
50
51// Utility function to verify that the buttons in this folder are all
52// evenly spaced in a progressive manner.
53- (void)validateMenuSpacing {
54  BOOL firstButton = YES;
55  CGFloat lastVerticalOffset = 0.0;
56  for (BookmarkButton* button in [self buttons]) {
57    if (firstButton) {
58      firstButton = NO;
59      lastVerticalOffset = [button frame].origin.y;
60    } else {
61      CGFloat nextVerticalOffset = [button frame].origin.y;
62      EXPECT_CGFLOAT_EQ(lastVerticalOffset -
63                            bookmarks::kBookmarkFolderButtonHeight,
64                        nextVerticalOffset);
65      lastVerticalOffset = nextVerticalOffset;
66    }
67  }
68}
69@end
70
71// Don't use a high window level when running unit tests -- it'll
72// interfere with anything else you are working on.
73// For testing.
74@interface BookmarkBarFolderControllerNoLevel : BookmarkBarFolderController
75@end
76
77@implementation BookmarkBarFolderControllerNoLevel
78- (void)configureWindowLevel {
79  // Intentionally empty.
80}
81@end
82
83@interface BookmarkBarFolderControllerPong : BookmarkBarFolderController {
84  BOOL childFolderWillShow_;
85  BOOL childFolderWillClose_;
86}
87@property (nonatomic, readonly) BOOL childFolderWillShow;
88@property (nonatomic, readonly) BOOL childFolderWillClose;
89@end
90
91@implementation BookmarkBarFolderControllerPong
92@synthesize childFolderWillShow = childFolderWillShow_;
93@synthesize childFolderWillClose = childFolderWillClose_;
94
95- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
96  childFolderWillShow_ = YES;
97}
98
99- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
100  childFolderWillClose_ = YES;
101}
102
103// We don't have a real BookmarkBarController as our parent root so
104// we fake this one out.
105- (void)closeAllBookmarkFolders {
106  [self closeBookmarkFolder:self];
107}
108
109@end
110
111// Redirect certain calls so they can be seen by tests.
112
113@interface BookmarkBarControllerChildFolderRedirect : BookmarkBarController {
114  BookmarkBarFolderController* childFolderDelegate_;
115}
116@property (nonatomic, assign) BookmarkBarFolderController* childFolderDelegate;
117@end
118
119@implementation BookmarkBarControllerChildFolderRedirect
120
121@synthesize childFolderDelegate = childFolderDelegate_;
122
123- (void)childFolderWillShow:(id<BookmarkButtonControllerProtocol>)child {
124  [childFolderDelegate_ childFolderWillShow:child];
125}
126
127- (void)childFolderWillClose:(id<BookmarkButtonControllerProtocol>)child {
128  [childFolderDelegate_ childFolderWillClose:child];
129}
130
131@end
132
133
134class BookmarkBarFolderControllerTest : public CocoaProfileTest {
135 public:
136  base::scoped_nsobject<BookmarkBarControllerChildFolderRedirect> bar_;
137  const BookmarkNode* folderA_;  // Owned by model.
138  const BookmarkNode* longTitleNode_;  // Owned by model.
139
140  virtual void SetUp() {
141    CocoaProfileTest::SetUp();
142    ASSERT_TRUE(profile());
143
144    CreateModel();
145  }
146
147  void CreateModel() {
148    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
149    const BookmarkNode* parent = model->bookmark_bar_node();
150    const BookmarkNode* folderA = model->AddFolder(parent,
151                                                   parent->child_count(),
152                                                   ASCIIToUTF16("folder"));
153    folderA_ = folderA;
154    model->AddFolder(parent, parent->child_count(),
155                     ASCIIToUTF16("sibbling folder"));
156    const BookmarkNode* folderB = model->AddFolder(folderA,
157                                                   folderA->child_count(),
158                                                   ASCIIToUTF16("subfolder 1"));
159    model->AddFolder(folderA,
160                     folderA->child_count(),
161                     ASCIIToUTF16("subfolder 2"));
162    model->AddURL(folderA, folderA->child_count(), ASCIIToUTF16("title a"),
163                  GURL("http://www.google.com/a"));
164    longTitleNode_ = model->AddURL(
165      folderA, folderA->child_count(),
166      ASCIIToUTF16("title super duper long long whoa momma title you betcha"),
167      GURL("http://www.google.com/b"));
168    model->AddURL(folderB, folderB->child_count(), ASCIIToUTF16("t"),
169                  GURL("http://www.google.com/c"));
170
171    bar_.reset(
172      [[BookmarkBarControllerChildFolderRedirect alloc]
173          initWithBrowser:browser()
174             initialWidth:300
175                 delegate:nil
176           resizeDelegate:nil]);
177    [bar_ loaded:model];
178    // Make parent frame for bookmark bar then open it.
179    NSRect frame = [[test_window() contentView] frame];
180    frame = NSMakeRect(frame.origin.x,
181                       frame.size.height - chrome::kNTPBookmarkBarHeight,
182                       frame.size.width, chrome::kNTPBookmarkBarHeight);
183    NSView* fakeToolbarView = [[[NSView alloc] initWithFrame:frame]
184                                autorelease];
185    [[test_window() contentView] addSubview:fakeToolbarView];
186    [fakeToolbarView addSubview:[bar_ view]];
187    [bar_ setBookmarkBarEnabled:YES];
188  }
189
190  // Remove the bookmark with the long title.
191  void RemoveLongTitleNode() {
192    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
193    model->Remove(longTitleNode_->parent(),
194                  longTitleNode_->parent()->GetIndexOf(longTitleNode_));
195  }
196
197  // Add LOTS of nodes to our model if needed (e.g. scrolling).
198  // Returns the number of nodes added.
199  int AddLotsOfNodes() {
200    BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
201    for (int i = 0; i < kLotsOfNodesCount; i++) {
202      model->AddURL(folderA_, folderA_->child_count(),
203                    ASCIIToUTF16("repeated title"),
204                    GURL("http://www.google.com/repeated/url"));
205    }
206    return kLotsOfNodesCount;
207  }
208
209  // Return a simple BookmarkBarFolderController.
210  BookmarkBarFolderControllerPong* SimpleBookmarkBarFolderController() {
211    BookmarkButton* parentButton = [[bar_ buttons] objectAtIndex:0];
212    BookmarkBarFolderControllerPong* c =
213      [[BookmarkBarFolderControllerPong alloc]
214               initWithParentButton:parentButton
215                   parentController:nil
216                      barController:bar_
217                            profile:profile()];
218    [c window];  // Force nib load.
219    return c;
220  }
221};
222
223TEST_F(BookmarkBarFolderControllerTest, InitCreateAndDelete) {
224  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
225  bbfc.reset(SimpleBookmarkBarFolderController());
226
227  // Make sure none of the buttons overlap, that all are inside
228  // the content frame, and their cells are of the proper class.
229  NSArray* buttons = [bbfc buttons];
230  EXPECT_TRUE([buttons count]);
231  for (unsigned int i = 0; i < ([buttons count]-1); i++) {
232    EXPECT_FALSE(NSContainsRect([[buttons objectAtIndex:i] frame],
233                              [[buttons objectAtIndex:i+1] frame]));
234  }
235  Class cellClass = [BookmarkBarFolderButtonCell class];
236  for (BookmarkButton* button in buttons) {
237    NSRect r = [[bbfc folderView] convertRect:[button frame] fromView:button];
238    // TODO(jrg): remove this adjustment.
239    NSRect bigger = NSInsetRect([[bbfc folderView] frame], -2, 0);
240    EXPECT_TRUE(NSContainsRect(bigger, r));
241    EXPECT_TRUE([[button cell] isKindOfClass:cellClass]);
242  }
243
244  // Confirm folder buttons have no tooltip.  The important thing
245  // really is that we insure folders and non-folders are treated
246  // differently; not sure of any other generic way to do this.
247  for (BookmarkButton* button in buttons) {
248    if ([button isFolder])
249      EXPECT_FALSE([button toolTip]);
250    else
251      EXPECT_TRUE([button toolTip]);
252  }
253}
254
255// Make sure closing of the window releases the controller.
256// (e.g. valgrind shouldn't complain if we do this).
257TEST_F(BookmarkBarFolderControllerTest, ReleaseOnClose) {
258  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
259  bbfc.reset(SimpleBookmarkBarFolderController());
260  EXPECT_TRUE(bbfc.get());
261
262  [bbfc retain];  // stop the scoped_nsobject from doing anything
263  [[bbfc window] close];  // trigger an autorelease of bbfc.get()
264}
265
266TEST_F(BookmarkBarFolderControllerTest, BasicPosition) {
267  BookmarkButton* parentButton = [[bar_ buttons] objectAtIndex:0];
268  EXPECT_TRUE(parentButton);
269
270  // If parent is a BookmarkBarController, grow down.
271  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
272  bbfc.reset([[BookmarkBarFolderController alloc]
273               initWithParentButton:parentButton
274                   parentController:nil
275                      barController:bar_
276                            profile:profile()]);
277  [bbfc window];
278  NSPoint pt = [bbfc windowTopLeftForWidth:0 height:100];  // screen coords
279  NSPoint buttonOriginInWindow =
280      [parentButton convertRect:[parentButton bounds]
281                         toView:nil].origin;
282  NSPoint buttonOriginInScreen =
283      [[parentButton window] convertBaseToScreen:buttonOriginInWindow];
284  // Within margin
285  EXPECT_LE(std::abs(pt.x - buttonOriginInScreen.x),
286            bookmarks::kBookmarkMenuOverlap + 1);
287  EXPECT_LE(std::abs(pt.y - buttonOriginInScreen.y),
288            bookmarks::kBookmarkMenuOverlap + 1);
289
290  // Make sure we see the window shift left if it spills off the screen
291  pt = [bbfc windowTopLeftForWidth:0 height:100];
292  NSPoint shifted = [bbfc windowTopLeftForWidth:9999999 height:100];
293  EXPECT_LT(shifted.x, pt.x);
294
295  // If parent is a BookmarkBarFolderController, grow right.
296  base::scoped_nsobject<BookmarkBarFolderController> bbfc2;
297  bbfc2.reset([[BookmarkBarFolderController alloc]
298                initWithParentButton:[[bbfc buttons] objectAtIndex:0]
299                    parentController:bbfc.get()
300                       barController:bar_
301                             profile:profile()]);
302  [bbfc2 window];
303  pt = [bbfc2 windowTopLeftForWidth:0 height:100];
304  // We're now overlapping the window a bit.
305  EXPECT_EQ(pt.x, NSMaxX([[bbfc.get() window] frame]) -
306            bookmarks::kBookmarkMenuOverlap);
307}
308
309// Confirm we grow right until end of screen, then start growing left
310// until end of screen again, then right.
311TEST_F(BookmarkBarFolderControllerTest, PositionRightLeftRight) {
312  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
313  const BookmarkNode* parent = model->bookmark_bar_node();
314  const BookmarkNode* folder = parent;
315
316  const int count = 100;
317  int i;
318  // Make some super duper deeply nested folders.
319  for (i = 0; i < count; i++) {
320    folder = model->AddFolder(folder, 0, ASCIIToUTF16("nested folder"));
321  }
322
323  // Setup initial state for opening all folders.
324  folder = parent;
325  BookmarkButton* parentButton = [[bar_ buttons] objectAtIndex:0];
326  BookmarkBarFolderController* parentController = nil;
327  EXPECT_TRUE(parentButton);
328
329  // Open them all.
330  base::scoped_nsobject<NSMutableArray> folder_controller_array;
331  folder_controller_array.reset([[NSMutableArray array] retain]);
332  for (i=0; i<count; i++) {
333    BookmarkBarFolderControllerNoLevel* bbfcl =
334        [[BookmarkBarFolderControllerNoLevel alloc]
335          initWithParentButton:parentButton
336              parentController:parentController
337                 barController:bar_
338                       profile:profile()];
339    [folder_controller_array addObject:bbfcl];
340    [bbfcl autorelease];
341    [bbfcl window];
342    parentController = bbfcl;
343    parentButton = [[bbfcl buttons] objectAtIndex:0];
344  }
345
346  // Make vector of all x positions.
347  std::vector<CGFloat> leftPositions;
348  for (i=0; i<count; i++) {
349    CGFloat x = [[[folder_controller_array objectAtIndex:i] window]
350                  frame].origin.x;
351    leftPositions.push_back(x);
352  }
353
354  // Make sure the first few grow right.
355  for (i=0; i<3; i++)
356    EXPECT_TRUE(leftPositions[i+1] > leftPositions[i]);
357
358  // Look for the first "grow left".
359  while (leftPositions[i] > leftPositions[i-1])
360    i++;
361  // Confirm the next few also grow left.
362  int j;
363  for (j=i; j<i+3; j++)
364    EXPECT_TRUE(leftPositions[j+1] < leftPositions[j]);
365  i = j;
366
367  // Finally, confirm we see a "grow right" once more.
368  while (leftPositions[i] < leftPositions[i-1])
369    i++;
370  // (No need to EXPECT a final "grow right"; if we didn't find one
371  // we'd get a C++ array bounds exception).
372}
373
374TEST_F(BookmarkBarFolderControllerTest, DropDestination) {
375  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
376  bbfc.reset(SimpleBookmarkBarFolderController());
377  EXPECT_TRUE(bbfc.get());
378
379  // Confirm "off the top" and "off the bottom" match no buttons.
380  NSPoint p = NSMakePoint(NSMidX([[bbfc folderView] frame]), 10000);
381  EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:p]);
382  EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:p]);
383  p = NSMakePoint(NSMidX([[bbfc folderView] frame]), -1);
384  EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:p]);
385  EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:p]);
386
387  // Confirm "right in the center" (give or take a pixel) is a match,
388  // and confirm "just barely in the button" is not.  Anything more
389  // specific seems likely to be tweaked.  We don't loop over all
390  // buttons because the scroll view makes them not visible.
391  for (BookmarkButton* button in [bbfc buttons]) {
392    CGFloat x = NSMidX([button frame]);
393    CGFloat y = NSMidY([button frame]);
394    // Somewhere near the center: a match (but only if a folder!)
395    if ([button isFolder]) {
396      EXPECT_EQ(button,
397                [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x-1, y+1)]);
398      EXPECT_EQ(button,
399                [bbfc buttonForDroppingOnAtPoint:NSMakePoint(x+1, y-1)]);
400      EXPECT_FALSE([bbfc shouldShowIndicatorShownForPoint:NSMakePoint(x, y)]);;
401    } else {
402      // If not a folder we don't drop into it.
403      EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:NSMakePoint(x-1, y+1)]);
404      EXPECT_FALSE([bbfc buttonForDroppingOnAtPoint:NSMakePoint(x+1, y-1)]);
405      EXPECT_TRUE([bbfc shouldShowIndicatorShownForPoint:NSMakePoint(x, y)]);;
406    }
407  }
408}
409
410TEST_F(BookmarkBarFolderControllerTest, OpenFolder) {
411  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
412  bbfc.reset(SimpleBookmarkBarFolderController());
413  EXPECT_TRUE(bbfc.get());
414
415  EXPECT_FALSE([bbfc folderController]);
416  BookmarkButton* button = [[bbfc buttons] objectAtIndex:0];
417  [bbfc openBookmarkFolderFromButton:button];
418  id controller = [bbfc folderController];
419  EXPECT_TRUE(controller);
420  EXPECT_EQ([controller parentButton], button);
421
422  // Click the same one --> it gets closed.
423  [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
424  EXPECT_FALSE([bbfc folderController]);
425
426  // Open a new one --> change.
427  [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:1]];
428  EXPECT_NE(controller, [bbfc folderController]);
429  EXPECT_NE([[bbfc folderController] parentButton], button);
430
431  // Close it --> all gone!
432  [bbfc closeBookmarkFolder:nil];
433  EXPECT_FALSE([bbfc folderController]);
434}
435
436TEST_F(BookmarkBarFolderControllerTest, DeleteOpenFolder) {
437  base::scoped_nsobject<BookmarkBarFolderController> parent_controller(
438      SimpleBookmarkBarFolderController());
439
440  // Open a folder.
441  EXPECT_FALSE([parent_controller folderController]);
442  BookmarkButton* button = [[parent_controller buttons] objectAtIndex:0];
443  [parent_controller openBookmarkFolderFromButton:button];
444  EXPECT_EQ([[parent_controller folderController] parentButton], button);
445
446  // Delete the folder's button - the folder should close.
447  [parent_controller removeButton:0 animate:NO];
448  EXPECT_FALSE([parent_controller folderController]);
449}
450
451TEST_F(BookmarkBarFolderControllerTest, ChildFolderCallbacks) {
452  base::scoped_nsobject<BookmarkBarFolderControllerPong> bbfc;
453  bbfc.reset(SimpleBookmarkBarFolderController());
454  EXPECT_TRUE(bbfc.get());
455  [bar_ setChildFolderDelegate:bbfc.get()];
456
457  EXPECT_FALSE([bbfc childFolderWillShow]);
458  [bbfc openBookmarkFolderFromButton:[[bbfc buttons] objectAtIndex:0]];
459  EXPECT_TRUE([bbfc childFolderWillShow]);
460
461  EXPECT_FALSE([bbfc childFolderWillClose]);
462  [bbfc closeBookmarkFolder:nil];
463  EXPECT_TRUE([bbfc childFolderWillClose]);
464
465  [bar_ setChildFolderDelegate:nil];
466}
467
468// Make sure bookmark folders have variable widths.
469TEST_F(BookmarkBarFolderControllerTest, ChildFolderWidth) {
470  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
471
472  bbfc.reset(SimpleBookmarkBarFolderController());
473  EXPECT_TRUE(bbfc.get());
474  [bbfc showWindow:bbfc.get()];
475  CGFloat wideWidth = NSWidth([[bbfc window] frame]);
476
477  RemoveLongTitleNode();
478  bbfc.reset(SimpleBookmarkBarFolderController());
479  EXPECT_TRUE(bbfc.get());
480  CGFloat thinWidth = NSWidth([[bbfc window] frame]);
481
482  // Make sure window size changed as expected.
483  EXPECT_GT(wideWidth, thinWidth);
484}
485
486// Simple scrolling tests.
487// Currently flaky due to a changed definition of the correct menu boundaries.
488TEST_F(BookmarkBarFolderControllerTest, DISABLED_SimpleScroll) {
489  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
490  NSRect screenFrame = [[NSScreen mainScreen] visibleFrame];
491  CGFloat screenHeight = NSHeight(screenFrame);
492  int nodecount = AddLotsOfNodes();
493  bbfc.reset(SimpleBookmarkBarFolderController());
494  EXPECT_TRUE(bbfc.get());
495  [bbfc showWindow:bbfc.get()];
496  NSWindow* window = [bbfc window];
497
498  // The window should be shorter than the screen but reach exactly to the
499  // bottom of the screen since it's scrollable.
500  EXPECT_LT(NSHeight([window frame]), screenHeight);
501  EXPECT_CGFLOAT_EQ(0.0, [window frame].origin.y);
502
503  // Initially, should show scroll-up but not scroll-down.
504  EXPECT_TRUE([bbfc canScrollUp]);
505  EXPECT_FALSE([bbfc canScrollDown]);
506
507  // Scroll up a bit.  Make sure the window has gotten bigger each time.
508  // Also, for each scroll, make sure our hit test finds a new button
509  // (to confirm the content area changed).
510  NSView* savedHit = nil;
511  NSScrollView* scrollView = [bbfc scrollView];
512
513  // Find the next-to-last button showing at the bottom of the window and
514  // use its center for hit testing.
515  BookmarkButton* targetButton = nil;
516  NSPoint scrollPoint = [scrollView documentVisibleRect].origin;
517  for (BookmarkButton* button in [bbfc buttons]) {
518    NSRect buttonFrame = [button frame];
519    buttonFrame.origin.y -= scrollPoint.y;
520    if (buttonFrame.origin.y < 0.0)
521      break;
522    targetButton = button;
523  }
524  EXPECT_TRUE(targetButton != nil);
525  NSPoint hitPoint = [targetButton frame].origin;
526  hitPoint.x += 50.0;
527  hitPoint.y += (bookmarks::kBookmarkFolderButtonHeight / 2.0) - scrollPoint.y;
528  hitPoint = [targetButton convertPoint:hitPoint toView:scrollView];
529
530  for (int i = 0; i < 3; i++) {
531    CGFloat height = NSHeight([window frame]);
532    [bbfc performOneScroll:60];
533    EXPECT_GT(NSHeight([window frame]), height);
534    NSView* hit = [scrollView hitTest:hitPoint];
535    // We should hit a bookmark button.
536    EXPECT_TRUE([[hit className] isEqualToString:@"BookmarkButton"]);
537    EXPECT_NE(hit, savedHit);
538    savedHit = hit;
539  }
540
541  // Keep scrolling up; make sure we never get bigger than the screen.
542  // Also confirm we never scroll the window off the screen.
543  bool bothAtOnce = false;
544  while ([bbfc canScrollUp]) {
545    [bbfc performOneScroll:60];
546    EXPECT_TRUE(NSContainsRect([[NSScreen mainScreen] frame], [window frame]));
547    // Make sure, sometime during our scroll, we have the ability to
548    // scroll in either direction.
549    if ([bbfc canScrollUp] &&
550        [bbfc canScrollDown])
551      bothAtOnce = true;
552  }
553  EXPECT_TRUE(bothAtOnce);
554
555  // Once we've scrolled to the end, our only option should be to scroll back.
556  EXPECT_FALSE([bbfc canScrollUp]);
557  EXPECT_TRUE([bbfc canScrollDown]);
558
559  NSRect wholeScreenRect = [[NSScreen mainScreen] frame];
560
561  // Now scroll down and make sure the window size does not change.
562  // Also confirm we never scroll the window off the screen the other
563  // way.
564  for (int i = 0; i < nodecount+50; ++i) {
565    [bbfc performOneScroll:-60];
566    // Once we can no longer scroll down the window height changes.
567    if (![bbfc canScrollDown])
568      break;
569    EXPECT_TRUE(NSContainsRect(wholeScreenRect, [window frame]));
570  }
571
572  EXPECT_GT(NSHeight(wholeScreenRect), NSHeight([window frame]));
573  EXPECT_TRUE(NSContainsRect(wholeScreenRect, [window frame]));
574}
575
576// Folder menu sizing and placement while deleting bookmarks
577// and scrolling tests.
578TEST_F(BookmarkBarFolderControllerTest, MenuPlacementWhileScrollingDeleting) {
579  base::scoped_nsobject<BookmarkBarFolderController> bbfc;
580  AddLotsOfNodes();
581  bbfc.reset(SimpleBookmarkBarFolderController());
582  [bbfc showWindow:bbfc.get()];
583  NSWindow* menuWindow = [bbfc window];
584  BookmarkBarFolderController* folder = [bar_ folderController];
585  NSArray* buttons = [folder buttons];
586
587  // Before scrolling any, delete a bookmark and make sure the window top has
588  // not moved. Pick a button which is near the top and visible.
589  CGFloat oldTop = [menuWindow frame].origin.y + NSHeight([menuWindow frame]);
590  BookmarkButton* button = [buttons objectAtIndex:3];
591  DeleteBookmark(button, profile());
592  CGFloat newTop = [menuWindow frame].origin.y + NSHeight([menuWindow frame]);
593  EXPECT_CGFLOAT_EQ(oldTop, newTop);
594
595  // Scroll so that both the top and bottom scroll arrows show, make sure
596  // the top of the window has moved up, then delete a visible button and
597  // make sure the top has not moved.
598  oldTop = newTop;
599  const CGFloat scrollOneBookmark = bookmarks::kBookmarkFolderButtonHeight +
600      bookmarks::kBookmarkVerticalPadding;
601  NSUInteger buttonCounter = 0;
602  NSUInteger extraButtonLimit = 3;
603  while (![bbfc canScrollDown] || extraButtonLimit > 0) {
604    [bbfc performOneScroll:scrollOneBookmark];
605    ++buttonCounter;
606    if ([bbfc canScrollDown])
607      --extraButtonLimit;
608  }
609  newTop = [menuWindow frame].origin.y + NSHeight([menuWindow frame]);
610  EXPECT_NE(oldTop, newTop);
611  oldTop = newTop;
612  button = [buttons objectAtIndex:buttonCounter + 3];
613  DeleteBookmark(button, profile());
614  newTop = [menuWindow frame].origin.y + NSHeight([menuWindow frame]);
615  EXPECT_CGFLOAT_EQ(oldTop, newTop);
616
617  // Scroll so that the top scroll arrow is no longer showing, make sure
618  // the top of the window has not moved, then delete a visible button and
619  // make sure the top has not moved.
620  while ([bbfc canScrollDown]) {
621    [bbfc performOneScroll:-scrollOneBookmark];
622    --buttonCounter;
623  }
624  button = [buttons objectAtIndex:buttonCounter + 3];
625  DeleteBookmark(button, profile());
626  newTop = [menuWindow frame].origin.y + NSHeight([menuWindow frame]);
627  EXPECT_CGFLOAT_EQ(oldTop - bookmarks::kScrollWindowVerticalMargin, newTop);
628}
629
630// Make sure that we return the correct browser window.
631TEST_F(BookmarkBarFolderControllerTest, BrowserWindow) {
632  base::scoped_nsobject<BookmarkBarFolderController> controller(
633      SimpleBookmarkBarFolderController());
634  EXPECT_EQ([bar_ browserWindow], [controller browserWindow]);
635}
636
637@interface FakedDragInfo : NSObject {
638@public
639  NSPoint dropLocation_;
640  NSDragOperation sourceMask_;
641}
642@property (nonatomic, assign) NSPoint dropLocation;
643- (void)setDraggingSourceOperationMask:(NSDragOperation)mask;
644@end
645
646@implementation FakedDragInfo
647
648@synthesize dropLocation = dropLocation_;
649
650- (id)init {
651  if ((self = [super init])) {
652    dropLocation_ = NSZeroPoint;
653    sourceMask_ = NSDragOperationMove;
654  }
655  return self;
656}
657
658// NSDraggingInfo protocol functions.
659
660- (id)draggingPasteboard {
661  return self;
662}
663
664- (id)draggingSource {
665  return self;
666}
667
668- (NSDragOperation)draggingSourceOperationMask {
669  return sourceMask_;
670}
671
672- (NSPoint)draggingLocation {
673  return dropLocation_;
674}
675
676// Other functions.
677
678- (void)setDraggingSourceOperationMask:(NSDragOperation)mask {
679  sourceMask_ = mask;
680}
681
682@end
683
684
685class BookmarkBarFolderControllerMenuTest : public CocoaProfileTest {
686 public:
687  base::scoped_nsobject<NSView> parent_view_;
688  base::scoped_nsobject<ViewResizerPong> resizeDelegate_;
689  base::scoped_nsobject<BookmarkBarController> bar_;
690
691  virtual void SetUp() {
692    CocoaProfileTest::SetUp();
693    ASSERT_TRUE(browser());
694
695    resizeDelegate_.reset([[ViewResizerPong alloc] init]);
696    NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
697    parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
698    [parent_view_ setHidden:YES];
699    bar_.reset([[BookmarkBarController alloc]
700                initWithBrowser:browser()
701                   initialWidth:NSWidth(parent_frame)
702                       delegate:nil
703                 resizeDelegate:resizeDelegate_.get()]);
704    InstallAndToggleBar(bar_.get());
705  }
706
707  void InstallAndToggleBar(BookmarkBarController* bar) {
708    // Force loading of the nib.
709    [bar view];
710    // Awkwardness to look like we've been installed.
711    [parent_view_ addSubview:[bar view]];
712    NSRect frame = [[[bar view] superview] frame];
713    frame.origin.y = 400;
714    [[[bar view] superview] setFrame:frame];
715
716    // Make sure it's on in a window so viewDidMoveToWindow is called
717    [[test_window() contentView] addSubview:parent_view_];
718
719    // Make sure it's open so certain things aren't no-ops.
720    [bar updateState:BookmarkBar::SHOW
721          changeType:BookmarkBar::DONT_ANIMATE_STATE_CHANGE];
722  }
723};
724
725TEST_F(BookmarkBarFolderControllerMenuTest, DragMoveBarBookmarkToFolder) {
726  WithNoAnimation at_all;
727  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
728  const BookmarkNode* root = model->bookmark_bar_node();
729  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
730      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
731      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
732  test::AddNodesFromModelString(model, root, model_string);
733
734  // Validate initial model.
735  std::string actualModelString = test::ModelStringFromNode(root);
736  EXPECT_EQ(model_string, actualModelString);
737
738  // Pop up a folder menu and drag in a button from the bar.
739  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
740  NSRect oldToFolderFrame = [toFolder frame];
741  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
742                          withObject:toFolder];
743  BookmarkBarFolderController* folderController = [bar_ folderController];
744  EXPECT_TRUE(folderController);
745  NSWindow* toWindow = [folderController window];
746  EXPECT_TRUE(toWindow);
747  NSRect oldToWindowFrame = [toWindow frame];
748  // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
749  // so it should end up below the target bookmark.
750  BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"1b"];
751  ASSERT_TRUE(draggedButton);
752  CGFloat horizontalShift =
753      NSWidth([draggedButton frame]) + bookmarks::kBookmarkHorizontalPadding;
754  BookmarkButton* targetButton =
755      [folderController buttonWithTitleEqualTo:@"2f1b"];
756  ASSERT_TRUE(targetButton);
757  [folderController dragButton:draggedButton
758                            to:[targetButton center]
759                          copy:NO];
760  // The button should have landed just after "2f1b".
761  const std::string expected_string("2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
762      "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
763      "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
764  EXPECT_EQ(expected_string, test::ModelStringFromNode(root));
765
766  // Verify the window still appears by looking for its controller.
767  EXPECT_TRUE([bar_ folderController]);
768
769  // Gather the new frames.
770  NSRect newToFolderFrame = [toFolder frame];
771  NSRect newToWindowFrame = [toWindow frame];
772  // The toFolder should have shifted left horizontally but not vertically.
773  NSRect expectedToFolderFrame =
774      NSOffsetRect(oldToFolderFrame, -horizontalShift, 0);
775  EXPECT_NSRECT_EQ(expectedToFolderFrame, newToFolderFrame);
776  // The toWindow should have shifted left horizontally, down vertically,
777  // and grown vertically.
778  NSRect expectedToWindowFrame = oldToWindowFrame;
779  expectedToWindowFrame.origin.x -= horizontalShift;
780  expectedToWindowFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
781  expectedToWindowFrame.size.height += bookmarks::kBookmarkFolderButtonHeight;
782  EXPECT_NSRECT_EQ(expectedToWindowFrame, newToWindowFrame);
783
784  // Check button spacing.
785  [folderController validateMenuSpacing];
786
787  // Move the button back to the bar at the beginning.
788  draggedButton = [folderController buttonWithTitleEqualTo:@"1b"];
789  ASSERT_TRUE(draggedButton);
790  targetButton = [bar_ buttonWithTitleEqualTo:@"2f"];
791  ASSERT_TRUE(targetButton);
792  [bar_ dragButton:draggedButton
793                to:[targetButton left]
794              copy:NO];
795  EXPECT_EQ(model_string, test::ModelStringFromNode(root));
796  // Don't check the folder window since it's not supposed to be showing.
797}
798
799TEST_F(BookmarkBarFolderControllerMenuTest, DragCopyBarBookmarkToFolder) {
800  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
801  const BookmarkNode* root = model->bookmark_bar_node();
802  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
803      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
804      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
805  test::AddNodesFromModelString(model, root, model_string);
806
807  // Validate initial model.
808  std::string actualModelString = test::ModelStringFromNode(root);
809  EXPECT_EQ(model_string, actualModelString);
810
811  // Pop up a folder menu and copy in a button from the bar.
812  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
813  ASSERT_TRUE(toFolder);
814  NSRect oldToFolderFrame = [toFolder frame];
815  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
816                          withObject:toFolder];
817  BookmarkBarFolderController* folderController = [bar_ folderController];
818  EXPECT_TRUE(folderController);
819  NSWindow* toWindow = [folderController window];
820  EXPECT_TRUE(toWindow);
821  NSRect oldToWindowFrame = [toWindow frame];
822  // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
823  // so it should end up below the target bookmark.
824  BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"1b"];
825  ASSERT_TRUE(draggedButton);
826  BookmarkButton* targetButton =
827      [folderController buttonWithTitleEqualTo:@"2f1b"];
828  ASSERT_TRUE(targetButton);
829  [folderController dragButton:draggedButton
830                            to:[targetButton center]
831                          copy:YES];
832  // The button should have landed just after "2f1b".
833  const std::string expected_1("1b 2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
834    "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
835    "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
836  EXPECT_EQ(expected_1, test::ModelStringFromNode(root));
837
838  // Gather the new frames.
839  NSRect newToFolderFrame = [toFolder frame];
840  NSRect newToWindowFrame = [toWindow frame];
841  // The toFolder should have shifted.
842  EXPECT_NSRECT_EQ(oldToFolderFrame, newToFolderFrame);
843  // The toWindow should have shifted down vertically and grown vertically.
844  NSRect expectedToWindowFrame = oldToWindowFrame;
845  expectedToWindowFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
846  expectedToWindowFrame.size.height += bookmarks::kBookmarkFolderButtonHeight;
847  EXPECT_NSRECT_EQ(expectedToWindowFrame, newToWindowFrame);
848
849  // Copy the button back to the bar after "3b".
850  draggedButton = [folderController buttonWithTitleEqualTo:@"1b"];
851  ASSERT_TRUE(draggedButton);
852  targetButton = [bar_ buttonWithTitleEqualTo:@"4f"];
853  ASSERT_TRUE(targetButton);
854  [bar_ dragButton:draggedButton
855                to:[targetButton left]
856              copy:YES];
857  const std::string expected_2("1b 2f:[ 2f1b 1b 2f2f:[ 2f2f1b "
858      "2f2f2b 2f2f3b ] 2f3b ] 3b 1b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
859      "4f2f1b 4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
860  EXPECT_EQ(expected_2, test::ModelStringFromNode(root));
861}
862
863TEST_F(BookmarkBarFolderControllerMenuTest, DragMoveBarBookmarkToSubfolder) {
864  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
865  const BookmarkNode* root = model->bookmark_bar_node();
866  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
867      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
868      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
869  test::AddNodesFromModelString(model, root, model_string);
870
871  // Validate initial model.
872  std::string actualModelString = test::ModelStringFromNode(root);
873  EXPECT_EQ(model_string, actualModelString);
874
875  // Pop up a folder menu and a subfolder menu.
876  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
877  ASSERT_TRUE(toFolder);
878  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
879                          withObject:toFolder];
880  BookmarkBarFolderController* folderController = [bar_ folderController];
881  EXPECT_TRUE(folderController);
882  NSWindow* toWindow = [folderController window];
883  EXPECT_TRUE(toWindow);
884  NSRect oldToWindowFrame = [toWindow frame];
885  BookmarkButton* toSubfolder =
886      [folderController buttonWithTitleEqualTo:@"4f2f"];
887  ASSERT_TRUE(toSubfolder);
888  [[toSubfolder target] performSelector:@selector(openBookmarkFolderFromButton:)
889                             withObject:toSubfolder];
890  BookmarkBarFolderController* subfolderController =
891      [folderController folderController];
892  EXPECT_TRUE(subfolderController);
893  NSWindow* toSubwindow = [subfolderController window];
894  EXPECT_TRUE(toSubwindow);
895  NSRect oldToSubwindowFrame = [toSubwindow frame];
896  // Drag a bar button onto a bookmark (i.e. not a folder) in a folder
897  // so it should end up below the target bookmark.
898  BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"5b"];
899  ASSERT_TRUE(draggedButton);
900  BookmarkButton* targetButton =
901      [subfolderController buttonWithTitleEqualTo:@"4f2f3b"];
902  ASSERT_TRUE(targetButton);
903  [subfolderController dragButton:draggedButton
904                               to:[targetButton center]
905                             copy:NO];
906  // The button should have landed just after "2f".
907  const std::string expected_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b "
908      "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ "
909      "4f2f1b 4f2f2b 4f2f3b 5b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] ");
910  EXPECT_EQ(expected_string, test::ModelStringFromNode(root));
911
912  // Check button spacing.
913  [folderController validateMenuSpacing];
914  [subfolderController validateMenuSpacing];
915
916  // Check the window layouts. The folder window should not have changed,
917  // but the subfolder window should have shifted vertically and grown.
918  NSRect newToWindowFrame = [toWindow frame];
919  EXPECT_NSRECT_EQ(oldToWindowFrame, newToWindowFrame);
920  NSRect newToSubwindowFrame = [toSubwindow frame];
921  NSRect expectedToSubwindowFrame = oldToSubwindowFrame;
922  expectedToSubwindowFrame.origin.y -= bookmarks::kBookmarkFolderButtonHeight;
923  expectedToSubwindowFrame.size.height +=
924      bookmarks::kBookmarkFolderButtonHeight;
925  EXPECT_NSRECT_EQ(expectedToSubwindowFrame, newToSubwindowFrame);
926}
927
928TEST_F(BookmarkBarFolderControllerMenuTest, DragMoveWithinFolder) {
929  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
930  const BookmarkNode* root = model->bookmark_bar_node();
931  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
932      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
933      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
934  test::AddNodesFromModelString(model, root, model_string);
935
936  // Validate initial model.
937  std::string actualModelString = test::ModelStringFromNode(root);
938  EXPECT_EQ(model_string, actualModelString);
939
940  // Pop up a folder menu.
941  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
942  ASSERT_TRUE(toFolder);
943  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
944                          withObject:toFolder];
945  BookmarkBarFolderController* folderController = [bar_ folderController];
946  EXPECT_TRUE(folderController);
947  NSWindow* toWindow = [folderController window];
948  EXPECT_TRUE(toWindow);
949  NSRect oldToWindowFrame = [toWindow frame];
950  // Drag a folder button to the top within the same parent.
951  BookmarkButton* draggedButton =
952      [folderController buttonWithTitleEqualTo:@"4f2f"];
953  ASSERT_TRUE(draggedButton);
954  BookmarkButton* targetButton =
955      [folderController buttonWithTitleEqualTo:@"4f1f"];
956  ASSERT_TRUE(targetButton);
957  [folderController dragButton:draggedButton
958                            to:[targetButton top]
959                          copy:NO];
960  // The button should have landed above "4f1f".
961  const std::string expected_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b "
962      "2f2f2b 2f2f3b ] 2f3b ] 3b 4f:[ 4f2f:[ 4f2f1b 4f2f2b 4f2f3b ] "
963      "4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
964  EXPECT_EQ(expected_string, test::ModelStringFromNode(root));
965
966  // The window should not have gone away.
967  EXPECT_TRUE([bar_ folderController]);
968
969  // The folder window should not have changed.
970  NSRect newToWindowFrame = [toWindow frame];
971  EXPECT_NSRECT_EQ(oldToWindowFrame, newToWindowFrame);
972
973  // Check button spacing.
974  [folderController validateMenuSpacing];
975}
976
977TEST_F(BookmarkBarFolderControllerMenuTest, DragParentOntoChild) {
978  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
979  const BookmarkNode* root = model->bookmark_bar_node();
980  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
981      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
982      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
983  test::AddNodesFromModelString(model, root, model_string);
984
985  // Validate initial model.
986  std::string actualModelString = test::ModelStringFromNode(root);
987  EXPECT_EQ(model_string, actualModelString);
988
989  // Pop up a folder menu.
990  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
991  ASSERT_TRUE(toFolder);
992  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
993                          withObject:toFolder];
994  BookmarkBarFolderController* folderController = [bar_ folderController];
995  EXPECT_TRUE(folderController);
996  NSWindow* toWindow = [folderController window];
997  EXPECT_TRUE(toWindow);
998  // Drag a folder button to one of its children.
999  BookmarkButton* draggedButton = [bar_ buttonWithTitleEqualTo:@"4f"];
1000  ASSERT_TRUE(draggedButton);
1001  BookmarkButton* targetButton =
1002      [folderController buttonWithTitleEqualTo:@"4f3f"];
1003  ASSERT_TRUE(targetButton);
1004  [folderController dragButton:draggedButton
1005                            to:[targetButton top]
1006                          copy:NO];
1007  // The model should not have changed.
1008  EXPECT_EQ(model_string, test::ModelStringFromNode(root));
1009
1010  // Check button spacing.
1011  [folderController validateMenuSpacing];
1012}
1013
1014TEST_F(BookmarkBarFolderControllerMenuTest, DragMoveChildToParent) {
1015  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1016  const BookmarkNode* root = model->bookmark_bar_node();
1017  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
1018      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f:[ 4f2f1b "
1019      "4f2f2b 4f2f3b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
1020  test::AddNodesFromModelString(model, root, model_string);
1021
1022  // Validate initial model.
1023  std::string actualModelString = test::ModelStringFromNode(root);
1024  EXPECT_EQ(model_string, actualModelString);
1025
1026  // Pop up a folder menu and a subfolder menu.
1027  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"4f"];
1028  ASSERT_TRUE(toFolder);
1029  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
1030                          withObject:toFolder];
1031  BookmarkBarFolderController* folderController = [bar_ folderController];
1032  EXPECT_TRUE(folderController);
1033  BookmarkButton* toSubfolder =
1034      [folderController buttonWithTitleEqualTo:@"4f2f"];
1035  ASSERT_TRUE(toSubfolder);
1036  [[toSubfolder target] performSelector:@selector(openBookmarkFolderFromButton:)
1037                             withObject:toSubfolder];
1038  BookmarkBarFolderController* subfolderController =
1039      [folderController folderController];
1040  EXPECT_TRUE(subfolderController);
1041
1042  // Drag a subfolder bookmark to the parent folder.
1043  BookmarkButton* draggedButton =
1044      [subfolderController buttonWithTitleEqualTo:@"4f2f3b"];
1045  ASSERT_TRUE(draggedButton);
1046  BookmarkButton* targetButton =
1047      [folderController buttonWithTitleEqualTo:@"4f2f"];
1048  ASSERT_TRUE(targetButton);
1049  [folderController dragButton:draggedButton
1050                            to:[targetButton top]
1051                          copy:NO];
1052  // The button should have landed above "4f2f".
1053  const std::string expected_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b "
1054      "2f2f3b ] 2f3b ] 3b 4f:[ 4f1f:[ 4f1f1b 4f1f2b 4f1f3b ] 4f2f3b 4f2f:[ "
1055      "4f2f1b 4f2f2b ] 4f3f:[ 4f3f1b 4f3f2b 4f3f3b ] ] 5b ");
1056  EXPECT_EQ(expected_string, test::ModelStringFromNode(root));
1057
1058  // Check button spacing.
1059  [folderController validateMenuSpacing];
1060  // The window should not have gone away.
1061  EXPECT_TRUE([bar_ folderController]);
1062  // The subfolder should have gone away.
1063  EXPECT_FALSE([folderController folderController]);
1064}
1065
1066TEST_F(BookmarkBarFolderControllerMenuTest, DragWindowResizing) {
1067  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1068  const BookmarkNode* root = model->bookmark_bar_node();
1069  const std::string model_string(
1070      "a b:[ b1 b2 b3 ] reallyReallyLongBookmarkName c ");
1071  test::AddNodesFromModelString(model, root, model_string);
1072
1073  // Validate initial model.
1074  std::string actualModelString = test::ModelStringFromNode(root);
1075  EXPECT_EQ(model_string, actualModelString);
1076
1077  // Pop up a folder menu.
1078  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"b"];
1079  ASSERT_TRUE(toFolder);
1080  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
1081                          withObject:toFolder];
1082  BookmarkBarFolderController* folderController = [bar_ folderController];
1083  EXPECT_TRUE(folderController);
1084  NSWindow* toWindow = [folderController window];
1085  EXPECT_TRUE(toWindow);
1086  CGFloat oldWidth = NSWidth([toWindow frame]);
1087  // Drag the bookmark with a long name to the folder.
1088  BookmarkButton* draggedButton =
1089      [bar_ buttonWithTitleEqualTo:@"reallyReallyLongBookmarkName"];
1090  ASSERT_TRUE(draggedButton);
1091  BookmarkButton* targetButton =
1092      [folderController buttonWithTitleEqualTo:@"b1"];
1093  ASSERT_TRUE(targetButton);
1094  [folderController dragButton:draggedButton
1095                            to:[targetButton center]
1096                          copy:NO];
1097  // Verify the model change.
1098  const std::string expected_string(
1099      "a b:[ b1 reallyReallyLongBookmarkName b2 b3 ] c ");
1100  EXPECT_EQ(expected_string, test::ModelStringFromNode(root));
1101  // Verify the window grew. Just test a reasonable width gain.
1102  CGFloat newWidth = NSWidth([toWindow frame]);
1103  EXPECT_LT(oldWidth + 30.0, newWidth);
1104}
1105
1106TEST_F(BookmarkBarFolderControllerMenuTest, MoveRemoveAddButtons) {
1107  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1108  const BookmarkNode* root = model->bookmark_bar_node();
1109  const std::string model_string("1b 2f:[ 2f1b 2f2b 2f3b ] 3b 4b ");
1110  test::AddNodesFromModelString(model, root, model_string);
1111
1112  // Validate initial model.
1113  std::string actualModelString = test::ModelStringFromNode(root);
1114  EXPECT_EQ(model_string, actualModelString);
1115
1116  // Pop up a folder menu.
1117  BookmarkButton* toFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
1118  ASSERT_TRUE(toFolder);
1119  [[toFolder target] performSelector:@selector(openBookmarkFolderFromButton:)
1120                          withObject:toFolder];
1121  BookmarkBarFolderController* folder = [bar_ folderController];
1122  EXPECT_TRUE(folder);
1123
1124  // Remember how many buttons are showing.
1125  NSArray* buttons = [folder buttons];
1126  NSUInteger oldDisplayedButtons = [buttons count];
1127
1128  // Move a button around a bit.
1129  [folder moveButtonFromIndex:0 toIndex:2];
1130  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:0] title]);
1131  EXPECT_NSEQ(@"2f3b", [[buttons objectAtIndex:1] title]);
1132  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:2] title]);
1133  EXPECT_EQ(oldDisplayedButtons, [buttons count]);
1134  [folder moveButtonFromIndex:2 toIndex:0];
1135  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:0] title]);
1136  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:1] title]);
1137  EXPECT_NSEQ(@"2f3b", [[buttons objectAtIndex:2] title]);
1138  EXPECT_EQ(oldDisplayedButtons, [buttons count]);
1139
1140  // Add a couple of buttons.
1141  const BookmarkNode* node = root->GetChild(2); // Purloin an existing node.
1142  [folder addButtonForNode:node atIndex:0];
1143  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:0] title]);
1144  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:1] title]);
1145  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:2] title]);
1146  EXPECT_NSEQ(@"2f3b", [[buttons objectAtIndex:3] title]);
1147  EXPECT_EQ(oldDisplayedButtons + 1, [buttons count]);
1148  node = root->GetChild(3);
1149  [folder addButtonForNode:node atIndex:-1];
1150  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:0] title]);
1151  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:1] title]);
1152  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:2] title]);
1153  EXPECT_NSEQ(@"2f3b", [[buttons objectAtIndex:3] title]);
1154  EXPECT_NSEQ(@"4b", [[buttons objectAtIndex:4] title]);
1155  EXPECT_EQ(oldDisplayedButtons + 2, [buttons count]);
1156
1157  // Remove a couple of buttons.
1158  [folder removeButton:4 animate:NO];
1159  [folder removeButton:1 animate:NO];
1160  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:0] title]);
1161  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:1] title]);
1162  EXPECT_NSEQ(@"2f3b", [[buttons objectAtIndex:2] title]);
1163  EXPECT_EQ(oldDisplayedButtons, [buttons count]);
1164
1165  // Check button spacing.
1166  [folder validateMenuSpacing];
1167}
1168
1169TEST_F(BookmarkBarFolderControllerMenuTest, RemoveLastButtonOtherBookmarks) {
1170  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1171  const BookmarkNode* otherBookmarks = model->other_node();
1172
1173  BookmarkButton* otherButton = [bar_ otherBookmarksButton];
1174  ASSERT_TRUE(otherButton);
1175
1176  // Open the folder to get the folderController_.
1177  [[otherButton target] openBookmarkFolderFromButton:otherButton];
1178  BookmarkBarFolderController* folder = [bar_ folderController];
1179  EXPECT_TRUE(folder);
1180
1181  // Initially there is only (empty) placeholder button, hence buttonCount
1182  // should be 1.
1183  NSArray* buttons = [folder buttons];
1184  EXPECT_TRUE(buttons);
1185  EXPECT_EQ(1U, [buttons count]);
1186
1187  // Add a new bookmark into 'Other bookmarks' folder.
1188  model->AddURL(otherBookmarks, otherBookmarks->child_count(),
1189                ASCIIToUTF16("TheOther"),
1190                GURL("http://www.other.com"));
1191
1192  // buttonCount still should be 1, as we remove the (empty) placeholder button
1193  // when adding a new button to an empty folder.
1194  EXPECT_EQ(1U, [buttons count]);
1195
1196  // Now we have only 1 button; remove it so that 'Other bookmarks' folder
1197  // is hidden.
1198  [folder removeButton:0 animate:NO];
1199  EXPECT_EQ(0U, [buttons count]);
1200
1201  // 'Other bookmarks' folder gets closed once we remove the last button. Hence
1202  // folderController_ should be NULL.
1203  EXPECT_FALSE([bar_ folderController]);
1204}
1205
1206TEST_F(BookmarkBarFolderControllerMenuTest, ControllerForNode) {
1207  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1208  const BookmarkNode* root = model->bookmark_bar_node();
1209  const std::string model_string("1b 2f:[ 2f1b 2f2b ] 3b ");
1210  test::AddNodesFromModelString(model, root, model_string);
1211
1212  // Validate initial model.
1213  std::string actualModelString = test::ModelStringFromNode(root);
1214  EXPECT_EQ(model_string, actualModelString);
1215
1216  // Find the main bar controller.
1217  const void* expectedController = bar_;
1218  const void* actualController = [bar_ controllerForNode:root];
1219  EXPECT_EQ(expectedController, actualController);
1220
1221  // Pop up the folder menu.
1222  BookmarkButton* targetFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
1223  ASSERT_TRUE(targetFolder);
1224  [[targetFolder target]
1225   performSelector:@selector(openBookmarkFolderFromButton:)
1226   withObject:targetFolder];
1227  BookmarkBarFolderController* folder = [bar_ folderController];
1228  EXPECT_TRUE(folder);
1229
1230  // Find the folder controller using the folder controller.
1231  const BookmarkNode* targetNode = root->GetChild(1);
1232  expectedController = folder;
1233  actualController = [bar_ controllerForNode:targetNode];
1234  EXPECT_EQ(expectedController, actualController);
1235
1236  // Find the folder controller from the bar.
1237  actualController = [folder controllerForNode:targetNode];
1238  EXPECT_EQ(expectedController, actualController);
1239}
1240
1241TEST_F(BookmarkBarFolderControllerMenuTest, MenuSizingAndScrollArrows) {
1242  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1243  const BookmarkNode* root = model->bookmark_bar_node();
1244  const std::string model_string("1b 2b 3b ");
1245  test::AddNodesFromModelString(model, root, model_string);
1246
1247  // Validate initial model.
1248  std::string actualModelString = test::ModelStringFromNode(root);
1249  EXPECT_EQ(model_string, actualModelString);
1250
1251  const BookmarkNode* parent = model->bookmark_bar_node();
1252  const BookmarkNode* folder = model->AddFolder(parent,
1253                                                parent->child_count(),
1254                                                ASCIIToUTF16("BIG"));
1255
1256  // Pop open the new folder window and verify it has one (empty) item.
1257  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"BIG"];
1258  [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1259                        withObject:button];
1260  BookmarkBarFolderController* folderController = [bar_ folderController];
1261  EXPECT_TRUE(folderController);
1262  NSWindow* folderWindow = [folderController window];
1263  EXPECT_TRUE(folderWindow);
1264  CGFloat expectedHeight = (CGFloat)bookmarks::kBookmarkFolderButtonHeight +
1265      (2*bookmarks::kBookmarkVerticalPadding);
1266  NSRect windowFrame = [folderWindow frame];
1267  CGFloat windowHeight = NSHeight(windowFrame);
1268  EXPECT_CGFLOAT_EQ(expectedHeight, windowHeight);
1269  EXPECT_FALSE([folderController canScrollUp]);
1270  EXPECT_FALSE([folderController canScrollDown]);
1271
1272  // Now add a real bookmark and reopen.
1273  model->AddURL(folder, folder->child_count(), ASCIIToUTF16("a"),
1274                GURL("http://a.com/"));
1275  folderController = [bar_ folderController];
1276  EXPECT_TRUE(folderController);
1277  NSView* folderView = [folderController folderView];
1278  EXPECT_TRUE(folderView);
1279  NSRect menuFrame = [folderView frame];
1280  NSView* visibleView = [folderController visibleView];
1281  NSRect visibleFrame = [visibleView frame];
1282  NSScrollView* scrollView = [folderController scrollView];
1283  NSRect scrollFrame = [scrollView frame];
1284
1285  // Determine the margins between the scroll frame and the visible frame.
1286  CGFloat widthDelta = NSWidth(visibleFrame) - NSWidth(scrollFrame);
1287
1288  CGFloat menuHeight = NSHeight(menuFrame);
1289  EXPECT_CGFLOAT_EQ(expectedHeight, menuHeight);
1290  CGFloat scrollerWidth = NSWidth(scrollFrame);
1291  button = [folderController buttonWithTitleEqualTo:@"a"];
1292  CGFloat buttonWidth = NSWidth([button frame]);
1293  EXPECT_CGFLOAT_EQ(scrollerWidth, buttonWidth);
1294  CGFloat visibleWidth = NSWidth(visibleFrame);
1295  EXPECT_CGFLOAT_EQ(visibleWidth - widthDelta, buttonWidth);
1296  EXPECT_LT(scrollerWidth, NSWidth([folderView frame]));
1297
1298  // Add a wider bookmark and make sure the button widths match.
1299  int reallyWideButtonNumber = folder->child_count();
1300  model->AddURL(folder, reallyWideButtonNumber,
1301                ASCIIToUTF16("A really, really, really, really, really, "
1302                            "really long name"),
1303                GURL("http://www.google.com/a"));
1304  BookmarkButton* bigButton =
1305      [folderController buttonWithTitleEqualTo:
1306       @"A really, really, really, really, really, really long name"];
1307  EXPECT_TRUE(bigButton);
1308  CGFloat buttonWidthB = NSWidth([bigButton frame]);
1309  EXPECT_LT(buttonWidth, buttonWidthB);
1310  // Add a bunch of bookmarks until the window becomes scrollable, then check
1311  // for a scroll up arrow.
1312  NSUInteger tripWire = 0;  // Prevent a runaway.
1313  while (![folderController canScrollUp] && ++tripWire < 1000) {
1314    model->AddURL(folder, folder->child_count(), ASCIIToUTF16("B"),
1315                  GURL("http://b.com/"));
1316  }
1317  EXPECT_TRUE([folderController canScrollUp]);
1318
1319  // Remove one bookmark and make sure the scroll down arrow has been removed.
1320  // We'll remove the really long node so we can see if the buttons get resized.
1321  scrollerWidth = NSWidth([folderView frame]);
1322  buttonWidth = NSWidth([button frame]);
1323  model->Remove(folder, reallyWideButtonNumber);
1324  EXPECT_FALSE([folderController canScrollUp]);
1325  EXPECT_FALSE([folderController canScrollDown]);
1326
1327  // Check the size. It should have reduced.
1328  EXPECT_GT(scrollerWidth, NSWidth([folderView frame]));
1329  EXPECT_GT(buttonWidth, NSWidth([button frame]));
1330
1331  // Check button spacing.
1332  [folderController validateMenuSpacing];
1333}
1334
1335// See http://crbug.com/46101
1336TEST_F(BookmarkBarFolderControllerMenuTest, HoverThenDeleteBookmark) {
1337  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1338  const BookmarkNode* root = model->bookmark_bar_node();
1339  const BookmarkNode* folder = model->AddFolder(root,
1340                                                root->child_count(),
1341                                                ASCIIToUTF16("BIG"));
1342  for (int i = 0; i < kLotsOfNodesCount; i++)
1343    model->AddURL(folder, folder->child_count(), ASCIIToUTF16("kid"),
1344                  GURL("http://kid.com/smile"));
1345
1346  // Pop open the new folder window and hover one of its kids.
1347  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"BIG"];
1348  [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1349                        withObject:button];
1350  BookmarkBarFolderController* bbfc = [bar_ folderController];
1351  NSArray* buttons = [bbfc buttons];
1352
1353  // Hover over a button and verify that it is now known.
1354  button = [buttons objectAtIndex:3];
1355  BookmarkButton* buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1356  EXPECT_FALSE(buttonThatMouseIsIn);
1357  [bbfc mouseEnteredButton:button event:nil];
1358  buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1359  EXPECT_EQ(button, buttonThatMouseIsIn);
1360
1361  // Delete the bookmark and verify that it is now not known.
1362  model->Remove(folder, 3);
1363  buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1364  EXPECT_FALSE(buttonThatMouseIsIn);
1365}
1366
1367// Just like a BookmarkBarFolderController but intercedes when providing
1368// pasteboard drag data.
1369@interface BookmarkBarFolderControllerDragData : BookmarkBarFolderController {
1370  const BookmarkNode* dragDataNode_;  // Weak
1371}
1372- (void)setDragDataNode:(const BookmarkNode*)node;
1373@end
1374
1375@implementation BookmarkBarFolderControllerDragData
1376
1377- (id)initWithParentButton:(BookmarkButton*)button
1378          parentController:(BookmarkBarFolderController*)parentController
1379             barController:(BookmarkBarController*)barController
1380                   profile:(Profile*)profile {
1381  if ((self = [super initWithParentButton:button
1382                         parentController:parentController
1383                            barController:barController
1384                                  profile:profile])) {
1385    dragDataNode_ = NULL;
1386  }
1387  return self;
1388}
1389
1390- (void)setDragDataNode:(const BookmarkNode*)node {
1391  dragDataNode_ = node;
1392}
1393
1394- (std::vector<const BookmarkNode*>)retrieveBookmarkNodeData {
1395  std::vector<const BookmarkNode*> dragDataNodes;
1396  if(dragDataNode_) {
1397    dragDataNodes.push_back(dragDataNode_);
1398  }
1399  return dragDataNodes;
1400}
1401
1402@end
1403
1404TEST_F(BookmarkBarFolderControllerMenuTest, DragBookmarkData) {
1405  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1406  const BookmarkNode* root = model->bookmark_bar_node();
1407  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1408                                 "2f3b ] 3b 4b ");
1409  test::AddNodesFromModelString(model, root, model_string);
1410  const BookmarkNode* other = model->other_node();
1411  const std::string other_string("O1b O2b O3f:[ O3f1b O3f2f ] "
1412                                 "O4f:[ O4f1b O4f2f ] 05b ");
1413  test::AddNodesFromModelString(model, other, other_string);
1414
1415  // Validate initial model.
1416  std::string actual = test::ModelStringFromNode(root);
1417  EXPECT_EQ(model_string, actual);
1418  actual = test::ModelStringFromNode(other);
1419  EXPECT_EQ(other_string, actual);
1420
1421  // Pop open a folder.
1422  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1423  base::scoped_nsobject<BookmarkBarFolderControllerDragData> folderController;
1424  folderController.reset([[BookmarkBarFolderControllerDragData alloc]
1425                          initWithParentButton:button
1426                              parentController:nil
1427                                 barController:bar_
1428                                       profile:profile()]);
1429  BookmarkButton* targetButton =
1430      [folderController buttonWithTitleEqualTo:@"2f1b"];
1431  ASSERT_TRUE(targetButton);
1432
1433  // Gen up some dragging data.
1434  const BookmarkNode* newNode = other->GetChild(2);
1435  [folderController setDragDataNode:newNode];
1436  base::scoped_nsobject<FakedDragInfo> dragInfo([[FakedDragInfo alloc] init]);
1437  [dragInfo setDropLocation:[targetButton top]];
1438  [folderController dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
1439
1440  // Verify the model.
1441  const std::string expected("1b 2f:[ O3f:[ O3f1b O3f2f ] 2f1b 2f2f:[ 2f2f1b "
1442                             "2f2f2b 2f2f3b ] 2f3b ] 3b 4b ");
1443  actual = test::ModelStringFromNode(root);
1444  EXPECT_EQ(expected, actual);
1445
1446  // Now drag over a folder button.
1447  targetButton = [folderController buttonWithTitleEqualTo:@"2f2f"];
1448  ASSERT_TRUE(targetButton);
1449  newNode = other->GetChild(2);  // Should be O4f.
1450  EXPECT_EQ(newNode->GetTitle(), ASCIIToUTF16("O4f"));
1451  [folderController setDragDataNode:newNode];
1452  [dragInfo setDropLocation:[targetButton center]];
1453  [folderController dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
1454
1455  // Verify the model.
1456  const std::string expectedA("1b 2f:[ O3f:[ O3f1b O3f2f ] 2f1b 2f2f:[ "
1457                              "2f2f1b 2f2f2b 2f2f3b O4f:[ O4f1b O4f2f ] ] "
1458                              "2f3b ] 3b 4b ");
1459  actual = test::ModelStringFromNode(root);
1460  EXPECT_EQ(expectedA, actual);
1461
1462  // Check button spacing.
1463  [folderController validateMenuSpacing];
1464}
1465
1466TEST_F(BookmarkBarFolderControllerMenuTest, DragBookmarkDataToTrash) {
1467  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1468  const BookmarkNode* root = model->bookmark_bar_node();
1469  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1470                                 "2f3b ] 3b 4b ");
1471  test::AddNodesFromModelString(model, root, model_string);
1472
1473  // Validate initial model.
1474  std::string actual = test::ModelStringFromNode(root);
1475  EXPECT_EQ(model_string, actual);
1476
1477  const BookmarkNode* folderNode = root->GetChild(1);
1478  int oldFolderChildCount = folderNode->child_count();
1479
1480  // Pop open a folder.
1481  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1482  base::scoped_nsobject<BookmarkBarFolderControllerDragData> folderController;
1483  folderController.reset([[BookmarkBarFolderControllerDragData alloc]
1484                          initWithParentButton:button
1485                              parentController:nil
1486                                 barController:bar_
1487                                       profile:profile()]);
1488
1489  // Drag a button to the trash.
1490  BookmarkButton* buttonToDelete =
1491      [folderController buttonWithTitleEqualTo:@"2f1b"];
1492  ASSERT_TRUE(buttonToDelete);
1493  EXPECT_TRUE([folderController canDragBookmarkButtonToTrash:buttonToDelete]);
1494  [folderController didDragBookmarkToTrash:buttonToDelete];
1495
1496  // There should be one less button in the folder.
1497  int newFolderChildCount = folderNode->child_count();
1498  EXPECT_EQ(oldFolderChildCount - 1, newFolderChildCount);
1499  // Verify the model.
1500  const std::string expected("1b 2f:[ 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1501                             "2f3b ] 3b 4b ");
1502  actual = test::ModelStringFromNode(root);
1503  EXPECT_EQ(expected, actual);
1504
1505  // Check button spacing.
1506  [folderController validateMenuSpacing];
1507}
1508
1509TEST_F(BookmarkBarFolderControllerMenuTest, AddURLs) {
1510  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1511  const BookmarkNode* root = model->bookmark_bar_node();
1512  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1513                                 "2f3b ] 3b 4b ");
1514  test::AddNodesFromModelString(model, root, model_string);
1515
1516  // Validate initial model.
1517  std::string actual = test::ModelStringFromNode(root);
1518  EXPECT_EQ(model_string, actual);
1519
1520  // Pop open a folder.
1521  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1522  [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1523                        withObject:button];
1524  BookmarkBarFolderController* folderController = [bar_ folderController];
1525  EXPECT_TRUE(folderController);
1526  NSArray* buttons = [folderController buttons];
1527  EXPECT_TRUE(buttons);
1528
1529  // Remember how many buttons are showing.
1530  int oldDisplayedButtons = [buttons count];
1531
1532  BookmarkButton* targetButton =
1533      [folderController buttonWithTitleEqualTo:@"2f1b"];
1534  ASSERT_TRUE(targetButton);
1535
1536  NSArray* urls = [NSArray arrayWithObjects: @"http://www.a.com/",
1537                   @"http://www.b.com/", nil];
1538  NSArray* titles = [NSArray arrayWithObjects: @"SiteA", @"SiteB", nil];
1539  [folderController addURLs:urls withTitles:titles at:[targetButton top]];
1540
1541  // There should two more buttons in the folder.
1542  int newDisplayedButtons = [buttons count];
1543  EXPECT_EQ(oldDisplayedButtons + 2, newDisplayedButtons);
1544  // Verify the model.
1545  const std::string expected("1b 2f:[ SiteA SiteB 2f1b 2f2f:[ 2f2f1b 2f2f2b "
1546                             "2f2f3b ] 2f3b ] 3b 4b ");
1547  actual = test::ModelStringFromNode(root);
1548  EXPECT_EQ(expected, actual);
1549
1550  // Check button spacing.
1551  [folderController validateMenuSpacing];
1552}
1553
1554TEST_F(BookmarkBarFolderControllerMenuTest, DropPositionIndicator) {
1555  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1556  const BookmarkNode* root = model->bookmark_bar_node();
1557  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1558                                 "2f3b ] 3b 4b ");
1559  test::AddNodesFromModelString(model, root, model_string);
1560
1561  // Validate initial model.
1562  std::string actual = test::ModelStringFromNode(root);
1563  EXPECT_EQ(model_string, actual);
1564
1565  // Pop open the folder.
1566  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1567  [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1568                        withObject:button];
1569  BookmarkBarFolderController* folder = [bar_ folderController];
1570  EXPECT_TRUE(folder);
1571
1572  // Test a series of points starting at the top of the folder.
1573  const CGFloat yOffset = 0.5 * bookmarks::kBookmarkVerticalPadding;
1574  BookmarkButton* targetButton = [folder buttonWithTitleEqualTo:@"2f1b"];
1575  ASSERT_TRUE(targetButton);
1576  NSPoint targetPoint = [targetButton top];
1577  CGFloat pos = [folder indicatorPosForDragToPoint:targetPoint];
1578  EXPECT_CGFLOAT_EQ(targetPoint.y + yOffset, pos);
1579  pos = [folder indicatorPosForDragToPoint:[targetButton bottom]];
1580  targetButton = [folder buttonWithTitleEqualTo:@"2f2f"];
1581  EXPECT_CGFLOAT_EQ([targetButton top].y + yOffset, pos);
1582  pos = [folder indicatorPosForDragToPoint:NSMakePoint(10,0)];
1583  targetButton = [folder buttonWithTitleEqualTo:@"2f3b"];
1584  EXPECT_CGFLOAT_EQ([targetButton bottom].y - yOffset, pos);
1585}
1586
1587@interface BookmarkBarControllerNoDelete : BookmarkBarController
1588- (IBAction)deleteBookmark:(id)sender;
1589@end
1590
1591@implementation BookmarkBarControllerNoDelete
1592- (IBAction)deleteBookmark:(id)sender {
1593  // NOP
1594}
1595@end
1596
1597class BookmarkBarFolderControllerClosingTest : public
1598    BookmarkBarFolderControllerMenuTest {
1599 public:
1600  virtual void SetUp() {
1601    BookmarkBarFolderControllerMenuTest::SetUp();
1602    ASSERT_TRUE(browser());
1603
1604    bar_.reset([[BookmarkBarControllerNoDelete alloc]
1605                initWithBrowser:browser()
1606                   initialWidth:NSWidth([parent_view_ frame])
1607                       delegate:nil
1608                 resizeDelegate:resizeDelegate_.get()]);
1609    InstallAndToggleBar(bar_.get());
1610  }
1611};
1612
1613TEST_F(BookmarkBarFolderControllerClosingTest, DeleteClosesFolder) {
1614  BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1615  const BookmarkNode* root = model->bookmark_bar_node();
1616  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b ] "
1617                                 "2f3b ] 3b ");
1618  test::AddNodesFromModelString(model, root, model_string);
1619
1620  // Validate initial model.
1621  std::string actualModelString = test::ModelStringFromNode(root);
1622  EXPECT_EQ(model_string, actualModelString);
1623
1624  // Open the folder menu and submenu.
1625  BookmarkButton* target = [bar_ buttonWithTitleEqualTo:@"2f"];
1626  ASSERT_TRUE(target);
1627  [[target target] performSelector:@selector(openBookmarkFolderFromButton:)
1628                              withObject:target];
1629  BookmarkBarFolderController* folder = [bar_ folderController];
1630  EXPECT_TRUE(folder);
1631  BookmarkButton* subTarget = [folder buttonWithTitleEqualTo:@"2f2f"];
1632  ASSERT_TRUE(subTarget);
1633  [[subTarget target] performSelector:@selector(openBookmarkFolderFromButton:)
1634                           withObject:subTarget];
1635  BookmarkBarFolderController* subFolder = [folder folderController];
1636  EXPECT_TRUE(subFolder);
1637
1638  // Delete the folder node and verify the window closed down by looking
1639  // for its controller again.
1640  DeleteBookmark([folder parentButton], profile());
1641  EXPECT_FALSE([folder folderController]);
1642}
1643
1644// TODO(jrg): draggingEntered: and draggingExited: trigger timers so
1645// they are hard to test.  Factor out "fire timers" into routines
1646// which can be overridden to fire immediately to make behavior
1647// confirmable.
1648// There is a similar problem with mouseEnteredButton: and
1649// mouseExitedButton:.
1650