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