| // 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 "base/run_loop.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/views/bubble/bubble_delegate.h" |
| #include "ui/views/bubble/bubble_frame_view.h" |
| #include "ui/views/test/test_widget_observer.h" |
| #include "ui/views/test/views_test_base.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_observer.h" |
| |
| #if defined(USE_AURA) |
| #include "ui/aura/env.h" |
| #endif |
| |
| namespace views { |
| |
| namespace { |
| |
| class TestBubbleDelegateView : public BubbleDelegateView { |
| public: |
| TestBubbleDelegateView(View* anchor_view) |
| : BubbleDelegateView(anchor_view, BubbleBorder::TOP_LEFT), |
| view_(new View()) { |
| view_->set_focusable(true); |
| AddChildView(view_); |
| } |
| virtual ~TestBubbleDelegateView() {} |
| |
| void SetAnchorRectForTest(gfx::Rect rect) { |
| SetAnchorRect(rect); |
| } |
| |
| void SetAnchorViewForTest(View* view) { |
| SetAnchorView(view); |
| } |
| |
| // BubbleDelegateView overrides: |
| virtual View* GetInitiallyFocusedView() OVERRIDE { return view_; } |
| virtual gfx::Size GetPreferredSize() OVERRIDE { return gfx::Size(200, 200); } |
| virtual int GetFadeDuration() OVERRIDE { return 1; } |
| |
| private: |
| View* view_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestBubbleDelegateView); |
| }; |
| |
| class BubbleDelegateTest : public ViewsTestBase { |
| public: |
| BubbleDelegateTest() {} |
| virtual ~BubbleDelegateTest() {} |
| |
| // Creates a test widget that owns its native widget. |
| Widget* CreateTestWidget() { |
| Widget* widget = new Widget(); |
| Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| widget->Init(params); |
| return widget; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BubbleDelegateTest); |
| }; |
| |
| } // namespace |
| |
| TEST_F(BubbleDelegateTest, CreateDelegate) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| BubbleDelegateView* bubble_delegate = new BubbleDelegateView( |
| anchor_widget->GetContentsView(), BubbleBorder::NONE); |
| bubble_delegate->set_color(SK_ColorGREEN); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate()); |
| EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget()); |
| test::TestWidgetObserver bubble_observer(bubble_widget); |
| bubble_widget->Show(); |
| |
| BubbleBorder* border = bubble_delegate->GetBubbleFrameView()->bubble_border(); |
| EXPECT_EQ(bubble_delegate->arrow(), border->arrow()); |
| EXPECT_EQ(bubble_delegate->color(), border->background_color()); |
| |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| bubble_widget->CloseNow(); |
| EXPECT_TRUE(bubble_observer.widget_closed()); |
| } |
| |
| TEST_F(BubbleDelegateTest, CloseAnchorWidget) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| BubbleDelegateView* bubble_delegate = new BubbleDelegateView( |
| anchor_widget->GetContentsView(), BubbleBorder::NONE); |
| // Preventing close on deactivate should not prevent closing with the anchor. |
| bubble_delegate->set_close_on_deactivate(false); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate()); |
| EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget()); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| test::TestWidgetObserver bubble_observer(bubble_widget); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| |
| bubble_widget->Show(); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| |
| #if defined(USE_AURA) |
| // TODO(msw): Remove activation hack to prevent bookkeeping errors in: |
| // aura::test::TestActivationClient::OnWindowDestroyed(). |
| scoped_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget()); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| #endif |
| |
| // Ensure that closing the anchor widget also closes the bubble itself. |
| anchor_widget->CloseNow(); |
| EXPECT_TRUE(bubble_observer.widget_closed()); |
| } |
| |
| // This test checks that the bubble delegate is capable to handle an early |
| // destruction of the used anchor view. (Animations and delayed closure of the |
| // bubble will call upon the anchor view to get its location). |
| TEST_F(BubbleDelegateTest, CloseAnchorViewTest) { |
| // Create an anchor widget and add a view to be used as an anchor view. |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| scoped_ptr<View> anchor_view(new View()); |
| anchor_widget->GetContentsView()->AddChildView(anchor_view.get()); |
| TestBubbleDelegateView* bubble_delegate = new TestBubbleDelegateView( |
| anchor_view.get()); |
| // Prevent flakes by avoiding closing on activation changes. |
| bubble_delegate->set_close_on_deactivate(false); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| |
| // Check that the anchor view is correct and set up an anchor view rect. |
| // Make sure that this rect will get ignored (as long as the anchor view is |
| // attached). |
| EXPECT_EQ(anchor_view, bubble_delegate->GetAnchorView()); |
| const gfx::Rect set_anchor_rect = gfx::Rect(10, 10, 100, 100); |
| bubble_delegate->SetAnchorRectForTest(set_anchor_rect); |
| const gfx::Rect view_rect = bubble_delegate->GetAnchorRect(); |
| EXPECT_NE(view_rect.ToString(), set_anchor_rect.ToString()); |
| |
| // Create the bubble. |
| bubble_widget->Show(); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| |
| // Remove now the anchor view and make sure that the original found rect |
| // is still kept, so that the bubble does not jump when the view gets deleted. |
| anchor_widget->GetContentsView()->RemoveChildView(anchor_view.get()); |
| anchor_view.reset(); |
| EXPECT_EQ(NULL, bubble_delegate->GetAnchorView()); |
| EXPECT_EQ(view_rect.ToString(), bubble_delegate->GetAnchorRect().ToString()); |
| } |
| |
| // Testing that a move of the anchor view will lead to new bubble locations. |
| TEST_F(BubbleDelegateTest, TestAnchorRectMovesWithViewTest) { |
| // Create an anchor widget and add a view to be used as anchor view. |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| TestBubbleDelegateView* bubble_delegate = new TestBubbleDelegateView( |
| anchor_widget->GetContentsView()); |
| BubbleDelegateView::CreateBubble(bubble_delegate); |
| |
| anchor_widget->GetContentsView()->SetBounds(10, 10, 100, 100); |
| const gfx::Rect view_rect = bubble_delegate->GetAnchorRect(); |
| |
| anchor_widget->GetContentsView()->SetBounds(20, 10, 100, 100); |
| const gfx::Rect view_rect_2 = bubble_delegate->GetAnchorRect(); |
| EXPECT_NE(view_rect.ToString(), view_rect_2.ToString()); |
| } |
| |
| TEST_F(BubbleDelegateTest, ResetAnchorWidget) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| BubbleDelegateView* bubble_delegate = new BubbleDelegateView( |
| anchor_widget->GetContentsView(), BubbleBorder::NONE); |
| |
| // Make sure the bubble widget is parented to a widget other than the anchor |
| // widget so that closing the anchor widget does not close the bubble widget. |
| scoped_ptr<Widget> parent_widget(CreateTestWidget()); |
| bubble_delegate->set_parent_window(parent_widget->GetNativeView()); |
| // Preventing close on deactivate should not prevent closing with the parent. |
| bubble_delegate->set_close_on_deactivate(false); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate()); |
| EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget()); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| test::TestWidgetObserver bubble_observer(bubble_widget); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| |
| // Showing and hiding the bubble widget should have no effect on its anchor. |
| bubble_widget->Show(); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| bubble_widget->Hide(); |
| EXPECT_EQ(anchor_widget, bubble_delegate->anchor_widget()); |
| |
| // Ensure that closing the anchor widget clears the bubble's reference to that |
| // anchor widget, but the bubble itself does not close. |
| anchor_widget->CloseNow(); |
| EXPECT_NE(anchor_widget, bubble_delegate->anchor_widget()); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| |
| #if defined(USE_AURA) |
| // TODO(msw): Remove activation hack to prevent bookkeeping errors in: |
| // aura::test::TestActivationClient::OnWindowDestroyed(). |
| scoped_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget()); |
| EXPECT_FALSE(bubble_observer.widget_closed()); |
| #endif |
| |
| // Ensure that closing the parent widget also closes the bubble itself. |
| parent_widget->CloseNow(); |
| EXPECT_TRUE(bubble_observer.widget_closed()); |
| } |
| |
| TEST_F(BubbleDelegateTest, InitiallyFocusedView) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| BubbleDelegateView* bubble_delegate = new BubbleDelegateView( |
| anchor_widget->GetContentsView(), BubbleBorder::NONE); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| EXPECT_EQ(bubble_delegate->GetInitiallyFocusedView(), |
| bubble_widget->GetFocusManager()->GetFocusedView()); |
| bubble_widget->CloseNow(); |
| } |
| |
| TEST_F(BubbleDelegateTest, NonClientHitTest) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| TestBubbleDelegateView* bubble_delegate = |
| new TestBubbleDelegateView(anchor_widget->GetContentsView()); |
| BubbleDelegateView::CreateBubble(bubble_delegate); |
| BubbleFrameView* frame = bubble_delegate->GetBubbleFrameView(); |
| const int border = frame->bubble_border()->GetBorderThickness(); |
| |
| struct { |
| const int point; |
| const int hit; |
| } cases[] = { |
| { border, HTNOWHERE }, |
| { border + 50, HTCLIENT }, |
| { 1000, HTNOWHERE }, |
| }; |
| |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) { |
| gfx::Point point(cases[i].point, cases[i].point); |
| EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point)) |
| << " with border: " << border << ", at point " << cases[i].point; |
| } |
| } |
| |
| // This class provides functionality to verify that the BubbleView shows up |
| // when we call BubbleDelegateView::StartFade(true) and is destroyed when we |
| // call BubbleDelegateView::StartFade(false). |
| class BubbleWidgetClosingTest : public BubbleDelegateTest, |
| public views::WidgetObserver { |
| public: |
| BubbleWidgetClosingTest() : bubble_destroyed_(false) { |
| #if defined(USE_AURA) |
| aura::Env::CreateInstance(); |
| loop_.set_dispatcher(aura::Env::GetInstance()->GetDispatcher()); |
| #endif |
| } |
| |
| virtual ~BubbleWidgetClosingTest() {} |
| |
| void Observe(views::Widget* widget) { |
| widget->AddObserver(this); |
| } |
| |
| // views::WidgetObserver overrides. |
| virtual void OnWidgetDestroyed(Widget* widget) OVERRIDE { |
| bubble_destroyed_ = true; |
| widget->RemoveObserver(this); |
| loop_.Quit(); |
| } |
| |
| bool bubble_destroyed() const { return bubble_destroyed_; } |
| |
| void RunNestedLoop() { |
| loop_.Run(); |
| } |
| |
| private: |
| bool bubble_destroyed_; |
| base::RunLoop loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BubbleWidgetClosingTest); |
| }; |
| |
| TEST_F(BubbleWidgetClosingTest, TestBubbleVisibilityAndClose) { |
| scoped_ptr<Widget> anchor_widget(CreateTestWidget()); |
| TestBubbleDelegateView* bubble_delegate = |
| new TestBubbleDelegateView(anchor_widget->GetContentsView()); |
| Widget* bubble_widget = BubbleDelegateView::CreateBubble(bubble_delegate); |
| EXPECT_FALSE(bubble_widget->IsVisible()); |
| |
| bubble_delegate->StartFade(true); |
| EXPECT_TRUE(bubble_widget->IsVisible()); |
| |
| EXPECT_EQ(bubble_delegate->GetInitiallyFocusedView(), |
| bubble_widget->GetFocusManager()->GetFocusedView()); |
| |
| Observe(bubble_widget); |
| |
| bubble_delegate->StartFade(false); |
| RunNestedLoop(); |
| EXPECT_TRUE(bubble_destroyed()); |
| } |
| |
| } // namespace views |