• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import <Cocoa/Cocoa.h>
6
7#include "base/basictypes.h"
8#include "base/command_line.h"
9#include "base/memory/scoped_nsobject.h"
10#include "base/string16.h"
11#include "base/string_util.h"
12#include "base/sys_string_conversions.h"
13#include "base/utf_string_conversions.h"
14#include "chrome/browser/bookmarks/bookmark_model.h"
15#import "chrome/browser/ui/cocoa/animation_utils.h"
16#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_constants.h"
17#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
18#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_window.h"
19#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_unittest_helper.h"
20#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_view.h"
21#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button.h"
22#import "chrome/browser/ui/cocoa/bookmarks/bookmark_button_cell.h"
23#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu.h"
24#include "chrome/browser/ui/cocoa/browser_test_helper.h"
25#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
26#include "chrome/browser/ui/cocoa/test_event_utils.h"
27#import "chrome/browser/ui/cocoa/view_resizer_pong.h"
28#include "chrome/test/model_test_utils.h"
29#include "testing/gtest/include/gtest/gtest.h"
30#import "testing/gtest_mac.h"
31#include "testing/platform_test.h"
32#import "third_party/ocmock/OCMock/OCMock.h"
33#include "ui/base/theme_provider.h"
34
35// Unit tests don't need time-consuming asynchronous animations.
36@interface BookmarkBarControllerTestable : BookmarkBarController {
37}
38@end
39@implementation BookmarkBarControllerTestable
40- (BOOL)animationEnabled {
41  return NO;
42}
43@end
44
45// Just like a BookmarkBarController but openURL: is stubbed out.
46@interface BookmarkBarControllerNoOpen : BookmarkBarControllerTestable {
47 @public
48  std::vector<GURL> urls_;
49  std::vector<WindowOpenDisposition> dispositions_;
50}
51@end
52
53@implementation BookmarkBarControllerNoOpen
54- (void)openURL:(GURL)url disposition:(WindowOpenDisposition)disposition {
55  urls_.push_back(url);
56  dispositions_.push_back(disposition);
57}
58- (void)clear {
59  urls_.clear();
60  dispositions_.clear();
61}
62@end
63
64
65// NSCell that is pre-provided with a desired size that becomes the
66// return value for -(NSSize)cellSize:.
67@interface CellWithDesiredSize : NSCell {
68 @private
69  NSSize cellSize_;
70}
71@property (nonatomic, readonly) NSSize cellSize;
72@end
73
74@implementation CellWithDesiredSize
75
76@synthesize cellSize = cellSize_;
77
78- (id)initTextCell:(NSString*)string desiredSize:(NSSize)size {
79  if ((self = [super initTextCell:string])) {
80    cellSize_ = size;
81  }
82  return self;
83}
84
85@end
86
87// Remember the number of times we've gotten a frameDidChange notification.
88@interface BookmarkBarControllerTogglePong : BookmarkBarControllerNoOpen {
89 @private
90  int toggles_;
91}
92@property (nonatomic, readonly) int toggles;
93@end
94
95@implementation BookmarkBarControllerTogglePong
96
97@synthesize toggles = toggles_;
98
99- (void)frameDidChange {
100  toggles_++;
101}
102
103@end
104
105// Remembers if a notification callback was called.
106@interface BookmarkBarControllerNotificationPong : BookmarkBarControllerNoOpen {
107  BOOL windowWillCloseReceived_;
108  BOOL windowDidResignKeyReceived_;
109}
110@property (nonatomic, readonly) BOOL windowWillCloseReceived;
111@property (nonatomic, readonly) BOOL windowDidResignKeyReceived;
112@end
113
114@implementation BookmarkBarControllerNotificationPong
115@synthesize windowWillCloseReceived = windowWillCloseReceived_;
116@synthesize windowDidResignKeyReceived = windowDidResignKeyReceived_;
117
118// Override NSNotificationCenter callback.
119- (void)parentWindowWillClose:(NSNotification*)notification {
120  windowWillCloseReceived_ = YES;
121}
122
123// NSNotificationCenter callback.
124- (void)parentWindowDidResignKey:(NSNotification*)notification {
125  windowDidResignKeyReceived_ = YES;
126}
127@end
128
129// Remembers if and what kind of openAll was performed.
130@interface BookmarkBarControllerOpenAllPong : BookmarkBarControllerNoOpen {
131  WindowOpenDisposition dispositionDetected_;
132}
133@property (nonatomic) WindowOpenDisposition dispositionDetected;
134@end
135
136@implementation BookmarkBarControllerOpenAllPong
137@synthesize dispositionDetected = dispositionDetected_;
138
139// Intercede for the openAll:disposition: method.
140- (void)openAll:(const BookmarkNode*)node
141    disposition:(WindowOpenDisposition)disposition {
142  [self setDispositionDetected:disposition];
143}
144
145@end
146
147// Just like a BookmarkBarController but intercedes when providing
148// pasteboard drag data.
149@interface BookmarkBarControllerDragData : BookmarkBarControllerTestable {
150  const BookmarkNode* dragDataNode_;  // Weak
151}
152- (void)setDragDataNode:(const BookmarkNode*)node;
153@end
154
155@implementation BookmarkBarControllerDragData
156
157- (id)initWithBrowser:(Browser*)browser
158         initialWidth:(CGFloat)initialWidth
159             delegate:(id<BookmarkBarControllerDelegate>)delegate
160       resizeDelegate:(id<ViewResizer>)resizeDelegate {
161  if ((self = [super initWithBrowser:browser
162                        initialWidth:initialWidth
163                            delegate:delegate
164                      resizeDelegate:resizeDelegate])) {
165    dragDataNode_ = NULL;
166  }
167  return self;
168}
169
170- (void)setDragDataNode:(const BookmarkNode*)node {
171  dragDataNode_ = node;
172}
173
174- (std::vector<const BookmarkNode*>)retrieveBookmarkNodeData {
175  std::vector<const BookmarkNode*> dragDataNodes;
176  if(dragDataNode_) {
177    dragDataNodes.push_back(dragDataNode_);
178  }
179  return dragDataNodes;
180}
181
182@end
183
184
185class FakeTheme : public ui::ThemeProvider {
186 public:
187  FakeTheme(NSColor* color) : color_(color) { }
188  scoped_nsobject<NSColor> color_;
189
190  virtual void Init(Profile* profile) { }
191  virtual SkBitmap* GetBitmapNamed(int id) const { return nil; }
192  virtual SkColor GetColor(int id) const { return SkColor(); }
193  virtual bool GetDisplayProperty(int id, int* result) const { return false; }
194  virtual bool ShouldUseNativeFrame() const { return false; }
195  virtual bool HasCustomImage(int id) const { return false; }
196  virtual RefCountedMemory* GetRawData(int id) const { return NULL; }
197  virtual NSImage* GetNSImageNamed(int id, bool allow_default) const {
198    return nil;
199  }
200  virtual NSColor* GetNSImageColorNamed(int id, bool allow_default) const {
201    return nil;
202  }
203  virtual NSColor* GetNSColor(int id, bool allow_default) const {
204    return color_.get();
205  }
206  virtual NSColor* GetNSColorTint(int id, bool allow_default) const {
207    return nil;
208  }
209  virtual NSGradient* GetNSGradient(int id) const {
210    return nil;
211  }
212};
213
214
215@interface FakeDragInfo : NSObject {
216 @public
217  NSPoint dropLocation_;
218  NSDragOperation sourceMask_;
219}
220@property (nonatomic, assign) NSPoint dropLocation;
221- (void)setDraggingSourceOperationMask:(NSDragOperation)mask;
222@end
223
224@implementation FakeDragInfo
225
226@synthesize dropLocation = dropLocation_;
227
228- (id)init {
229  if ((self = [super init])) {
230    dropLocation_ = NSZeroPoint;
231    sourceMask_ = NSDragOperationMove;
232  }
233  return self;
234}
235
236// NSDraggingInfo protocol functions.
237
238- (id)draggingPasteboard {
239  return self;
240}
241
242- (id)draggingSource {
243  return self;
244}
245
246- (NSDragOperation)draggingSourceOperationMask {
247  return sourceMask_;
248}
249
250- (NSPoint)draggingLocation {
251  return dropLocation_;
252}
253
254// Other functions.
255
256- (void)setDraggingSourceOperationMask:(NSDragOperation)mask {
257  sourceMask_ = mask;
258}
259
260@end
261
262
263namespace {
264
265class BookmarkBarControllerTestBase : public CocoaTest {
266 public:
267  BrowserTestHelper helper_;
268  scoped_nsobject<NSView> parent_view_;
269  scoped_nsobject<ViewResizerPong> resizeDelegate_;
270
271  BookmarkBarControllerTestBase() {
272    FilePath extension_dir;
273    helper_.profile()->CreateExtensionService(CommandLine::ForCurrentProcess(),
274                                              extension_dir, false);
275    resizeDelegate_.reset([[ViewResizerPong alloc] init]);
276    NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
277    parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
278    [parent_view_ setHidden:YES];
279  }
280
281  void InstallAndToggleBar(BookmarkBarController* bar) {
282    // Force loading of the nib.
283    [bar view];
284    // Awkwardness to look like we've been installed.
285    for (NSView* subView in [parent_view_ subviews])
286      [subView removeFromSuperview];
287    [parent_view_ addSubview:[bar view]];
288    NSRect frame = [[[bar view] superview] frame];
289    frame.origin.y = 100;
290    [[[bar view] superview] setFrame:frame];
291
292    // Make sure it's on in a window so viewDidMoveToWindow is called
293    NSView* contentView = [test_window() contentView];
294    if (![parent_view_ isDescendantOf:contentView])
295      [contentView addSubview:parent_view_];
296
297    // Make sure it's open so certain things aren't no-ops.
298    [bar updateAndShowNormalBar:YES
299                showDetachedBar:NO
300                  withAnimation:NO];
301  }
302};
303
304class BookmarkBarControllerTest : public BookmarkBarControllerTestBase {
305 public:
306  scoped_nsobject<BookmarkMenu> menu_;
307  scoped_nsobject<NSMenuItem> menu_item_;
308  scoped_nsobject<NSButtonCell> cell_;
309  scoped_nsobject<BookmarkBarControllerNoOpen> bar_;
310
311  BookmarkBarControllerTest() {
312    bar_.reset(
313      [[BookmarkBarControllerNoOpen alloc]
314          initWithBrowser:helper_.browser()
315             initialWidth:NSWidth([parent_view_ frame])
316                 delegate:nil
317           resizeDelegate:resizeDelegate_.get()]);
318
319    InstallAndToggleBar(bar_.get());
320
321    // Create a menu/item to act like a sender
322    menu_.reset([[BookmarkMenu alloc] initWithTitle:@"I_dont_care"]);
323    menu_item_.reset([[NSMenuItem alloc]
324                       initWithTitle:@"still_dont_care"
325                              action:NULL
326                       keyEquivalent:@""]);
327    cell_.reset([[NSButtonCell alloc] init]);
328    [menu_item_ setMenu:menu_.get()];
329    [menu_ setDelegate:cell_.get()];
330  }
331
332  // Return a menu item that points to the given URL.
333  NSMenuItem* ItemForBookmarkBarMenu(GURL& gurl) {
334    BookmarkModel* model = helper_.profile()->GetBookmarkModel();
335    const BookmarkNode* parent = model->GetBookmarkBarNode();
336    const BookmarkNode* node = model->AddURL(parent, parent->child_count(),
337                                             ASCIIToUTF16("A title"), gurl);
338    [menu_ setRepresentedObject:[NSNumber numberWithLongLong:node->id()]];
339    return menu_item_;
340  }
341
342  // Does NOT take ownership of node.
343  NSMenuItem* ItemForBookmarkBarMenu(const BookmarkNode* node) {
344    [menu_ setRepresentedObject:[NSNumber numberWithLongLong:node->id()]];
345    return menu_item_;
346  }
347
348  BookmarkBarControllerNoOpen* noOpenBar() {
349    return (BookmarkBarControllerNoOpen*)bar_.get();
350  }
351};
352
353TEST_F(BookmarkBarControllerTest, ShowWhenShowBookmarkBarTrue) {
354  [bar_ updateAndShowNormalBar:YES
355               showDetachedBar:NO
356                 withAnimation:NO];
357  EXPECT_TRUE([bar_ isInState:bookmarks::kShowingState]);
358  EXPECT_FALSE([bar_ isInState:bookmarks::kDetachedState]);
359  EXPECT_TRUE([bar_ isVisible]);
360  EXPECT_FALSE([bar_ isAnimationRunning]);
361  EXPECT_FALSE([[bar_ view] isHidden]);
362  EXPECT_GT([resizeDelegate_ height], 0);
363  EXPECT_GT([[bar_ view] frame].size.height, 0);
364}
365
366TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarFalse) {
367  [bar_ updateAndShowNormalBar:NO
368               showDetachedBar:NO
369                 withAnimation:NO];
370  EXPECT_FALSE([bar_ isInState:bookmarks::kShowingState]);
371  EXPECT_FALSE([bar_ isInState:bookmarks::kDetachedState]);
372  EXPECT_FALSE([bar_ isVisible]);
373  EXPECT_FALSE([bar_ isAnimationRunning]);
374  EXPECT_TRUE([[bar_ view] isHidden]);
375  EXPECT_EQ(0, [resizeDelegate_ height]);
376  EXPECT_EQ(0, [[bar_ view] frame].size.height);
377}
378
379TEST_F(BookmarkBarControllerTest, HideWhenShowBookmarkBarTrueButDisabled) {
380  [bar_ setBookmarkBarEnabled:NO];
381  [bar_ updateAndShowNormalBar:YES
382               showDetachedBar:NO
383                 withAnimation:NO];
384  EXPECT_TRUE([bar_ isInState:bookmarks::kShowingState]);
385  EXPECT_FALSE([bar_ isInState:bookmarks::kDetachedState]);
386  EXPECT_FALSE([bar_ isVisible]);
387  EXPECT_FALSE([bar_ isAnimationRunning]);
388  EXPECT_TRUE([[bar_ view] isHidden]);
389  EXPECT_EQ(0, [resizeDelegate_ height]);
390  EXPECT_EQ(0, [[bar_ view] frame].size.height);
391}
392
393TEST_F(BookmarkBarControllerTest, ShowOnNewTabPage) {
394  [bar_ updateAndShowNormalBar:NO
395               showDetachedBar:YES
396                 withAnimation:NO];
397  EXPECT_FALSE([bar_ isInState:bookmarks::kShowingState]);
398  EXPECT_TRUE([bar_ isInState:bookmarks::kDetachedState]);
399  EXPECT_TRUE([bar_ isVisible]);
400  EXPECT_FALSE([bar_ isAnimationRunning]);
401  EXPECT_FALSE([[bar_ view] isHidden]);
402  EXPECT_GT([resizeDelegate_ height], 0);
403  EXPECT_GT([[bar_ view] frame].size.height, 0);
404
405  // Make sure no buttons fall off the bar, either now or when resized
406  // bigger or smaller.
407  CGFloat sizes[] = { 300.0, -100.0, 200.0, -420.0 };
408  CGFloat previousX = 0.0;
409  for (unsigned x = 0; x < arraysize(sizes); x++) {
410    // Confirm the buttons moved from the last check (which may be
411    // init but that's fine).
412    CGFloat newX = [[bar_ offTheSideButton] frame].origin.x;
413    EXPECT_NE(previousX, newX);
414    previousX = newX;
415
416    // Confirm the buttons have a reasonable bounds. Recall that |-frame|
417    // returns rectangles in the superview's coordinates.
418    NSRect buttonViewFrame =
419        [[bar_ buttonView] convertRect:[[bar_ buttonView] frame]
420                              fromView:[[bar_ buttonView] superview]];
421    EXPECT_EQ([bar_ buttonView], [[bar_ offTheSideButton] superview]);
422    EXPECT_TRUE(NSContainsRect(buttonViewFrame,
423                               [[bar_ offTheSideButton] frame]));
424    EXPECT_EQ([bar_ buttonView], [[bar_ otherBookmarksButton] superview]);
425    EXPECT_TRUE(NSContainsRect(buttonViewFrame,
426                               [[bar_ otherBookmarksButton] frame]));
427
428    // Now move them implicitly.
429    // We confirm FrameChangeNotification works in the next unit test;
430    // we simply assume it works here to resize or reposition the
431    // buttons above.
432    NSRect frame = [[bar_ view] frame];
433    frame.size.width += sizes[x];
434    [[bar_ view] setFrame:frame];
435  }
436}
437
438// Test whether |-updateAndShowNormalBar:...| sets states as we expect. Make
439// sure things don't crash.
440TEST_F(BookmarkBarControllerTest, StateChanges) {
441  // First, go in one-at-a-time cycle.
442  [bar_ updateAndShowNormalBar:NO
443               showDetachedBar:NO
444                 withAnimation:NO];
445  EXPECT_EQ(bookmarks::kHiddenState, [bar_ visualState]);
446  EXPECT_FALSE([bar_ isVisible]);
447  EXPECT_FALSE([bar_ isAnimationRunning]);
448  [bar_ updateAndShowNormalBar:YES
449               showDetachedBar:NO
450                 withAnimation:NO];
451  EXPECT_EQ(bookmarks::kShowingState, [bar_ visualState]);
452  EXPECT_TRUE([bar_ isVisible]);
453  EXPECT_FALSE([bar_ isAnimationRunning]);
454  [bar_ updateAndShowNormalBar:YES
455               showDetachedBar:YES
456                 withAnimation:NO];
457  EXPECT_EQ(bookmarks::kShowingState, [bar_ visualState]);
458  EXPECT_TRUE([bar_ isVisible]);
459  EXPECT_FALSE([bar_ isAnimationRunning]);
460  [bar_ updateAndShowNormalBar:NO
461               showDetachedBar:YES
462                 withAnimation:NO];
463  EXPECT_EQ(bookmarks::kDetachedState, [bar_ visualState]);
464  EXPECT_TRUE([bar_ isVisible]);
465  EXPECT_FALSE([bar_ isAnimationRunning]);
466
467  // Now try some "jumps".
468  for (int i = 0; i < 2; i++) {
469    [bar_ updateAndShowNormalBar:NO
470                 showDetachedBar:NO
471                   withAnimation:NO];
472    EXPECT_EQ(bookmarks::kHiddenState, [bar_ visualState]);
473    EXPECT_FALSE([bar_ isVisible]);
474    EXPECT_FALSE([bar_ isAnimationRunning]);
475    [bar_ updateAndShowNormalBar:YES
476                 showDetachedBar:YES
477                   withAnimation:NO];
478    EXPECT_EQ(bookmarks::kShowingState, [bar_ visualState]);
479    EXPECT_TRUE([bar_ isVisible]);
480    EXPECT_FALSE([bar_ isAnimationRunning]);
481  }
482
483  // Now try some "jumps".
484  for (int i = 0; i < 2; i++) {
485    [bar_ updateAndShowNormalBar:YES
486                 showDetachedBar:NO
487                   withAnimation:NO];
488    EXPECT_EQ(bookmarks::kShowingState, [bar_ visualState]);
489    EXPECT_TRUE([bar_ isVisible]);
490    EXPECT_FALSE([bar_ isAnimationRunning]);
491    [bar_ updateAndShowNormalBar:NO
492                 showDetachedBar:YES
493                   withAnimation:NO];
494    EXPECT_EQ(bookmarks::kDetachedState, [bar_ visualState]);
495    EXPECT_TRUE([bar_ isVisible]);
496    EXPECT_FALSE([bar_ isAnimationRunning]);
497  }
498}
499
500// Make sure we're watching for frame change notifications.
501TEST_F(BookmarkBarControllerTest, FrameChangeNotification) {
502  scoped_nsobject<BookmarkBarControllerTogglePong> bar;
503  bar.reset(
504    [[BookmarkBarControllerTogglePong alloc]
505          initWithBrowser:helper_.browser()
506             initialWidth:100  // arbitrary
507                 delegate:nil
508           resizeDelegate:resizeDelegate_.get()]);
509  InstallAndToggleBar(bar.get());
510
511  // Send a frame did change notification for the pong's view.
512  [[NSNotificationCenter defaultCenter]
513    postNotificationName:NSViewFrameDidChangeNotification
514                  object:[bar view]];
515
516  EXPECT_GT([bar toggles], 0);
517}
518
519// Confirm our "no items" container goes away when we add the 1st
520// bookmark, and comes back when we delete the bookmark.
521TEST_F(BookmarkBarControllerTest, NoItemContainerGoesAway) {
522  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
523  const BookmarkNode* bar = model->GetBookmarkBarNode();
524
525  [bar_ loaded:model];
526  BookmarkBarView* view = [bar_ buttonView];
527  DCHECK(view);
528  NSView* noItemContainer = [view noItemContainer];
529  DCHECK(noItemContainer);
530
531  EXPECT_FALSE([noItemContainer isHidden]);
532  const BookmarkNode* node = model->AddURL(bar, bar->child_count(),
533                                           ASCIIToUTF16("title"),
534                                           GURL("http://www.google.com"));
535  EXPECT_TRUE([noItemContainer isHidden]);
536  model->Remove(bar, bar->GetIndexOf(node));
537  EXPECT_FALSE([noItemContainer isHidden]);
538
539  // Now try it using a bookmark from the Other Bookmarks.
540  const BookmarkNode* otherBookmarks = model->other_node();
541  node = model->AddURL(otherBookmarks, otherBookmarks->child_count(),
542                       ASCIIToUTF16("TheOther"),
543                       GURL("http://www.other.com"));
544  EXPECT_FALSE([noItemContainer isHidden]);
545  // Move it from Other Bookmarks to the bar.
546  model->Move(node, bar, 0);
547  EXPECT_TRUE([noItemContainer isHidden]);
548  // Move it back to Other Bookmarks from the bar.
549  model->Move(node, otherBookmarks, 0);
550  EXPECT_FALSE([noItemContainer isHidden]);
551}
552
553// Confirm off the side button only enabled when reasonable.
554TEST_F(BookmarkBarControllerTest, OffTheSideButtonHidden) {
555  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
556  [bar_ setIgnoreAnimations:YES];
557
558  [bar_ loaded:model];
559  EXPECT_TRUE([bar_ offTheSideButtonIsHidden]);
560
561  for (int i = 0; i < 2; i++) {
562    model->SetURLStarred(GURL("http://www.foo.com"), ASCIIToUTF16("small"),
563                         true);
564    EXPECT_TRUE([bar_ offTheSideButtonIsHidden]);
565  }
566
567  const BookmarkNode* parent = model->GetBookmarkBarNode();
568  for (int i = 0; i < 20; i++) {
569    model->AddURL(parent, parent->child_count(),
570                  ASCIIToUTF16("super duper wide title"),
571                  GURL("http://superfriends.hall-of-justice.edu"));
572  }
573  EXPECT_FALSE([bar_ offTheSideButtonIsHidden]);
574
575  // Open the "off the side" and start deleting nodes.  Make sure
576  // deletion of the last node in "off the side" causes the folder to
577  // close.
578  EXPECT_FALSE([bar_ offTheSideButtonIsHidden]);
579  NSButton* offTheSideButton = [bar_ offTheSideButton];
580  // Open "off the side" menu.
581  [bar_ openOffTheSideFolderFromButton:offTheSideButton];
582  BookmarkBarFolderController* bbfc = [bar_ folderController];
583  EXPECT_TRUE(bbfc);
584  [bbfc setIgnoreAnimations:YES];
585  while (parent->child_count()) {
586    // We've completed the job so we're done.
587    if ([bar_ offTheSideButtonIsHidden])
588      break;
589    // Delete the last button.
590    model->Remove(parent, parent->child_count()-1);
591    // If last one make sure the menu is closed and the button is hidden.
592    // Else make sure menu stays open.
593    if ([bar_ offTheSideButtonIsHidden]) {
594      EXPECT_FALSE([bar_ folderController]);
595    } else {
596      EXPECT_TRUE([bar_ folderController]);
597    }
598  }
599}
600
601// http://crbug.com/46175 is a crash when deleting bookmarks from the
602// off-the-side menu while it is open.  This test tries to bang hard
603// in this area to reproduce the crash.
604TEST_F(BookmarkBarControllerTest, DeleteFromOffTheSideWhileItIsOpen) {
605  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
606  [bar_ setIgnoreAnimations:YES];
607  [bar_ loaded:model];
608
609  // Add a lot of bookmarks (per the bug).
610  const BookmarkNode* parent = model->GetBookmarkBarNode();
611  for (int i = 0; i < 100; i++) {
612    std::ostringstream title;
613    title << "super duper wide title " << i;
614    model->AddURL(parent, parent->child_count(), ASCIIToUTF16(title.str()),
615                  GURL("http://superfriends.hall-of-justice.edu"));
616  }
617  EXPECT_FALSE([bar_ offTheSideButtonIsHidden]);
618
619  // Open "off the side" menu.
620  NSButton* offTheSideButton = [bar_ offTheSideButton];
621  [bar_ openOffTheSideFolderFromButton:offTheSideButton];
622  BookmarkBarFolderController* bbfc = [bar_ folderController];
623  EXPECT_TRUE(bbfc);
624  [bbfc setIgnoreAnimations:YES];
625
626  // Start deleting items; try and delete randomish ones in case it
627  // makes a difference.
628  int indices[] = { 2, 4, 5, 1, 7, 9, 2, 0, 10, 9 };
629  while (parent->child_count()) {
630    for (unsigned int i = 0; i < arraysize(indices); i++) {
631      if (indices[i] < parent->child_count()) {
632        // First we mouse-enter the button to make things harder.
633        NSArray* buttons = [bbfc buttons];
634        for (BookmarkButton* button in buttons) {
635          if ([button bookmarkNode] == parent->GetChild(indices[i])) {
636            [bbfc mouseEnteredButton:button event:nil];
637            break;
638          }
639        }
640        // Then we remove the node.  This triggers the button to get
641        // deleted.
642        model->Remove(parent, indices[i]);
643        // Force visual update which is otherwise delayed.
644        [[bbfc window] displayIfNeeded];
645      }
646    }
647  }
648}
649
650// Test whether |-dragShouldLockBarVisibility| returns NO iff the bar is
651// detached.
652TEST_F(BookmarkBarControllerTest, TestDragShouldLockBarVisibility) {
653  [bar_ updateAndShowNormalBar:NO
654               showDetachedBar:NO
655                 withAnimation:NO];
656  EXPECT_TRUE([bar_ dragShouldLockBarVisibility]);
657
658  [bar_ updateAndShowNormalBar:YES
659               showDetachedBar:NO
660                 withAnimation:NO];
661  EXPECT_TRUE([bar_ dragShouldLockBarVisibility]);
662
663  [bar_ updateAndShowNormalBar:YES
664               showDetachedBar:YES
665                 withAnimation:NO];
666  EXPECT_TRUE([bar_ dragShouldLockBarVisibility]);
667
668  [bar_ updateAndShowNormalBar:NO
669               showDetachedBar:YES
670                 withAnimation:NO];
671  EXPECT_FALSE([bar_ dragShouldLockBarVisibility]);
672}
673
674TEST_F(BookmarkBarControllerTest, TagMap) {
675  int64 ids[] = { 1, 3, 4, 40, 400, 4000, 800000000, 2, 123456789 };
676  std::vector<int32> tags;
677
678  // Generate some tags
679  for (unsigned int i = 0; i < arraysize(ids); i++) {
680    tags.push_back([bar_ menuTagFromNodeId:ids[i]]);
681  }
682
683  // Confirm reverse mapping.
684  for (unsigned int i = 0; i < arraysize(ids); i++) {
685    EXPECT_EQ(ids[i], [bar_ nodeIdFromMenuTag:tags[i]]);
686  }
687
688  // Confirm uniqueness.
689  std::sort(tags.begin(), tags.end());
690  for (unsigned int i=0; i<(tags.size()-1); i++) {
691    EXPECT_NE(tags[i], tags[i+1]);
692  }
693}
694
695TEST_F(BookmarkBarControllerTest, MenuForFolderNode) {
696  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
697
698  // First make sure something (e.g. "(empty)" string) is always present.
699  NSMenu* menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()];
700  EXPECT_GT([menu numberOfItems], 0);
701
702  // Test two bookmarks.
703  GURL gurl("http://www.foo.com");
704  model->SetURLStarred(gurl, ASCIIToUTF16("small"), true);
705  model->SetURLStarred(GURL("http://www.cnn.com"), ASCIIToUTF16("bigger title"),
706                       true);
707  menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()];
708  EXPECT_EQ([menu numberOfItems], 2);
709  NSMenuItem *item = [menu itemWithTitle:@"bigger title"];
710  EXPECT_TRUE(item);
711  item = [menu itemWithTitle:@"small"];
712  EXPECT_TRUE(item);
713  if (item) {
714    int64 tag = [bar_ nodeIdFromMenuTag:[item tag]];
715    const BookmarkNode* node = model->GetNodeByID(tag);
716    EXPECT_TRUE(node);
717    EXPECT_EQ(gurl, node->GetURL());
718  }
719
720  // Test with an actual folder as well
721  const BookmarkNode* parent = model->GetBookmarkBarNode();
722  const BookmarkNode* folder = model->AddFolder(parent,
723                                                parent->child_count(),
724                                                ASCIIToUTF16("folder"));
725  model->AddURL(folder, folder->child_count(),
726                ASCIIToUTF16("f1"), GURL("http://framma-lamma.com"));
727  model->AddURL(folder, folder->child_count(),
728                ASCIIToUTF16("f2"), GURL("http://framma-lamma-ding-dong.com"));
729  menu = [bar_ menuForFolderNode:model->GetBookmarkBarNode()];
730  EXPECT_EQ([menu numberOfItems], 3);
731
732  item = [menu itemWithTitle:@"folder"];
733  EXPECT_TRUE(item);
734  EXPECT_TRUE([item hasSubmenu]);
735  NSMenu *submenu = [item submenu];
736  EXPECT_TRUE(submenu);
737  EXPECT_EQ(2, [submenu numberOfItems]);
738  EXPECT_TRUE([submenu itemWithTitle:@"f1"]);
739  EXPECT_TRUE([submenu itemWithTitle:@"f2"]);
740}
741
742// Confirm openBookmark: forwards the request to the controller's delegate
743TEST_F(BookmarkBarControllerTest, OpenBookmark) {
744  GURL gurl("http://walla.walla.ding.dong.com");
745  scoped_ptr<BookmarkNode> node(new BookmarkNode(gurl));
746
747  scoped_nsobject<BookmarkButtonCell> cell([[BookmarkButtonCell alloc] init]);
748  [cell setBookmarkNode:node.get()];
749  scoped_nsobject<BookmarkButton> button([[BookmarkButton alloc] init]);
750  [button setCell:cell.get()];
751  [cell setRepresentedObject:[NSValue valueWithPointer:node.get()]];
752
753  [bar_ openBookmark:button];
754  EXPECT_EQ(noOpenBar()->urls_[0], node->GetURL());
755  EXPECT_EQ(noOpenBar()->dispositions_[0], CURRENT_TAB);
756}
757
758// Confirm opening of bookmarks works from the menus (different
759// dispositions than clicking on the button).
760TEST_F(BookmarkBarControllerTest, OpenBookmarkFromMenus) {
761  const char* urls[] = { "http://walla.walla.ding.dong.com",
762                         "http://i_dont_know.com",
763                         "http://cee.enn.enn.dot.com" };
764  SEL selectors[] = { @selector(openBookmarkInNewForegroundTab:),
765                      @selector(openBookmarkInNewWindow:),
766                      @selector(openBookmarkInIncognitoWindow:) };
767  WindowOpenDisposition dispositions[] = { NEW_FOREGROUND_TAB,
768                                           NEW_WINDOW,
769                                           OFF_THE_RECORD };
770  for (unsigned int i = 0; i < arraysize(dispositions); i++) {
771    GURL gurl(urls[i]);
772    [bar_ performSelector:selectors[i]
773               withObject:ItemForBookmarkBarMenu(gurl)];
774    EXPECT_EQ(noOpenBar()->urls_[0], gurl);
775    EXPECT_EQ(noOpenBar()->dispositions_[0], dispositions[i]);
776    [bar_ clear];
777  }
778}
779
780TEST_F(BookmarkBarControllerTest, TestAddRemoveAndClear) {
781  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
782  NSView* buttonView = [bar_ buttonView];
783  EXPECT_EQ(0U, [[bar_ buttons] count]);
784  unsigned int initial_subview_count = [[buttonView subviews] count];
785
786  // Make sure a redundant call doesn't choke
787  [bar_ clearBookmarkBar];
788  EXPECT_EQ(0U, [[bar_ buttons] count]);
789  EXPECT_EQ(initial_subview_count, [[buttonView subviews] count]);
790
791  GURL gurl1("http://superfriends.hall-of-justice.edu");
792  // Short titles increase the chances of this test succeeding if the view is
793  // narrow.
794  // TODO(viettrungluu): make the test independent of window/view size, font
795  // metrics, button size and spacing, and everything else.
796  string16 title1(ASCIIToUTF16("x"));
797  model->SetURLStarred(gurl1, title1, true);
798  EXPECT_EQ(1U, [[bar_ buttons] count]);
799  EXPECT_EQ(1+initial_subview_count, [[buttonView subviews] count]);
800
801  GURL gurl2("http://legion-of-doom.gov");
802  string16 title2(ASCIIToUTF16("y"));
803  model->SetURLStarred(gurl2, title2, true);
804  EXPECT_EQ(2U, [[bar_ buttons] count]);
805  EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]);
806
807  for (int i = 0; i < 3; i++) {
808    // is_starred=false --> remove the bookmark
809    model->SetURLStarred(gurl2, title2, false);
810    EXPECT_EQ(1U, [[bar_ buttons] count]);
811    EXPECT_EQ(1+initial_subview_count, [[buttonView subviews] count]);
812
813    // and bring it back
814    model->SetURLStarred(gurl2, title2, true);
815    EXPECT_EQ(2U, [[bar_ buttons] count]);
816    EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]);
817  }
818
819  [bar_ clearBookmarkBar];
820  EXPECT_EQ(0U, [[bar_ buttons] count]);
821  EXPECT_EQ(initial_subview_count, [[buttonView subviews] count]);
822
823  // Explicit test of loaded: since this is a convenient spot
824  [bar_ loaded:model];
825  EXPECT_EQ(2U, [[bar_ buttons] count]);
826  EXPECT_EQ(2+initial_subview_count, [[buttonView subviews] count]);
827}
828
829// Make sure we don't create too many buttons; we only really need
830// ones that will be visible.
831TEST_F(BookmarkBarControllerTest, TestButtonLimits) {
832  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
833  EXPECT_EQ(0U, [[bar_ buttons] count]);
834  // Add one; make sure we see it.
835  const BookmarkNode* parent = model->GetBookmarkBarNode();
836  model->AddURL(parent, parent->child_count(),
837                ASCIIToUTF16("title"), GURL("http://www.google.com"));
838  EXPECT_EQ(1U, [[bar_ buttons] count]);
839
840  // Add 30 which we expect to be 'too many'.  Make sure we don't see
841  // 30 buttons.
842  model->Remove(parent, 0);
843  EXPECT_EQ(0U, [[bar_ buttons] count]);
844  for (int i=0; i<30; i++) {
845    model->AddURL(parent, parent->child_count(),
846                  ASCIIToUTF16("title"), GURL("http://www.google.com"));
847  }
848  int count = [[bar_ buttons] count];
849  EXPECT_LT(count, 30L);
850
851  // Add 10 more (to the front of the list so the on-screen buttons
852  // would change) and make sure the count stays the same.
853  for (int i=0; i<10; i++) {
854    model->AddURL(parent, 0,  /* index is 0, so front, not end */
855                  ASCIIToUTF16("title"), GURL("http://www.google.com"));
856  }
857
858  // Finally, grow the view and make sure the button count goes up.
859  NSRect frame = [[bar_ view] frame];
860  frame.size.width += 600;
861  [[bar_ view] setFrame:frame];
862  int finalcount = [[bar_ buttons] count];
863  EXPECT_GT(finalcount, count);
864}
865
866// Make sure that each button we add marches to the right and does not
867// overlap with the previous one.
868TEST_F(BookmarkBarControllerTest, TestButtonMarch) {
869  scoped_nsobject<NSMutableArray> cells([[NSMutableArray alloc] init]);
870
871  CGFloat widths[] = { 10, 10, 100, 10, 500, 500, 80000, 60000, 1, 345 };
872  for (unsigned int i = 0; i < arraysize(widths); i++) {
873    NSCell* cell = [[CellWithDesiredSize alloc]
874                     initTextCell:@"foo"
875                      desiredSize:NSMakeSize(widths[i], 30)];
876    [cells addObject:cell];
877    [cell release];
878  }
879
880  int x_offset = 0;
881  CGFloat x_end = x_offset;  // end of the previous button
882  for (unsigned int i = 0; i < arraysize(widths); i++) {
883    NSRect r = [bar_ frameForBookmarkButtonFromCell:[cells objectAtIndex:i]
884                                            xOffset:&x_offset];
885    EXPECT_GE(r.origin.x, x_end);
886    x_end = NSMaxX(r);
887  }
888}
889
890TEST_F(BookmarkBarControllerTest, CheckForGrowth) {
891  WithNoAnimation at_all; // Turn off Cocoa auto animation in this scope.
892  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
893  GURL gurl1("http://www.google.com");
894  string16 title1(ASCIIToUTF16("x"));
895  model->SetURLStarred(gurl1, title1, true);
896
897  GURL gurl2("http://www.google.com/blah");
898  string16 title2(ASCIIToUTF16("y"));
899  model->SetURLStarred(gurl2, title2, true);
900
901  EXPECT_EQ(2U, [[bar_ buttons] count]);
902  CGFloat width_1 = [[[bar_ buttons] objectAtIndex:0] frame].size.width;
903  CGFloat x_2 = [[[bar_ buttons] objectAtIndex:1] frame].origin.x;
904
905  NSButton* first = [[bar_ buttons] objectAtIndex:0];
906  [[first cell] setTitle:@"This is a really big title; watch out mom!"];
907  [bar_ checkForBookmarkButtonGrowth:first];
908
909  // Make sure the 1st button is now wider, the 2nd one is moved over,
910  // and they don't overlap.
911  NSRect frame_1 = [[[bar_ buttons] objectAtIndex:0] frame];
912  NSRect frame_2 = [[[bar_ buttons] objectAtIndex:1] frame];
913  EXPECT_GT(frame_1.size.width, width_1);
914  EXPECT_GT(frame_2.origin.x, x_2);
915  EXPECT_GE(frame_2.origin.x, frame_1.origin.x + frame_1.size.width);
916}
917
918TEST_F(BookmarkBarControllerTest, DeleteBookmark) {
919  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
920
921  const char* urls[] = { "https://secret.url.com",
922                         "http://super.duper.web.site.for.doodz.gov",
923                         "http://www.foo-bar-baz.com/" };
924  const BookmarkNode* parent = model->GetBookmarkBarNode();
925  for (unsigned int i = 0; i < arraysize(urls); i++) {
926    model->AddURL(parent, parent->child_count(),
927                  ASCIIToUTF16("title"), GURL(urls[i]));
928  }
929  EXPECT_EQ(3, parent->child_count());
930  const BookmarkNode* middle_node = parent->GetChild(1);
931
932  NSMenuItem* item = ItemForBookmarkBarMenu(middle_node);
933  [bar_ deleteBookmark:item];
934  EXPECT_EQ(2, parent->child_count());
935  EXPECT_EQ(parent->GetChild(0)->GetURL(), GURL(urls[0]));
936  // node 2 moved into spot 1
937  EXPECT_EQ(parent->GetChild(1)->GetURL(), GURL(urls[2]));
938}
939
940// TODO(jrg): write a test to confirm that nodeFaviconLoaded calls
941// checkForBookmarkButtonGrowth:.
942
943TEST_F(BookmarkBarControllerTest, Cell) {
944  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
945  [bar_ loaded:model];
946
947  const BookmarkNode* parent = model->GetBookmarkBarNode();
948  model->AddURL(parent, parent->child_count(),
949                ASCIIToUTF16("supertitle"),
950                GURL("http://superfriends.hall-of-justice.edu"));
951  const BookmarkNode* node = parent->GetChild(0);
952
953  NSCell* cell = [bar_ cellForBookmarkNode:node];
954  EXPECT_TRUE(cell);
955  EXPECT_NSEQ(@"supertitle", [cell title]);
956  EXPECT_EQ(node, [[cell representedObject] pointerValue]);
957  EXPECT_TRUE([cell menu]);
958
959  // Empty cells have no menu.
960  cell = [bar_ cellForBookmarkNode:nil];
961  EXPECT_FALSE([cell menu]);
962  // Even empty cells have a title (of "(empty)")
963  EXPECT_TRUE([cell title]);
964
965  // cell is autoreleased; no need to release here
966}
967
968// Test drawing, mostly to ensure nothing leaks or crashes.
969TEST_F(BookmarkBarControllerTest, Display) {
970  [[bar_ view] display];
971}
972
973// Test that middle clicking on a bookmark button results in an open action.
974TEST_F(BookmarkBarControllerTest, MiddleClick) {
975  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
976  GURL gurl1("http://www.google.com/");
977  string16 title1(ASCIIToUTF16("x"));
978  model->SetURLStarred(gurl1, title1, true);
979
980  EXPECT_EQ(1U, [[bar_ buttons] count]);
981  NSButton* first = [[bar_ buttons] objectAtIndex:0];
982  EXPECT_TRUE(first);
983
984  [first otherMouseUp:test_event_utils::MakeMouseEvent(NSOtherMouseUp, 0)];
985  EXPECT_EQ(noOpenBar()->urls_.size(), 1U);
986}
987
988TEST_F(BookmarkBarControllerTest, DisplaysHelpMessageOnEmpty) {
989  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
990  [bar_ loaded:model];
991  EXPECT_FALSE([[[bar_ buttonView] noItemContainer] isHidden]);
992}
993
994TEST_F(BookmarkBarControllerTest, HidesHelpMessageWithBookmark) {
995  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
996
997  const BookmarkNode* parent = model->GetBookmarkBarNode();
998  model->AddURL(parent, parent->child_count(),
999                ASCIIToUTF16("title"), GURL("http://one.com"));
1000
1001  [bar_ loaded:model];
1002  EXPECT_TRUE([[[bar_ buttonView] noItemContainer] isHidden]);
1003}
1004
1005TEST_F(BookmarkBarControllerTest, BookmarkButtonSizing) {
1006  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1007
1008  const BookmarkNode* parent = model->GetBookmarkBarNode();
1009  model->AddURL(parent, parent->child_count(),
1010                ASCIIToUTF16("title"), GURL("http://one.com"));
1011
1012  [bar_ loaded:model];
1013
1014  // Make sure the internal bookmark button also is the correct height.
1015  NSArray* buttons = [bar_ buttons];
1016  EXPECT_GT([buttons count], 0u);
1017  for (NSButton* button in buttons) {
1018    EXPECT_FLOAT_EQ(
1019        (bookmarks::kBookmarkBarHeight + bookmarks::kVisualHeightOffset) - 2 *
1020                    bookmarks::kBookmarkVerticalPadding,
1021        [button frame].size.height);
1022  }
1023}
1024
1025TEST_F(BookmarkBarControllerTest, DropBookmarks) {
1026  const char* urls[] = {
1027    "http://qwantz.com",
1028    "http://xkcd.com",
1029    "javascript:alert('lolwut')",
1030    "file://localhost/tmp/local-file.txt"  // As if dragged from the desktop.
1031  };
1032  const char* titles[] = {
1033    "Philosophoraptor",
1034    "Can't draw",
1035    "Inspiration",
1036    "Frum stuf"
1037  };
1038  EXPECT_EQ(arraysize(urls), arraysize(titles));
1039
1040  NSMutableArray* nsurls = [NSMutableArray array];
1041  NSMutableArray* nstitles = [NSMutableArray array];
1042  for (size_t i = 0; i < arraysize(urls); ++i) {
1043    [nsurls addObject:base::SysUTF8ToNSString(urls[i])];
1044    [nstitles addObject:base::SysUTF8ToNSString(titles[i])];
1045  }
1046
1047  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1048  const BookmarkNode* parent = model->GetBookmarkBarNode();
1049  [bar_ addURLs:nsurls withTitles:nstitles at:NSZeroPoint];
1050  EXPECT_EQ(4, parent->child_count());
1051  for (int i = 0; i < parent->child_count(); ++i) {
1052    GURL gurl = parent->GetChild(i)->GetURL();
1053    if (gurl.scheme() == "http" ||
1054        gurl.scheme() == "javascript") {
1055      EXPECT_EQ(parent->GetChild(i)->GetURL(), GURL(urls[i]));
1056    } else {
1057      // Be flexible if the scheme needed to be added.
1058      std::string gurl_string = gurl.spec();
1059      std::string my_string = parent->GetChild(i)->GetURL().spec();
1060      EXPECT_NE(gurl_string.find(my_string), std::string::npos);
1061    }
1062    EXPECT_EQ(parent->GetChild(i)->GetTitle(), ASCIIToUTF16(titles[i]));
1063  }
1064}
1065
1066TEST_F(BookmarkBarControllerTest, TestButtonOrBar) {
1067  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1068  GURL gurl1("http://www.google.com");
1069  string16 title1(ASCIIToUTF16("x"));
1070  model->SetURLStarred(gurl1, title1, true);
1071
1072  GURL gurl2("http://www.google.com/gurl_power");
1073  string16 title2(ASCIIToUTF16("gurl power"));
1074  model->SetURLStarred(gurl2, title2, true);
1075
1076  NSButton* first = [[bar_ buttons] objectAtIndex:0];
1077  NSButton* second = [[bar_ buttons] objectAtIndex:1];
1078  EXPECT_TRUE(first && second);
1079
1080  NSMenuItem* menuItem = [[[first cell] menu] itemAtIndex:0];
1081  const BookmarkNode* node = [bar_ nodeFromMenuItem:menuItem];
1082  EXPECT_TRUE(node);
1083  EXPECT_EQ(node, model->GetBookmarkBarNode()->GetChild(0));
1084
1085  menuItem = [[[second cell] menu] itemAtIndex:0];
1086  node = [bar_ nodeFromMenuItem:menuItem];
1087  EXPECT_TRUE(node);
1088  EXPECT_EQ(node, model->GetBookmarkBarNode()->GetChild(1));
1089
1090  menuItem = [[[bar_ view] menu] itemAtIndex:0];
1091  node = [bar_ nodeFromMenuItem:menuItem];
1092  EXPECT_TRUE(node);
1093  EXPECT_EQ(node, model->GetBookmarkBarNode());
1094}
1095
1096TEST_F(BookmarkBarControllerTest, TestMenuNodeAndDisable) {
1097  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1098  const BookmarkNode* parent = model->GetBookmarkBarNode();
1099  const BookmarkNode* folder = model->AddFolder(parent,
1100                                                parent->child_count(),
1101                                                ASCIIToUTF16("folder"));
1102  NSButton* button = [[bar_ buttons] objectAtIndex:0];
1103  EXPECT_TRUE(button);
1104
1105  // Confirm the menu knows which node it is talking about
1106  BookmarkMenu* menu = static_cast<BookmarkMenu*>([[button cell] menu]);
1107  EXPECT_TRUE(menu);
1108  EXPECT_TRUE([menu isKindOfClass:[BookmarkMenu class]]);
1109  EXPECT_EQ(folder->id(), [menu id]);
1110
1111  // Make sure "Open All" is disabled (nothing to open -- no children!)
1112  // (Assumes "Open All" is the 1st item)
1113  NSMenuItem* item = [menu itemAtIndex:0];
1114  EXPECT_FALSE([bar_ validateUserInterfaceItem:item]);
1115
1116  // Now add a child and make sure the item would be enabled.
1117  model->AddURL(folder, folder->child_count(),
1118                ASCIIToUTF16("super duper wide title"),
1119                GURL("http://superfriends.hall-of-justice.edu"));
1120  EXPECT_TRUE([bar_ validateUserInterfaceItem:item]);
1121}
1122
1123TEST_F(BookmarkBarControllerTest, TestDragButton) {
1124  WithNoAnimation at_all;
1125  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1126
1127  GURL gurls[] = { GURL("http://www.google.com/a"),
1128                   GURL("http://www.google.com/b"),
1129                   GURL("http://www.google.com/c") };
1130  string16 titles[] = { ASCIIToUTF16("a"),
1131                        ASCIIToUTF16("b"),
1132                        ASCIIToUTF16("c") };
1133  for (unsigned i = 0; i < arraysize(titles); i++) {
1134    model->SetURLStarred(gurls[i], titles[i], true);
1135  }
1136
1137  EXPECT_EQ([[bar_ buttons] count], arraysize(titles));
1138  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:0] title]);
1139
1140  [bar_ dragButton:[[bar_ buttons] objectAtIndex:2]
1141                to:NSMakePoint(0, 0)
1142              copy:NO];
1143  EXPECT_NSEQ(@"c", [[[bar_ buttons] objectAtIndex:0] title]);
1144  // Make sure a 'copy' did not happen.
1145  EXPECT_EQ([[bar_ buttons] count], arraysize(titles));
1146
1147  [bar_ dragButton:[[bar_ buttons] objectAtIndex:1]
1148                to:NSMakePoint(1000, 0)
1149              copy:NO];
1150  EXPECT_NSEQ(@"c", [[[bar_ buttons] objectAtIndex:0] title]);
1151  EXPECT_NSEQ(@"b", [[[bar_ buttons] objectAtIndex:1] title]);
1152  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:2] title]);
1153  EXPECT_EQ([[bar_ buttons] count], arraysize(titles));
1154
1155  // A drop of the 1st between the next 2.
1156  CGFloat x = NSMinX([[[bar_ buttons] objectAtIndex:2] frame]);
1157  x += [[bar_ view] frame].origin.x;
1158  [bar_ dragButton:[[bar_ buttons] objectAtIndex:0]
1159                to:NSMakePoint(x, 0)
1160              copy:NO];
1161  EXPECT_NSEQ(@"b", [[[bar_ buttons] objectAtIndex:0] title]);
1162  EXPECT_NSEQ(@"c", [[[bar_ buttons] objectAtIndex:1] title]);
1163  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:2] title]);
1164  EXPECT_EQ([[bar_ buttons] count], arraysize(titles));
1165
1166  // A drop on a non-folder button.  (Shouldn't try and go in it.)
1167  x = NSMidX([[[bar_ buttons] objectAtIndex:0] frame]);
1168  x += [[bar_ view] frame].origin.x;
1169  [bar_ dragButton:[[bar_ buttons] objectAtIndex:2]
1170                to:NSMakePoint(x, 0)
1171              copy:NO];
1172  EXPECT_EQ(arraysize(titles), [[bar_ buttons] count]);
1173
1174  // A drop on a folder button.
1175  const BookmarkNode* folder = model->AddFolder(model->GetBookmarkBarNode(),
1176                                                0,
1177                                                ASCIIToUTF16("awesome folder"));
1178  DCHECK(folder);
1179  model->AddURL(folder, 0, ASCIIToUTF16("already"),
1180                GURL("http://www.google.com"));
1181  EXPECT_EQ(arraysize(titles) + 1, [[bar_ buttons] count]);
1182  EXPECT_EQ(1, folder->child_count());
1183  x = NSMidX([[[bar_ buttons] objectAtIndex:0] frame]);
1184  x += [[bar_ view] frame].origin.x;
1185  string16 title = [[[bar_ buttons] objectAtIndex:2] bookmarkNode]->GetTitle();
1186  [bar_ dragButton:[[bar_ buttons] objectAtIndex:2]
1187                to:NSMakePoint(x, 0)
1188              copy:NO];
1189  // Gone from the bar
1190  EXPECT_EQ(arraysize(titles), [[bar_ buttons] count]);
1191  // In the folder
1192  EXPECT_EQ(2, folder->child_count());
1193  // At the end
1194  EXPECT_EQ(title, folder->GetChild(1)->GetTitle());
1195}
1196
1197TEST_F(BookmarkBarControllerTest, TestCopyButton) {
1198  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1199
1200  GURL gurls[] = { GURL("http://www.google.com/a"),
1201                   GURL("http://www.google.com/b"),
1202                   GURL("http://www.google.com/c") };
1203  string16 titles[] = { ASCIIToUTF16("a"),
1204                        ASCIIToUTF16("b"),
1205                        ASCIIToUTF16("c") };
1206  for (unsigned i = 0; i < arraysize(titles); i++) {
1207    model->SetURLStarred(gurls[i], titles[i], true);
1208  }
1209  EXPECT_EQ([[bar_ buttons] count], arraysize(titles));
1210  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:0] title]);
1211
1212  // Drag 'a' between 'b' and 'c'.
1213  CGFloat x = NSMinX([[[bar_ buttons] objectAtIndex:2] frame]);
1214  x += [[bar_ view] frame].origin.x;
1215  [bar_ dragButton:[[bar_ buttons] objectAtIndex:0]
1216                to:NSMakePoint(x, 0)
1217              copy:YES];
1218  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:0] title]);
1219  EXPECT_NSEQ(@"b", [[[bar_ buttons] objectAtIndex:1] title]);
1220  EXPECT_NSEQ(@"a", [[[bar_ buttons] objectAtIndex:2] title]);
1221  EXPECT_NSEQ(@"c", [[[bar_ buttons] objectAtIndex:3] title]);
1222  EXPECT_EQ([[bar_ buttons] count], 4U);
1223}
1224
1225// Fake a theme with colored text.  Apply it and make sure bookmark
1226// buttons have the same colored text.  Repeat more than once.
1227TEST_F(BookmarkBarControllerTest, TestThemedButton) {
1228  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1229  model->SetURLStarred(GURL("http://www.foo.com"), ASCIIToUTF16("small"), true);
1230  BookmarkButton* button = [[bar_ buttons] objectAtIndex:0];
1231  EXPECT_TRUE(button);
1232
1233  NSArray* colors = [NSArray arrayWithObjects:[NSColor redColor],
1234                                              [NSColor blueColor],
1235                                              nil];
1236  for (NSColor* color in colors) {
1237    FakeTheme theme(color);
1238    [bar_ updateTheme:&theme];
1239    NSAttributedString* astr = [button attributedTitle];
1240    EXPECT_TRUE(astr);
1241    EXPECT_NSEQ(@"small", [astr string]);
1242    // Pick a char in the middle to test (index 3)
1243    NSDictionary* attributes = [astr attributesAtIndex:3 effectiveRange:NULL];
1244    NSColor* newColor =
1245        [attributes objectForKey:NSForegroundColorAttributeName];
1246    EXPECT_NSEQ(newColor, color);
1247  }
1248}
1249
1250// Test that delegates and targets of buttons are cleared on dealloc.
1251TEST_F(BookmarkBarControllerTest, TestClearOnDealloc) {
1252  // Make some bookmark buttons.
1253  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1254  GURL gurls[] = { GURL("http://www.foo.com/"),
1255                   GURL("http://www.bar.com/"),
1256                   GURL("http://www.baz.com/") };
1257  string16 titles[] = { ASCIIToUTF16("a"),
1258                        ASCIIToUTF16("b"),
1259                        ASCIIToUTF16("c") };
1260  for (size_t i = 0; i < arraysize(titles); i++)
1261    model->SetURLStarred(gurls[i], titles[i], true);
1262
1263  // Get and retain the buttons so we can examine them after dealloc.
1264  scoped_nsobject<NSArray> buttons([[bar_ buttons] retain]);
1265  EXPECT_EQ([buttons count], arraysize(titles));
1266
1267  // Make sure that everything is set.
1268  for (BookmarkButton* button in buttons.get()) {
1269    ASSERT_TRUE([button isKindOfClass:[BookmarkButton class]]);
1270    EXPECT_TRUE([button delegate]);
1271    EXPECT_TRUE([button target]);
1272    EXPECT_TRUE([button action]);
1273  }
1274
1275  // This will dealloc....
1276  bar_.reset();
1277
1278  // Make sure that everything is cleared.
1279  for (BookmarkButton* button in buttons.get()) {
1280    EXPECT_FALSE([button delegate]);
1281    EXPECT_FALSE([button target]);
1282    EXPECT_FALSE([button action]);
1283  }
1284}
1285
1286TEST_F(BookmarkBarControllerTest, TestFolders) {
1287  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1288
1289  // Create some folder buttons.
1290  const BookmarkNode* parent = model->GetBookmarkBarNode();
1291  const BookmarkNode* folder = model->AddFolder(parent,
1292                                                parent->child_count(),
1293                                                ASCIIToUTF16("folder"));
1294  model->AddURL(folder, folder->child_count(),
1295                ASCIIToUTF16("f1"), GURL("http://framma-lamma.com"));
1296  folder = model->AddFolder(parent, parent->child_count(),
1297                            ASCIIToUTF16("empty"));
1298
1299  EXPECT_EQ([[bar_ buttons] count], 2U);
1300
1301  // First confirm mouseEntered does nothing if "menus" aren't active.
1302  NSEvent* event = test_event_utils::MakeMouseEvent(NSOtherMouseUp, 0);
1303  [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:0] event:event];
1304  EXPECT_FALSE([bar_ folderController]);
1305
1306  // Make one active.  Entering it is now a no-op.
1307  [bar_ openBookmarkFolderFromButton:[[bar_ buttons] objectAtIndex:0]];
1308  BookmarkBarFolderController* bbfc = [bar_ folderController];
1309  EXPECT_TRUE(bbfc);
1310  [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:0] event:event];
1311  EXPECT_EQ(bbfc, [bar_ folderController]);
1312
1313  // Enter a different one; a new folderController is active.
1314  [bar_ mouseEnteredButton:[[bar_ buttons] objectAtIndex:1] event:event];
1315  EXPECT_NE(bbfc, [bar_ folderController]);
1316
1317  // Confirm exited is a no-op.
1318  [bar_ mouseExitedButton:[[bar_ buttons] objectAtIndex:1] event:event];
1319  EXPECT_NE(bbfc, [bar_ folderController]);
1320
1321  // Clean up.
1322  [bar_ closeBookmarkFolder:nil];
1323}
1324
1325// Verify that the folder menu presentation properly tracks mouse movements
1326// over the bar. Until there is a click no folder menus should show. After a
1327// click on a folder folder menus should show until another click on a folder
1328// button, and a click outside the bar and its folder menus.
1329TEST_F(BookmarkBarControllerTest, TestFolderButtons) {
1330  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
1331  const BookmarkNode* root = model.GetBookmarkBarNode();
1332  const std::string model_string("1b 2f:[ 2f1b 2f2b ] 3b 4f:[ 4f1b 4f2b ] ");
1333  model_test_utils::AddNodesFromModelString(model, root, model_string);
1334
1335  // Validate initial model and that we do not have a folder controller.
1336  std::string actualModelString = model_test_utils::ModelStringFromNode(root);
1337  EXPECT_EQ(model_string, actualModelString);
1338  EXPECT_FALSE([bar_ folderController]);
1339
1340  // Add a real bookmark so we can click on it.
1341  const BookmarkNode* folder = root->GetChild(3);
1342  model.AddURL(folder, folder->child_count(), ASCIIToUTF16("CLICK ME"),
1343               GURL("http://www.google.com/"));
1344
1345  // Click on a folder button.
1346  BookmarkButton* button = [bar_ buttonWithTitleEqualTo:@"4f"];
1347  EXPECT_TRUE(button);
1348  [bar_ openBookmarkFolderFromButton:button];
1349  BookmarkBarFolderController* bbfc = [bar_ folderController];
1350  EXPECT_TRUE(bbfc);
1351
1352  // Make sure a 2nd click on the same button closes things.
1353  [bar_ openBookmarkFolderFromButton:button];
1354  EXPECT_FALSE([bar_ folderController]);
1355
1356  // Next open is a different button.
1357  button = [bar_ buttonWithTitleEqualTo:@"2f"];
1358  EXPECT_TRUE(button);
1359  [bar_ openBookmarkFolderFromButton:button];
1360  EXPECT_TRUE([bar_ folderController]);
1361
1362  // Mouse over a non-folder button and confirm controller has gone away.
1363  button = [bar_ buttonWithTitleEqualTo:@"1b"];
1364  EXPECT_TRUE(button);
1365  NSEvent* event = test_event_utils::MouseEventAtPoint([button center],
1366                                                       NSMouseMoved, 0);
1367  [bar_ mouseEnteredButton:button event:event];
1368  EXPECT_FALSE([bar_ folderController]);
1369
1370  // Mouse over the original folder and confirm a new controller.
1371  button = [bar_ buttonWithTitleEqualTo:@"2f"];
1372  EXPECT_TRUE(button);
1373  [bar_ mouseEnteredButton:button event:event];
1374  BookmarkBarFolderController* oldBBFC = [bar_ folderController];
1375  EXPECT_TRUE(oldBBFC);
1376
1377  // 'Jump' over to a different folder and confirm a new controller.
1378  button = [bar_ buttonWithTitleEqualTo:@"4f"];
1379  EXPECT_TRUE(button);
1380  [bar_ mouseEnteredButton:button event:event];
1381  BookmarkBarFolderController* newBBFC = [bar_ folderController];
1382  EXPECT_TRUE(newBBFC);
1383  EXPECT_NE(oldBBFC, newBBFC);
1384}
1385
1386// Make sure the "off the side" folder looks like a bookmark folder
1387// but only contains "off the side" items.
1388TEST_F(BookmarkBarControllerTest, OffTheSideFolder) {
1389
1390  // It starts hidden.
1391  EXPECT_TRUE([bar_ offTheSideButtonIsHidden]);
1392
1393  // Create some buttons.
1394  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1395  const BookmarkNode* parent = model->GetBookmarkBarNode();
1396  for (int x = 0; x < 30; x++) {
1397    model->AddURL(parent, parent->child_count(),
1398                  ASCIIToUTF16("medium-size-title"),
1399                  GURL("http://framma-lamma.com"));
1400  }
1401  // Add a couple more so we can delete one and make sure its button goes away.
1402  model->AddURL(parent, parent->child_count(),
1403                ASCIIToUTF16("DELETE_ME"), GURL("http://ashton-tate.com"));
1404  model->AddURL(parent, parent->child_count(),
1405                ASCIIToUTF16("medium-size-title"),
1406                GURL("http://framma-lamma.com"));
1407
1408  // Should no longer be hidden.
1409  EXPECT_FALSE([bar_ offTheSideButtonIsHidden]);
1410
1411  // Open it; make sure we have a folder controller.
1412  EXPECT_FALSE([bar_ folderController]);
1413  [bar_ openOffTheSideFolderFromButton:[bar_ offTheSideButton]];
1414  BookmarkBarFolderController* bbfc = [bar_ folderController];
1415  EXPECT_TRUE(bbfc);
1416
1417  // Confirm the contents are only buttons which fell off the side by
1418  // making sure that none of the nodes in the off-the-side folder are
1419  // found in bar buttons.  Be careful since not all the bar buttons
1420  // may be currently displayed.
1421  NSArray* folderButtons = [bbfc buttons];
1422  NSArray* barButtons = [bar_ buttons];
1423  for (BookmarkButton* folderButton in folderButtons) {
1424    for (BookmarkButton* barButton in barButtons) {
1425      if ([barButton superview]) {
1426        EXPECT_NE([folderButton bookmarkNode], [barButton bookmarkNode]);
1427      }
1428    }
1429  }
1430
1431  // Delete a bookmark in the off-the-side and verify it's gone.
1432  BookmarkButton* button = [bbfc buttonWithTitleEqualTo:@"DELETE_ME"];
1433  EXPECT_TRUE(button);
1434  model->Remove(parent, parent->child_count() - 2);
1435  button = [bbfc buttonWithTitleEqualTo:@"DELETE_ME"];
1436  EXPECT_FALSE(button);
1437}
1438
1439TEST_F(BookmarkBarControllerTest, EventToExitCheck) {
1440  NSEvent* event = test_event_utils::MakeMouseEvent(NSMouseMoved, 0);
1441  EXPECT_FALSE([bar_ isEventAnExitEvent:event]);
1442
1443  BookmarkBarFolderWindow* folderWindow = [[[BookmarkBarFolderWindow alloc]
1444                                             init] autorelease];
1445  [[[bar_ view] window] addChildWindow:folderWindow
1446                               ordered:NSWindowAbove];
1447  event = test_event_utils::LeftMouseDownAtPointInWindow(NSMakePoint(1,1),
1448                                                         folderWindow);
1449  EXPECT_FALSE([bar_ isEventAnExitEvent:event]);
1450
1451  event = test_event_utils::LeftMouseDownAtPointInWindow(NSMakePoint(100,100),
1452                                                         test_window());
1453  EXPECT_TRUE([bar_ isEventAnExitEvent:event]);
1454
1455  // Many components are arbitrary (e.g. location, keycode).
1456  event = [NSEvent keyEventWithType:NSKeyDown
1457                           location:NSMakePoint(1,1)
1458                      modifierFlags:0
1459                          timestamp:0
1460                       windowNumber:0
1461                            context:nil
1462                         characters:@"x"
1463        charactersIgnoringModifiers:@"x"
1464                          isARepeat:NO
1465                            keyCode:87];
1466  EXPECT_FALSE([bar_ isEventAnExitEvent:event]);
1467
1468  [[[bar_ view] window] removeChildWindow:folderWindow];
1469}
1470
1471TEST_F(BookmarkBarControllerTest, DropDestination) {
1472  // Make some buttons.
1473  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1474  const BookmarkNode* parent = model->GetBookmarkBarNode();
1475  model->AddFolder(parent, parent->child_count(), ASCIIToUTF16("folder 1"));
1476  model->AddFolder(parent, parent->child_count(), ASCIIToUTF16("folder 2"));
1477  EXPECT_EQ([[bar_ buttons] count], 2U);
1478
1479  // Confirm "off to left" and "off to right" match nothing.
1480  NSPoint p = NSMakePoint(-1, 2);
1481  EXPECT_FALSE([bar_ buttonForDroppingOnAtPoint:p]);
1482  EXPECT_TRUE([bar_ shouldShowIndicatorShownForPoint:p]);
1483  p = NSMakePoint(50000, 10);
1484  EXPECT_FALSE([bar_ buttonForDroppingOnAtPoint:p]);
1485  EXPECT_TRUE([bar_ shouldShowIndicatorShownForPoint:p]);
1486
1487  // Confirm "right in the center" (give or take a pixel) is a match,
1488  // and confirm "just barely in the button" is not.  Anything more
1489  // specific seems likely to be tweaked.
1490  CGFloat viewFrameXOffset = [[bar_ view] frame].origin.x;
1491  for (BookmarkButton* button in [bar_ buttons]) {
1492    CGFloat x = NSMidX([button frame]) + viewFrameXOffset;
1493    // Somewhere near the center: a match
1494    EXPECT_EQ(button,
1495              [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x-1, 10)]);
1496    EXPECT_EQ(button,
1497              [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x+1, 10)]);
1498    EXPECT_FALSE([bar_ shouldShowIndicatorShownForPoint:NSMakePoint(x, 10)]);;
1499
1500    // On the very edges: NOT a match
1501    x = NSMinX([button frame]) + viewFrameXOffset;
1502    EXPECT_NE(button,
1503              [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x, 9)]);
1504    x = NSMaxX([button frame]) + viewFrameXOffset;
1505    EXPECT_NE(button,
1506              [bar_ buttonForDroppingOnAtPoint:NSMakePoint(x, 11)]);
1507  }
1508}
1509
1510TEST_F(BookmarkBarControllerTest, NodeDeletedWhileMenuIsOpen) {
1511  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1512  [bar_ loaded:model];
1513
1514  const BookmarkNode* parent = model->GetBookmarkBarNode();
1515  const BookmarkNode* initialNode = model->AddURL(
1516      parent, parent->child_count(),
1517      ASCIIToUTF16("initial"),
1518      GURL("http://www.google.com"));
1519
1520  NSMenuItem* item = ItemForBookmarkBarMenu(initialNode);
1521  EXPECT_EQ(0U, noOpenBar()->urls_.size());
1522
1523  // Basic check of the menu item and an IBOutlet it can call.
1524  EXPECT_EQ(initialNode, [bar_ nodeFromMenuItem:item]);
1525  [bar_ openBookmarkInNewWindow:item];
1526  EXPECT_EQ(1U, noOpenBar()->urls_.size());
1527  [bar_ clear];
1528
1529  // Now delete the node and make sure things are happy (no crash,
1530  // NULL node caught).
1531  model->Remove(parent, parent->GetIndexOf(initialNode));
1532  EXPECT_EQ(nil, [bar_ nodeFromMenuItem:item]);
1533  // Should not crash by referencing a deleted node.
1534  [bar_ openBookmarkInNewWindow:item];
1535  // Confirm the above did nothing in case it somehow didn't crash.
1536  EXPECT_EQ(0U, noOpenBar()->urls_.size());
1537
1538  // Confirm some more non-crashes.
1539  [bar_ openBookmarkInNewForegroundTab:item];
1540  [bar_ openBookmarkInIncognitoWindow:item];
1541  [bar_ editBookmark:item];
1542  [bar_ copyBookmark:item];
1543  [bar_ deleteBookmark:item];
1544  [bar_ openAllBookmarks:item];
1545  [bar_ openAllBookmarksNewWindow:item];
1546  [bar_ openAllBookmarksIncognitoWindow:item];
1547}
1548
1549TEST_F(BookmarkBarControllerTest, NodeDeletedWhileContextMenuIsOpen) {
1550  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1551  [bar_ loaded:model];
1552
1553  const BookmarkNode* parent = model->GetBookmarkBarNode();
1554  const BookmarkNode* folder = model->AddFolder(parent,
1555                                                parent->child_count(),
1556                                                ASCIIToUTF16("folder"));
1557  const BookmarkNode* framma = model->AddURL(folder, folder->child_count(),
1558                                             ASCIIToUTF16("f1"),
1559                                             GURL("http://framma-lamma.com"));
1560
1561  // Mock in a menu
1562  id origMenu = [bar_ buttonContextMenu];
1563  id fakeMenu = [OCMockObject partialMockForObject:origMenu];
1564  [[fakeMenu expect] cancelTracking];
1565  [bar_ setButtonContextMenu:fakeMenu];
1566
1567  // Force a delete which should cancelTracking on the menu.
1568  model->Remove(framma->parent(), framma->parent()->GetIndexOf(framma));
1569
1570  // Restore, then confirm cancelTracking was called.
1571  [bar_ setButtonContextMenu:origMenu];
1572  [fakeMenu verify];
1573}
1574
1575TEST_F(BookmarkBarControllerTest, CloseFolderOnAnimate) {
1576  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1577  const BookmarkNode* parent = model->GetBookmarkBarNode();
1578  const BookmarkNode* folder = model->AddFolder(parent,
1579                                                parent->child_count(),
1580                                                ASCIIToUTF16("folder"));
1581  model->AddFolder(parent, parent->child_count(),
1582                  ASCIIToUTF16("sibbling folder"));
1583  model->AddURL(folder, folder->child_count(), ASCIIToUTF16("title a"),
1584                GURL("http://www.google.com/a"));
1585  model->AddURL(folder, folder->child_count(),
1586      ASCIIToUTF16("title super duper long long whoa momma title you betcha"),
1587      GURL("http://www.google.com/b"));
1588  BookmarkButton* button = [[bar_ buttons] objectAtIndex:0];
1589  EXPECT_FALSE([bar_ folderController]);
1590  [bar_ openBookmarkFolderFromButton:button];
1591  BookmarkBarFolderController* bbfc = [bar_ folderController];
1592  // The following tells us that the folder menu is showing. We want to make
1593  // sure the folder menu goes away if the bookmark bar is hidden.
1594  EXPECT_TRUE(bbfc);
1595  EXPECT_TRUE([bar_ isVisible]);
1596
1597  // Hide the bookmark bar.
1598  [bar_ updateAndShowNormalBar:NO
1599               showDetachedBar:YES
1600                 withAnimation:YES];
1601  EXPECT_TRUE([bar_ isAnimationRunning]);
1602
1603  // Now that we've closed the bookmark bar (with animation) the folder menu
1604  // should have been closed thus releasing the folderController.
1605  EXPECT_FALSE([bar_ folderController]);
1606}
1607
1608TEST_F(BookmarkBarControllerTest, MoveRemoveAddButtons) {
1609  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
1610  const BookmarkNode* root = model.GetBookmarkBarNode();
1611  const std::string model_string("1b 2f:[ 2f1b 2f2b ] 3b ");
1612  model_test_utils::AddNodesFromModelString(model, root, model_string);
1613
1614  // Validate initial model.
1615  std::string actualModelString = model_test_utils::ModelStringFromNode(root);
1616  EXPECT_EQ(model_string, actualModelString);
1617
1618  // Remember how many buttons are showing.
1619  int oldDisplayedButtons = [bar_ displayedButtonCount];
1620  NSArray* buttons = [bar_ buttons];
1621
1622  // Move a button around a bit.
1623  [bar_ moveButtonFromIndex:0 toIndex:2];
1624  EXPECT_NSEQ(@"2f", [[buttons objectAtIndex:0] title]);
1625  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:1] title]);
1626  EXPECT_NSEQ(@"1b", [[buttons objectAtIndex:2] title]);
1627  EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
1628  [bar_ moveButtonFromIndex:2 toIndex:0];
1629  EXPECT_NSEQ(@"1b", [[buttons objectAtIndex:0] title]);
1630  EXPECT_NSEQ(@"2f", [[buttons objectAtIndex:1] title]);
1631  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:2] title]);
1632  EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
1633
1634  // Add a couple of buttons.
1635  const BookmarkNode* parent = root->GetChild(1); // Purloin an existing node.
1636  const BookmarkNode* node = parent->GetChild(0);
1637  [bar_ addButtonForNode:node atIndex:0];
1638  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:0] title]);
1639  EXPECT_NSEQ(@"1b", [[buttons objectAtIndex:1] title]);
1640  EXPECT_NSEQ(@"2f", [[buttons objectAtIndex:2] title]);
1641  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:3] title]);
1642  EXPECT_EQ(oldDisplayedButtons + 1, [bar_ displayedButtonCount]);
1643  node = parent->GetChild(1);
1644  [bar_ addButtonForNode:node atIndex:-1];
1645  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:0] title]);
1646  EXPECT_NSEQ(@"1b", [[buttons objectAtIndex:1] title]);
1647  EXPECT_NSEQ(@"2f", [[buttons objectAtIndex:2] title]);
1648  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:3] title]);
1649  EXPECT_NSEQ(@"2f2b", [[buttons objectAtIndex:4] title]);
1650  EXPECT_EQ(oldDisplayedButtons + 2, [bar_ displayedButtonCount]);
1651
1652  // Remove a couple of buttons.
1653  [bar_ removeButton:4 animate:NO];
1654  [bar_ removeButton:1 animate:NO];
1655  EXPECT_NSEQ(@"2f1b", [[buttons objectAtIndex:0] title]);
1656  EXPECT_NSEQ(@"2f", [[buttons objectAtIndex:1] title]);
1657  EXPECT_NSEQ(@"3b", [[buttons objectAtIndex:2] title]);
1658  EXPECT_EQ(oldDisplayedButtons, [bar_ displayedButtonCount]);
1659}
1660
1661TEST_F(BookmarkBarControllerTest, ShrinkOrHideView) {
1662  NSRect viewFrame = NSMakeRect(0.0, 0.0, 500.0, 50.0);
1663  NSView* view = [[[NSView alloc] initWithFrame:viewFrame] autorelease];
1664  EXPECT_FALSE([view isHidden]);
1665  [bar_ shrinkOrHideView:view forMaxX:500.0];
1666  EXPECT_EQ(500.0, NSWidth([view frame]));
1667  EXPECT_FALSE([view isHidden]);
1668  [bar_ shrinkOrHideView:view forMaxX:450.0];
1669  EXPECT_EQ(450.0, NSWidth([view frame]));
1670  EXPECT_FALSE([view isHidden]);
1671  [bar_ shrinkOrHideView:view forMaxX:40.0];
1672  EXPECT_EQ(40.0, NSWidth([view frame]));
1673  EXPECT_FALSE([view isHidden]);
1674  [bar_ shrinkOrHideView:view forMaxX:31.0];
1675  EXPECT_EQ(31.0, NSWidth([view frame]));
1676  EXPECT_FALSE([view isHidden]);
1677  [bar_ shrinkOrHideView:view forMaxX:29.0];
1678  EXPECT_TRUE([view isHidden]);
1679}
1680
1681class BookmarkBarControllerOpenAllTest : public BookmarkBarControllerTest {
1682public:
1683  BookmarkBarControllerOpenAllTest() {
1684    resizeDelegate_.reset([[ViewResizerPong alloc] init]);
1685    NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
1686    bar_.reset(
1687               [[BookmarkBarControllerOpenAllPong alloc]
1688                initWithBrowser:helper_.browser()
1689                   initialWidth:NSWidth(parent_frame)
1690                       delegate:nil
1691                 resizeDelegate:resizeDelegate_.get()]);
1692    [bar_ view];
1693    // Awkwardness to look like we've been installed.
1694    [parent_view_ addSubview:[bar_ view]];
1695    NSRect frame = [[[bar_ view] superview] frame];
1696    frame.origin.y = 100;
1697    [[[bar_ view] superview] setFrame:frame];
1698
1699    BookmarkModel* model = helper_.profile()->GetBookmarkModel();
1700    parent_ = model->GetBookmarkBarNode();
1701    // { one, { two-one, two-two }, three }
1702    model->AddURL(parent_, parent_->child_count(), ASCIIToUTF16("title"),
1703                  GURL("http://one.com"));
1704    folder_ = model->AddFolder(parent_, parent_->child_count(),
1705                               ASCIIToUTF16("folder"));
1706    model->AddURL(folder_, folder_->child_count(),
1707                  ASCIIToUTF16("title"), GURL("http://two-one.com"));
1708    model->AddURL(folder_, folder_->child_count(),
1709                  ASCIIToUTF16("title"), GURL("http://two-two.com"));
1710    model->AddURL(parent_, parent_->child_count(),
1711                  ASCIIToUTF16("title"), GURL("https://three.com"));
1712  }
1713  const BookmarkNode* parent_;  // Weak
1714  const BookmarkNode* folder_;  // Weak
1715};
1716
1717TEST_F(BookmarkBarControllerOpenAllTest, OpenAllBookmarks) {
1718  // Our first OpenAll... is from the bar itself.
1719  [bar_ openAllBookmarks:ItemForBookmarkBarMenu(parent_)];
1720  BookmarkBarControllerOpenAllPong* specialBar =
1721      (BookmarkBarControllerOpenAllPong*)bar_.get();
1722  EXPECT_EQ([specialBar dispositionDetected], NEW_FOREGROUND_TAB);
1723
1724  // Now try an OpenAll... from a folder node.
1725  [specialBar setDispositionDetected:IGNORE_ACTION]; // Reset
1726  [bar_ openAllBookmarks:ItemForBookmarkBarMenu(folder_)];
1727  EXPECT_EQ([specialBar dispositionDetected], NEW_FOREGROUND_TAB);
1728}
1729
1730TEST_F(BookmarkBarControllerOpenAllTest, OpenAllNewWindow) {
1731  // Our first OpenAll... is from the bar itself.
1732  [bar_ openAllBookmarksNewWindow:ItemForBookmarkBarMenu(parent_)];
1733  BookmarkBarControllerOpenAllPong* specialBar =
1734      (BookmarkBarControllerOpenAllPong*)bar_.get();
1735  EXPECT_EQ([specialBar dispositionDetected], NEW_WINDOW);
1736
1737  // Now try an OpenAll... from a folder node.
1738  [specialBar setDispositionDetected:IGNORE_ACTION]; // Reset
1739  [bar_ openAllBookmarksNewWindow:ItemForBookmarkBarMenu(folder_)];
1740  EXPECT_EQ([specialBar dispositionDetected], NEW_WINDOW);
1741}
1742
1743TEST_F(BookmarkBarControllerOpenAllTest, OpenAllIncognito) {
1744  // Our first OpenAll... is from the bar itself.
1745  [bar_ openAllBookmarksIncognitoWindow:ItemForBookmarkBarMenu(parent_)];
1746  BookmarkBarControllerOpenAllPong* specialBar =
1747  (BookmarkBarControllerOpenAllPong*)bar_.get();
1748  EXPECT_EQ([specialBar dispositionDetected], OFF_THE_RECORD);
1749
1750  // Now try an OpenAll... from a folder node.
1751  [specialBar setDispositionDetected:IGNORE_ACTION]; // Reset
1752  [bar_ openAllBookmarksIncognitoWindow:ItemForBookmarkBarMenu(folder_)];
1753  EXPECT_EQ([specialBar dispositionDetected], OFF_THE_RECORD);
1754}
1755
1756// Command-click on a folder should open all the bookmarks in it.
1757TEST_F(BookmarkBarControllerOpenAllTest, CommandClickOnFolder) {
1758  NSButton* first = [[bar_ buttons] objectAtIndex:0];
1759  EXPECT_TRUE(first);
1760
1761  // Create the right kind of event; mock NSApp so [NSApp
1762  // currentEvent] finds it.
1763  NSEvent* commandClick = test_event_utils::MouseEventAtPoint(NSZeroPoint,
1764                                                              NSLeftMouseDown,
1765                                                              NSCommandKeyMask);
1766  id fakeApp = [OCMockObject partialMockForObject:NSApp];
1767  [[[fakeApp stub] andReturn:commandClick] currentEvent];
1768  id oldApp = NSApp;
1769  NSApp = fakeApp;
1770  size_t originalDispositionCount = noOpenBar()->dispositions_.size();
1771
1772  // Click!
1773  [first performClick:first];
1774
1775  size_t dispositionCount = noOpenBar()->dispositions_.size();
1776  EXPECT_EQ(originalDispositionCount+1, dispositionCount);
1777  EXPECT_EQ(noOpenBar()->dispositions_[dispositionCount-1], NEW_BACKGROUND_TAB);
1778
1779  // Replace NSApp
1780  NSApp = oldApp;
1781}
1782
1783class BookmarkBarControllerNotificationTest : public CocoaTest {
1784 public:
1785  BookmarkBarControllerNotificationTest() {
1786    resizeDelegate_.reset([[ViewResizerPong alloc] init]);
1787    NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
1788    parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
1789    [parent_view_ setHidden:YES];
1790    bar_.reset(
1791      [[BookmarkBarControllerNotificationPong alloc]
1792          initWithBrowser:helper_.browser()
1793             initialWidth:NSWidth(parent_frame)
1794                 delegate:nil
1795           resizeDelegate:resizeDelegate_.get()]);
1796
1797    // Force loading of the nib.
1798    [bar_ view];
1799    // Awkwardness to look like we've been installed.
1800    [parent_view_ addSubview:[bar_ view]];
1801    NSRect frame = [[[bar_ view] superview] frame];
1802    frame.origin.y = 100;
1803    [[[bar_ view] superview] setFrame:frame];
1804
1805    // Do not add the bar to a window, yet.
1806  }
1807
1808  BrowserTestHelper helper_;
1809  scoped_nsobject<NSView> parent_view_;
1810  scoped_nsobject<ViewResizerPong> resizeDelegate_;
1811  scoped_nsobject<BookmarkBarControllerNotificationPong> bar_;
1812};
1813
1814TEST_F(BookmarkBarControllerNotificationTest, DeregistersForNotifications) {
1815  NSWindow* window = [[CocoaTestHelperWindow alloc] init];
1816  [window setReleasedWhenClosed:YES];
1817
1818  // First add the bookmark bar to the temp window, then to another window.
1819  [[window contentView] addSubview:parent_view_];
1820  [[test_window() contentView] addSubview:parent_view_];
1821
1822  // Post a fake windowDidResignKey notification for the temp window and make
1823  // sure the bookmark bar controller wasn't listening.
1824  [[NSNotificationCenter defaultCenter]
1825      postNotificationName:NSWindowDidResignKeyNotification
1826                    object:window];
1827  EXPECT_FALSE([bar_ windowDidResignKeyReceived]);
1828
1829  // Close the temp window and make sure no notification was received.
1830  [window close];
1831  EXPECT_FALSE([bar_ windowWillCloseReceived]);
1832}
1833
1834
1835// TODO(jrg): draggingEntered: and draggingExited: trigger timers so
1836// they are hard to test.  Factor out "fire timers" into routines
1837// which can be overridden to fire immediately to make behavior
1838// confirmable.
1839
1840// TODO(jrg): add unit test to make sure "Other Bookmarks" responds
1841// properly to a hover open.
1842
1843// TODO(viettrungluu): figure out how to test animations.
1844
1845class BookmarkBarControllerDragDropTest : public BookmarkBarControllerTestBase {
1846 public:
1847  scoped_nsobject<BookmarkBarControllerDragData> bar_;
1848
1849  BookmarkBarControllerDragDropTest() {
1850    bar_.reset(
1851               [[BookmarkBarControllerDragData alloc]
1852                initWithBrowser:helper_.browser()
1853                   initialWidth:NSWidth([parent_view_ frame])
1854                       delegate:nil
1855                 resizeDelegate:resizeDelegate_.get()]);
1856    InstallAndToggleBar(bar_.get());
1857  }
1858};
1859
1860TEST_F(BookmarkBarControllerDragDropTest, DragMoveBarBookmarkToOffTheSide) {
1861  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
1862  const BookmarkNode* root = model.GetBookmarkBarNode();
1863  const std::string model_string("1bWithLongName 2fWithLongName:[ "
1864      "2f1bWithLongName 2f2fWithLongName:[ 2f2f1bWithLongName "
1865      "2f2f2bWithLongName 2f2f3bWithLongName 2f4b ] 2f3bWithLongName ] "
1866      "3bWithLongName 4bWithLongName 5bWithLongName 6bWithLongName "
1867      "7bWithLongName 8bWithLongName 9bWithLongName 10bWithLongName "
1868      "11bWithLongName 12bWithLongName 13b ");
1869  model_test_utils::AddNodesFromModelString(model, root, model_string);
1870
1871  // Validate initial model.
1872  std::string actualModelString = model_test_utils::ModelStringFromNode(root);
1873  EXPECT_EQ(model_string, actualModelString);
1874
1875  // Insure that the off-the-side is not showing.
1876  ASSERT_FALSE([bar_ offTheSideButtonIsHidden]);
1877
1878  // Remember how many buttons are showing and are available.
1879  int oldDisplayedButtons = [bar_ displayedButtonCount];
1880  int oldChildCount = root->child_count();
1881
1882  // Pop up the off-the-side menu.
1883  BookmarkButton* otsButton = (BookmarkButton*)[bar_ offTheSideButton];
1884  ASSERT_TRUE(otsButton);
1885  [[otsButton target] performSelector:@selector(openOffTheSideFolderFromButton:)
1886                           withObject:otsButton];
1887  BookmarkBarFolderController* otsController = [bar_ folderController];
1888  EXPECT_TRUE(otsController);
1889  NSWindow* toWindow = [otsController window];
1890  EXPECT_TRUE(toWindow);
1891  BookmarkButton* draggedButton =
1892      [bar_ buttonWithTitleEqualTo:@"3bWithLongName"];
1893  ASSERT_TRUE(draggedButton);
1894  int oldOTSCount = (int)[[otsController buttons] count];
1895  EXPECT_EQ(oldOTSCount, oldChildCount - oldDisplayedButtons);
1896  BookmarkButton* targetButton = [[otsController buttons] objectAtIndex:0];
1897  ASSERT_TRUE(targetButton);
1898  [otsController dragButton:draggedButton
1899                         to:[targetButton center]
1900                       copy:YES];
1901  // There should still be the same number of buttons in the bar
1902  // and off-the-side should have one more.
1903  int newDisplayedButtons = [bar_ displayedButtonCount];
1904  int newChildCount = root->child_count();
1905  int newOTSCount = (int)[[otsController buttons] count];
1906  EXPECT_EQ(oldDisplayedButtons, newDisplayedButtons);
1907  EXPECT_EQ(oldChildCount + 1, newChildCount);
1908  EXPECT_EQ(oldOTSCount + 1, newOTSCount);
1909  EXPECT_EQ(newOTSCount, newChildCount - newDisplayedButtons);
1910}
1911
1912TEST_F(BookmarkBarControllerDragDropTest, DragOffTheSideToOther) {
1913  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
1914  const BookmarkNode* root = model.GetBookmarkBarNode();
1915  const std::string model_string("1bWithLongName 2bWithLongName "
1916      "3bWithLongName 4bWithLongName 5bWithLongName 6bWithLongName "
1917      "7bWithLongName 8bWithLongName 9bWithLongName 10bWithLongName "
1918      "11bWithLongName 12bWithLongName 13bWithLongName 14bWithLongName "
1919      "15bWithLongName 16bWithLongName 17bWithLongName 18bWithLongName "
1920      "19bWithLongName 20bWithLongName ");
1921  model_test_utils::AddNodesFromModelString(model, root, model_string);
1922
1923  const BookmarkNode* other = model.other_node();
1924  const std::string other_string("1other 2other 3other ");
1925  model_test_utils::AddNodesFromModelString(model, other, other_string);
1926
1927  // Validate initial model.
1928  std::string actualModelString = model_test_utils::ModelStringFromNode(root);
1929  EXPECT_EQ(model_string, actualModelString);
1930  std::string actualOtherString = model_test_utils::ModelStringFromNode(other);
1931  EXPECT_EQ(other_string, actualOtherString);
1932
1933  // Insure that the off-the-side is showing.
1934  ASSERT_FALSE([bar_ offTheSideButtonIsHidden]);
1935
1936  // Remember how many buttons are showing and are available.
1937  int oldDisplayedButtons = [bar_ displayedButtonCount];
1938  int oldRootCount = root->child_count();
1939  int oldOtherCount = other->child_count();
1940
1941  // Pop up the off-the-side menu.
1942  BookmarkButton* otsButton = (BookmarkButton*)[bar_ offTheSideButton];
1943  ASSERT_TRUE(otsButton);
1944  [[otsButton target] performSelector:@selector(openOffTheSideFolderFromButton:)
1945                           withObject:otsButton];
1946  BookmarkBarFolderController* otsController = [bar_ folderController];
1947  EXPECT_TRUE(otsController);
1948  int oldOTSCount = (int)[[otsController buttons] count];
1949  EXPECT_EQ(oldOTSCount, oldRootCount - oldDisplayedButtons);
1950
1951  // Pick an off-the-side button and drag it to the other bookmarks.
1952  BookmarkButton* draggedButton =
1953      [otsController buttonWithTitleEqualTo:@"20bWithLongName"];
1954  ASSERT_TRUE(draggedButton);
1955  BookmarkButton* targetButton = [bar_ otherBookmarksButton];
1956  ASSERT_TRUE(targetButton);
1957  [bar_ dragButton:draggedButton to:[targetButton center] copy:NO];
1958
1959  // There should one less button in the bar, one less in off-the-side,
1960  // and one more in other bookmarks.
1961  int newRootCount = root->child_count();
1962  int newOTSCount = (int)[[otsController buttons] count];
1963  int newOtherCount = other->child_count();
1964  EXPECT_EQ(oldRootCount - 1, newRootCount);
1965  EXPECT_EQ(oldOTSCount - 1, newOTSCount);
1966  EXPECT_EQ(oldOtherCount + 1, newOtherCount);
1967}
1968
1969TEST_F(BookmarkBarControllerDragDropTest, DragBookmarkData) {
1970  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
1971  const BookmarkNode* root = model.GetBookmarkBarNode();
1972  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
1973                                  "2f3b ] 3b 4b ");
1974  model_test_utils::AddNodesFromModelString(model, root, model_string);
1975  const BookmarkNode* other = model.other_node();
1976  const std::string other_string("O1b O2b O3f:[ O3f1b O3f2f ] "
1977                                 "O4f:[ O4f1b O4f2f ] 05b ");
1978  model_test_utils::AddNodesFromModelString(model, other, other_string);
1979
1980  // Validate initial model.
1981  std::string actual = model_test_utils::ModelStringFromNode(root);
1982  EXPECT_EQ(model_string, actual);
1983  actual = model_test_utils::ModelStringFromNode(other);
1984  EXPECT_EQ(other_string, actual);
1985
1986  // Remember the little ones.
1987  int oldChildCount = root->child_count();
1988
1989  BookmarkButton* targetButton = [bar_ buttonWithTitleEqualTo:@"3b"];
1990  ASSERT_TRUE(targetButton);
1991
1992  // Gen up some dragging data.
1993  const BookmarkNode* newNode = other->GetChild(2);
1994  [bar_ setDragDataNode:newNode];
1995  scoped_nsobject<FakeDragInfo> dragInfo([[FakeDragInfo alloc] init]);
1996  [dragInfo setDropLocation:[targetButton center]];
1997  [bar_ dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
1998
1999  // There should one more button in the bar.
2000  int newChildCount = root->child_count();
2001  EXPECT_EQ(oldChildCount + 1, newChildCount);
2002  // Verify the model.
2003  const std::string expected("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2004                             "2f3b ] O3f:[ O3f1b O3f2f ] 3b 4b ");
2005  actual = model_test_utils::ModelStringFromNode(root);
2006  EXPECT_EQ(expected, actual);
2007  oldChildCount = newChildCount;
2008
2009  // Now do it over a folder button.
2010  targetButton = [bar_ buttonWithTitleEqualTo:@"2f"];
2011  ASSERT_TRUE(targetButton);
2012  NSPoint targetPoint = [targetButton center];
2013  newNode = other->GetChild(2);  // Should be O4f.
2014  EXPECT_EQ(newNode->GetTitle(), ASCIIToUTF16("O4f"));
2015  [bar_ setDragDataNode:newNode];
2016  [dragInfo setDropLocation:targetPoint];
2017  [bar_ dragBookmarkData:(id<NSDraggingInfo>)dragInfo.get()];
2018
2019  newChildCount = root->child_count();
2020  EXPECT_EQ(oldChildCount, newChildCount);
2021  // Verify the model.
2022  const std::string expected1("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2023                              "2f3b O4f:[ O4f1b O4f2f ] ] O3f:[ O3f1b O3f2f ] "
2024                              "3b 4b ");
2025  actual = model_test_utils::ModelStringFromNode(root);
2026  EXPECT_EQ(expected1, actual);
2027}
2028
2029TEST_F(BookmarkBarControllerDragDropTest, AddURLs) {
2030  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
2031  const BookmarkNode* root = model.GetBookmarkBarNode();
2032  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2033                                 "2f3b ] 3b 4b ");
2034  model_test_utils::AddNodesFromModelString(model, root, model_string);
2035
2036  // Validate initial model.
2037  std::string actual = model_test_utils::ModelStringFromNode(root);
2038  EXPECT_EQ(model_string, actual);
2039
2040  // Remember the children.
2041  int oldChildCount = root->child_count();
2042
2043  BookmarkButton* targetButton = [bar_ buttonWithTitleEqualTo:@"3b"];
2044  ASSERT_TRUE(targetButton);
2045
2046  NSArray* urls = [NSArray arrayWithObjects: @"http://www.a.com/",
2047                   @"http://www.b.com/", nil];
2048  NSArray* titles = [NSArray arrayWithObjects: @"SiteA", @"SiteB", nil];
2049  [bar_ addURLs:urls withTitles:titles at:[targetButton center]];
2050
2051  // There should two more nodes in the bar.
2052  int newChildCount = root->child_count();
2053  EXPECT_EQ(oldChildCount + 2, newChildCount);
2054  // Verify the model.
2055  const std::string expected("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2056                             "2f3b ] SiteA SiteB 3b 4b ");
2057  actual = model_test_utils::ModelStringFromNode(root);
2058  EXPECT_EQ(expected, actual);
2059}
2060
2061TEST_F(BookmarkBarControllerDragDropTest, ControllerForNode) {
2062  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
2063  const BookmarkNode* root = model.GetBookmarkBarNode();
2064  const std::string model_string("1b 2f:[ 2f1b 2f2b ] 3b ");
2065  model_test_utils::AddNodesFromModelString(model, root, model_string);
2066
2067  // Validate initial model.
2068  std::string actualModelString = model_test_utils::ModelStringFromNode(root);
2069  EXPECT_EQ(model_string, actualModelString);
2070
2071  // Find the main bar controller.
2072  const void* expectedController = bar_;
2073  const void* actualController = [bar_ controllerForNode:root];
2074  EXPECT_EQ(expectedController, actualController);
2075}
2076
2077TEST_F(BookmarkBarControllerDragDropTest, DropPositionIndicator) {
2078  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
2079  const BookmarkNode* root = model.GetBookmarkBarNode();
2080  const std::string model_string("1b 2f:[ 2f1b 2f2b 2f3b ] 3b 4b ");
2081  model_test_utils::AddNodesFromModelString(model, root, model_string);
2082
2083  // Validate initial model.
2084  std::string actualModel = model_test_utils::ModelStringFromNode(root);
2085  EXPECT_EQ(model_string, actualModel);
2086
2087  // Test a series of points starting at the right edge of the bar.
2088  BookmarkButton* targetButton = [bar_ buttonWithTitleEqualTo:@"1b"];
2089  ASSERT_TRUE(targetButton);
2090  NSPoint targetPoint = [targetButton left];
2091  const CGFloat xDelta = 0.5 * bookmarks::kBookmarkHorizontalPadding;
2092  const CGFloat baseOffset = targetPoint.x;
2093  CGFloat expected = xDelta;
2094  CGFloat actual = [bar_ indicatorPosForDragToPoint:targetPoint];
2095  EXPECT_CGFLOAT_EQ(expected, actual);
2096  targetButton = [bar_ buttonWithTitleEqualTo:@"2f"];
2097  actual = [bar_ indicatorPosForDragToPoint:[targetButton right]];
2098  targetButton = [bar_ buttonWithTitleEqualTo:@"3b"];
2099  expected = [targetButton left].x - baseOffset + xDelta;
2100  EXPECT_CGFLOAT_EQ(expected, actual);
2101  targetButton = [bar_ buttonWithTitleEqualTo:@"4b"];
2102  targetPoint = [targetButton right];
2103  targetPoint.x += 100;  // Somewhere off to the right.
2104  expected = NSMaxX([targetButton frame]) + xDelta;
2105  actual = [bar_ indicatorPosForDragToPoint:targetPoint];
2106  EXPECT_CGFLOAT_EQ(expected, actual);
2107}
2108
2109TEST_F(BookmarkBarControllerDragDropTest, PulseButton) {
2110  BookmarkModel* model = helper_.profile()->GetBookmarkModel();
2111  const BookmarkNode* root = model->GetBookmarkBarNode();
2112  GURL gurl("http://www.google.com");
2113  const BookmarkNode* node = model->AddURL(root, root->child_count(),
2114                                           ASCIIToUTF16("title"), gurl);
2115
2116  BookmarkButton* button = [[bar_ buttons] objectAtIndex:0];
2117  EXPECT_FALSE([button isContinuousPulsing]);
2118
2119  NSValue *value = [NSValue valueWithPointer:node];
2120  NSDictionary *dict = [NSDictionary
2121                         dictionaryWithObjectsAndKeys:value,
2122                         bookmark_button::kBookmarkKey,
2123                         [NSNumber numberWithBool:YES],
2124                         bookmark_button::kBookmarkPulseFlagKey,
2125                         nil];
2126  [[NSNotificationCenter defaultCenter]
2127        postNotificationName:bookmark_button::kPulseBookmarkButtonNotification
2128                      object:nil
2129                    userInfo:dict];
2130  EXPECT_TRUE([button isContinuousPulsing]);
2131
2132  dict = [NSDictionary dictionaryWithObjectsAndKeys:value,
2133                       bookmark_button::kBookmarkKey,
2134                       [NSNumber numberWithBool:NO],
2135                       bookmark_button::kBookmarkPulseFlagKey,
2136                       nil];
2137  [[NSNotificationCenter defaultCenter]
2138        postNotificationName:bookmark_button::kPulseBookmarkButtonNotification
2139                      object:nil
2140                    userInfo:dict];
2141  EXPECT_FALSE([button isContinuousPulsing]);
2142}
2143
2144TEST_F(BookmarkBarControllerDragDropTest, DragBookmarkDataToTrash) {
2145  BookmarkModel& model(*helper_.profile()->GetBookmarkModel());
2146  const BookmarkNode* root = model.GetBookmarkBarNode();
2147  const std::string model_string("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2148                                  "2f3b ] 3b 4b ");
2149  model_test_utils::AddNodesFromModelString(model, root, model_string);
2150
2151  // Validate initial model.
2152  std::string actual = model_test_utils::ModelStringFromNode(root);
2153  EXPECT_EQ(model_string, actual);
2154
2155  int oldChildCount = root->child_count();
2156
2157  // Drag a button to the trash.
2158  BookmarkButton* buttonToDelete = [bar_ buttonWithTitleEqualTo:@"3b"];
2159  ASSERT_TRUE(buttonToDelete);
2160  EXPECT_TRUE([bar_ canDragBookmarkButtonToTrash:buttonToDelete]);
2161  [bar_ didDragBookmarkToTrash:buttonToDelete];
2162
2163  // There should be one less button in the bar.
2164  int newChildCount = root->child_count();
2165  EXPECT_EQ(oldChildCount - 1, newChildCount);
2166  // Verify the model.
2167  const std::string expected("1b 2f:[ 2f1b 2f2f:[ 2f2f1b 2f2f2b 2f2f3b ] "
2168                             "2f3b ] 4b ");
2169  actual = model_test_utils::ModelStringFromNode(root);
2170  EXPECT_EQ(expected, actual);
2171
2172  // Verify that the other bookmark folder can't be deleted.
2173  BookmarkButton *otherButton = [bar_ otherBookmarksButton];
2174  EXPECT_FALSE([bar_ canDragBookmarkButtonToTrash:otherButton]);
2175}
2176
2177}  // namespace
2178