// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <Cocoa/Cocoa.h>

#include "base/memory/scoped_nsobject.h"
#include "base/memory/scoped_ptr.h"
#include "base/utf_string_conversions.h"
#import "chrome/browser/ui/cocoa/bubble_view.h"
#import "chrome/browser/ui/cocoa/browser_test_helper.h"
#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#import "chrome/browser/ui/cocoa/status_bubble_mac.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#include "testing/platform_test.h"

// The test delegate records all of the status bubble object's state
// transitions.
@interface StatusBubbleMacTestDelegate : NSObject {
 @private
  NSWindow* window_;  // Weak.
  NSPoint baseFrameOffset_;
  std::vector<StatusBubbleMac::StatusBubbleState> states_;
}
- (id)initWithWindow:(NSWindow*)window;
- (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset;
- (NSRect)statusBubbleBaseFrame;
- (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state;
@end
@implementation StatusBubbleMacTestDelegate
- (id)initWithWindow:(NSWindow*)window {
  if ((self = [super init])) {
    window_ = window;
    baseFrameOffset_ = NSMakePoint(0, 0);
  }
  return self;
}
- (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset {
  baseFrameOffset_ = baseFrameOffset;
}
- (NSRect)statusBubbleBaseFrame {
  NSView* contentView = [window_ contentView];
  NSRect baseFrame = [contentView convertRect:[contentView frame] toView:nil];
  if (baseFrameOffset_.x > 0 || baseFrameOffset_.y > 0) {
    baseFrame = NSOffsetRect(baseFrame, baseFrameOffset_.x, baseFrameOffset_.y);
    baseFrame.size.width -= baseFrameOffset_.x;
    baseFrame.size.height -= baseFrameOffset_.y;
  }
  return baseFrame;
}
- (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state {
  states_.push_back(state);
}
- (std::vector<StatusBubbleMac::StatusBubbleState>*)states {
  return &states_;
}
@end

// This class implements, for testing purposes, a subclass of |StatusBubbleMac|
// whose |MouseMoved()| method does nothing. (Ideally, we'd have a way of
// controlling the "mouse" location, but the current implementation of
// |StatusBubbleMac| uses |[NSEvent mouseLocation]| directly.) Without this,
// tests can be flaky since results may depend on the mouse location.
class StatusBubbleMacIgnoreMouseMoved : public StatusBubbleMac {
 public:
  StatusBubbleMacIgnoreMouseMoved(NSWindow* parent, id delegate)
      : StatusBubbleMac(parent, delegate) {}

  virtual void MouseMoved(const gfx::Point& location, bool left_content) {}
};

class StatusBubbleMacTest : public CocoaTest {
 public:
  virtual void SetUp() {
    CocoaTest::SetUp();
    NSWindow* window = test_window();
    EXPECT_TRUE(window);
    delegate_.reset(
        [[StatusBubbleMacTestDelegate alloc] initWithWindow: window]);
    EXPECT_TRUE(delegate_.get());
    bubble_ = new StatusBubbleMacIgnoreMouseMoved(window, delegate_);
    EXPECT_TRUE(bubble_);

    // Turn off delays and transitions for test mode.  This doesn't just speed
    // things along, it's actually required to get StatusBubbleMac to behave
    // synchronously, because the tests here don't know how to wait for
    // results.  This allows these tests to be much more complete with a
    // minimal loss of coverage and without any heinous rearchitecting.
    bubble_->immediate_ = true;

    EXPECT_TRUE(bubble_->window_);  // immediately creates window
  }

  virtual void TearDown() {
    // Not using a scoped_ptr because bubble must be deleted before calling
    // TearDown to get rid of bubble's window.
    delete bubble_;
    CocoaTest::TearDown();
  }

  bool IsVisible() {
    if (![bubble_->window_ isVisible])
      return false;
    return [bubble_->window_ alphaValue] > 0.0;
  }
  NSString* GetText() {
    return bubble_->status_text_;
  }
  NSString* GetURLText() {
    return bubble_->url_text_;
  }
  NSString* GetBubbleViewText() {
    BubbleView* bubbleView = [bubble_->window_ contentView];
    return [bubbleView content];
  }
  NSWindow* GetWindow() {
    return bubble_->window_;
  }
  NSWindow* parent() {
    return bubble_->parent_;
  }
  StatusBubbleMac::StatusBubbleState GetState() {
    return bubble_->state_;
  }
  void SetState(StatusBubbleMac::StatusBubbleState state) {
    bubble_->SetState(state);
  }
  std::vector<StatusBubbleMac::StatusBubbleState>* States() {
    return [delegate_ states];
  }
  StatusBubbleMac::StatusBubbleState StateAt(int index) {
    return (*States())[index];
  }
  BrowserTestHelper browser_helper_;
  scoped_nsobject<StatusBubbleMacTestDelegate> delegate_;
  StatusBubbleMac* bubble_;  // Strong.
};

TEST_F(StatusBubbleMacTest, SetStatus) {
  bubble_->SetStatus(string16());
  bubble_->SetStatus(UTF8ToUTF16("This is a test"));
  EXPECT_NSEQ(@"This is a test", GetText());
  EXPECT_TRUE(IsVisible());

  // Set the status to the exact same thing again
  bubble_->SetStatus(UTF8ToUTF16("This is a test"));
  EXPECT_NSEQ(@"This is a test", GetText());

  // Hide it
  bubble_->SetStatus(string16());
  EXPECT_FALSE(IsVisible());
}

TEST_F(StatusBubbleMacTest, SetURL) {
  bubble_->SetURL(GURL(), string16());
  EXPECT_FALSE(IsVisible());
  bubble_->SetURL(GURL("bad url"), string16());
  EXPECT_FALSE(IsVisible());
  bubble_->SetURL(GURL("http://"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"http:", GetURLText());
  bubble_->SetURL(GURL("about:blank"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"about:blank", GetURLText());
  bubble_->SetURL(GURL("foopy://"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"foopy://", GetURLText());
  bubble_->SetURL(GURL("http://www.cnn.com"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"www.cnn.com", GetURLText());
}

// Test hiding bubble that's already hidden.
TEST_F(StatusBubbleMacTest, Hides) {
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  EXPECT_TRUE(IsVisible());
  bubble_->Hide();
  EXPECT_FALSE(IsVisible());
  bubble_->Hide();
  EXPECT_FALSE(IsVisible());
}

// Test the "main"/"backup" behavior in StatusBubbleMac::SetText().
TEST_F(StatusBubbleMacTest, SetStatusAndURL) {
  EXPECT_FALSE(IsVisible());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"Status", GetBubbleViewText());
  bubble_->SetURL(GURL("http://www.nytimes.com"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
  bubble_->SetURL(GURL(), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"Status", GetBubbleViewText());
  bubble_->SetStatus(string16());
  EXPECT_FALSE(IsVisible());
  bubble_->SetURL(GURL("http://www.nytimes.com"), string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"Status", GetBubbleViewText());
  bubble_->SetStatus(string16());
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText());
  bubble_->SetURL(GURL(), string16());
  EXPECT_FALSE(IsVisible());
}

// Test that the status bubble goes through the correct delay and fade states.
// The delay and fade duration are simulated and not actually experienced
// during the test because StatusBubbleMacTest sets immediate_ mode.
TEST_F(StatusBubbleMacTest, StateTransitions) {
  // First, some sanity

  EXPECT_FALSE(IsVisible());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());

  States()->clear();
  EXPECT_TRUE(States()->empty());

  bubble_->SetStatus(string16());
  EXPECT_FALSE(IsVisible());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_TRUE(States()->empty());  // no change from initial kBubbleHidden state

  // Next, a few ordinary cases

  // Test StartShowing from kBubbleHidden
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_TRUE(IsVisible());
  // Check GetState before checking States to make sure that all state
  // transitions have been flushed to States.
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_EQ(3u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleShowingTimer, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(1));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(2));

  // Test StartShowing from kBubbleShown with the same message
  States()->clear();
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_TRUE(IsVisible());
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_TRUE(States()->empty());

  // Test StartShowing from kBubbleShown with a different message
  bubble_->SetStatus(UTF8ToUTF16("New Status"));
  EXPECT_TRUE(IsVisible());
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_TRUE(States()->empty());

  // Test StartHiding from kBubbleShown
  bubble_->SetStatus(string16());
  EXPECT_FALSE(IsVisible());
  // Check GetState before checking States to make sure that all state
  // transitions have been flushed to States.
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(3u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidingTimer, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(1));
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(2));

  // Test StartHiding from kBubbleHidden
  States()->clear();
  bubble_->SetStatus(string16());
  EXPECT_FALSE(IsVisible());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_TRUE(States()->empty());

  // Now, the edge cases

  // Test StartShowing from kBubbleShowingTimer
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingTimer);
  [GetWindow() setAlphaValue:0.0];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_EQ(2u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));

  // Test StartShowing from kBubbleShowingFadeIn
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingFadeIn);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  // The actual state values can't be tested in immediate_ mode because
  // the window wasn't actually fading in.  Without immediate_ mode,
  // expect kBubbleShown.
  bubble_->SetStatus(string16());  // Go back to a deterministic state.

  // Test StartShowing from kBubbleHidingTimer
  bubble_->SetStatus(string16());
  SetState(StatusBubbleMac::kBubbleHidingTimer);
  [GetWindow() setAlphaValue:1.0];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(0));

  // Test StartShowing from kBubbleHidingFadeOut
  bubble_->SetStatus(string16());
  SetState(StatusBubbleMac::kBubbleHidingFadeOut);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState());
  EXPECT_EQ(2u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1));

  // Test StartHiding from kBubbleShowingTimer
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingTimer);
  [GetWindow() setAlphaValue:0.0];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(string16());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));

  // Test StartHiding from kBubbleShowingFadeIn
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingFadeIn);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(string16());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(2u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));

  // Test StartHiding from kBubbleHidingTimer
  bubble_->SetStatus(string16());
  SetState(StatusBubbleMac::kBubbleHidingTimer);
  [GetWindow() setAlphaValue:1.0];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(string16());
  // The actual state values can't be tested in immediate_ mode because
  // the timer wasn't actually running.  Without immediate_ mode, expect
  // kBubbleHidingFadeOut and kBubbleHidden.
  // Go back to a deterministic state.
  bubble_->SetStatus(UTF8ToUTF16("Status"));

  // Test StartHiding from kBubbleHidingFadeOut
  bubble_->SetStatus(string16());
  SetState(StatusBubbleMac::kBubbleHidingFadeOut);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->SetStatus(string16());
  // The actual state values can't be tested in immediate_ mode because
  // the window wasn't actually fading out.  Without immediate_ mode, expect
  // kBubbleHidden.
  // Go back to a deterministic state.
  bubble_->SetStatus(UTF8ToUTF16("Status"));

  // Test Hide from kBubbleHidden
  bubble_->SetStatus(string16());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_TRUE(States()->empty());

  // Test Hide from kBubbleShowingTimer
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingTimer);
  [GetWindow() setAlphaValue:0.0];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));

  // Test Hide from kBubbleShowingFadeIn
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleShowingFadeIn);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(2u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0));
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1));

  // Test Hide from kBubbleShown
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));

  // Test Hide from kBubbleHidingTimer
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleHidingTimer);
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));

  // Test Hide from kBubbleHidingFadeOut
  bubble_->SetStatus(UTF8ToUTF16("Status"));
  SetState(StatusBubbleMac::kBubbleHidingFadeOut);
  [GetWindow() setAlphaValue:0.5];
  States()->clear();
  EXPECT_TRUE(States()->empty());
  bubble_->Hide();
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState());
  EXPECT_EQ(1u, States()->size());
  EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0));
}

TEST_F(StatusBubbleMacTest, Delete) {
  NSWindow* window = test_window();
  // Create and delete immediately.
  StatusBubbleMac* bubble = new StatusBubbleMac(window, nil);
  delete bubble;

  // Create then delete while visible.
  bubble = new StatusBubbleMac(window, nil);
  bubble->SetStatus(UTF8ToUTF16("showing"));
  delete bubble;
}

TEST_F(StatusBubbleMacTest, UpdateSizeAndPosition) {
  // Test |UpdateSizeAndPosition()| when status bubble does not exist (shouldn't
  // crash; shouldn't create window).
  EXPECT_TRUE(GetWindow());
  bubble_->UpdateSizeAndPosition();
  EXPECT_TRUE(GetWindow());

  // Create a status bubble (with contents) and call resize (without actually
  // resizing); the frame size shouldn't change.
  bubble_->SetStatus(UTF8ToUTF16("UpdateSizeAndPosition test"));
  ASSERT_TRUE(GetWindow());
  NSRect rect_before = [GetWindow() frame];
  bubble_->UpdateSizeAndPosition();
  NSRect rect_after = [GetWindow() frame];
  EXPECT_TRUE(NSEqualRects(rect_before, rect_after));

  // Move the window and call resize; only the origin should change.
  NSWindow* window = test_window();
  ASSERT_TRUE(window);
  NSRect frame = [window frame];
  rect_before = [GetWindow() frame];
  frame.origin.x += 10.0;  // (fairly arbitrary nonzero value)
  frame.origin.y += 10.0;  // (fairly arbitrary nonzero value)
  [window setFrame:frame display:YES];
  bubble_->UpdateSizeAndPosition();
  rect_after = [GetWindow() frame];
  EXPECT_NE(rect_before.origin.x, rect_after.origin.x);
  EXPECT_NE(rect_before.origin.y, rect_after.origin.y);
  EXPECT_EQ(rect_before.size.width, rect_after.size.width);
  EXPECT_EQ(rect_before.size.height, rect_after.size.height);

  // Resize the window (without moving). The origin shouldn't change. The width
  // should change (in the current implementation), but not the height.
  frame = [window frame];
  rect_before = [GetWindow() frame];
  frame.size.width += 50.0;   // (fairly arbitrary nonzero value)
  frame.size.height += 50.0;  // (fairly arbitrary nonzero value)
  [window setFrame:frame display:YES];
  bubble_->UpdateSizeAndPosition();
  rect_after = [GetWindow() frame];
  EXPECT_EQ(rect_before.origin.x, rect_after.origin.x);
  EXPECT_EQ(rect_before.origin.y, rect_after.origin.y);
  EXPECT_NE(rect_before.size.width, rect_after.size.width);
  EXPECT_EQ(rect_before.size.height, rect_after.size.height);
}

TEST_F(StatusBubbleMacTest, MovingWindowUpdatesPosition) {
  NSWindow* window = test_window();

  // Show the bubble and make sure it has the same origin as |window|.
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  NSWindow* child = GetWindow();
  EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));

  // Hide the bubble, move the window, and show it again.
  bubble_->Hide();
  NSRect frame = [window frame];
  frame.origin.x += 50;
  [window setFrame:frame display:YES];
  bubble_->SetStatus(UTF8ToUTF16("Reshowing"));

  // The bubble should reattach in the correct location.
  child = GetWindow();
  EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));
}

TEST_F(StatusBubbleMacTest, StatuBubbleRespectsBaseFrameLimits) {
  NSWindow* window = test_window();

  // Show the bubble and make sure it has the same origin as |window|.
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  NSWindow* child = GetWindow();
  EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin));

  // Hide the bubble, change base frame offset, and show it again.
  bubble_->Hide();

  NSPoint baseFrameOffset = NSMakePoint(0, [window frame].size.height / 3);
  EXPECT_GT(baseFrameOffset.y, 0);
  [delegate_ forceBaseFrameOffset:baseFrameOffset];

  bubble_->SetStatus(UTF8ToUTF16("Reshowing"));

  // The bubble should reattach in the correct location.
  child = GetWindow();
  NSPoint expectedOrigin = [window frame].origin;
  expectedOrigin.x += baseFrameOffset.x;
  expectedOrigin.y += baseFrameOffset.y;
  EXPECT_TRUE(NSEqualPoints(expectedOrigin, [child frame].origin));
}

TEST_F(StatusBubbleMacTest, ExpandBubble) {
  NSWindow* window = test_window();
  ASSERT_TRUE(window);
  NSRect window_frame = [window frame];
  window_frame.size.width = 600.0;
  [window setFrame:window_frame display:YES];

  // Check basic expansion
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  EXPECT_TRUE(IsVisible());
  bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"),
                  string16());
  EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
  bubble_->ExpandBubble();
  EXPECT_TRUE(IsVisible());
  EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText());
  bubble_->Hide();

  // Make sure bubble resets after hide.
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  bubble_->SetURL(GURL("http://www.snickersnee.com/pioneer_fishstix.html"),
                  string16());
  EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
  // ...and that it expands again properly.
  bubble_->ExpandBubble();
  EXPECT_NSEQ(@"www.snickersnee.com/pioneer_fishstix.html", GetURLText());
  // ...again, again!
  bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"),
                  string16());
  bubble_->ExpandBubble();
  EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText());
  bubble_->Hide();

  window_frame = [window frame];
  window_frame.size.width = 300.0;
  [window setFrame:window_frame display:YES];

  // Very long URL's will be cut off even in the expanded state.
  bubble_->SetStatus(UTF8ToUTF16("Showing"));
  const char veryLongUrl[] =
      "http://www.diewahrscheinlichlaengstepralinederwelt.com/duuuuplo.html";
  bubble_->SetURL(GURL(veryLongUrl), string16());
  EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
  bubble_->ExpandBubble();
  EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]);
}