blob: e4d7eebb2024cce4e9b19a208b4c48fe5633a059 [file]
// 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/window/dialog_client_view.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "ui/base/ui_base_types.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_delegate.h"
namespace views {
// Base class for tests. Also acts as the dialog delegate and contents view for
// TestDialogClientView.
class DialogClientViewTest : public test::WidgetTest,
public DialogDelegateView {
public:
DialogClientViewTest() {}
// testing::Test:
void SetUp() override {
WidgetTest::SetUp();
// Note: not using DialogDelegate::CreateDialogWidget(..), since that can
// alter the frame type according to the platform.
widget_ = new views::Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.delegate = this;
widget_->Init(params);
EXPECT_EQ(this, GetContentsView());
}
void TearDown() override {
widget_->CloseNow();
WidgetTest::TearDown();
}
// DialogDelegateView:
ClientView* CreateClientView(Widget* widget) override {
client_view_ = new DialogClientView(widget, this);
return client_view_;
}
bool ShouldUseCustomFrame() const override { return false; }
void DeleteDelegate() override {
// DialogDelegateView would delete this, but |this| is owned by the test.
}
View* CreateExtraView() override { return next_extra_view_.release(); }
bool GetExtraViewPadding(int* padding) override {
if (extra_view_padding_)
*padding = *extra_view_padding_;
return extra_view_padding_.get() != nullptr;
}
int GetDialogButtons() const override { return dialog_buttons_; }
protected:
gfx::Rect GetUpdatedClientBounds() {
client_view_->SizeToPreferredSize();
client_view_->Layout();
return client_view_->bounds();
}
// Makes sure that the content view is sized correctly. Width must be at least
// the requested amount, but height should always match exactly.
void CheckContentsIsSetToPreferredSize() {
const gfx::Rect client_bounds = GetUpdatedClientBounds();
const gfx::Size preferred_size = this->GetPreferredSize();
EXPECT_EQ(preferred_size.height(), this->bounds().height());
EXPECT_LE(preferred_size.width(), this->bounds().width());
EXPECT_EQ(gfx::Point(), this->origin());
EXPECT_EQ(client_bounds.width(), this->width());
}
// Sets the buttons to show in the dialog and refreshes the dialog.
void SetDialogButtons(int dialog_buttons) {
dialog_buttons_ = dialog_buttons;
client_view_->UpdateDialogButtons();
}
// Sets the view to provide to CreateExtraView() and updates the dialog. This
// can only be called a single time because DialogClientView caches the result
// of CreateExtraView() and never calls it again.
void SetExtraView(View* view) {
EXPECT_FALSE(next_extra_view_);
next_extra_view_ = base::WrapUnique(view);
client_view_->UpdateDialogButtons();
EXPECT_FALSE(next_extra_view_);
}
// Sets the extra view padding.
void SetExtraViewPadding(int padding) {
DCHECK(!extra_view_padding_);
extra_view_padding_.reset(new int(padding));
client_view_->Layout();
}
View* FocusableViewAfter(View* view) {
const bool dont_loop = false;
const bool reverse = false;
return GetFocusManager()->GetNextFocusableView(view, GetWidget(), reverse,
dont_loop);
}
DialogClientView* client_view() { return client_view_; }
private:
// The dialog Widget.
Widget* widget_ = nullptr;
// The DialogClientView that's being tested. Owned by |widget_|.
DialogClientView* client_view_;
// The bitmask of buttons to show in the dialog.
int dialog_buttons_ = ui::DIALOG_BUTTON_NONE;
// Set and cleared in SetExtraView().
std::unique_ptr<View> next_extra_view_;
std::unique_ptr<int> extra_view_padding_;
DISALLOW_COPY_AND_ASSIGN(DialogClientViewTest);
};
TEST_F(DialogClientViewTest, UpdateButtons) {
// This dialog should start with no buttons.
EXPECT_EQ(GetDialogButtons(), ui::DIALOG_BUTTON_NONE);
EXPECT_EQ(NULL, client_view()->ok_button());
EXPECT_EQ(NULL, client_view()->cancel_button());
const int height_without_buttons = GetUpdatedClientBounds().height();
// Update to use both buttons.
SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
EXPECT_TRUE(client_view()->ok_button()->is_default());
EXPECT_FALSE(client_view()->cancel_button()->is_default());
const int height_with_buttons = GetUpdatedClientBounds().height();
EXPECT_GT(height_with_buttons, height_without_buttons);
// Remove the dialog buttons.
SetDialogButtons(ui::DIALOG_BUTTON_NONE);
EXPECT_EQ(NULL, client_view()->ok_button());
EXPECT_EQ(NULL, client_view()->cancel_button());
EXPECT_EQ(GetUpdatedClientBounds().height(), height_without_buttons);
// Reset with just an ok button.
SetDialogButtons(ui::DIALOG_BUTTON_OK);
EXPECT_TRUE(client_view()->ok_button()->is_default());
EXPECT_EQ(NULL, client_view()->cancel_button());
EXPECT_EQ(GetUpdatedClientBounds().height(), height_with_buttons);
// Reset with just a cancel button.
SetDialogButtons(ui::DIALOG_BUTTON_CANCEL);
EXPECT_EQ(NULL, client_view()->ok_button());
EXPECT_EQ(client_view()->cancel_button()->is_default(),
PlatformStyle::kDialogDefaultButtonCanBeCancel);
EXPECT_EQ(GetUpdatedClientBounds().height(), height_with_buttons);
}
TEST_F(DialogClientViewTest, RemoveAndUpdateButtons) {
// Removing buttons from another context should clear the local pointer.
SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
delete client_view()->ok_button();
EXPECT_EQ(NULL, client_view()->ok_button());
delete client_view()->cancel_button();
EXPECT_EQ(NULL, client_view()->cancel_button());
// Updating should restore the requested buttons properly.
SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
EXPECT_TRUE(client_view()->ok_button()->is_default());
EXPECT_FALSE(client_view()->cancel_button()->is_default());
}
// Test that views inside the dialog client view have the correct focus order.
TEST_F(DialogClientViewTest, SetupFocusChain) {
#if defined(OS_WIN) || defined(OS_CHROMEOS)
const bool kIsOkButtonOnLeftSide = true;
#else
const bool kIsOkButtonOnLeftSide = false;
#endif
// Initially the dialog client view only contains the content view.
EXPECT_EQ(nullptr, GetContentsView()->GetNextFocusableView());
// Add OK and cancel buttons.
SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
if (kIsOkButtonOnLeftSide) {
EXPECT_EQ(client_view()->ok_button(),
GetContentsView()->GetNextFocusableView());
EXPECT_EQ(client_view()->cancel_button(),
client_view()->ok_button()->GetNextFocusableView());
EXPECT_EQ(nullptr, client_view()->cancel_button()->GetNextFocusableView());
} else {
EXPECT_EQ(client_view()->cancel_button(),
GetContentsView()->GetNextFocusableView());
EXPECT_EQ(client_view()->ok_button(),
client_view()->cancel_button()->GetNextFocusableView());
EXPECT_EQ(nullptr, client_view()->ok_button()->GetNextFocusableView());
}
// Add extra view and remove OK button.
View* extra_view = new StaticSizedView(gfx::Size(200, 200));
extra_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
SetExtraView(extra_view);
SetDialogButtons(ui::DIALOG_BUTTON_CANCEL);
EXPECT_EQ(extra_view, GetContentsView()->GetNextFocusableView());
EXPECT_EQ(client_view()->cancel_button(), extra_view->GetNextFocusableView());
EXPECT_EQ(nullptr, client_view()->cancel_button()->GetNextFocusableView());
// Add a dummy view to the contents view. Consult the FocusManager for the
// traversal order since it now spans different levels of the view hierarchy.
View* dummy_view = new StaticSizedView(gfx::Size(200, 200));
dummy_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
GetContentsView()->AddChildView(dummy_view);
EXPECT_EQ(dummy_view, FocusableViewAfter(client_view()->cancel_button()));
EXPECT_EQ(extra_view, FocusableViewAfter(dummy_view));
EXPECT_EQ(client_view()->cancel_button(), FocusableViewAfter(extra_view));
// Views are added to the contents view, not the client view, so the focus
// chain within the client view is not affected.
EXPECT_EQ(nullptr, client_view()->cancel_button()->GetNextFocusableView());
}
// Test that the contents view gets its preferred size in the basic dialog
// configuration.
TEST_F(DialogClientViewTest, ContentsSize) {
CheckContentsIsSetToPreferredSize();
EXPECT_EQ(GetContentsView()->size(), client_view()->size());
// There's nothing in the contents view (i.e. |this|), so it should be 0x0.
EXPECT_EQ(gfx::Size(), client_view()->size());
}
// Test the effect of the button strip on layout.
TEST_F(DialogClientViewTest, LayoutWithButtons) {
SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
CheckContentsIsSetToPreferredSize();
EXPECT_LT(GetContentsView()->bounds().bottom(),
client_view()->bounds().bottom());
gfx::Size no_extra_view_size = client_view()->bounds().size();
View* extra_view = new StaticSizedView(gfx::Size(200, 200));
SetExtraView(extra_view);
CheckContentsIsSetToPreferredSize();
EXPECT_GT(client_view()->bounds().height(), no_extra_view_size.height());
int width_of_dialog = client_view()->bounds().width();
int width_of_extra_view = extra_view->bounds().width();
// Try with an adjusted padding for the extra view.
SetExtraViewPadding(250);
CheckContentsIsSetToPreferredSize();
EXPECT_GT(client_view()->bounds().width(), width_of_dialog);
// Visibility of extra view is respected.
extra_view->SetVisible(false);
CheckContentsIsSetToPreferredSize();
EXPECT_EQ(no_extra_view_size.height(), client_view()->bounds().height());
EXPECT_EQ(no_extra_view_size.width(), client_view()->bounds().width());
// Try with a reduced-size dialog.
extra_view->SetVisible(true);
client_view()->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), no_extra_view_size));
client_view()->Layout();
EXPECT_GT(width_of_extra_view, extra_view->bounds().width());
}
} // namespace views