blob: 4c17527621129a837ae998c0b47b8444258db2d1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/bubble/bubble_frame_view.h"
#include <memory>
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/text_utils.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/bubble/footnote_container_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/metrics.h"
#include "ui/views/test/test_layout_provider.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace views {
using BubbleFrameViewTest = ViewsTestBase;
namespace {
const BubbleBorder::Arrow kArrow = BubbleBorder::TOP_LEFT;
const SkColor kColor = SK_ColorRED;
const int kMargin = 6;
const int kMinimumClientWidth = 100;
const int kMinimumClientHeight = 200;
const int kMaximumClientWidth = 300;
const int kMaximumClientHeight = 300;
const int kPreferredClientWidth = 150;
const int kPreferredClientHeight = 250;
// These account for non-client areas like the title bar, footnote etc. However
// these do not take the bubble border into consideration.
const int kExpectedAdditionalWidth = 12;
const int kExpectedAdditionalHeight = 12;
class TestBubbleFrameViewWidgetDelegate : public WidgetDelegate {
public:
explicit TestBubbleFrameViewWidgetDelegate(Widget* widget)
: widget_(widget) {}
~TestBubbleFrameViewWidgetDelegate() override = default;
// WidgetDelegate overrides:
Widget* GetWidget() override { return widget_; }
const Widget* GetWidget() const override { return widget_; }
View* GetContentsView() override {
if (!contents_view_) {
StaticSizedView* contents_view = new StaticSizedView(
gfx::Size(kPreferredClientWidth, kPreferredClientHeight));
contents_view->set_minimum_size(
gfx::Size(kMinimumClientWidth, kMinimumClientHeight));
contents_view->set_maximum_size(
gfx::Size(kMaximumClientWidth, kMaximumClientHeight));
contents_view_ = contents_view;
}
return contents_view_;
}
bool ShouldShowCloseButton() const override { return should_show_close_; }
void SetShouldShowCloseButton(bool should_show_close) {
should_show_close_ = should_show_close;
}
private:
Widget* const widget_;
View* contents_view_ = nullptr; // Owned by |widget_|.
bool should_show_close_ = false;
};
class TestBubbleFrameView : public BubbleFrameView {
public:
explicit TestBubbleFrameView(ViewsTestBase* test_base)
: BubbleFrameView(gfx::Insets(), gfx::Insets(kMargin)) {
SetBubbleBorder(std::make_unique<BubbleBorder>(
kArrow, BubbleBorder::BIG_SHADOW, kColor));
widget_ = std::make_unique<Widget>();
widget_delegate_ =
std::make_unique<TestBubbleFrameViewWidgetDelegate>(widget_.get());
Widget::InitParams params =
test_base->CreateParams(Widget::InitParams::TYPE_BUBBLE);
params.delegate = widget_delegate_.get();
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget_->Init(params);
}
~TestBubbleFrameView() override = default;
void SetAvailableAnchorWindowBounds(gfx::Rect bounds) {
available_anchor_window_bounds_ = bounds;
}
BubbleBorder::Arrow GetBorderArrow() const {
return bubble_border_for_testing()->arrow();
}
SkColor GetBorderBackgroundColor() const {
return bubble_border_for_testing()->background_color();
}
gfx::Insets GetBorderInsets() const {
return bubble_border_for_testing()->GetInsets();
}
// View overrides:
const Widget* GetWidget() const override {
return widget_.get();
}
// BubbleFrameView overrides:
gfx::Rect GetAvailableScreenBounds(const gfx::Rect& rect) const override {
return available_bounds_;
}
gfx::Rect GetAvailableAnchorWindowBounds() const override {
return available_anchor_window_bounds_;
}
TestBubbleFrameViewWidgetDelegate* widget_delegate() {
return widget_delegate_.get();
}
private:
const gfx::Rect available_bounds_ = gfx::Rect(0, 0, 1000, 1000);
gfx::Rect available_anchor_window_bounds_;
std::unique_ptr<TestBubbleFrameViewWidgetDelegate> widget_delegate_;
std::unique_ptr<Widget> widget_;
DISALLOW_COPY_AND_ASSIGN(TestBubbleFrameView);
};
} // namespace
TEST_F(BubbleFrameViewTest, GetBoundsForClientView) {
TestBubbleFrameView frame(this);
EXPECT_EQ(kArrow, frame.GetBorderArrow());
EXPECT_EQ(kColor, frame.GetBorderBackgroundColor());
const gfx::Insets content_margins = frame.content_margins();
const gfx::Insets insets = frame.GetBorderInsets();
const gfx::Rect client_view_bounds = frame.GetBoundsForClientView();
EXPECT_EQ(insets.left() + content_margins.left(), client_view_bounds.x());
EXPECT_EQ(insets.top() + content_margins.top(), client_view_bounds.y());
}
TEST_F(BubbleFrameViewTest, GetBoundsForClientViewWithClose) {
TestBubbleFrameView frame(this);
frame.widget_delegate()->SetShouldShowCloseButton(true);
frame.ResetWindowControls();
EXPECT_EQ(kArrow, frame.GetBorderArrow());
EXPECT_EQ(kColor, frame.GetBorderBackgroundColor());
const gfx::Insets content_margins = frame.content_margins();
const gfx::Insets insets = frame.GetBorderInsets();
const int close_margin =
frame.GetCloseButtonForTest()->height() +
LayoutProvider::Get()->GetDistanceMetric(DISTANCE_CLOSE_BUTTON_MARGIN);
const gfx::Rect client_view_bounds = frame.GetBoundsForClientView();
EXPECT_EQ(insets.left() + content_margins.left(), client_view_bounds.x());
EXPECT_EQ(insets.top() + content_margins.top() + close_margin,
client_view_bounds.y());
}
TEST_F(BubbleFrameViewTest, RemoveFootnoteView) {
TestBubbleFrameView frame(this);
EXPECT_EQ(nullptr, frame.footnote_container_);
auto footnote = std::make_unique<StaticSizedView>(gfx::Size(200, 200));
View* footnote_dummy_view = footnote.get();
frame.SetFootnoteView(std::move(footnote));
EXPECT_EQ(footnote_dummy_view->parent(), frame.footnote_container_);
frame.SetFootnoteView(nullptr);
EXPECT_EQ(nullptr, frame.footnote_container_);
}
TEST_F(BubbleFrameViewTest,
FootnoteContainerViewShouldMatchVisibilityOfFirstChild) {
TestBubbleFrameView frame(this);
std::unique_ptr<View> footnote =
std::make_unique<StaticSizedView>(gfx::Size(200, 200));
footnote->SetVisible(false);
View* footnote_dummy_view = footnote.get();
frame.SetFootnoteView(std::move(footnote));
View* footnote_container_view = footnote_dummy_view->parent();
EXPECT_FALSE(footnote_container_view->GetVisible());
footnote_dummy_view->SetVisible(true);
EXPECT_TRUE(footnote_container_view->GetVisible());
footnote_dummy_view->SetVisible(false);
EXPECT_FALSE(footnote_container_view->GetVisible());
}
// Tests that the arrow is mirrored as needed to better fit the screen.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBounds) {
TestBubbleFrameView frame(this);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test that the info bubble displays normally when it fits.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on left.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on left or top.
frame.SetArrow(BubbleBorder::BOTTOM_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_RIGHT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on top.
frame.SetArrow(BubbleBorder::BOTTOM_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on top and right.
frame.SetArrow(BubbleBorder::BOTTOM_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 900);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on right.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 100, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 900);
EXPECT_EQ(window_bounds.y(), 100);
// Test bubble not fitting on bottom and right.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 900, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 900);
EXPECT_EQ(window_bounds.bottom(), 900);
// Test bubble not fitting at the bottom.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 900, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_LEFT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.bottom(), 900);
// Test bubble not fitting at the bottom and left.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 900, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_LEFT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.x(), 100);
EXPECT_EQ(window_bounds.bottom(), 900);
}
// Tests that the arrow is not moved when the info-bubble does not fit the
// screen but moving it would make matter worse.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsMirroringFails) {
TestBubbleFrameView frame(this);
frame.SetArrow(BubbleBorder::TOP_LEFT);
frame.GetUpdatedWindowBounds(
gfx::Rect(400, 100, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(500, 700), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
}
TEST_F(BubbleFrameViewTest, TestMirroringForCenteredArrow) {
TestBubbleFrameView frame(this);
// Test bubble not fitting above the anchor.
frame.SetArrow(BubbleBorder::BOTTOM_CENTER);
frame.GetUpdatedWindowBounds(
gfx::Rect(100, 100, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_CENTER, // |delegate_arrow|
gfx::Size(500, 700), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_CENTER, frame.GetBorderArrow());
// Test bubble not fitting below the anchor.
frame.SetArrow(BubbleBorder::TOP_CENTER);
frame.GetUpdatedWindowBounds(
gfx::Rect(300, 800, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::TOP_CENTER, // |delegate_arrow|
gfx::Size(500, 200), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.GetBorderArrow());
// Test bubble not fitting to the right of the anchor.
frame.SetArrow(BubbleBorder::LEFT_CENTER);
frame.GetUpdatedWindowBounds(
gfx::Rect(800, 300, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::LEFT_CENTER, // |delegate_arrow|
gfx::Size(200, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::RIGHT_CENTER, frame.GetBorderArrow());
// Test bubble not fitting to the left of the anchor.
frame.SetArrow(BubbleBorder::RIGHT_CENTER);
frame.GetUpdatedWindowBounds(
gfx::Rect(100, 300, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::RIGHT_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::LEFT_CENTER, frame.GetBorderArrow());
}
// Test that the arrow will not be mirrored when
// |adjust_to_fit_available_bounds| is false.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsDontTryMirror) {
TestBubbleFrameView frame(this);
frame.SetBubbleBorder(std::make_unique<BubbleBorder>(
BubbleBorder::TOP_RIGHT, BubbleBorder::NO_SHADOW, kColor));
gfx::Rect window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 900, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
false); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
// The coordinates should be pointing to anchor_rect from TOP_RIGHT.
EXPECT_EQ(window_bounds.right(), 100);
EXPECT_EQ(window_bounds.y(), 900);
}
// Test that the center arrow is moved as needed to fit the screen.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsCenterArrows) {
TestBubbleFrameView frame(this);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Some of these tests may go away once --secondary-ui-md becomes the
// default. Under Material Design mode, the BubbleBorder doesn't support all
// "arrow" positions. If this changes, then the tests should be updated or
// added for MD mode.
// Test that the bubble displays normally when it fits.
frame.SetArrow(BubbleBorder::BOTTOM_CENTER);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(500, 900, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x() + window_bounds.width() / 2, 525);
frame.SetArrow(BubbleBorder::LEFT_CENTER);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 400, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::LEFT_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::LEFT_CENTER, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.y() + window_bounds.height() / 2, 425);
frame.SetArrow(BubbleBorder::RIGHT_CENTER);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 400, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::RIGHT_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::RIGHT_CENTER, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.y() + window_bounds.height() / 2, 425);
// Test bubble not fitting left screen edge.
frame.SetArrow(BubbleBorder::BOTTOM_CENTER);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(100, 900, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 0);
// Test bubble not fitting right screen edge.
frame.SetArrow(BubbleBorder::BOTTOM_CENTER);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 900, 50, 50), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_CENTER, // |delegate_arrow|
gfx::Size(500, 500), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_CENTER, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 1000);
}
// Tests that the arrow is mirrored as needed to better fit the anchor window's
// bounds.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsForBubbleWithAnchorWindow) {
TestBubbleFrameView frame(this);
frame.SetAvailableAnchorWindowBounds(gfx::Rect(100, 100, 500, 500));
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test that the bubble displays normally when it fits.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on left for anchor window displays left aligned
// with the left side of the anchor rect.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on left or top displays left and top aligned
// with the left and bottom sides of the anchor rect.
frame.SetArrow(BubbleBorder::BOTTOM_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_RIGHT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on top displays top aligned with the bottom side of
// the anchor rect.
frame.SetArrow(BubbleBorder::BOTTOM_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on top and right displays right and top aligned
// with the right and bottom sides of the anchor rect.
frame.SetArrow(BubbleBorder::BOTTOM_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(500, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::BOTTOM_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 500);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on right display in line with the right edge of
// the anchor rect.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(500, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 500);
EXPECT_EQ(window_bounds.y(), 200);
// Test bubble not fitting on bottom and right displays in line with the right
// edge of the anchor rect and the bottom in line with the top of the anchor
// rect.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(500, 500, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 500);
EXPECT_EQ(window_bounds.bottom(), 500);
// Test bubble not fitting at the bottom displays line with the top of the
// anchor rect.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 500, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.bottom(), 500);
// Test bubble not fitting at the bottom and left displays right aligned with
// the anchor rect and the bottom in line with the top of the anchor rect.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 500, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
EXPECT_EQ(window_bounds.bottom(), 500);
}
// Tests that the arrow is mirrored as needed to better fit the screen.
TEST_F(BubbleFrameViewTest,
GetUpdatedWindowBoundsForBubbleWithAnchorWindowExitingScreen) {
TestBubbleFrameView frame(this);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test bubble fitting anchor window and not fitting screen on right.
// ________________________
// |screen _________________|__________
// | |anchor window ___|___ |
// | | |bubble | |
// | | |_______| |
// | |_________________|__________|
// |________________________|
frame.SetAvailableAnchorWindowBounds(gfx::Rect(700, 200, 400, 400));
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(800, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.right(), 800);
EXPECT_EQ(window_bounds.y(), 300);
// Test bubble fitting anchor window and not fitting screen on right and
// bottom.
// ________________________
// |screen |
// | _________________|__________
// | |anchor window ___|___ |
// |______|_____________|bubble | |
// | |_______| |
// |____________________________|
frame.SetAvailableAnchorWindowBounds(gfx::Rect(700, 700, 400, 400));
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(800, 800, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.right(), 800);
EXPECT_EQ(window_bounds.bottom(), 800);
// Test bubble not fitting anchor window on bottom and not fitting screen on
// right.
// ________________________
// |screen _________________|__________
// | |anchor window | |
// | | ___|___ |
// | |_____________|bubble |______|
// | |_______|
// |________________________|
frame.SetAvailableAnchorWindowBounds(gfx::Rect(700, 200, 400, 400));
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(800, 500, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::BOTTOM_RIGHT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.right(), 800);
EXPECT_EQ(window_bounds.bottom(), 500);
}
// Tests that the arrow is mirrored as needed to better fit the anchor window's
// bounds.
TEST_F(BubbleFrameViewTest, MirroringNotStickyForGetUpdatedWindowBounds) {
TestBubbleFrameView frame(this);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test bubble fitting anchor window and not fitting screen on right.
frame.SetAvailableAnchorWindowBounds(gfx::Rect(700, 200, 400, 400));
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(800, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.right(), 800);
EXPECT_EQ(window_bounds.y(), 300);
// Test that the bubble mirrors again if it can fit on screen with its
// original anchor.
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(700, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
// The window should be right aligned with the anchor_rect.
EXPECT_EQ(window_bounds.x(), 700);
EXPECT_EQ(window_bounds.y(), 300);
}
// Tests that the arrow is offset as needed to better fit the window.
TEST_F(BubbleFrameViewTest, GetUpdatedWindowBoundsForBubbleSetToOffset) {
TestBubbleFrameView frame(this);
frame.SetAvailableAnchorWindowBounds(gfx::Rect(100, 100, 500, 500));
frame.set_preferred_arrow_adjustment(
BubbleFrameView::PreferredArrowAdjustment::kOffset);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test that the bubble displays normally when it fits.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
// Test bubble not fitting left window edge displayed against left window
// edge.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(200, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 100);
// Test bubble not fitting right window edge displays against the right edge
// of the anchor window.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(500, 200, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 600);
// Test bubble fitting anchor window and not fitting screen on right displays
// against the right edge of the screen.
frame.SetAvailableAnchorWindowBounds(gfx::Rect(800, 300, 500, 500));
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(900, 500, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(250, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.right(), 1000);
}
// Tests that the arrow is offset as needed to better fit the window for
// windows larger than the available bounds.
TEST_F(BubbleFrameViewTest,
GetUpdatedWindowBoundsForBubbleSetToOffsetLargerThanAvailableBounds) {
TestBubbleFrameView frame(this);
frame.SetAvailableAnchorWindowBounds(gfx::Rect(200, 200, 500, 500));
frame.set_preferred_arrow_adjustment(
BubbleFrameView::PreferredArrowAdjustment::kOffset);
gfx::Rect window_bounds;
frame.SetBubbleBorder(
std::make_unique<BubbleBorder>(kArrow, BubbleBorder::NO_SHADOW, kColor));
// Test that the bubble exiting right side of anchor window displays against
// left edge of anchor window bounds if larger than anchor window.
frame.SetArrow(BubbleBorder::TOP_LEFT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(300, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_LEFT, // |delegate_arrow|
gfx::Size(600, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_LEFT, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.x(), 200);
// Test that the bubble exiting left side of anchor window displays against
// right edge of anchor window bounds if larger than anchor window.
frame.SetArrow(BubbleBorder::TOP_RIGHT);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(300, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::TOP_RIGHT, // |delegate_arrow|
gfx::Size(600, 250), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::TOP_RIGHT, frame.GetBorderArrow());
// Check that the right edge of the bubble equals the right edge of the
// anchor window.
EXPECT_EQ(window_bounds.right(), 700);
// Test that the bubble exiting bottom side of anchor window displays against
// top edge of anchor window bounds if larger than anchor window.
frame.SetArrow(BubbleBorder::LEFT_TOP);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(400, 400, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::LEFT_TOP, // |delegate_arrow|
gfx::Size(250, 600), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::LEFT_TOP, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.y(), 200);
// Test that the bubble exiting top side of anchor window displays against
// bottom edge of anchor window bounds if larger than anchor window.
frame.SetArrow(BubbleBorder::LEFT_BOTTOM);
window_bounds = frame.GetUpdatedWindowBounds(
gfx::Rect(300, 300, 0, 0), // |anchor_rect|
BubbleBorder::Arrow::LEFT_BOTTOM, // |delegate_arrow|
gfx::Size(250, 600), // |client_size|
true); // |adjust_to_fit_available_bounds|
EXPECT_EQ(BubbleBorder::LEFT_BOTTOM, frame.GetBorderArrow());
EXPECT_EQ(window_bounds.bottom(), 700);
}
TEST_F(BubbleFrameViewTest, GetPreferredSize) {
// Test border/insets.
TestBubbleFrameView frame(this);
gfx::Rect preferred_rect(frame.GetPreferredSize());
// Expect that a border has been added to the preferred size.
preferred_rect.Inset(frame.GetBorderInsets());
gfx::Size expected_size(kPreferredClientWidth + kExpectedAdditionalWidth,
kPreferredClientHeight + kExpectedAdditionalHeight);
EXPECT_EQ(expected_size, preferred_rect.size());
}
TEST_F(BubbleFrameViewTest, GetPreferredSizeWithFootnote) {
// Test footnote view: adding a footnote should increase the preferred size,
// but only when the footnote is visible.
TestBubbleFrameView frame(this);
constexpr int kFootnoteHeight = 20;
const gfx::Size no_footnote_size = frame.GetPreferredSize();
std::unique_ptr<View> footnote =
std::make_unique<StaticSizedView>(gfx::Size(10, kFootnoteHeight));
footnote->SetVisible(false);
View* footnote_dummy_view = footnote.get();
frame.SetFootnoteView(std::move(footnote));
EXPECT_EQ(no_footnote_size, frame.GetPreferredSize()); // No change.
footnote_dummy_view->SetVisible(true);
gfx::Size with_footnote_size = no_footnote_size;
constexpr int kFootnoteTopBorderThickness = 1;
with_footnote_size.Enlarge(0, kFootnoteHeight + kFootnoteTopBorderThickness +
frame.content_margins().height());
EXPECT_EQ(with_footnote_size, frame.GetPreferredSize());
footnote_dummy_view->SetVisible(false);
EXPECT_EQ(no_footnote_size, frame.GetPreferredSize());
}
TEST_F(BubbleFrameViewTest, GetMinimumSize) {
TestBubbleFrameView frame(this);
gfx::Rect minimum_rect(frame.GetMinimumSize());
// Expect that a border has been added to the minimum size.
minimum_rect.Inset(frame.GetBorderInsets());
gfx::Size expected_size(kMinimumClientWidth + kExpectedAdditionalWidth,
kMinimumClientHeight + kExpectedAdditionalHeight);
EXPECT_EQ(expected_size, minimum_rect.size());
}
TEST_F(BubbleFrameViewTest, GetMaximumSize) {
TestBubbleFrameView frame(this);
gfx::Rect maximum_rect(frame.GetMaximumSize());
#if defined(OS_WIN)
// On Windows, GetMaximumSize causes problems with DWM, so it should just be 0
// (unlimited). See http://crbug.com/506206.
EXPECT_EQ(gfx::Size(), maximum_rect.size());
#else
maximum_rect.Inset(frame.GetBorderInsets());
// Should ignore the contents view's maximum size and use the preferred size.
gfx::Size expected_size(kPreferredClientWidth + kExpectedAdditionalWidth,
kPreferredClientHeight + kExpectedAdditionalHeight);
EXPECT_EQ(expected_size, maximum_rect.size());
#endif
}
namespace {
class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
public:
TestBubbleDialogDelegateView()
: BubbleDialogDelegateView(nullptr, BubbleBorder::NONE) {
set_shadow(BubbleBorder::NO_ASSETS);
SetAnchorRect(gfx::Rect());
}
~TestBubbleDialogDelegateView() override = default;
void ChangeTitle(const base::string16& title) {
title_ = title;
// Note UpdateWindowTitle() always does a layout, which will be invalid if
// the Widget needs to change size. But also SizeToContents() _only_ does a
// layout if the size is actually changing.
GetWidget()->UpdateWindowTitle();
SizeToContents();
}
void set_icon(const gfx::ImageSkia& icon) { icon_ = icon; }
// BubbleDialogDelegateView:
using BubbleDialogDelegateView::SetAnchorView;
using BubbleDialogDelegateView::SizeToContents;
gfx::ImageSkia GetWindowIcon() override { return icon_; }
bool ShouldShowWindowIcon() const override { return !icon_.isNull(); }
base::string16 GetWindowTitle() const override { return title_; }
bool ShouldShowWindowTitle() const override { return !title_.empty(); }
void DeleteDelegate() override {
// This delegate is owned by the test case itself, so it should not delete
// itself here. But DialogDelegates shouldn't be reused, so check for that.
destroyed_ = true;
}
int GetDialogButtons() const override { return ui::DIALOG_BUTTON_OK; }
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(200, 200);
}
void Init() override { DCHECK(!destroyed_); }
BubbleFrameView* GetBubbleFrameView() const {
return static_cast<BubbleFrameView*>(
GetWidget()->non_client_view()->frame_view());
}
private:
gfx::ImageSkia icon_;
base::string16 title_;
bool destroyed_ = false;
DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView);
};
class TestAnchor {
public:
explicit TestAnchor(Widget::InitParams params) {
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget_.Init(params);
widget_.Show();
}
Widget& widget() { return widget_; }
private:
Widget widget_;
DISALLOW_COPY_AND_ASSIGN(TestAnchor);
};
// BubbleDialogDelegate with no margins to test width snapping.
class TestWidthSnapDelegate : public TestBubbleDialogDelegateView {
public:
TestWidthSnapDelegate(TestAnchor* anchor, bool should_snap)
: should_snap_(should_snap) {
SetAnchorView(anchor->widget().GetContentsView());
set_margins(gfx::Insets());
BubbleDialogDelegateView::CreateBubble(this);
GetWidget()->Show();
}
~TestWidthSnapDelegate() override { GetWidget()->CloseNow(); }
// TestBubbleDialogDelegateView:
bool ShouldSnapFrameWidth() const override { return should_snap_; }
private:
bool should_snap_;
DISALLOW_COPY_AND_ASSIGN(TestWidthSnapDelegate);
};
} // namespace
// This test ensures that if the installed LayoutProvider snaps dialog widths,
// BubbleFrameView correctly sizes itself to that width.
TEST_F(BubbleFrameViewTest, WidthSnaps) {
test::TestLayoutProvider provider;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
{
TestWidthSnapDelegate delegate(&anchor, true);
EXPECT_EQ(delegate.GetPreferredSize().width(),
delegate.GetWidget()->GetWindowBoundsInScreen().width());
}
constexpr int kTestWidth = 300;
provider.SetSnappedDialogWidth(kTestWidth);
{
TestWidthSnapDelegate delegate(&anchor, true);
// The Widget's snapped width should exactly match the width returned by the
// LayoutProvider.
EXPECT_EQ(kTestWidth,
delegate.GetWidget()->GetWindowBoundsInScreen().width());
}
{
// If the DialogDelegate asks not to snap, it should not snap.
TestWidthSnapDelegate delegate(&anchor, false);
EXPECT_EQ(delegate.GetPreferredSize().width(),
delegate.GetWidget()->GetWindowBoundsInScreen().width());
}
}
// Tests edge cases when the frame's title view starts to wrap text. This is to
// ensure that the calculations BubbleFrameView does to determine the Widget
// size for a given client view are consistent with the eventual size that the
// client view takes after Layout().
TEST_F(BubbleFrameViewTest, LayoutEdgeCases) {
test::TestLayoutProvider provider;
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
bubble->Show();
// Even though the bubble has default margins, the dialog view should have
// been given its preferred size.
EXPECT_FALSE(delegate.margins().IsEmpty());
EXPECT_EQ(delegate.size(), delegate.GetPreferredSize());
// Starting with a short title.
base::string16 title(1, 'i');
delegate.ChangeTitle(title);
const int min_bubble_height = bubble->GetWindowBoundsInScreen().height();
EXPECT_LT(delegate.GetPreferredSize().height(), min_bubble_height);
// Grow the title incrementally until word wrap is required. There should
// never be a point where the BubbleFrameView over- or under-estimates the
// size required for the title. If it did, it would cause SizeToContents() to
// Widget size requiring the subsequent Layout() to fill the remaining client
// area with something other than |delegate|'s preferred size.
while (bubble->GetWindowBoundsInScreen().height() == min_bubble_height) {
title += ' ';
title += 'i';
delegate.ChangeTitle(title);
EXPECT_EQ(delegate.GetPreferredSize(), delegate.size()) << title;
}
// Sanity check that something interesting happened. The bubble should have
// grown by "a line" for the wrapped title, and the title should have reached
// a length that would have likely caused word wrap. A typical result would be
// a +17-20 change in height and title length of 53 characters.
const int two_line_height = bubble->GetWindowBoundsInScreen().height();
EXPECT_LT(12, two_line_height - min_bubble_height);
EXPECT_GT(25, two_line_height - min_bubble_height);
EXPECT_LT(30u, title.size());
EXPECT_GT(80u, title.size());
// Now add dialog snapping.
provider.SetSnappedDialogWidth(300);
delegate.SizeToContents();
// Height should go back to |min_bubble_height| since the window is wider:
// word wrapping should no longer happen.
EXPECT_EQ(min_bubble_height, bubble->GetWindowBoundsInScreen().height());
EXPECT_EQ(300, bubble->GetWindowBoundsInScreen().width());
// Now we are allowed to diverge from the client view width, but not height.
EXPECT_EQ(delegate.GetPreferredSize().height(), delegate.height());
EXPECT_LT(delegate.GetPreferredSize().width(), delegate.width());
EXPECT_GT(300, delegate.width()); // Greater, since there are margins.
const gfx::Size snapped_size = delegate.size();
const size_t old_title_size = title.size();
// Grow the title again with width snapping until word wrapping occurs.
while (bubble->GetWindowBoundsInScreen().height() == min_bubble_height) {
title += ' ';
title += 'i';
delegate.ChangeTitle(title);
EXPECT_EQ(snapped_size, delegate.size()) << title;
}
// Change to the height should have been the same as before. Title should
// have grown about 50%.
EXPECT_EQ(two_line_height, bubble->GetWindowBoundsInScreen().height());
EXPECT_LT(15u, title.size() - old_title_size);
EXPECT_GT(40u, title.size() - old_title_size);
// When |anchor| goes out of scope it should take |bubble| with it.
}
TEST_F(BubbleFrameViewTest, LayoutWithIcon) {
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
SkBitmap bitmap;
bitmap.allocN32Pixels(20, 80);
bitmap.eraseColor(SK_ColorYELLOW);
delegate.set_icon(gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
Widget* widget = BubbleDialogDelegateView::CreateBubble(&delegate);
widget->Show();
delegate.ChangeTitle(base::ASCIIToUTF16("test title"));
BubbleFrameView* frame = delegate.GetBubbleFrameView();
View* icon = frame->title_icon_;
View* title = frame->title();
// There should be equal amounts of space on the left and right of the icon.
EXPECT_EQ(icon->x() * 2 + icon->width(), title->x());
// The title should be vertically centered relative to the icon.
EXPECT_LT(title->height(), icon->height());
const int title_offset_y = (icon->height() - title->height()) / 2;
EXPECT_EQ(icon->y() + title_offset_y, title->y());
}
// Test the size of the bubble allows a |gfx::NO_ELIDE| title to fit, even if
// there is no content.
TEST_F(BubbleFrameViewTest, NoElideTitle) {
test::TestLayoutProvider provider;
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
// Make sure the client area size doesn't interfere with the final size.
delegate.SetPreferredSize(gfx::Size());
Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
bubble->Show();
// Before changing the title, get the base width of the bubble when there's no
// title or content in it.
const int empty_bubble_width = bubble->GetClientAreaBoundsInScreen().width();
base::string16 title = base::ASCIIToUTF16("This is a title string");
delegate.ChangeTitle(title);
Label* title_label =
static_cast<Label*>(delegate.GetBubbleFrameView()->title());
// Sanity check: Title labels default to multiline and elide tail. Either of
// which result in the Layout system making the title and resulting dialog
// very narrow.
EXPECT_EQ(gfx::ELIDE_TAIL, title_label->elide_behavior());
EXPECT_TRUE(title_label->multi_line());
EXPECT_GT(empty_bubble_width, title_label->size().width());
EXPECT_EQ(empty_bubble_width, bubble->GetClientAreaBoundsInScreen().width());
// Set the title to a non-eliding label.
title_label->SetElideBehavior(gfx::NO_ELIDE);
title_label->SetMultiLine(false);
// Update the bubble size now that some properties of the title have changed.
delegate.SizeToContents();
// The title/bubble should now be bigger than in multiline tail-eliding mode.
EXPECT_LT(empty_bubble_width, title_label->size().width());
EXPECT_LT(empty_bubble_width, bubble->GetClientAreaBoundsInScreen().width());
// Make sure the bubble is wide enough to fit the title's full size. Frame
// sizing is done off the title label's minimum size. But since that label is
// set to NO_ELIDE, the minimum size should match the preferred size.
EXPECT_GE(bubble->GetClientAreaBoundsInScreen().width(),
title_label->GetPreferredSize().width());
EXPECT_LE(title_label->GetPreferredSize().width(),
title_label->size().width());
EXPECT_EQ(title, title_label->GetDisplayTextForTesting());
}
// Ensures that clicks are ignored for short time after view has been shown.
TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks) {
TestBubbleDialogDelegateView delegate;
TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW));
delegate.SetAnchorView(anchor.widget().GetContentsView());
Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate);
bubble->Show();
BubbleFrameView* frame = delegate.GetBubbleFrameView();
frame->ButtonPressed(
frame->close_,
ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE));
EXPECT_FALSE(bubble->IsClosed());
frame->ButtonPressed(
frame->close_,
ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow() + base::TimeDelta::FromMilliseconds(
GetDoubleClickInterval()),
ui::EF_NONE, ui::EF_NONE));
EXPECT_TRUE(bubble->IsClosed());
}
} // namespace views