910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/bubble/bubble_border.h"

#include <stddef.h>

#include <memory>

#include "base/strings/stringprintf.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/bubble/bubble_border_arrow_utils.h"
#include "ui/views/test/views_test_base.h"

namespace views {

using BubbleBorderTest = views::ViewsTestBase;

TEST_F(BubbleBorderTest, GetMirroredArrow) {
  // Horizontal mirroring.
  EXPECT_EQ(BubbleBorder::TOP_RIGHT,
            BubbleBorder::horizontal_mirror(BubbleBorder::TOP_LEFT));
  EXPECT_EQ(BubbleBorder::TOP_LEFT,
            BubbleBorder::horizontal_mirror(BubbleBorder::TOP_RIGHT));

  EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT,
            BubbleBorder::horizontal_mirror(BubbleBorder::BOTTOM_LEFT));
  EXPECT_EQ(BubbleBorder::BOTTOM_LEFT,
            BubbleBorder::horizontal_mirror(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_EQ(BubbleBorder::RIGHT_TOP,
            BubbleBorder::horizontal_mirror(BubbleBorder::LEFT_TOP));
  EXPECT_EQ(BubbleBorder::LEFT_TOP,
            BubbleBorder::horizontal_mirror(BubbleBorder::RIGHT_TOP));

  EXPECT_EQ(BubbleBorder::RIGHT_BOTTOM,
            BubbleBorder::horizontal_mirror(BubbleBorder::LEFT_BOTTOM));
  EXPECT_EQ(BubbleBorder::LEFT_BOTTOM,
            BubbleBorder::horizontal_mirror(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_EQ(BubbleBorder::TOP_CENTER,
            BubbleBorder::horizontal_mirror(BubbleBorder::TOP_CENTER));
  EXPECT_EQ(BubbleBorder::BOTTOM_CENTER,
            BubbleBorder::horizontal_mirror(BubbleBorder::BOTTOM_CENTER));

  EXPECT_EQ(BubbleBorder::RIGHT_CENTER,
            BubbleBorder::horizontal_mirror(BubbleBorder::LEFT_CENTER));
  EXPECT_EQ(BubbleBorder::LEFT_CENTER,
            BubbleBorder::horizontal_mirror(BubbleBorder::RIGHT_CENTER));

  EXPECT_EQ(BubbleBorder::NONE,
            BubbleBorder::horizontal_mirror(BubbleBorder::NONE));
  EXPECT_EQ(BubbleBorder::FLOAT,
            BubbleBorder::horizontal_mirror(BubbleBorder::FLOAT));

  // Vertical mirroring.
  EXPECT_EQ(BubbleBorder::BOTTOM_LEFT,
            BubbleBorder::vertical_mirror(BubbleBorder::TOP_LEFT));
  EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT,
            BubbleBorder::vertical_mirror(BubbleBorder::TOP_RIGHT));

  EXPECT_EQ(BubbleBorder::TOP_LEFT,
            BubbleBorder::vertical_mirror(BubbleBorder::BOTTOM_LEFT));
  EXPECT_EQ(BubbleBorder::TOP_RIGHT,
            BubbleBorder::vertical_mirror(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_EQ(BubbleBorder::LEFT_BOTTOM,
            BubbleBorder::vertical_mirror(BubbleBorder::LEFT_TOP));
  EXPECT_EQ(BubbleBorder::RIGHT_BOTTOM,
            BubbleBorder::vertical_mirror(BubbleBorder::RIGHT_TOP));

  EXPECT_EQ(BubbleBorder::LEFT_TOP,
            BubbleBorder::vertical_mirror(BubbleBorder::LEFT_BOTTOM));
  EXPECT_EQ(BubbleBorder::RIGHT_TOP,
            BubbleBorder::vertical_mirror(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_EQ(BubbleBorder::BOTTOM_CENTER,
            BubbleBorder::vertical_mirror(BubbleBorder::TOP_CENTER));
  EXPECT_EQ(BubbleBorder::TOP_CENTER,
            BubbleBorder::vertical_mirror(BubbleBorder::BOTTOM_CENTER));

  EXPECT_EQ(BubbleBorder::LEFT_CENTER,
            BubbleBorder::vertical_mirror(BubbleBorder::LEFT_CENTER));
  EXPECT_EQ(BubbleBorder::RIGHT_CENTER,
            BubbleBorder::vertical_mirror(BubbleBorder::RIGHT_CENTER));

  EXPECT_EQ(BubbleBorder::NONE,
            BubbleBorder::vertical_mirror(BubbleBorder::NONE));
  EXPECT_EQ(BubbleBorder::FLOAT,
            BubbleBorder::vertical_mirror(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, HasArrow) {
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::TOP_LEFT));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::TOP_RIGHT));

  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::BOTTOM_LEFT));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::LEFT_TOP));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::RIGHT_TOP));

  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::LEFT_BOTTOM));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::TOP_CENTER));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::BOTTOM_CENTER));

  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::LEFT_CENTER));
  EXPECT_TRUE(BubbleBorder::has_arrow(BubbleBorder::RIGHT_CENTER));

  EXPECT_FALSE(BubbleBorder::has_arrow(BubbleBorder::NONE));
  EXPECT_FALSE(BubbleBorder::has_arrow(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, IsArrowOnLeft) {
  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(BubbleBorder::TOP_LEFT));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::TOP_RIGHT));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(BubbleBorder::BOTTOM_LEFT));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(BubbleBorder::LEFT_TOP));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::RIGHT_TOP));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(BubbleBorder::LEFT_BOTTOM));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::TOP_CENTER));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::BOTTOM_CENTER));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_left(BubbleBorder::LEFT_CENTER));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::RIGHT_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::NONE));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_left(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, IsArrowOnTop) {
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(BubbleBorder::TOP_LEFT));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(BubbleBorder::TOP_RIGHT));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::BOTTOM_LEFT));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(BubbleBorder::LEFT_TOP));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(BubbleBorder::RIGHT_TOP));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::LEFT_BOTTOM));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_top(BubbleBorder::TOP_CENTER));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::BOTTOM_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::LEFT_CENTER));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::RIGHT_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::NONE));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_top(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, IsArrowOnHorizontal) {
  EXPECT_TRUE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::TOP_LEFT));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::TOP_RIGHT));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::BOTTOM_LEFT));
  EXPECT_TRUE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::LEFT_TOP));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::RIGHT_TOP));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::LEFT_BOTTOM));
  EXPECT_FALSE(
      BubbleBorder::is_arrow_on_horizontal(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_TRUE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::TOP_CENTER));
  EXPECT_TRUE(
      BubbleBorder::is_arrow_on_horizontal(BubbleBorder::BOTTOM_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::LEFT_CENTER));
  EXPECT_FALSE(
      BubbleBorder::is_arrow_on_horizontal(BubbleBorder::RIGHT_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::NONE));
  EXPECT_FALSE(BubbleBorder::is_arrow_on_horizontal(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, IsArrowAtCenter) {
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::TOP_LEFT));
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::TOP_RIGHT));

  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::BOTTOM_LEFT));
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::BOTTOM_RIGHT));

  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::LEFT_TOP));
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::RIGHT_TOP));

  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::LEFT_BOTTOM));
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::RIGHT_BOTTOM));

  EXPECT_TRUE(BubbleBorder::is_arrow_at_center(BubbleBorder::TOP_CENTER));
  EXPECT_TRUE(BubbleBorder::is_arrow_at_center(BubbleBorder::BOTTOM_CENTER));

  EXPECT_TRUE(BubbleBorder::is_arrow_at_center(BubbleBorder::LEFT_CENTER));
  EXPECT_TRUE(BubbleBorder::is_arrow_at_center(BubbleBorder::RIGHT_CENTER));

  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::NONE));
  EXPECT_FALSE(BubbleBorder::is_arrow_at_center(BubbleBorder::FLOAT));
}

TEST_F(BubbleBorderTest, GetSizeForContentsSizeTest) {
  views::BubbleBorder border(BubbleBorder::NONE, BubbleBorder::NO_SHADOW);

  const gfx::Insets kInsets = border.GetInsets();

  // kSmallSize is smaller than the minimum allowable size and does not
  // contribute to the resulting size.
  const gfx::Size kSmallSize = gfx::Size(1, 2);
  // kMediumSize is larger than the minimum allowable size and contributes to
  // the resulting size.
  const gfx::Size kMediumSize = gfx::Size(50, 60);

  const gfx::Size kSmallHorizArrow(kSmallSize.width() + kInsets.width(),
                                   kSmallSize.height() + kInsets.height());

  const gfx::Size kSmallVertArrow(kSmallHorizArrow.width(),
                                  kSmallHorizArrow.height());

  const gfx::Size kSmallNoArrow(kSmallHorizArrow.width(),
                                kSmallHorizArrow.height());

  const gfx::Size kMediumHorizArrow(kMediumSize.width() + kInsets.width(),
                                    kMediumSize.height() + kInsets.height());

  const gfx::Size kMediumVertArrow(kMediumHorizArrow.width(),
                                   kMediumHorizArrow.height());

  const gfx::Size kMediumNoArrow(kMediumHorizArrow.width(),
                                 kMediumHorizArrow.height());

  struct TestCase {
    BubbleBorder::Arrow arrow;
    gfx::Size content;
    gfx::Size expected_without_arrow;
  };

  const auto cases = std::to_array<TestCase>(
      {// Content size: kSmallSize
       {BubbleBorder::TOP_LEFT, kSmallSize, kSmallNoArrow},
       {BubbleBorder::TOP_CENTER, kSmallSize, kSmallNoArrow},
       {BubbleBorder::TOP_RIGHT, kSmallSize, kSmallNoArrow},
       {BubbleBorder::BOTTOM_LEFT, kSmallSize, kSmallNoArrow},
       {BubbleBorder::BOTTOM_CENTER, kSmallSize, kSmallNoArrow},
       {BubbleBorder::BOTTOM_RIGHT, kSmallSize, kSmallNoArrow},
       {BubbleBorder::LEFT_TOP, kSmallSize, kSmallNoArrow},
       {BubbleBorder::LEFT_CENTER, kSmallSize, kSmallNoArrow},
       {BubbleBorder::LEFT_BOTTOM, kSmallSize, kSmallNoArrow},
       {BubbleBorder::RIGHT_TOP, kSmallSize, kSmallNoArrow},
       {BubbleBorder::RIGHT_CENTER, kSmallSize, kSmallNoArrow},
       {BubbleBorder::RIGHT_BOTTOM, kSmallSize, kSmallNoArrow},
       {BubbleBorder::NONE, kSmallSize, kSmallNoArrow},
       {BubbleBorder::FLOAT, kSmallSize, kSmallNoArrow},

       // Content size: kMediumSize
       {BubbleBorder::TOP_LEFT, kMediumSize, kMediumNoArrow},
       {BubbleBorder::TOP_CENTER, kMediumSize, kMediumNoArrow},
       {BubbleBorder::TOP_RIGHT, kMediumSize, kMediumNoArrow},
       {BubbleBorder::BOTTOM_LEFT, kMediumSize, kMediumNoArrow},
       {BubbleBorder::BOTTOM_CENTER, kMediumSize, kMediumNoArrow},
       {BubbleBorder::BOTTOM_RIGHT, kMediumSize, kMediumNoArrow},
       {BubbleBorder::LEFT_TOP, kMediumSize, kMediumNoArrow},
       {BubbleBorder::LEFT_CENTER, kMediumSize, kMediumNoArrow},
       {BubbleBorder::LEFT_BOTTOM, kMediumSize, kMediumNoArrow},
       {BubbleBorder::RIGHT_TOP, kMediumSize, kMediumNoArrow},
       {BubbleBorder::RIGHT_CENTER, kMediumSize, kMediumNoArrow},
       {BubbleBorder::RIGHT_BOTTOM, kMediumSize, kMediumNoArrow},
       {BubbleBorder::NONE, kMediumSize, kMediumNoArrow},
       {BubbleBorder::FLOAT, kMediumSize, kMediumNoArrow}});

  for (size_t i = 0; i < std::size(cases); ++i) {
    SCOPED_TRACE(base::StringPrintf("i=%d arrow=%d", static_cast<int>(i),
                                    cases[i].arrow));

    border.set_arrow(cases[i].arrow);
    EXPECT_EQ(cases[i].expected_without_arrow,
              border.GetSizeForContentsSize(cases[i].content));
  }
}

TEST_F(BubbleBorderTest, GetBoundsOriginTest) {
  for (int i = 0; i < BubbleBorder::SHADOW_COUNT; ++i) {
    const BubbleBorder::Shadow shadow = static_cast<BubbleBorder::Shadow>(i);
    SCOPED_TRACE(testing::Message() << "BubbleBorder::Shadow: " << shadow);
    views::BubbleBorder border(BubbleBorder::TOP_LEFT, shadow);

    const gfx::Rect kAnchor(100, 100, 20, 30);
    const gfx::Size kContentSize(500, 600);
    const gfx::Insets kInsets = border.GetInsets();

    border.set_arrow(BubbleBorder::TOP_LEFT);
    const gfx::Size kTotalSize = border.GetSizeForContentsSize(kContentSize);

    border.set_arrow(BubbleBorder::RIGHT_BOTTOM);
    EXPECT_EQ(kTotalSize, border.GetSizeForContentsSize(kContentSize));

    border.set_arrow(BubbleBorder::NONE);
    EXPECT_EQ(kTotalSize, border.GetSizeForContentsSize(kContentSize));

    const int kStrokeWidth =
        shadow == BubbleBorder::NO_SHADOW ? 0 : BubbleBorder::kStroke;

    const int kBorderedContentHeight =
        kContentSize.height() + (2 * kStrokeWidth);

    const int kStrokeTopInset = kStrokeWidth - kInsets.top();
    const int kStrokeBottomInset = kStrokeWidth - kInsets.bottom();
    const int kStrokeLeftInset = kStrokeWidth - kInsets.left();
    const int kStrokeRightInset = kStrokeWidth - kInsets.right();

    const int kTopHorizArrowY = kAnchor.bottom() + kStrokeTopInset;
    const int kBottomHorizArrowY =
        kAnchor.y() - kTotalSize.height() - kStrokeBottomInset;
    const int kLeftVertArrowX =
        kAnchor.x() + kAnchor.width() + kStrokeLeftInset;
    const int kRightVertArrowX =
        kAnchor.x() - kTotalSize.width() - kStrokeRightInset;

    struct TestCase {
      BubbleBorder::Arrow arrow;
      int expected_x;
      int expected_y;
    };

    const auto cases = std::to_array<TestCase>({
        // Horizontal arrow tests.
        {BubbleBorder::TOP_LEFT, kAnchor.x() + kStrokeLeftInset,
         kTopHorizArrowY},
        {BubbleBorder::TOP_CENTER,
         kAnchor.CenterPoint().x() - (kTotalSize.width() / 2), kTopHorizArrowY},
        {BubbleBorder::BOTTOM_RIGHT,
         kAnchor.x() + kAnchor.width() - kTotalSize.width() - kStrokeRightInset,
         kBottomHorizArrowY},

        // Vertical arrow tests.
        {BubbleBorder::LEFT_TOP, kLeftVertArrowX,
         kAnchor.y() + kStrokeTopInset},
        {BubbleBorder::LEFT_CENTER, kLeftVertArrowX,
         kAnchor.CenterPoint().y() - (kBorderedContentHeight / 2) +
             kStrokeTopInset},
        {BubbleBorder::RIGHT_BOTTOM, kRightVertArrowX,
         kAnchor.y() + kAnchor.height() - kTotalSize.height() -
             kStrokeBottomInset},

        // No arrow tests.
        {BubbleBorder::NONE,
         kAnchor.x() + (kAnchor.width() - kTotalSize.width()) / 2,
         kAnchor.y() + kAnchor.height()},
        {BubbleBorder::FLOAT,
         kAnchor.x() + (kAnchor.width() - kTotalSize.width()) / 2,
         kAnchor.y() + (kAnchor.height() - kTotalSize.height()) / 2},
    });

    for (size_t j = 0; j < std::size(cases); ++j) {
      SCOPED_TRACE(base::StringPrintf("shadow=%d j=%d arrow=%d",
                                      static_cast<int>(shadow),
                                      static_cast<int>(j), cases[j].arrow));
      const BubbleBorder::Arrow arrow = cases[j].arrow;
      border.set_arrow(arrow);
      gfx::Point origin = border.GetBounds(kAnchor, kContentSize).origin();
      EXPECT_EQ(cases[j].expected_x, origin.x());
      EXPECT_EQ(cases[j].expected_y, origin.y());
    }
  }
}

TEST_F(BubbleBorderTest, BubblePositionedCorrectlyWithVisibleArrow) {
  views::BubbleBorder border(BubbleBorder::TOP_LEFT,
                             BubbleBorder::STANDARD_SHADOW);
  const gfx::Insets kInsets = border.GetInsets();
  border.set_visible_arrow(true);

  constexpr gfx::Size kContentSize(200, 150);

  // Anchor position.
  constexpr gfx::Point kAnchorOrigin(100, 100);

  // Anchor smaller than contents.
  constexpr gfx::Rect kAnchor1(kAnchorOrigin, gfx::Size(40, 50));

  // Anchor larger than contents.
  constexpr gfx::Rect kAnchor2(kAnchorOrigin, gfx::Size(400, 300));

  // Anchor extremely small.
  constexpr gfx::Rect kAnchor3(kAnchorOrigin, gfx::Size(10, 12));

  // TODO(dfried): in all of these tests, the border is factored into the height
  // of the bubble an extra time, because of the fact that the arrow doesn't
  // properly overlap the border in the calculation (though it does visually).
  // Please fix at some point.

  // TOP_LEFT:

  border.set_arrow(BubbleBorder::TOP_LEFT);
  gfx::Rect bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor1.x() - kInsets.left(), bounds.x());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor2.x() - kInsets.left() + BubbleBorder::kBorderThicknessDip,
            bounds.x());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_GT(kAnchor3.x() - kInsets.left() + BubbleBorder::kBorderThicknessDip,
            bounds.x());

  // TOP_CENTER:

  border.set_arrow(BubbleBorder::TOP_CENTER);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor1.bottom_center().x() - bounds.width() / 2, bounds.x());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor2.bottom_center().x() - bounds.width() / 2, bounds.x());

  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor3.bottom_center().x() - bounds.width() / 2, bounds.x());

  // TOP_RIGHT:

  border.set_arrow(BubbleBorder::TOP_RIGHT);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(kAnchor1.right() + kInsets.right(), bounds.right());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_EQ(
      kAnchor2.right() + kInsets.right() - BubbleBorder::kBorderThicknessDip,
      bounds.right());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.bottom() +
                BubbleBorder::kVisibleArrowLength,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.bottom() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.y());
  EXPECT_LT(
      kAnchor3.right() + kInsets.right() - BubbleBorder::kBorderThicknessDip,
      bounds.right());

  // BOTTOM_LEFT:

  border.set_arrow(BubbleBorder::BOTTOM_LEFT);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor1.x() - kInsets.left(), bounds.x());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor2.x() - kInsets.left() + BubbleBorder::kBorderThicknessDip,
            bounds.x());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_GT(kAnchor3.x() - kInsets.left() + BubbleBorder::kBorderThicknessDip,
            bounds.x());

  // BOTTOM_CENTER:

  border.set_arrow(BubbleBorder::BOTTOM_CENTER);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor1.bottom_center().x() - bounds.width() / 2, bounds.x());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor2.bottom_center().x() - bounds.width() / 2, bounds.x());

  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor3.bottom_center().x() - bounds.width() / 2, bounds.x());

  // BOTTOM_RIGHT:

  border.set_arrow(BubbleBorder::BOTTOM_RIGHT);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor1.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(kAnchor1.right() + kInsets.right(), bounds.right());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor2.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_EQ(
      kAnchor2.right() + kInsets.right() - BubbleBorder::kBorderThicknessDip,
      bounds.right());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.height() + kInsets.top() +
                BubbleBorder::kVisibleArrowLength +
                BubbleBorder::kBorderThicknessDip,
            bounds.height());
  EXPECT_EQ(kContentSize.width() + kInsets.width(), bounds.width());
  EXPECT_EQ(kAnchor3.y() - BubbleBorder::kVisibleArrowGap, bounds.bottom());
  EXPECT_LT(
      kAnchor3.right() + kInsets.right() - BubbleBorder::kBorderThicknessDip,
      bounds.right());

  // LEFT_TOP:

  border.set_arrow(BubbleBorder::LEFT_TOP);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_EQ(kAnchor1.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_EQ(kAnchor2.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_GT(kAnchor3.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  // LEFT_CENTER:

  // Because the shadow counts as part of the bounds, the shadow offset (which
  // is applied vertically) will affect the vertical positioning of a bubble
  // which is placed next to the anchor by a similar amount.
  border.set_arrow(BubbleBorder::LEFT_CENTER);
  const auto insets = border.GetInsets();
  const int shadow_offset = (insets.bottom() - insets.top()) / 2;
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_NEAR(kAnchor1.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_NEAR(kAnchor2.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_NEAR(kAnchor3.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  // LEFT_BOTTOM:

  border.set_arrow(BubbleBorder::LEFT_BOTTOM);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_EQ(
      kAnchor1.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_EQ(
      kAnchor2.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.right() + BubbleBorder::kVisibleArrowGap +
                BubbleBorder::kBorderThicknessDip,
            bounds.x());
  EXPECT_LT(
      kAnchor3.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());

  // RIGHT_TOP:

  border.set_arrow(BubbleBorder::RIGHT_TOP);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_EQ(kAnchor1.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_EQ(kAnchor2.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_GT(kAnchor3.y() - kInsets.top() + BubbleBorder::kBorderThicknessDip,
            bounds.y());

  // // RIGHT_CENTER:

  // Because the shadow counts as part of the bounds, the shadow offset (which
  // is applied vertically) will affect the vertical positioning of a bubble
  // which is placed next to the anchor by a similar amount.
  border.set_arrow(BubbleBorder::RIGHT_CENTER);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_NEAR(kAnchor1.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_NEAR(kAnchor2.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_NEAR(kAnchor3.right_center().y() - bounds.height() / 2, bounds.y(),
              shadow_offset);

  // RIGHT_BOTTOM:

  border.set_arrow(BubbleBorder::RIGHT_BOTTOM);
  bounds = border.GetBounds(kAnchor1, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor1.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_EQ(
      kAnchor1.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());

  bounds = border.GetBounds(kAnchor2, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor2.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_EQ(
      kAnchor2.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());

  // Too small of an anchor view will shift the bubble to make sure the arrow
  // is not too close to the edge of the bubble.
  bounds = border.GetBounds(kAnchor3, kContentSize);
  EXPECT_EQ(kContentSize.width() + kInsets.right() +
                BubbleBorder::kVisibleArrowLength,
            bounds.width());
  EXPECT_EQ(kContentSize.height() + kInsets.height(), bounds.height());
  EXPECT_EQ(kAnchor3.x() - BubbleBorder::kVisibleArrowGap -
                BubbleBorder::kBorderThicknessDip,
            bounds.right());
  EXPECT_LT(
      kAnchor3.bottom() + kInsets.bottom() - BubbleBorder::kBorderThicknessDip,
      bounds.bottom());
}

TEST_F(BubbleBorderTest, AddArrowToBubbleCornerAndPointTowardsAnchor) {
  // Create bubble bounds located at pixel x=400,y=600 with a dimension of
  // 300x200 pixels.
  const gfx::Rect bubble_bounds(400, 600, 300, 200);
  // The element will have a fixed size as well.
  const gfx::Size element_size(350, 100);

  int most_left_x_position =
      bubble_bounds.x() + BubbleBorder::kVisibleArrowBuffer;
  int most_right_x_position = bubble_bounds.right() -
                              BubbleBorder::kVisibleArrowBuffer -
                              BubbleBorder::kVisibleArrowRadius * 2;
  // The y position of an arrow at the upper edge of the bubble.
  int upper_arrow_y_position = bubble_bounds.y();
  // The y position of an arrow at the lower edge of the bubble.
  int lower_arrow_y_position =
      bubble_bounds.bottom() - BubbleBorder::kVisibleArrowLength;

  struct TestCase {
    gfx::Point element_origin;
    BubbleBorder::Arrow supplied_arrow;
    gfx::Point expected_arrow_position;
    bool expected_arrow_visibility_and_return_value;
    gfx::Rect expected_bubble_bounds;
    int popup_min_y = 0;
  } test_cases[]{
      // First are using the following scenario:
      //
      //  y=200        -----------------
      //               |       x       |   element
      //               -----------------
      //
      //  y=600         -----------
      //                |         |
      //                |         |   bubble
      //                |         |
      //  y=800         -----------
      //
      //               | x=380
      //                | x=400
      {{380, 200},
       BubbleBorder::Arrow::TOP_LEFT,
       // The arrow sits close to the right edge of the bubble.
       // The bubble is located above the upper edge. Note that
       // insets need to be taken into account.
       {most_left_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{380, 200},
       BubbleBorder::Arrow::TOP_CENTER,
       // The arrow points to the horizontal center of the element.
       // Note that the spatial extension of the arrow has to be
       // taken into account. The bubble is located above the upper
       // edge. Note that insets need to be taken into account.
       {380 + element_size.width() / 2 - BubbleBorder::kVisibleArrowRadius,
        upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{380, 200},
       BubbleBorder::Arrow::TOP_RIGHT,
       // The arrow points to the horizontal center of the element.
       // Note that the spatial extension of the arrow has to be
       // taken into account. The bubble is located above the upper
       // edge. Note that insets need to be taken into account.
       {most_right_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      // The following tests are using a bubble that is highly displaced from
      // the
      // element:
      //
      //  y=200                     -----------------
      //                            |       x       |   element
      //                            -----------------
      //
      //  y=600         -----------
      //                |         |
      //                |         |   bubble
      //                |         |
      //  y=800         -----------
      //
      //                            | x=750
      //                | x=400
      // The arrow should always be located on the most right position.
      {{750, 200},
       BubbleBorder::Arrow::TOP_LEFT,
       {most_right_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{750, 200},
       BubbleBorder::Arrow::TOP_CENTER,
       {most_right_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{750, 200},
       BubbleBorder::Arrow::TOP_RIGHT,
       {most_right_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      // And the reverse scenario:
      //
      //  y=200    -----------------
      //           |       x       |   element
      //           -----------------
      //
      //  y=600                             -----------
      //                                    |         |
      //                                    |         |    bubble
      //                                    |         |
      //  y=800                             -----------
      //
      //           | x=0
      //                                    | x=400
      // The arrow should always be located on the most right position.
      {{0, 200},
       BubbleBorder::Arrow::TOP_LEFT,
       {most_left_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{0, 200},
       BubbleBorder::Arrow::TOP_CENTER,
       {most_left_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{0, 200},
       BubbleBorder::Arrow::TOP_RIGHT,
       {most_left_x_position, upper_arrow_y_position},
       true,
       bubble_bounds + gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      // The following tests use a BOTTOM arrow. This should only replace the
      // upper_arrow_y_position with the lower_arrow_y_position in all tests.
      {{380, 200},
       BubbleBorder::Arrow::BOTTOM_LEFT,
       {most_left_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{380, 200},
       BubbleBorder::Arrow::BOTTOM_CENTER,
       {380 + element_size.width() / 2 - BubbleBorder::kVisibleArrowRadius,
        lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{380, 200},
       BubbleBorder::Arrow::BOTTOM_RIGHT,
       {most_right_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{750, 200},
       BubbleBorder::Arrow::BOTTOM_LEFT,
       {most_right_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{750, 200},
       BubbleBorder::Arrow::BOTTOM_CENTER,
       {most_right_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{750, 200},
       BubbleBorder::Arrow::BOTTOM_RIGHT,
       {most_right_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{0, 200},
       BubbleBorder::Arrow::BOTTOM_LEFT,
       {most_left_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{0, 200},
       BubbleBorder::Arrow::BOTTOM_CENTER,
       {most_left_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      {{0, 200},
       BubbleBorder::Arrow::BOTTOM_RIGHT,
       {most_left_x_position, lower_arrow_y_position},
       true,
       bubble_bounds - gfx::Vector2d(0, BubbleBorder::kVisibleArrowLength)},
      // Now, the horizontal arrow scenario is tested
      //  y=600                            -----------
      //  y=650    -----------------       |         |
      //           |       x       |       |         |
      //  y=750    -----------------       |         |
      //  y=800                            -----------
      //           | x=0
      //                                   | x=400
      // The arrow is always located on the right side to point towards the
      // vertical center of the element.
      {{0, 650},
       BubbleBorder::Arrow::LEFT_TOP,
       {bubble_bounds.x(),
        650 + element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       bubble_bounds + gfx::Vector2d(BubbleBorder::kVisibleArrowLength, 0)},
      {{0, 650},
       BubbleBorder::Arrow::LEFT_CENTER,
       {bubble_bounds.x(),
        650 + element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       bubble_bounds + gfx::Vector2d(BubbleBorder::kVisibleArrowLength, 0)},
      {{0, 650},
       BubbleBorder::Arrow::LEFT_BOTTOM,
       {bubble_bounds.x(),
        650 + element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       bubble_bounds + gfx::Vector2d(BubbleBorder::kVisibleArrowLength, 0)},
      // With the element moved to the top of the screen, the arrow should
      // always be placed at the most top position on the bubble, the bubble
      // position is adjusted as well
      {{0, 0},
       BubbleBorder::Arrow::LEFT_TOP,
       {bubble_bounds.x(),
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       {bubble_bounds.x() + BubbleBorder::kVisibleArrowLength,
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius -
            BubbleBorder::kVisibleArrowBuffer,
        bubble_bounds.width(), bubble_bounds.height()}},
      {{0, 0},
       BubbleBorder::Arrow::LEFT_CENTER,
       {bubble_bounds.x(),
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       {bubble_bounds.x() + BubbleBorder::kVisibleArrowLength,
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius -
            BubbleBorder::kVisibleArrowBuffer,
        bubble_bounds.width(), bubble_bounds.height()}},
      {{0, 0},
       BubbleBorder::Arrow::LEFT_BOTTOM,
       {bubble_bounds.x(),
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius},
       true,
       {bubble_bounds.x() + BubbleBorder::kVisibleArrowLength,
        element_size.height() / 2 - BubbleBorder::kVisibleArrowRadius -
            BubbleBorder::kVisibleArrowBuffer,
        bubble_bounds.width(), bubble_bounds.height()}},
      {{0, 0},
       BubbleBorder::Arrow::LEFT_TOP,
       {bubble_bounds.x(),
        element_size.height() / 2 - 10 + BubbleBorder::kVisibleArrowBuffer},
       true,
       {bubble_bounds.x() + BubbleBorder::kVisibleArrowLength,
        element_size.height() / 2 - 10, bubble_bounds.width(),
        bubble_bounds.height()},
       element_size.height() / 2 - 10},
  };

  for (auto test_case : test_cases) {
    gfx::Rect bubble_bounds_copy = bubble_bounds;
    views::BubbleBorder border(BubbleBorder::Arrow::NONE,
                               BubbleBorder::STANDARD_SHADOW);
    border.set_arrow(test_case.supplied_arrow);
    EXPECT_EQ(border.AddArrowToBubbleCornerAndPointTowardsAnchor(
                  {test_case.element_origin, element_size}, bubble_bounds_copy,
                  test_case.popup_min_y),
              test_case.expected_arrow_visibility_and_return_value);
    EXPECT_EQ(border.visible_arrow(),
              test_case.expected_arrow_visibility_and_return_value);
    EXPECT_EQ(border.GetVisibibleArrowRectForTesting().origin(),
              test_case.expected_arrow_position);
    EXPECT_EQ(GetVisibleArrowSize(test_case.supplied_arrow),
              border.GetVisibibleArrowRectForTesting().size());
    EXPECT_EQ(test_case.expected_bubble_bounds, bubble_bounds_copy);
  }
}

TEST_F(BubbleBorderTest,
       AddArrowToBubbleCornerAndPointTowardsAnchorWithInsufficientSpace) {
  // This bubble bound has uinsufficient width to place an arrow on the top or
  // the bottom.
  const gfx::Rect insufficient_width_bubble_bounds(0, 0, 10, 200);

  // This bound has insufficient height to place an arrow on the left or right.
  const gfx::Rect insufficient_height_bubble_bounds(0, 0, 100, 10);

  // Create bounds for the element, the specifics do no matter.
  const gfx::Rect element_bounds(0, 0, 350, 100);

  struct TestCase {
    gfx::Rect bubble_bounds;
    BubbleBorder::Arrow supplied_arrow;
    bool expected_arrow_visibility_and_return_value;
  } test_cases[]{
      // Bubble is placeable on top because there is sufficient width.
      {insufficient_height_bubble_bounds, BubbleBorder::Arrow::TOP_CENTER,
       true},
      // Bubble is not placeable on top because the width is insufficient.
      {insufficient_width_bubble_bounds, BubbleBorder::Arrow::TOP_CENTER,
       false},
      // Bubble is not placeable on the side because the height is insufficient.
      {insufficient_height_bubble_bounds, BubbleBorder::Arrow::LEFT_CENTER,
       false},
      // Bubble is placeable on the side because the height is sufficient.
      {insufficient_width_bubble_bounds, BubbleBorder::Arrow::LEFT_CENTER,
       true},
  };

  for (auto test_case : test_cases) {
    views::BubbleBorder border(BubbleBorder::Arrow::NONE,
                               BubbleBorder::STANDARD_SHADOW);
    border.set_arrow(test_case.supplied_arrow);
    EXPECT_EQ(border.AddArrowToBubbleCornerAndPointTowardsAnchor(
                  element_bounds, test_case.bubble_bounds, 0),
              test_case.expected_arrow_visibility_and_return_value);
  }
}

TEST_F(BubbleBorderTest, IsVerticalArrow) {
  struct TestCase {
    BubbleBorder::Arrow arrow;
    bool is_vertical_expected;
  };

  TestCase test_cases[] = {
      // BOTTOM and TOP arrows are vertical.
      {BubbleBorder::Arrow::BOTTOM_CENTER, true},
      {BubbleBorder::Arrow::BOTTOM_LEFT, true},
      {BubbleBorder::Arrow::BOTTOM_RIGHT, true},
      {BubbleBorder::Arrow::TOP_CENTER, true},
      {BubbleBorder::Arrow::TOP_LEFT, true},
      {BubbleBorder::Arrow::TOP_RIGHT, true},
      // The rest is horizontal.
      {BubbleBorder::Arrow::LEFT_BOTTOM, false},
      {BubbleBorder::Arrow::LEFT_CENTER, false},
      {BubbleBorder::Arrow::LEFT_TOP, false},
      {BubbleBorder::Arrow::RIGHT_BOTTOM, false},
      {BubbleBorder::Arrow::RIGHT_CENTER, false},
      {BubbleBorder::Arrow::RIGHT_TOP, false},
  };

  for (const auto& test_case : test_cases) {
    EXPECT_EQ(IsVerticalArrow(test_case.arrow), test_case.is_vertical_expected);
  }
}

// Test that the correct arrow size is returned for a given arrow position.
TEST_F(BubbleBorderTest, GetVisibleArrowSize) {
  const gfx::Size vertical_size(2 * BubbleBorder::kVisibleArrowRadius,
                                BubbleBorder::kVisibleArrowLength);
  const gfx::Size horizontal_size(BubbleBorder::kVisibleArrowLength,
                                  2 * BubbleBorder::kVisibleArrowRadius);

  struct TestCase {
    BubbleBorder::Arrow arrow;
    gfx::Size expected_size;
  };

  TestCase test_cases[] = {
      // BOTTOM and TOP arrows have a vertical size.
      {BubbleBorder::Arrow::BOTTOM_CENTER, vertical_size},
      {BubbleBorder::Arrow::BOTTOM_LEFT, vertical_size},
      {BubbleBorder::Arrow::BOTTOM_RIGHT, vertical_size},
      {BubbleBorder::Arrow::TOP_CENTER, vertical_size},
      {BubbleBorder::Arrow::TOP_LEFT, vertical_size},
      {BubbleBorder::Arrow::TOP_RIGHT, vertical_size},
      // The rest has a horizontal size.
      {BubbleBorder::Arrow::LEFT_BOTTOM, horizontal_size},
      {BubbleBorder::Arrow::LEFT_CENTER, horizontal_size},
      {BubbleBorder::Arrow::LEFT_TOP, horizontal_size},
      {BubbleBorder::Arrow::RIGHT_BOTTOM, horizontal_size},
      {BubbleBorder::Arrow::RIGHT_CENTER, horizontal_size},
      {BubbleBorder::Arrow::RIGHT_TOP, horizontal_size},
  };

  for (const auto& test_case : test_cases) {
    EXPECT_EQ(GetVisibleArrowSize(test_case.arrow), test_case.expected_size);
  }
}

// Test that the contents bounds are moved correctly to place the visible arrow
// at the appropriate position.
TEST_F(BubbleBorderTest, MoveContentsBoundsToPlaceVisibleArrow) {
  const int arrow_length =
      BubbleBorder::kVisibleArrowLength + BubbleBorder::kVisibleArrowGap;

  struct TestCase {
    BubbleBorder::Arrow arrow;
    gfx::Vector2d expected_contents_bounds_move;
    gfx::Point initial_bubble_origin = gfx::Point(0, 0);
  };

  TestCase test_cases[] = {
      // BOTTOM cases: The contents is moved to the top of the screen.
      {BubbleBorder::Arrow::BOTTOM_LEFT, gfx::Vector2d(0, -arrow_length)},
      {BubbleBorder::Arrow::BOTTOM_CENTER, gfx::Vector2d(0, -arrow_length)},
      {BubbleBorder::Arrow::BOTTOM_RIGHT, gfx::Vector2d(0, -arrow_length)},
      // TOP cases: The contents is moved to the bottom of the screen.
      {BubbleBorder::Arrow::TOP_LEFT, gfx::Vector2d(0, arrow_length)},
      {BubbleBorder::Arrow::TOP_CENTER, gfx::Vector2d(0, arrow_length)},
      {BubbleBorder::Arrow::TOP_RIGHT, gfx::Vector2d(0, arrow_length)},
      // LEFT cases: The contents is moved to the right.
      {BubbleBorder::Arrow::LEFT_BOTTOM, gfx::Vector2d(arrow_length, 0)},
      {BubbleBorder::Arrow::LEFT_CENTER, gfx::Vector2d(arrow_length, 0)},
      {BubbleBorder::Arrow::LEFT_TOP, gfx::Vector2d(arrow_length, 0)},
      // RIGHT cases: The contents is moved to the left.
      {BubbleBorder::Arrow::RIGHT_BOTTOM, gfx::Vector2d(-arrow_length, 0)},
      {BubbleBorder::Arrow::RIGHT_CENTER, gfx::Vector2d(-arrow_length, 0)},
      {BubbleBorder::Arrow::RIGHT_TOP, gfx::Vector2d(-arrow_length, 0)},
  };

  for (const auto& test_case : test_cases) {
    // Create a bubble border with a visible arrow.
    views::BubbleBorder border(test_case.arrow, BubbleBorder::STANDARD_SHADOW);
    border.set_visible_arrow(true);

    // Create, move and verify the contents bounds.
    EXPECT_EQ(border.GetContentsBoundsOffsetToPlaceVisibleArrow(
                  test_case.arrow, /*include_gap=*/true),
              test_case.expected_contents_bounds_move);
  }
}

}  // namespace views