Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / bookmarks / bookmark_bar_folder_controller_unittest.mm
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/core/browser/bookmark_model.h"
19 #include "components/bookmarks/core/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
27 using base::ASCIIToUTF16;
28
29 namespace {
30
31 const int kLotsOfNodesCount = 150;
32
33 // Deletes the bookmark corresponding to |button|.
34 void 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
134 class 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
223 TEST_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).
257 TEST_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
266 TEST_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.
311 TEST_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
374 TEST_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
410 TEST_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
436 TEST_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
451 TEST_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.
469 TEST_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.
488 TEST_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.
578 TEST_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.
631 TEST_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
685 class 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
725 TEST_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
799 TEST_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
863 TEST_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
928 TEST_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
977 TEST_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
1014 TEST_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
1066 TEST_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
1106 TEST_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
1169 TEST_F(BookmarkBarFolderControllerMenuTest, ControllerForNode) {
1170   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1171   const BookmarkNode* root = model->bookmark_bar_node();
1172   const std::string model_string("1b 2f:[ 2f1b 2f2b ] 3b ");
1173   test::AddNodesFromModelString(model, root, model_string);
1174
1175   // Validate initial model.
1176   std::string actualModelString = test::ModelStringFromNode(root);
1177   EXPECT_EQ(model_string, actualModelString);
1178
1179   // Find the main bar controller.
1180   const void* expectedController = bar_;
1181   const void* actualController = [bar_ controllerForNode:root];
1182   EXPECT_EQ(expectedController, actualController);
1183
1184   // Pop up the folder menu.
1185   BookmarkButton* targetFolder = [bar_ buttonWithTitleEqualTo:@"2f"];
1186   ASSERT_TRUE(targetFolder);
1187   [[targetFolder target]
1188    performSelector:@selector(openBookmarkFolderFromButton:)
1189    withObject:targetFolder];
1190   BookmarkBarFolderController* folder = [bar_ folderController];
1191   EXPECT_TRUE(folder);
1192
1193   // Find the folder controller using the folder controller.
1194   const BookmarkNode* targetNode = root->GetChild(1);
1195   expectedController = folder;
1196   actualController = [bar_ controllerForNode:targetNode];
1197   EXPECT_EQ(expectedController, actualController);
1198
1199   // Find the folder controller from the bar.
1200   actualController = [folder controllerForNode:targetNode];
1201   EXPECT_EQ(expectedController, actualController);
1202 }
1203
1204 TEST_F(BookmarkBarFolderControllerMenuTest, MenuSizingAndScrollArrows) {
1205   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1206   const BookmarkNode* root = model->bookmark_bar_node();
1207   const std::string model_string("1b 2b 3b ");
1208   test::AddNodesFromModelString(model, root, model_string);
1209
1210   // Validate initial model.
1211   std::string actualModelString = test::ModelStringFromNode(root);
1212   EXPECT_EQ(model_string, actualModelString);
1213
1214   const BookmarkNode* parent = model->bookmark_bar_node();
1215   const BookmarkNode* folder = model->AddFolder(parent,
1216                                                 parent->child_count(),
1217                                                 ASCIIToUTF16("BIG"));
1218
1219   // Pop open the new folder window and verify it has one (empty) item.
1220   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"BIG"];
1221   [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1222                         withObject:button];
1223   BookmarkBarFolderController* folderController = [bar_ folderController];
1224   EXPECT_TRUE(folderController);
1225   NSWindow* folderWindow = [folderController window];
1226   EXPECT_TRUE(folderWindow);
1227   CGFloat expectedHeight = (CGFloat)bookmarks::kBookmarkFolderButtonHeight +
1228       (2*bookmarks::kBookmarkVerticalPadding);
1229   NSRect windowFrame = [folderWindow frame];
1230   CGFloat windowHeight = NSHeight(windowFrame);
1231   EXPECT_CGFLOAT_EQ(expectedHeight, windowHeight);
1232   EXPECT_FALSE([folderController canScrollUp]);
1233   EXPECT_FALSE([folderController canScrollDown]);
1234
1235   // Now add a real bookmark and reopen.
1236   model->AddURL(folder, folder->child_count(), ASCIIToUTF16("a"),
1237                 GURL("http://a.com/"));
1238   folderController = [bar_ folderController];
1239   EXPECT_TRUE(folderController);
1240   NSView* folderView = [folderController folderView];
1241   EXPECT_TRUE(folderView);
1242   NSRect menuFrame = [folderView frame];
1243   NSView* visibleView = [folderController visibleView];
1244   NSRect visibleFrame = [visibleView frame];
1245   NSScrollView* scrollView = [folderController scrollView];
1246   NSRect scrollFrame = [scrollView frame];
1247
1248   // Determine the margins between the scroll frame and the visible frame.
1249   CGFloat widthDelta = NSWidth(visibleFrame) - NSWidth(scrollFrame);
1250
1251   CGFloat menuHeight = NSHeight(menuFrame);
1252   EXPECT_CGFLOAT_EQ(expectedHeight, menuHeight);
1253   CGFloat scrollerWidth = NSWidth(scrollFrame);
1254   button = [folderController buttonWithTitleEqualTo:@"a"];
1255   CGFloat buttonWidth = NSWidth([button frame]);
1256   EXPECT_CGFLOAT_EQ(scrollerWidth, buttonWidth);
1257   CGFloat visibleWidth = NSWidth(visibleFrame);
1258   EXPECT_CGFLOAT_EQ(visibleWidth - widthDelta, buttonWidth);
1259   EXPECT_LT(scrollerWidth, NSWidth([folderView frame]));
1260
1261   // Add a wider bookmark and make sure the button widths match.
1262   int reallyWideButtonNumber = folder->child_count();
1263   model->AddURL(folder, reallyWideButtonNumber,
1264                 ASCIIToUTF16("A really, really, really, really, really, "
1265                             "really long name"),
1266                 GURL("http://www.google.com/a"));
1267   BookmarkButton* bigButton =
1268       [folderController buttonWithTitleEqualTo:
1269        @"A really, really, really, really, really, really long name"];
1270   EXPECT_TRUE(bigButton);
1271   CGFloat buttonWidthB = NSWidth([bigButton frame]);
1272   EXPECT_LT(buttonWidth, buttonWidthB);
1273   // Add a bunch of bookmarks until the window becomes scrollable, then check
1274   // for a scroll up arrow.
1275   NSUInteger tripWire = 0;  // Prevent a runaway.
1276   while (![folderController canScrollUp] && ++tripWire < 1000) {
1277     model->AddURL(folder, folder->child_count(), ASCIIToUTF16("B"),
1278                   GURL("http://b.com/"));
1279   }
1280   EXPECT_TRUE([folderController canScrollUp]);
1281
1282   // Remove one bookmark and make sure the scroll down arrow has been removed.
1283   // We'll remove the really long node so we can see if the buttons get resized.
1284   scrollerWidth = NSWidth([folderView frame]);
1285   buttonWidth = NSWidth([button frame]);
1286   model->Remove(folder, reallyWideButtonNumber);
1287   EXPECT_FALSE([folderController canScrollUp]);
1288   EXPECT_FALSE([folderController canScrollDown]);
1289
1290   // Check the size. It should have reduced.
1291   EXPECT_GT(scrollerWidth, NSWidth([folderView frame]));
1292   EXPECT_GT(buttonWidth, NSWidth([button frame]));
1293
1294   // Check button spacing.
1295   [folderController validateMenuSpacing];
1296 }
1297
1298 // See http://crbug.com/46101
1299 TEST_F(BookmarkBarFolderControllerMenuTest, HoverThenDeleteBookmark) {
1300   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1301   const BookmarkNode* root = model->bookmark_bar_node();
1302   const BookmarkNode* folder = model->AddFolder(root,
1303                                                 root->child_count(),
1304                                                 ASCIIToUTF16("BIG"));
1305   for (int i = 0; i < kLotsOfNodesCount; i++)
1306     model->AddURL(folder, folder->child_count(), ASCIIToUTF16("kid"),
1307                   GURL("http://kid.com/smile"));
1308
1309   // Pop open the new folder window and hover one of its kids.
1310   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"BIG"];
1311   [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1312                         withObject:button];
1313   BookmarkBarFolderController* bbfc = [bar_ folderController];
1314   NSArray* buttons = [bbfc buttons];
1315
1316   // Hover over a button and verify that it is now known.
1317   button = [buttons objectAtIndex:3];
1318   BookmarkButton* buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1319   EXPECT_FALSE(buttonThatMouseIsIn);
1320   [bbfc mouseEnteredButton:button event:nil];
1321   buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1322   EXPECT_EQ(button, buttonThatMouseIsIn);
1323
1324   // Delete the bookmark and verify that it is now not known.
1325   model->Remove(folder, 3);
1326   buttonThatMouseIsIn = [bbfc buttonThatMouseIsIn];
1327   EXPECT_FALSE(buttonThatMouseIsIn);
1328 }
1329
1330 // Just like a BookmarkBarFolderController but intercedes when providing
1331 // pasteboard drag data.
1332 @interface BookmarkBarFolderControllerDragData : BookmarkBarFolderController {
1333   const BookmarkNode* dragDataNode_;  // Weak
1334 }
1335 - (void)setDragDataNode:(const BookmarkNode*)node;
1336 @end
1337
1338 @implementation BookmarkBarFolderControllerDragData
1339
1340 - (id)initWithParentButton:(BookmarkButton*)button
1341           parentController:(BookmarkBarFolderController*)parentController
1342              barController:(BookmarkBarController*)barController
1343                    profile:(Profile*)profile {
1344   if ((self = [super initWithParentButton:button
1345                          parentController:parentController
1346                             barController:barController
1347                                   profile:profile])) {
1348     dragDataNode_ = NULL;
1349   }
1350   return self;
1351 }
1352
1353 - (void)setDragDataNode:(const BookmarkNode*)node {
1354   dragDataNode_ = node;
1355 }
1356
1357 - (std::vector<const BookmarkNode*>)retrieveBookmarkNodeData {
1358   std::vector<const BookmarkNode*> dragDataNodes;
1359   if(dragDataNode_) {
1360     dragDataNodes.push_back(dragDataNode_);
1361   }
1362   return dragDataNodes;
1363 }
1364
1365 @end
1366
1367 TEST_F(BookmarkBarFolderControllerMenuTest, DragBookmarkData) {
1368   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1369   const BookmarkNode* root = model->bookmark_bar_node();
1370   const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1371                                  "2f3b ] 3b 4b ");
1372   test::AddNodesFromModelString(model, root, model_string);
1373   const BookmarkNode* other = model->other_node();
1374   const std::string other_string("O1b O2b O3f:[ O3f1b O3f2f ] "
1375                                  "O4f:[ O4f1b O4f2f ] 05b ");
1376   test::AddNodesFromModelString(model, other, other_string);
1377
1378   // Validate initial model.
1379   std::string actual = test::ModelStringFromNode(root);
1380   EXPECT_EQ(model_string, actual);
1381   actual = test::ModelStringFromNode(other);
1382   EXPECT_EQ(other_string, actual);
1383
1384   // Pop open a folder.
1385   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1386   base::scoped_nsobject<BookmarkBarFolderControllerDragData> folderController;
1387   folderController.reset([[BookmarkBarFolderControllerDragData alloc]
1388                           initWithParentButton:button
1389                               parentController:nil
1390                                  barController:bar_
1391                                        profile:profile()]);
1392   BookmarkButton* targetButton =
1393       [folderController buttonWithTitleEqualTo:@"2f1b"];
1394   ASSERT_TRUE(targetButton);
1395
1396   // Gen up some dragging data.
1397   const BookmarkNode* newNode = other->GetChild(2);
1398   [folderController setDragDataNode:newNode];
1399   base::scoped_nsobject<FakedDragInfo> dragInfo([[FakedDragInfo alloc] init]);
1400   [dragInfo setDropLocation:[targetButton top]];
1401   [folderController dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
1402
1403   // Verify the model.
1404   const std::string expected("1b 2f:[ O3f:[ O3f1b O3f2f ] 2f1b 2f2f:[ 2f2f1b "
1405                              "2f2f2b 2f2f3b ] 2f3b ] 3b 4b ");
1406   actual = test::ModelStringFromNode(root);
1407   EXPECT_EQ(expected, actual);
1408
1409   // Now drag over a folder button.
1410   targetButton = [folderController buttonWithTitleEqualTo:@"2f2f"];
1411   ASSERT_TRUE(targetButton);
1412   newNode = other->GetChild(2);  // Should be O4f.
1413   EXPECT_EQ(newNode->GetTitle(), ASCIIToUTF16("O4f"));
1414   [folderController setDragDataNode:newNode];
1415   [dragInfo setDropLocation:[targetButton center]];
1416   [folderController dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
1417
1418   // Verify the model.
1419   const std::string expectedA("1b 2f:[ O3f:[ O3f1b O3f2f ] 2f1b 2f2f:[ "
1420                               "2f2f1b 2f2f2b 2f2f3b O4f:[ O4f1b O4f2f ] ] "
1421                               "2f3b ] 3b 4b ");
1422   actual = test::ModelStringFromNode(root);
1423   EXPECT_EQ(expectedA, actual);
1424
1425   // Check button spacing.
1426   [folderController validateMenuSpacing];
1427 }
1428
1429 TEST_F(BookmarkBarFolderControllerMenuTest, DragBookmarkDataToTrash) {
1430   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1431   const BookmarkNode* root = model->bookmark_bar_node();
1432   const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1433                                  "2f3b ] 3b 4b ");
1434   test::AddNodesFromModelString(model, root, model_string);
1435
1436   // Validate initial model.
1437   std::string actual = test::ModelStringFromNode(root);
1438   EXPECT_EQ(model_string, actual);
1439
1440   const BookmarkNode* folderNode = root->GetChild(1);
1441   int oldFolderChildCount = folderNode->child_count();
1442
1443   // Pop open a folder.
1444   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1445   base::scoped_nsobject<BookmarkBarFolderControllerDragData> folderController;
1446   folderController.reset([[BookmarkBarFolderControllerDragData alloc]
1447                           initWithParentButton:button
1448                               parentController:nil
1449                                  barController:bar_
1450                                        profile:profile()]);
1451
1452   // Drag a button to the trash.
1453   BookmarkButton* buttonToDelete =
1454       [folderController buttonWithTitleEqualTo:@"2f1b"];
1455   ASSERT_TRUE(buttonToDelete);
1456   EXPECT_TRUE([folderController canDragBookmarkButtonToTrash:buttonToDelete]);
1457   [folderController didDragBookmarkToTrash:buttonToDelete];
1458
1459   // There should be one less button in the folder.
1460   int newFolderChildCount = folderNode->child_count();
1461   EXPECT_EQ(oldFolderChildCount - 1, newFolderChildCount);
1462   // Verify the model.
1463   const std::string expected("1b 2f:[ 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1464                              "2f3b ] 3b 4b ");
1465   actual = test::ModelStringFromNode(root);
1466   EXPECT_EQ(expected, actual);
1467
1468   // Check button spacing.
1469   [folderController validateMenuSpacing];
1470 }
1471
1472 TEST_F(BookmarkBarFolderControllerMenuTest, AddURLs) {
1473   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1474   const BookmarkNode* root = model->bookmark_bar_node();
1475   const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1476                                  "2f3b ] 3b 4b ");
1477   test::AddNodesFromModelString(model, root, model_string);
1478
1479   // Validate initial model.
1480   std::string actual = test::ModelStringFromNode(root);
1481   EXPECT_EQ(model_string, actual);
1482
1483   // Pop open a folder.
1484   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1485   [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1486                         withObject:button];
1487   BookmarkBarFolderController* folderController = [bar_ folderController];
1488   EXPECT_TRUE(folderController);
1489   NSArray* buttons = [folderController buttons];
1490   EXPECT_TRUE(buttons);
1491
1492   // Remember how many buttons are showing.
1493   int oldDisplayedButtons = [buttons count];
1494
1495   BookmarkButton* targetButton =
1496       [folderController buttonWithTitleEqualTo:@"2f1b"];
1497   ASSERT_TRUE(targetButton);
1498
1499   NSArray* urls = [NSArray arrayWithObjects: @"http://www.a.com/",
1500                    @"http://www.b.com/", nil];
1501   NSArray* titles = [NSArray arrayWithObjects: @"SiteA", @"SiteB", nil];
1502   [folderController addURLs:urls withTitles:titles at:[targetButton top]];
1503
1504   // There should two more buttons in the folder.
1505   int newDisplayedButtons = [buttons count];
1506   EXPECT_EQ(oldDisplayedButtons + 2, newDisplayedButtons);
1507   // Verify the model.
1508   const std::string expected("1b 2f:[ SiteA SiteB 2f1b 2f2f:[ 2f2f1b 2f2f2b "
1509                              "2f2f3b ] 2f3b ] 3b 4b ");
1510   actual = test::ModelStringFromNode(root);
1511   EXPECT_EQ(expected, actual);
1512
1513   // Check button spacing.
1514   [folderController validateMenuSpacing];
1515 }
1516
1517 TEST_F(BookmarkBarFolderControllerMenuTest, DropPositionIndicator) {
1518   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1519   const BookmarkNode* root = model->bookmark_bar_node();
1520   const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1521                                  "2f3b ] 3b 4b ");
1522   test::AddNodesFromModelString(model, root, model_string);
1523
1524   // Validate initial model.
1525   std::string actual = test::ModelStringFromNode(root);
1526   EXPECT_EQ(model_string, actual);
1527
1528   // Pop open the folder.
1529   BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"2f"];
1530   [[button target] performSelector:@selector(openBookmarkFolderFromButton:)
1531                         withObject:button];
1532   BookmarkBarFolderController* folder = [bar_ folderController];
1533   EXPECT_TRUE(folder);
1534
1535   // Test a series of points starting at the top of the folder.
1536   const CGFloat yOffset = 0.5 * bookmarks::kBookmarkVerticalPadding;
1537   BookmarkButton* targetButton = [folder buttonWithTitleEqualTo:@"2f1b"];
1538   ASSERT_TRUE(targetButton);
1539   NSPoint targetPoint = [targetButton top];
1540   CGFloat pos = [folder indicatorPosForDragToPoint:targetPoint];
1541   EXPECT_CGFLOAT_EQ(targetPoint.y + yOffset, pos);
1542   pos = [folder indicatorPosForDragToPoint:[targetButton bottom]];
1543   targetButton = [folder buttonWithTitleEqualTo:@"2f2f"];
1544   EXPECT_CGFLOAT_EQ([targetButton top].y + yOffset, pos);
1545   pos = [folder indicatorPosForDragToPoint:NSMakePoint(10,0)];
1546   targetButton = [folder buttonWithTitleEqualTo:@"2f3b"];
1547   EXPECT_CGFLOAT_EQ([targetButton bottom].y - yOffset, pos);
1548 }
1549
1550 @interface BookmarkBarControllerNoDelete : BookmarkBarController
1551 - (IBAction)deleteBookmark:(id)sender;
1552 @end
1553
1554 @implementation BookmarkBarControllerNoDelete
1555 - (IBAction)deleteBookmark:(id)sender {
1556   // NOP
1557 }
1558 @end
1559
1560 class BookmarkBarFolderControllerClosingTest : public
1561     BookmarkBarFolderControllerMenuTest {
1562  public:
1563   virtual void SetUp() {
1564     BookmarkBarFolderControllerMenuTest::SetUp();
1565     ASSERT_TRUE(browser());
1566
1567     bar_.reset([[BookmarkBarControllerNoDelete alloc]
1568                 initWithBrowser:browser()
1569                    initialWidth:NSWidth([parent_view_ frame])
1570                        delegate:nil
1571                  resizeDelegate:resizeDelegate_.get()]);
1572     InstallAndToggleBar(bar_.get());
1573   }
1574 };
1575
1576 TEST_F(BookmarkBarFolderControllerClosingTest, DeleteClosesFolder) {
1577   BookmarkModel* model = BookmarkModelFactory::GetForProfile(profile());
1578   const BookmarkNode* root = model->bookmark_bar_node();
1579   const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b ] "
1580                                  "2f3b ] 3b ");
1581   test::AddNodesFromModelString(model, root, model_string);
1582
1583   // Validate initial model.
1584   std::string actualModelString = test::ModelStringFromNode(root);
1585   EXPECT_EQ(model_string, actualModelString);
1586
1587   // Open the folder menu and submenu.
1588   BookmarkButton* target = [bar_ buttonWithTitleEqualTo:@"2f"];
1589   ASSERT_TRUE(target);
1590   [[target target] performSelector:@selector(openBookmarkFolderFromButton:)
1591                               withObject:target];
1592   BookmarkBarFolderController* folder = [bar_ folderController];
1593   EXPECT_TRUE(folder);
1594   BookmarkButton* subTarget = [folder buttonWithTitleEqualTo:@"2f2f"];
1595   ASSERT_TRUE(subTarget);
1596   [[subTarget target] performSelector:@selector(openBookmarkFolderFromButton:)
1597                            withObject:subTarget];
1598   BookmarkBarFolderController* subFolder = [folder folderController];
1599   EXPECT_TRUE(subFolder);
1600
1601   // Delete the folder node and verify the window closed down by looking
1602   // for its controller again.
1603   DeleteBookmark([folder parentButton], profile());
1604   EXPECT_FALSE([folder folderController]);
1605 }
1606
1607 // TODO(jrg): draggingEntered: and draggingExited: trigger timers so
1608 // they are hard to test.  Factor out "fire timers" into routines
1609 // which can be overridden to fire immediately to make behavior
1610 // confirmable.
1611 // There is a similar problem with mouseEnteredButton: and
1612 // mouseExitedButton:.