1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/basictypes.h"
6#include "base/strings/utf_string_conversions.h"
7#include "ui/base/ui_base_types.h"
8#include "ui/views/controls/button/label_button.h"
9#include "ui/views/test/test_views.h"
10#include "ui/views/test/views_test_base.h"
11#include "ui/views/widget/widget.h"
12#include "ui/views/window/dialog_client_view.h"
13#include "ui/views/window/dialog_delegate.h"
14
15namespace views {
16
17class TestDialogClientView : public DialogClientView {
18 public:
19  TestDialogClientView(View* contents_view,
20                       DialogDelegate* dialog_delegate)
21      : DialogClientView(contents_view),
22        dialog_(dialog_delegate) {}
23  virtual ~TestDialogClientView() {}
24
25  // DialogClientView implementation.
26  virtual DialogDelegate* GetDialogDelegate() const OVERRIDE { return dialog_; }
27
28  View* GetContentsView() { return contents_view(); }
29
30  void CreateExtraViews() {
31    CreateExtraView();
32    CreateFootnoteView();
33  }
34
35 private:
36  DialogDelegate* dialog_;
37
38  DISALLOW_COPY_AND_ASSIGN(TestDialogClientView);
39};
40
41class DialogClientViewTest : public ViewsTestBase,
42                             public DialogDelegateView {
43 public:
44  DialogClientViewTest()
45      : dialog_buttons_(ui::DIALOG_BUTTON_NONE),
46        extra_view_(NULL),
47        footnote_view_(NULL) {}
48  virtual ~DialogClientViewTest() {}
49
50  // testing::Test implementation.
51  virtual void SetUp() OVERRIDE {
52    dialog_buttons_ = ui::DIALOG_BUTTON_NONE;
53    contents_.reset(new StaticSizedView(gfx::Size(100, 200)));
54    client_view_.reset(new TestDialogClientView(contents_.get(), this));
55
56    ViewsTestBase::SetUp();
57  }
58
59  // DialogDelegateView implementation.
60  virtual View* GetContentsView() OVERRIDE { return contents_.get(); }
61  virtual View* CreateExtraView() OVERRIDE { return extra_view_; }
62  virtual View* CreateFootnoteView() OVERRIDE { return footnote_view_; }
63  virtual int GetDialogButtons() const OVERRIDE { return dialog_buttons_; }
64
65 protected:
66  gfx::Rect GetUpdatedClientBounds() {
67    client_view_->SizeToPreferredSize();
68    client_view_->Layout();
69    return client_view_->bounds();
70  }
71
72  // Makes sure that the content view is sized correctly. Width must be at least
73  // the requested amount, but height should always match exactly.
74  void CheckContentsIsSetToPreferredSize() {
75    const gfx::Rect client_bounds = GetUpdatedClientBounds();
76    const gfx::Size preferred_size = contents_->GetPreferredSize();
77    EXPECT_EQ(preferred_size.height(), contents_->bounds().height());
78    EXPECT_LE(preferred_size.width(), contents_->bounds().width());
79    EXPECT_EQ(contents_->bounds().origin(), client_bounds.origin());
80    EXPECT_EQ(contents_->bounds().right(), client_bounds.right());
81  }
82
83  // Sets the buttons to show in the dialog and refreshes the dialog.
84  void SetDialogButtons(int dialog_buttons) {
85    dialog_buttons_ = dialog_buttons;
86    client_view_->UpdateDialogButtons();
87  }
88
89  // Sets the extra view.
90  void SetExtraView(View* view) {
91    DCHECK(!extra_view_);
92    extra_view_ = view;
93    client_view_->CreateExtraViews();
94  }
95
96  // Sets the footnote view.
97  void SetFootnoteView(View* view) {
98    DCHECK(!footnote_view_);
99    footnote_view_ = view;
100    client_view_->CreateExtraViews();
101  }
102
103  TestDialogClientView* client_view() { return client_view_.get(); }
104
105 private:
106  // The contents of the dialog.
107  scoped_ptr<View> contents_;
108  // The DialogClientView that's being tested.
109  scoped_ptr<TestDialogClientView> client_view_;
110  // The bitmask of buttons to show in the dialog.
111  int dialog_buttons_;
112  View* extra_view_;  // weak
113  View* footnote_view_;  // weak
114
115  DISALLOW_COPY_AND_ASSIGN(DialogClientViewTest);
116};
117
118TEST_F(DialogClientViewTest, UpdateButtons) {
119  // This dialog should start with no buttons.
120  EXPECT_EQ(GetDialogButtons(), ui::DIALOG_BUTTON_NONE);
121  EXPECT_EQ(NULL, client_view()->ok_button());
122  EXPECT_EQ(NULL, client_view()->cancel_button());
123  const int height_without_buttons = GetUpdatedClientBounds().height();
124
125  // Update to use both buttons.
126  SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
127  EXPECT_TRUE(client_view()->ok_button()->is_default());
128  EXPECT_FALSE(client_view()->cancel_button()->is_default());
129  const int height_with_buttons = GetUpdatedClientBounds().height();
130  EXPECT_GT(height_with_buttons, height_without_buttons);
131
132  // Remove the dialog buttons.
133  SetDialogButtons(ui::DIALOG_BUTTON_NONE);
134  EXPECT_EQ(NULL, client_view()->ok_button());
135  EXPECT_EQ(NULL, client_view()->cancel_button());
136  EXPECT_EQ(GetUpdatedClientBounds().height(), height_without_buttons);
137
138  // Reset with just an ok button.
139  SetDialogButtons(ui::DIALOG_BUTTON_OK);
140  EXPECT_TRUE(client_view()->ok_button()->is_default());
141  EXPECT_EQ(NULL, client_view()->cancel_button());
142  EXPECT_EQ(GetUpdatedClientBounds().height(), height_with_buttons);
143
144  // Reset with just a cancel button.
145  SetDialogButtons(ui::DIALOG_BUTTON_CANCEL);
146  EXPECT_EQ(NULL, client_view()->ok_button());
147  EXPECT_TRUE(client_view()->cancel_button()->is_default());
148  EXPECT_EQ(GetUpdatedClientBounds().height(), height_with_buttons);
149}
150
151TEST_F(DialogClientViewTest, RemoveAndUpdateButtons) {
152  // Removing buttons from another context should clear the local pointer.
153  SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
154  delete client_view()->ok_button();
155  EXPECT_EQ(NULL, client_view()->ok_button());
156  delete client_view()->cancel_button();
157  EXPECT_EQ(NULL, client_view()->cancel_button());
158
159  // Updating should restore the requested buttons properly.
160  SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
161  EXPECT_TRUE(client_view()->ok_button()->is_default());
162  EXPECT_FALSE(client_view()->cancel_button()->is_default());
163}
164
165// Test that the contents view gets its preferred size in the basic dialog
166// configuration.
167TEST_F(DialogClientViewTest, ContentsSize) {
168  CheckContentsIsSetToPreferredSize();
169  EXPECT_EQ(GetContentsView()->bounds().bottom(),
170            client_view()->bounds().bottom());
171}
172
173// Test the effect of the button strip on layout.
174TEST_F(DialogClientViewTest, LayoutWithButtons) {
175  SetDialogButtons(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
176  CheckContentsIsSetToPreferredSize();
177  EXPECT_LT(GetContentsView()->bounds().bottom(),
178            client_view()->bounds().bottom());
179  gfx::Size no_extra_view_size = client_view()->bounds().size();
180
181  View* extra_view = new StaticSizedView(gfx::Size(200, 200));
182  SetExtraView(extra_view);
183  CheckContentsIsSetToPreferredSize();
184  EXPECT_GT(client_view()->bounds().height(), no_extra_view_size.height());
185  int width_of_extra_view = extra_view->bounds().width();
186
187  // Visibility of extra view is respected.
188  extra_view->SetVisible(false);
189  CheckContentsIsSetToPreferredSize();
190  EXPECT_EQ(no_extra_view_size.height(), client_view()->bounds().height());
191  EXPECT_EQ(no_extra_view_size.width(), client_view()->bounds().width());
192
193  // Try with a reduced-size dialog.
194  extra_view->SetVisible(true);
195  client_view()->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), no_extra_view_size));
196  client_view()->Layout();
197  DCHECK_GT(width_of_extra_view, extra_view->bounds().width());
198}
199
200// Test the effect of the footnote view on layout.
201TEST_F(DialogClientViewTest, LayoutWithFootnote) {
202  CheckContentsIsSetToPreferredSize();
203  gfx::Size no_footnote_size = client_view()->bounds().size();
204
205  View* footnote_view = new StaticSizedView(gfx::Size(200, 200));
206  SetFootnoteView(footnote_view);
207  CheckContentsIsSetToPreferredSize();
208  EXPECT_GT(client_view()->bounds().height(), no_footnote_size.height());
209  EXPECT_EQ(200, footnote_view->bounds().height());
210  gfx::Size with_footnote_size = client_view()->bounds().size();
211  EXPECT_EQ(with_footnote_size.width(), footnote_view->bounds().width());
212
213  SetDialogButtons(ui::DIALOG_BUTTON_CANCEL);
214  CheckContentsIsSetToPreferredSize();
215  EXPECT_LE(with_footnote_size.height(), client_view()->bounds().height());
216  EXPECT_LE(with_footnote_size.width(), client_view()->bounds().width());
217  gfx::Size with_footnote_and_button_size = client_view()->bounds().size();
218
219  SetDialogButtons(ui::DIALOG_BUTTON_NONE);
220  footnote_view->SetVisible(false);
221  CheckContentsIsSetToPreferredSize();
222  EXPECT_EQ(no_footnote_size.height(), client_view()->bounds().height());
223  EXPECT_EQ(no_footnote_size.width(), client_view()->bounds().width());
224}
225
226// Test that GetHeightForWidth is respected for the footnote view.
227TEST_F(DialogClientViewTest, LayoutWithFootnoteHeightForWidth) {
228  CheckContentsIsSetToPreferredSize();
229  gfx::Size no_footnote_size = client_view()->bounds().size();
230
231  View* footnote_view = new ProportionallySizedView(3);
232  SetFootnoteView(footnote_view);
233  CheckContentsIsSetToPreferredSize();
234  EXPECT_GT(client_view()->bounds().height(), no_footnote_size.height());
235  EXPECT_EQ(footnote_view->bounds().width() * 3,
236            footnote_view->bounds().height());
237}
238
239// Test that the DialogClientView's FocusManager is properly updated when the
240// DialogClientView belongs to a non top level widget and the widget is
241// reparented. The DialogClientView belongs to a non top level widget in the
242// case of constrained windows. The constrained window's widget is reparented
243// when a browser tab is dragged to a different browser window.
244TEST_F(DialogClientViewTest, FocusManager) {
245  scoped_ptr<Widget> toplevel1(new Widget);
246  Widget::InitParams toplevel1_params =
247      CreateParams(Widget::InitParams::TYPE_WINDOW);
248  toplevel1_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
249  toplevel1->Init(toplevel1_params);
250
251  scoped_ptr<Widget> toplevel2(new Widget);
252  Widget::InitParams toplevel2_params =
253      CreateParams(Widget::InitParams::TYPE_WINDOW);
254  toplevel2_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
255  toplevel2->Init(toplevel2_params);
256
257  Widget* dialog = new Widget;
258  Widget::InitParams dialog_params =
259      CreateParams(Widget::InitParams::TYPE_WINDOW);
260  dialog_params.child = true;
261  dialog_params.delegate = new DialogDelegateView();
262  dialog_params.parent = toplevel1->GetNativeView();
263  dialog->Init(dialog_params);
264
265  // Test that the FocusManager has been properly set when the DialogClientView
266  // was parented to |dialog|.
267  DialogClientView* client_view =
268      static_cast<DialogClientView*>(dialog->client_view());
269  EXPECT_EQ(toplevel1->GetFocusManager(), client_view->focus_manager_);
270
271  // Test that the FocusManager is properly updated when the DialogClientView's
272  // top level widget is changed.
273  Widget::ReparentNativeView(dialog->GetNativeView(), NULL);
274  EXPECT_EQ(NULL, client_view->focus_manager_);
275  Widget::ReparentNativeView(dialog->GetNativeView(),
276                             toplevel2->GetNativeView());
277  EXPECT_EQ(toplevel2->GetFocusManager(), client_view->focus_manager_);
278  Widget::ReparentNativeView(dialog->GetNativeView(),
279                             toplevel1->GetNativeView());
280  EXPECT_NE(toplevel1->GetFocusManager(), toplevel2->GetFocusManager());
281  EXPECT_EQ(toplevel1->GetFocusManager(), client_view->focus_manager_);
282
283  // Test that the FocusManager is properly cleared when the DialogClientView is
284  // removed from |dialog| during the widget's destruction.
285  client_view->set_owned_by_client();
286  scoped_ptr<DialogClientView> owned_client_view(client_view);
287  toplevel1->CloseNow();
288  toplevel2->CloseNow();
289  EXPECT_EQ(NULL, owned_client_view->focus_manager_);
290}
291
292}  // namespace views
293