blob: b34b91feeb8c5b9c96550a1a44eb0155b90f218b [file] [log] [blame]
// 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