// Copyright (c) 2010 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 "chrome/browser/views/info_bubble.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef testing::Test InfoBubbleTest;

class TestBorderContents : public BorderContents {
 public:
  TestBorderContents() {}

  void set_monitor_bounds(const gfx::Rect& bounds) {
    monitor_bounds_ = bounds;
  }

  BubbleBorder* bubble_border() const { return bubble_border_; }

 protected:
  virtual gfx::Rect GetMonitorBounds(const gfx::Rect& rect) {
    return monitor_bounds_;
  }

 private:
  gfx::Rect monitor_bounds_;

  DISALLOW_COPY_AND_ASSIGN(TestBorderContents);
};

// Tests that the arrow is moved appropriately when the info-bubble does not fit
// the screen.
TEST_F(InfoBubbleTest, BorderContentsSizeAndGetBounds) {
  TestBorderContents border_contents;
  border_contents.Init();

  // Test that the info bubble displays normally when it fits.
  gfx::Rect contents_bounds;
  gfx::Rect window_bounds;
  border_contents.set_monitor_bounds(gfx::Rect(0, 0, 1000, 1000));
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  // The arrow shouldn't have changed from TOP_LEFT.
  BubbleBorder::ArrowLocation arrow_location =
      border_contents.bubble_border()->arrow_location();
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_GT(window_bounds.x(), 100);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

  // Test bubble not fitting on left.
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_RIGHT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to TOP_LEFT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_GT(window_bounds.x(), 100);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

  // Test bubble not fitting on left or top.
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::BOTTOM_RIGHT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to TOP_LEFT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_GT(window_bounds.x(), 100);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

  // Test bubble not fitting on top.
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::BOTTOM_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to TOP_LEFT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_GT(window_bounds.x(), 100);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

  // Test bubble not fitting on top and right.
  border_contents.SizeAndGetBounds(
      gfx::Rect(900, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::BOTTOM_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to TOP_RIGHT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_LT(window_bounds.x(), 900 + 50 - 500);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

 // Test bubble not fitting on right.
  border_contents.SizeAndGetBounds(
      gfx::Rect(900, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to TOP_RIGHT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_LT(window_bounds.x(), 900 + 50 - 500);
  EXPECT_GT(window_bounds.y(), 100 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.

  // Test bubble not fitting on bottom and right.
  border_contents.SizeAndGetBounds(
      gfx::Rect(900, 900, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to BOTTOM_RIGHT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(arrow_location));
  EXPECT_LT(window_bounds.x(), 900 + 50 - 500);
  EXPECT_LT(window_bounds.y(), 900 - 500 - 15);  // -15 to roughly compensate
                                                 // for arrow height.

  // Test bubble not fitting at the bottom.
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 900, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to BOTTOM_LEFT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  // The window should be right aligned with the position_relative_to.
  EXPECT_LT(window_bounds.x(), 900 + 50 - 500);
  EXPECT_LT(window_bounds.y(), 900 - 500 - 15);  // -15 to roughly compensate
                                                 // for arrow height.

  // Test bubble not fitting at the bottom and left.
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 900, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_RIGHT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);
  arrow_location = border_contents.bubble_border()->arrow_location();
  // The arrow should have changed to BOTTOM_LEFT.
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
  // The window should be right aligned with the position_relative_to.
  EXPECT_LT(window_bounds.x(), 900 + 50 - 500);
  EXPECT_LT(window_bounds.y(), 900 - 500 - 15);  // -15 to roughly compensate
                                                 // for arrow height.
}

// Tests that the arrow is not moved when the info-bubble does not fit the
// screen but moving it would make matter worse.
TEST_F(InfoBubbleTest, BorderContentsSizeAndGetBoundsDontMoveArrow) {
  TestBorderContents border_contents;
  border_contents.Init();
  gfx::Rect contents_bounds;
  gfx::Rect window_bounds;
  border_contents.set_monitor_bounds(gfx::Rect(0, 0, 1000, 1000));
  border_contents.SizeAndGetBounds(
      gfx::Rect(400, 100, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_LEFT,
      false,                        // |allow_bubble_offscreen|
      gfx::Size(500, 700),          // |contents_size|
      &contents_bounds, &window_bounds);

  // The arrow should not have changed, as it would make it the bubble even more
  // offscreen.
  BubbleBorder::ArrowLocation arrow_location =
      border_contents.bubble_border()->arrow_location();
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(arrow_location));
}

// Test that the 'allow offscreen' prevents the bubble from moving.
TEST_F(InfoBubbleTest, BorderContentsSizeAndGetBoundsAllowOffscreen) {
  TestBorderContents border_contents;
  border_contents.Init();
  gfx::Rect contents_bounds;
  gfx::Rect window_bounds;
  border_contents.set_monitor_bounds(gfx::Rect(0, 0, 1000, 1000));
  border_contents.SizeAndGetBounds(
      gfx::Rect(100, 900, 50, 50),  // |position_relative_to|
      BubbleBorder::TOP_RIGHT,
      true,                         // |allow_bubble_offscreen|
      gfx::Size(500, 500),          // |contents_size|
      &contents_bounds, &window_bounds);

  // The arrow should not have changed (eventhough the bubble does not fit).
  BubbleBorder::ArrowLocation arrow_location =
      border_contents.bubble_border()->arrow_location();
  EXPECT_TRUE(BubbleBorder::has_arrow(arrow_location));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(arrow_location));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(arrow_location));
  // The coordinates should be pointing to 'positive relative to' from
  // TOP_RIGHT.
  EXPECT_LT(window_bounds.x(), 100 + 50 - 500);
  EXPECT_GT(window_bounds.y(), 900 + 50 - 10);  // -10 to roughly compensate for
                                                // arrow overlap.
}