Home | History | Annotate | Download | only in cocoa
      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 #include <Cocoa/Cocoa.h>
      6 
      7 #include "base/mac/scoped_nsobject.h"
      8 #include "base/memory/scoped_ptr.h"
      9 #include "base/message_loop/message_loop.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #import "chrome/browser/ui/cocoa/bubble_view.h"
     12 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
     13 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 #import "testing/gtest_mac.h"
     16 #include "testing/platform_test.h"
     17 #import "third_party/ocmock/OCMock/OCMock.h"
     18 #include "ui/gfx/point.h"
     19 #include "url/gurl.h"
     20 
     21 using base::UTF8ToUTF16;
     22 
     23 // The test delegate records all of the status bubble object's state
     24 // transitions.
     25 @interface StatusBubbleMacTestDelegate : NSObject {
     26  @private
     27   NSWindow* window_;  // Weak.
     28   NSPoint baseFrameOffset_;
     29   std::vector<StatusBubbleMac::StatusBubbleState> states_;
     30 }
     31 - (id)initWithWindow:(NSWindow*)window;
     32 - (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset;
     33 - (NSRect)statusBubbleBaseFrame;
     34 - (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state;
     35 @end
     36 @implementation StatusBubbleMacTestDelegate
     37 - (id)initWithWindow:(NSWindow*)window {
     38   if ((self = [super init])) {
     39     window_ = window;
     40     baseFrameOffset_ = NSZeroPoint;
     41   }
     42   return self;
     43 }
     44 - (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset {
     45   baseFrameOffset_ = baseFrameOffset;
     46 }
     47 - (NSRect)statusBubbleBaseFrame {
     48   NSView* contentView = [window_ contentView];
     49   NSRect baseFrame = [contentView convertRect:[contentView frame] toView:nil];
     50   if (baseFrameOffset_.x > 0 || baseFrameOffset_.y > 0) {
     51     baseFrame = NSOffsetRect(baseFrame, baseFrameOffset_.x, baseFrameOffset_.y);
     52     baseFrame.size.width -= baseFrameOffset_.x;
     53     baseFrame.size.height -= baseFrameOffset_.y;
     54   }
     55   return baseFrame;
     56 }
     57 - (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state {
     58   states_.push_back(state);
     59 }
     60 - (std::vector<StatusBubbleMac::StatusBubbleState>*)states {
     61   return &states_;
     62 }
     63 @end
     64 
     65 // This class implements, for testing purposes, a subclass of |StatusBubbleMac|
     66 // whose |MouseMoved()| method does nothing. This lets the tests fake the mouse
     67 // position and avoid being affected by the true mouse position.
     68 class StatusBubbleMacIgnoreMouseMoved : public StatusBubbleMac {
     69  public:
     70   StatusBubbleMacIgnoreMouseMoved(NSWindow* parent, id delegate)
     71       : StatusBubbleMac(parent, delegate), mouseLocation_(0, 0) {
     72     // Set the fake mouse position to the top right of the content area.
     73     NSRect contentBounds = [[parent contentView] bounds];
     74     mouseLocation_.SetPoint(NSMaxX(contentBounds), NSMaxY(contentBounds));
     75   }
     76 
     77   virtual void MouseMoved(
     78       const gfx::Point& location,
     79       bool left_content) OVERRIDE {
     80   }
     81 
     82   virtual gfx::Point GetMouseLocation() OVERRIDE {
     83     return mouseLocation_;
     84   }
     85 
     86   void SetMouseLocationForTesting(int x, int y) {
     87     mouseLocation_.SetPoint(x, y);
     88     StatusBubbleMac::MouseMoved(gfx::Point(x, y), false);
     89   }
     90 
     91  private:
     92   gfx::Point mouseLocation_;
     93 };
     94 
     95 class StatusBubbleMacTest : public CocoaTest {
     96  public:
     97   virtual void SetUp() {
     98     CocoaTest::SetUp();
     99     NSWindow* window = test_window();
    100     EXPECT_TRUE(window);
    101     delegate_.reset(
    102         [[StatusBubbleMacTestDelegate alloc] initWithWindow: window]);
    103     EXPECT_TRUE(delegate_.get());
    104     bubble_ = new StatusBubbleMacIgnoreMouseMoved(window, delegate_);
    105     EXPECT_TRUE(bubble_);
    106 
    107     // Turn off delays and transitions for test mode.  This doesn't just speed
    108     // things along, it's actually required to get StatusBubbleMac to behave
    109     // synchronously, because the tests here don't know how to wait for
    110     // results.  This allows these tests to be much more complete with a
    111     // minimal loss of coverage and without any heinous rearchitecting.
    112     bubble_->immediate_ = true;
    113 
    114     EXPECT_TRUE(bubble_->window_);  // immediately creates window
    115   }
    116 
    117   virtual void TearDown() {
    118     // Not using a scoped_ptr because bubble must be deleted before calling
    119     // TearDown to get rid of bubble's window.
    120     delete bubble_;
    121     CocoaTest::TearDown();
    122   }
    123 
    124   bool IsVisible() {
    125     if (![bubble_->window_ isVisible])
    126       return false;
    127     return [bubble_->window_ alphaValue] > 0.0;
    128   }
    129   NSString* GetText() {
    130     return bubble_->status_text_;
    131   }
    132   NSString* GetURLText() {
    133     return bubble_->url_text_;
    134   }
    135   NSString* GetBubbleViewText() {
    136     BubbleView* bubbleView = [bubble_->window_ contentView];
    137     return [bubbleView content];
    138   }
    139   StatusBubbleWindow* GetWindow() {
    140     return bubble_->window_;
    141   }
    142   NSWindow* parent() {
    143     return bubble_->parent_;
    144   }
    145   StatusBubbleMac::StatusBubbleState GetState() {
    146     return bubble_->state_;
    147   }
    148   void SetState(StatusBubbleMac::StatusBubbleState state) {
    149     bubble_->SetState(state);
    150   }
    151   std::vector<StatusBubbleMac::StatusBubbleState>* States() {
    152     return [delegate_ states];
    153   }
    154   StatusBubbleMac::StatusBubbleState StateAt(int index) {
    155     return (*States())[index];
    156   }
    157 
    158   bool IsPointInBubble(int x, int y) {
    159     return NSPointInRect(NSMakePoint(x, y), [GetWindow() frame]);
    160   }
    161 
    162   void SetMouseLocation(int relative_x, int relative_y) {
    163     // Convert to screen coordinates.
    164     NSRect window_frame = [test_window() frame];
    165     int x = relative_x + window_frame.origin.x;
    166     int y = relative_y + window_frame.origin.y;
    167 
    168     ((StatusBubbleMacIgnoreMouseMoved*)
    169       bubble_)->SetMouseLocationForTesting(x, y);
    170   }
    171 
    172   // Test helper for moving the fake mouse location, and checking that
    173   // the bubble avoids that location.
    174   // For convenience & clarity, coordinates are relative to the main window.
    175   bool CheckAvoidsMouse(int relative_x, int relative_y) {
    176     SetMouseLocation(relative_x, relative_y);
    177     return !IsPointInBubble(relative_x, relative_y);
    178   }
    179 
    180   base::MessageLoop message_loop_;
    181   base::scoped_nsobject<StatusBubbleMacTestDelegate> delegate_;
    182   StatusBubbleMac* bubble_;  // Strong.
    183 };
    184 
    185 TEST_F(StatusBubbleMacTest, SetStatus) {
    186   bubble_->SetStatus(base::string16());
    187   bubble_->SetStatus(UTF8ToUTF16("This is a test"));
    188   EXPECT_NSEQ(@"This is a test", GetText());
    189   EXPECT_TRUE(IsVisible());
    190 
    191   // Set the status to the exact same thing again
    192   bubble_->SetStatus(UTF8ToUTF16("This is a test"));
    193   EXPECT_NSEQ(@"This is a test", GetText());
    194 
    195   // Hide it
    196   bubble_->SetStatus(base::string16());
    197   EXPECT_FALSE(IsVisible());
    198 }
    199 
    200 TEST_F(StatusBubbleMacTest, SetURL) {
    201   bubble_->SetURL(GURL(), std::string());
    202   EXPECT_FALSE(IsVisible());
    203   bubble_->SetURL(GURL("bad url"), std::string());
    204   EXPECT_FALSE(IsVisible());
    205   bubble_->SetURL(GURL("http://"), std::string());
    206   EXPECT_TRUE(IsVisible());
    207   EXPECT_NSEQ(@"http:", GetURLText());
    208   bubble_->SetURL(GURL("about:blank"), std::string());
    209   EXPECT_TRUE(IsVisible());
    210   EXPECT_NSEQ(@"about:blank", GetURLText());
    211   bubble_->SetURL(GURL("foopy://"), std::string());
    212   EXPECT_TRUE(IsVisible());
    213   EXPECT_NSEQ(@"foopy://", GetURLText());
    214   bubble_->SetURL(GURL("http://www.cnn.com"), std::string());
    215   EXPECT_TRUE(IsVisible());
    216   EXPECT_NSEQ(@"www.cnn.com", GetURLText());
    217 }
    218 
    219 // Test hiding bubble that's already hidden.
    220 TEST_F(StatusBubbleMacTest, Hides) {
    221   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    222   EXPECT_TRUE(IsVisible());
    223   bubble_->Hide();
    224   EXPECT_FALSE(IsVisible());
    225   bubble_->Hide();
    226   EXPECT_FALSE(IsVisible());
    227 }
    228 
    229 // Test the "main"/"backup" behavior in StatusBubbleMac::SetText().
    230 TEST_F(StatusBubbleMacTest, SetStatusAndURL) {
    231   EXPECT_FALSE(IsVisible());
    232   bubble_->SetStatus(UTF8ToUTF16("Status"));
    233   EXPECT_TRUE(IsVisible());
    234   EXPECT_NSEQ(@"Status", GetBubbleViewText());
    235   bubble_->SetURL(GURL("http://www.nytimes.com"), std::string());
    236   EXPECT_TRUE(IsVisible());
    237   EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
    238   bubble_->SetURL(GURL(), std::string());
    239   EXPECT_TRUE(IsVisible());
    240   EXPECT_NSEQ(@"Status", GetBubbleViewText());
    241   bubble_->SetStatus(base::string16());
    242   EXPECT_FALSE(IsVisible());
    243   bubble_->SetURL(GURL("http://www.nytimes.com"), std::string());
    244   EXPECT_TRUE(IsVisible());
    245   EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
    246   bubble_->SetStatus(UTF8ToUTF16("Status"));
    247   EXPECT_TRUE(IsVisible());
    248   EXPECT_NSEQ(@"Status", GetBubbleViewText());
    249   bubble_->SetStatus(base::string16());
    250   EXPECT_TRUE(IsVisible());
    251   EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
    252   bubble_->SetURL(GURL(), std::string());
    253   EXPECT_FALSE(IsVisible());
    254 }
    255 
    256 // Test that the status bubble goes through the correct delay and fade states.
    257 // The delay and fade duration are simulated and not actually experienced
    258 // during the test because StatusBubbleMacTest sets immediate_ mode.
    259 TEST_F(StatusBubbleMacTest, StateTransitions) {
    260   // First, some sanity
    261 
    262   EXPECT_FALSE(IsVisible());
    263   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    264 
    265   States()->clear();
    266   EXPECT_TRUE(States()->empty());
    267 
    268   bubble_->SetStatus(base::string16());
    269   EXPECT_FALSE(IsVisible());
    270   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    271   EXPECT_TRUE(States()->empty());  // no change from initial kBubbleHidden state
    272 
    273   // Next, a few ordinary cases
    274 
    275   // Test StartShowing from kBubbleHidden
    276   bubble_->SetStatus(UTF8ToUTF16("Status"));
    277   EXPECT_TRUE(IsVisible());
    278   // Check GetState before checking States to make sure that all state
    279   // transitions have been flushed to States.
    280   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    281   EXPECT_EQ(3u, States()->size());
    282   EXPECT_EQ(StatusBubbleMac::kBubbleShowingTimer, StateAt(0));
    283   EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(1));
    284   EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(2));
    285 
    286   // Test StartShowing from kBubbleShown with the same message
    287   States()->clear();
    288   bubble_->SetStatus(UTF8ToUTF16("Status"));
    289   EXPECT_TRUE(IsVisible());
    290   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    291   EXPECT_TRUE(States()->empty());
    292 
    293   // Test StartShowing from kBubbleShown with a different message
    294   bubble_->SetStatus(UTF8ToUTF16("New Status"));
    295   EXPECT_TRUE(IsVisible());
    296   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    297   EXPECT_TRUE(States()->empty());
    298 
    299   // Test StartHiding from kBubbleShown
    300   bubble_->SetStatus(base::string16());
    301   EXPECT_FALSE(IsVisible());
    302   // Check GetState before checking States to make sure that all state
    303   // transitions have been flushed to States.
    304   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    305   EXPECT_EQ(3u, States()->size());
    306   EXPECT_EQ(StatusBubbleMac::kBubbleHidingTimer, StateAt(0));
    307   EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(1));
    308   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(2));
    309 
    310   // Test StartHiding from kBubbleHidden
    311   States()->clear();
    312   bubble_->SetStatus(base::string16());
    313   EXPECT_FALSE(IsVisible());
    314   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    315   EXPECT_TRUE(States()->empty());
    316 
    317   // Now, the edge cases
    318 
    319   // Test StartShowing from kBubbleShowingTimer
    320   bubble_->SetStatus(UTF8ToUTF16("Status"));
    321   SetState(StatusBubbleMac::kBubbleShowingTimer);
    322   [GetWindow() setAlphaValue:0.0];
    323   States()->clear();
    324   EXPECT_TRUE(States()->empty());
    325   bubble_->SetStatus(UTF8ToUTF16("Status"));
    326   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    327   EXPECT_EQ(2u, States()->size());
    328   EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
    329   EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));
    330 
    331   // Test StartShowing from kBubbleShowingFadeIn
    332   bubble_->SetStatus(UTF8ToUTF16("Status"));
    333   SetState(StatusBubbleMac::kBubbleShowingFadeIn);
    334   [GetWindow() setAlphaValue:0.5];
    335   States()->clear();
    336   EXPECT_TRUE(States()->empty());
    337   bubble_->SetStatus(UTF8ToUTF16("Status"));
    338   // The actual state values can't be tested in immediate_ mode because
    339   // the window wasn't actually fading in.  Without immediate_ mode,
    340   // expect kBubbleShown.
    341   bubble_->SetStatus(base::string16());  // Go back to a deterministic state.
    342 
    343   // Test StartShowing from kBubbleHidingTimer
    344   bubble_->SetStatus(base::string16());
    345   SetState(StatusBubbleMac::kBubbleHidingTimer);
    346   [GetWindow() setAlphaValue:1.0];
    347   States()->clear();
    348   EXPECT_TRUE(States()->empty());
    349   bubble_->SetStatus(UTF8ToUTF16("Status"));
    350   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    351   EXPECT_EQ(1u, States()->size());
    352   EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(0));
    353 
    354   // Test StartShowing from kBubbleHidingFadeOut
    355   bubble_->SetStatus(base::string16());
    356   SetState(StatusBubbleMac::kBubbleHidingFadeOut);
    357   [GetWindow() setAlphaValue:0.5];
    358   States()->clear();
    359   EXPECT_TRUE(States()->empty());
    360   bubble_->SetStatus(UTF8ToUTF16("Status"));
    361   EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
    362   EXPECT_EQ(2u, States()->size());
    363   EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
    364   EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));
    365 
    366   // Test StartHiding from kBubbleShowingTimer
    367   bubble_->SetStatus(UTF8ToUTF16("Status"));
    368   SetState(StatusBubbleMac::kBubbleShowingTimer);
    369   [GetWindow() setAlphaValue:0.0];
    370   States()->clear();
    371   EXPECT_TRUE(States()->empty());
    372   bubble_->SetStatus(base::string16());
    373   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    374   EXPECT_EQ(1u, States()->size());
    375   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
    376 
    377   // Test StartHiding from kBubbleShowingFadeIn
    378   bubble_->SetStatus(UTF8ToUTF16("Status"));
    379   SetState(StatusBubbleMac::kBubbleShowingFadeIn);
    380   [GetWindow() setAlphaValue:0.5];
    381   States()->clear();
    382   EXPECT_TRUE(States()->empty());
    383   bubble_->SetStatus(base::string16());
    384   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    385   EXPECT_EQ(2u, States()->size());
    386   EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
    387   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));
    388 
    389   // Test StartHiding from kBubbleHidingTimer
    390   bubble_->SetStatus(base::string16());
    391   SetState(StatusBubbleMac::kBubbleHidingTimer);
    392   [GetWindow() setAlphaValue:1.0];
    393   States()->clear();
    394   EXPECT_TRUE(States()->empty());
    395   bubble_->SetStatus(base::string16());
    396   // The actual state values can't be tested in immediate_ mode because
    397   // the timer wasn't actually running.  Without immediate_ mode, expect
    398   // kBubbleHidingFadeOut and kBubbleHidden.
    399   // Go back to a deterministic state.
    400   bubble_->SetStatus(UTF8ToUTF16("Status"));
    401 
    402   // Test StartHiding from kBubbleHidingFadeOut
    403   bubble_->SetStatus(base::string16());
    404   SetState(StatusBubbleMac::kBubbleHidingFadeOut);
    405   [GetWindow() setAlphaValue:0.5];
    406   States()->clear();
    407   EXPECT_TRUE(States()->empty());
    408   bubble_->SetStatus(base::string16());
    409   // The actual state values can't be tested in immediate_ mode because
    410   // the window wasn't actually fading out.  Without immediate_ mode, expect
    411   // kBubbleHidden.
    412   // Go back to a deterministic state.
    413   bubble_->SetStatus(UTF8ToUTF16("Status"));
    414 
    415   // Test Hide from kBubbleHidden
    416   bubble_->SetStatus(base::string16());
    417   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    418   States()->clear();
    419   EXPECT_TRUE(States()->empty());
    420   bubble_->Hide();
    421   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    422   EXPECT_TRUE(States()->empty());
    423 
    424   // Test Hide from kBubbleShowingTimer
    425   bubble_->SetStatus(UTF8ToUTF16("Status"));
    426   SetState(StatusBubbleMac::kBubbleShowingTimer);
    427   [GetWindow() setAlphaValue:0.0];
    428   States()->clear();
    429   EXPECT_TRUE(States()->empty());
    430   bubble_->Hide();
    431   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    432   EXPECT_EQ(1u, States()->size());
    433   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
    434 
    435   // Test Hide from kBubbleShowingFadeIn
    436   bubble_->SetStatus(UTF8ToUTF16("Status"));
    437   SetState(StatusBubbleMac::kBubbleShowingFadeIn);
    438   [GetWindow() setAlphaValue:0.5];
    439   States()->clear();
    440   EXPECT_TRUE(States()->empty());
    441   bubble_->Hide();
    442   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    443   EXPECT_EQ(2u, States()->size());
    444   EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
    445   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));
    446 
    447   // Test Hide from kBubbleShown
    448   bubble_->SetStatus(UTF8ToUTF16("Status"));
    449   States()->clear();
    450   EXPECT_TRUE(States()->empty());
    451   bubble_->Hide();
    452   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    453   EXPECT_EQ(1u, States()->size());
    454   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
    455 
    456   // Test Hide from kBubbleHidingTimer
    457   bubble_->SetStatus(UTF8ToUTF16("Status"));
    458   SetState(StatusBubbleMac::kBubbleHidingTimer);
    459   States()->clear();
    460   EXPECT_TRUE(States()->empty());
    461   bubble_->Hide();
    462   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    463   EXPECT_EQ(1u, States()->size());
    464   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
    465 
    466   // Test Hide from kBubbleHidingFadeOut
    467   bubble_->SetStatus(UTF8ToUTF16("Status"));
    468   SetState(StatusBubbleMac::kBubbleHidingFadeOut);
    469   [GetWindow() setAlphaValue:0.5];
    470   States()->clear();
    471   EXPECT_TRUE(States()->empty());
    472   bubble_->Hide();
    473   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
    474   EXPECT_EQ(1u, States()->size());
    475   EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
    476 }
    477 
    478 TEST_F(StatusBubbleMacTest, Delete) {
    479   NSWindow* window = test_window();
    480   // Create and delete immediately.
    481   StatusBubbleMac* bubble = new StatusBubbleMac(window, nil);
    482   delete bubble;
    483 
    484   // Create then delete while visible.
    485   bubble = new StatusBubbleMac(window, nil);
    486   bubble->SetStatus(UTF8ToUTF16("showing"));
    487   delete bubble;
    488 }
    489 
    490 TEST_F(StatusBubbleMacTest, UpdateSizeAndPosition) {
    491   // Test |UpdateSizeAndPosition()| when status bubble does not exist (shouldn't
    492   // crash; shouldn't create window).
    493   EXPECT_TRUE(GetWindow());
    494   bubble_->UpdateSizeAndPosition();
    495   EXPECT_TRUE(GetWindow());
    496 
    497   // Create a status bubble (with contents) and call resize (without actually
    498   // resizing); the frame size shouldn't change.
    499   bubble_->SetStatus(UTF8ToUTF16("UpdateSizeAndPosition test"));
    500   ASSERT_TRUE(GetWindow());
    501   NSRect rect_before = [GetWindow() frame];
    502   bubble_->UpdateSizeAndPosition();
    503   NSRect rect_after = [GetWindow() frame];
    504   EXPECT_TRUE(NSEqualRects(rect_before, rect_after));
    505 
    506   // Move the window and call resize; only the origin should change.
    507   NSWindow* window = test_window();
    508   ASSERT_TRUE(window);
    509   NSRect frame = [window frame];
    510   rect_before = [GetWindow() frame];
    511   frame.origin.x += 10.0;  // (fairly arbitrary nonzero value)
    512   frame.origin.y += 10.0;  // (fairly arbitrary nonzero value)
    513   [window setFrame:frame display:YES];
    514   bubble_->UpdateSizeAndPosition();
    515   rect_after = [GetWindow() frame];
    516   EXPECT_NE(rect_before.origin.x, rect_after.origin.x);
    517   EXPECT_NE(rect_before.origin.y, rect_after.origin.y);
    518   EXPECT_EQ(rect_before.size.width, rect_after.size.width);
    519   EXPECT_EQ(rect_before.size.height, rect_after.size.height);
    520 
    521   // Resize the window (without moving). The origin shouldn't change. The width
    522   // should change (in the current implementation), but not the height.
    523   frame = [window frame];
    524   rect_before = [GetWindow() frame];
    525   frame.size.width += 50.0;   // (fairly arbitrary nonzero value)
    526   frame.size.height += 50.0;  // (fairly arbitrary nonzero value)
    527   [window setFrame:frame display:YES];
    528   bubble_->UpdateSizeAndPosition();
    529   rect_after = [GetWindow() frame];
    530   EXPECT_EQ(rect_before.origin.x, rect_after.origin.x);
    531   EXPECT_EQ(rect_before.origin.y, rect_after.origin.y);
    532   EXPECT_NE(rect_before.size.width, rect_after.size.width);
    533   EXPECT_EQ(rect_before.size.height, rect_after.size.height);
    534 }
    535 
    536 TEST_F(StatusBubbleMacTest, MovingWindowUpdatesPosition) {
    537   NSWindow* window = test_window();
    538 
    539   // Show the bubble and make sure it has the same origin as |window|.
    540   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    541   StatusBubbleWindow* child = GetWindow();
    542   EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));
    543 
    544   // Hide the bubble, move the window, and show it again.
    545   bubble_->Hide();
    546   NSRect frame = [window frame];
    547   frame.origin.x += 50;
    548   [window setFrame:frame display:YES];
    549   bubble_->SetStatus(UTF8ToUTF16("Reshowing"));
    550 
    551   // The bubble should reattach in the correct location.
    552   child = GetWindow();
    553   EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));
    554 }
    555 
    556 TEST_F(StatusBubbleMacTest, StatuBubbleRespectsBaseFrameLimits) {
    557   NSWindow* window = test_window();
    558 
    559   // Show the bubble and make sure it has the same origin as |window|.
    560   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    561   StatusBubbleWindow* child = GetWindow();
    562   EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));
    563 
    564   // Hide the bubble, change base frame offset, and show it again.
    565   bubble_->Hide();
    566 
    567   NSPoint baseFrameOffset = NSMakePoint(0, [window frame].size.height / 3);
    568   EXPECT_GT(baseFrameOffset.y, 0);
    569   [delegate_ forceBaseFrameOffset:baseFrameOffset];
    570 
    571   bubble_->SetStatus(UTF8ToUTF16("Reshowing"));
    572 
    573   // The bubble should reattach in the correct location.
    574   child = GetWindow();
    575   NSPoint expectedOrigin = [window frame].origin;
    576   expectedOrigin.x += baseFrameOffset.x;
    577   expectedOrigin.y += baseFrameOffset.y;
    578   EXPECT_TRUE(NSEqualPoints(expectedOrigin, [child frame].origin));
    579 }
    580 
    581 TEST_F(StatusBubbleMacTest, ExpandBubble) {
    582   NSWindow* window = test_window();
    583 
    584   // The system font changes between OSX 10.9 and OSX 10.10. Use the system
    585   // font from OSX 10.9 for this test.
    586   id mockContentView =
    587       [OCMockObject partialMockForObject:[GetWindow() contentView]];
    588   [[[mockContentView stub]
    589       andReturn:[NSFont fontWithName:@"Lucida Grande" size:11]] font];
    590 
    591   ASSERT_TRUE(window);
    592   NSRect window_frame = [window frame];
    593   window_frame.size.width = 600.0;
    594   [window setFrame:window_frame display:YES];
    595 
    596   // Check basic expansion
    597   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    598   EXPECT_TRUE(IsVisible());
    599   bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"),
    600                   std::string());
    601   EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
    602   bubble_->ExpandBubble();
    603   EXPECT_TRUE(IsVisible());
    604   EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText());
    605   bubble_->Hide();
    606 
    607   // Make sure bubble resets after hide.
    608   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    609   bubble_->SetURL(GURL("http://www.snickersnee.com/pioneer_fishstix.html"),
    610                   std::string());
    611   EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
    612   // ...and that it expands again properly.
    613   bubble_->ExpandBubble();
    614   EXPECT_NSEQ(@"www.snickersnee.com/pioneer_fishstix.html", GetURLText());
    615   // ...again, again!
    616   bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"),
    617                   std::string());
    618   bubble_->ExpandBubble();
    619   EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText());
    620   bubble_->Hide();
    621 
    622   window_frame = [window frame];
    623   window_frame.size.width = 300.0;
    624   [window setFrame:window_frame display:YES];
    625 
    626   // Very long URL's will be cut off even in the expanded state.
    627   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    628   const char veryLongUrl[] =
    629       "http://www.diewahrscheinlichlaengstepralinederwelt.com/duuuuplo.html";
    630   bubble_->SetURL(GURL(veryLongUrl), std::string());
    631   EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
    632   bubble_->ExpandBubble();
    633   EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
    634 }
    635 
    636 TEST_F(StatusBubbleMacTest, BubbleAvoidsMouse) {
    637   NSWindow* window = test_window();
    638 
    639   // All coordinates here are relative to the window origin.
    640 
    641   // Initially, the bubble should appear in the bottom left.
    642   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    643   EXPECT_TRUE(IsPointInBubble(0, 0));
    644   bubble_->Hide();
    645 
    646   // Check that the bubble doesn't appear in the left corner if the
    647   // mouse is currently located there.
    648   SetMouseLocation(0, 0);
    649   bubble_->SetStatus(UTF8ToUTF16("Showing"));
    650   EXPECT_FALSE(IsPointInBubble(0, 0));
    651 
    652   // Leave the bubble visible, and try moving the mouse around.
    653   int smallValue = NSHeight([GetWindow() frame]) / 2;
    654   EXPECT_TRUE(CheckAvoidsMouse(0, 0));
    655   EXPECT_TRUE(CheckAvoidsMouse(smallValue, 0));
    656   EXPECT_TRUE(CheckAvoidsMouse(0, smallValue));
    657   EXPECT_TRUE(CheckAvoidsMouse(smallValue, smallValue));
    658 
    659   // Simulate moving the mouse down from the top of the window.
    660   for (int y = NSHeight([window frame]); y >= 0; y -= smallValue) {
    661     ASSERT_TRUE(CheckAvoidsMouse(smallValue, y));
    662   }
    663 
    664   // Simulate moving the mouse from left to right.
    665   int windowWidth = NSWidth([window frame]);
    666   for (int x = 0; x < windowWidth; x += smallValue) {
    667     ASSERT_TRUE(CheckAvoidsMouse(x, smallValue));
    668   }
    669 
    670   // Simulate moving the mouse from right to left.
    671   for (int x = windowWidth; x >= 0; x -= smallValue) {
    672     ASSERT_TRUE(CheckAvoidsMouse(x, smallValue));
    673   }
    674 }
    675