opaque_browser_frame_view_layout_unittest.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
1// Copyright 2013 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 "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
6
7#include "base/basictypes.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/ui/views/tab_icon_view.h"
10#include "chrome/browser/ui/views/tabs/tab.h"
11#include "ui/gfx/image/image_skia.h"
12#include "ui/gfx/image/image_skia_rep.h"
13#include "ui/gfx/text_constants.h"
14#include "ui/views/controls/button/image_button.h"
15#include "ui/views/controls/button/menu_button.h"
16#include "ui/views/controls/label.h"
17#include "ui/views/test/views_test_base.h"
18
19using views::Widget;
20
21namespace {
22
23const int kWidth = 500;
24
25class TestLayoutDelegate : public OpaqueBrowserFrameViewLayoutDelegate {
26 public:
27  enum WindowState {
28    STATE_NORMAL,
29    STATE_MAXIMIZED,
30    STATE_MINIMIZED,
31    STATE_FULLSCREEN
32  };
33
34  TestLayoutDelegate()
35      : show_avatar_(false),
36        window_state_(STATE_NORMAL) {
37  }
38
39  virtual ~TestLayoutDelegate() {}
40
41  void SetWindowTitle(const base::string16& title) {
42    window_title_ = title;
43  }
44
45  void SetShouldShowAvatar(bool show_avatar) {
46    show_avatar_ = show_avatar;
47  }
48
49  void SetWindowState(WindowState state) {
50    window_state_ = state;
51  }
52
53  virtual bool ShouldShowWindowIcon() const OVERRIDE {
54    return !window_title_.empty();
55  }
56
57  virtual bool ShouldShowWindowTitle() const OVERRIDE {
58    return !window_title_.empty();
59  }
60
61  virtual base::string16 GetWindowTitle() const OVERRIDE {
62    return window_title_;
63  }
64
65  virtual int GetIconSize() const OVERRIDE {
66    // The value on linux_aura and non-aura windows.
67    return 17;
68  }
69
70  virtual bool ShouldLeaveOffsetNearTopBorder() const OVERRIDE {
71    return !IsMaximized();
72  }
73
74  virtual gfx::Size GetBrowserViewMinimumSize() const OVERRIDE {
75    // Taken from a calculation in BrowserViewLayout.
76    return gfx::Size(168, 64);
77  }
78
79  virtual bool ShouldShowAvatar() const OVERRIDE {
80    return show_avatar_;
81  }
82
83  virtual gfx::ImageSkia GetOTRAvatarIcon() const OVERRIDE {
84    // The calculations depend on the size of the OTR resource, and chromeos
85    // uses a different sized image, so hard code the size of the current
86    // windows/linux one.
87    gfx::ImageSkiaRep rep(gfx::Size(40, 29), 1.0f);
88    gfx::ImageSkia image(rep);
89    return image;
90  }
91
92  virtual bool IsMaximized() const OVERRIDE {
93    return window_state_ == STATE_MAXIMIZED;
94  }
95
96  virtual bool IsMinimized() const OVERRIDE {
97    return window_state_ == STATE_MINIMIZED;
98  }
99
100  virtual bool IsFullscreen() const OVERRIDE {
101    return window_state_ == STATE_FULLSCREEN;
102  }
103
104  virtual bool IsTabStripVisible() const OVERRIDE {
105    return window_title_.empty();
106  }
107
108  virtual int GetTabStripHeight() const OVERRIDE {
109    return IsTabStripVisible() ? Tab::GetMinimumUnselectedSize().height() : 0;
110  }
111
112  virtual int GetAdditionalReservedSpaceInTabStrip() const OVERRIDE {
113    return 0;
114  }
115
116  virtual gfx::Size GetTabstripPreferredSize() const OVERRIDE {
117    // Measured from Tabstrip::GetPreferredSize().
118    return IsTabStripVisible() ? gfx::Size(78, 29) : gfx::Size(0, 0);
119  }
120
121 private:
122  base::string16 window_title_;
123  bool show_avatar_;
124  WindowState window_state_;
125
126  DISALLOW_COPY_AND_ASSIGN(TestLayoutDelegate);
127};
128
129}  // namespace
130
131class OpaqueBrowserFrameViewLayoutTest : public views::ViewsTestBase {
132 public:
133  OpaqueBrowserFrameViewLayoutTest() {}
134  virtual ~OpaqueBrowserFrameViewLayoutTest() {}
135
136  virtual void SetUp() OVERRIDE {
137    views::ViewsTestBase::SetUp();
138
139    delegate_.reset(new TestLayoutDelegate);
140    layout_manager_ = new OpaqueBrowserFrameViewLayout(delegate_.get());
141    layout_manager_->set_extra_caption_y(0);
142    layout_manager_->set_window_caption_spacing(0);
143    widget_ = new Widget;
144    widget_->Init(CreateParams(Widget::InitParams::TYPE_POPUP));
145    root_view_ = widget_->GetRootView();
146    root_view_->SetSize(gfx::Size(kWidth, kWidth));
147    root_view_->SetLayoutManager(layout_manager_);
148
149    // Add the caption buttons. We use fake images because we're modeling the
150    // Windows assets here, while the linux version uses differently sized
151    // assets.
152    //
153    // TODO(erg): In a follow up patch, separate these sizes out into virtual
154    // accessors so we can test both the windows and linux behaviours once we
155    // start modifying the code.
156    minimize_button_ = InitWindowCaptionButton(
157        VIEW_ID_MINIMIZE_BUTTON, gfx::Size(26, 18));
158    maximize_button_ = InitWindowCaptionButton(
159        VIEW_ID_MAXIMIZE_BUTTON, gfx::Size(25, 18));
160    restore_button_ = InitWindowCaptionButton(
161        VIEW_ID_RESTORE_BUTTON, gfx::Size(25, 18));
162    close_button_ = InitWindowCaptionButton(
163        VIEW_ID_CLOSE_BUTTON, gfx::Size(43, 18));
164  }
165
166  virtual void TearDown() OVERRIDE {
167    widget_->CloseNow();
168
169    views::ViewsTestBase::TearDown();
170  }
171
172 protected:
173  views::ImageButton* InitWindowCaptionButton(ViewID view_id,
174                                              const gfx::Size& size) {
175    views::ImageButton* button = new views::ImageButton(NULL);
176    gfx::ImageSkiaRep rep(size, 1.0f);
177    gfx::ImageSkia image(rep);
178    button->SetImage(views::CustomButton::STATE_NORMAL, &image);
179    button->set_id(view_id);
180    root_view_->AddChildView(button);
181    return button;
182  }
183
184  void AddWindowTitleIcons() {
185    tab_icon_view_ = new TabIconView(NULL);
186    tab_icon_view_->set_is_light(true);
187    tab_icon_view_->set_id(VIEW_ID_WINDOW_ICON);
188    root_view_->AddChildView(tab_icon_view_);
189
190    window_title_ = new views::Label(delegate_->GetWindowTitle(),
191                                     default_font_);
192    window_title_->SetVisible(delegate_->ShouldShowWindowTitle());
193    window_title_->SetEnabledColor(SK_ColorWHITE);
194    window_title_->SetBackgroundColor(0x00000000);
195    window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
196    window_title_->set_id(VIEW_ID_WINDOW_TITLE);
197    root_view_->AddChildView(window_title_);
198  }
199
200  void AddAvatarButton() {
201    menu_button_ = new views::MenuButton(NULL, string16(), NULL, false);
202    menu_button_->set_id(VIEW_ID_AVATAR_BUTTON);
203    delegate_->SetShouldShowAvatar(true);
204    root_view_->AddChildView(menu_button_);
205  }
206
207  void AddAvatarLabel() {
208    avatar_label_ = new views::MenuButton(NULL, string16(), NULL, false);
209    avatar_label_->set_id(VIEW_ID_AVATAR_LABEL);
210    root_view_->AddChildView(avatar_label_);
211
212    // The avatar label should only be used together with the avatar button.
213    AddAvatarButton();
214  }
215
216  void ExpectBasicWindowBounds() {
217    EXPECT_EQ("428,1 25x18", maximize_button_->bounds().ToString());
218    EXPECT_EQ("402,1 26x18", minimize_button_->bounds().ToString());
219    EXPECT_EQ("0,0 0x0", restore_button_->bounds().ToString());
220    EXPECT_EQ("453,1 43x18", close_button_->bounds().ToString());
221  }
222
223  gfx::Font default_font_;
224
225  Widget* widget_;
226  views::View* root_view_;
227  OpaqueBrowserFrameViewLayout* layout_manager_;
228  scoped_ptr<TestLayoutDelegate> delegate_;
229
230  // Widgets:
231  views::ImageButton* minimize_button_;
232  views::ImageButton* maximize_button_;
233  views::ImageButton* restore_button_;
234  views::ImageButton* close_button_;
235
236  TabIconView* tab_icon_view_;
237  views::Label* window_title_;
238
239  views::MenuButton* menu_button_;
240  views::MenuButton* avatar_label_;
241
242  DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayoutTest);
243};
244
245TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindow) {
246  // Tests the layout of a default chrome window with no avatars, no window
247  // titles, and a tabstrip.
248  root_view_->Layout();
249
250  ExpectBasicWindowBounds();
251
252  // After some visual inspection, it really does look like the tabstrip is
253  // initally positioned out of our view.
254  EXPECT_EQ("-1,13 398x29",
255            layout_manager_->GetBoundsForTabStrip(
256                delegate_->GetTabstripPreferredSize(), kWidth).ToString());
257  EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
258
259  // A normal window with no window icon still produces icon bounds for
260  // Windows, which has a hidden icon that a user can double click on to close
261  // the window.
262  EXPECT_EQ("6,4 17x17", layout_manager_->IconBounds().ToString());
263}
264
265TEST_F(OpaqueBrowserFrameViewLayoutTest, BasicWindowMaximized) {
266  // Tests the layout of a default chrome window with no avatars, no window
267  // titles, and a tabstrip, but maximized this time.
268  delegate_->SetWindowState(TestLayoutDelegate::STATE_MAXIMIZED);
269  root_view_->Layout();
270
271  // Note how the bonds start at the exact top of the window while maximized
272  // while they start 1 pixel below when unmaximized.
273  EXPECT_EQ("0,0 0x0", maximize_button_->bounds().ToString());
274  EXPECT_EQ("403,0 26x18", minimize_button_->bounds().ToString());
275  EXPECT_EQ("429,0 25x18", restore_button_->bounds().ToString());
276  EXPECT_EQ("454,0 46x18", close_button_->bounds().ToString());
277
278  EXPECT_EQ("-5,-3 392x29",
279            layout_manager_->GetBoundsForTabStrip(
280                delegate_->GetTabstripPreferredSize(), kWidth).ToString());
281  EXPECT_EQ("262x61", layout_manager_->GetMinimumSize(kWidth).ToString());
282
283  // In the maximized case, OpaqueBrowserFrameView::NonClientHitTest() uses
284  // this rect, extended to the top left corner of the window.
285  EXPECT_EQ("2,0 17x17", layout_manager_->IconBounds().ToString());
286}
287
288TEST_F(OpaqueBrowserFrameViewLayoutTest, WithWindowTitleAndIcon) {
289  // Tests the layout of pop up windows.
290  delegate_->SetWindowTitle(ASCIIToUTF16("Window Title"));
291  AddWindowTitleIcons();
292  root_view_->Layout();
293
294  // We should have the right hand side should match the BasicWindow case.
295  ExpectBasicWindowBounds();
296
297  // Check the location of the tab icon and window title.
298  EXPECT_EQ("6,3 17x17", tab_icon_view_->bounds().ToString());
299  EXPECT_EQ("27,3 370x17", window_title_->bounds().ToString());
300}
301
302TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatar) {
303  // Tests a normal tabstrip window with an avatar icon.
304  AddAvatarButton();
305  root_view_->Layout();
306
307  ExpectBasicWindowBounds();
308
309  // Check the location of the avatar
310  EXPECT_EQ("7,11 40x29", menu_button_->bounds().ToString());
311  EXPECT_EQ("45,13 352x29",
312            layout_manager_->GetBoundsForTabStrip(
313                delegate_->GetTabstripPreferredSize(), kWidth).ToString());
314  EXPECT_EQ("261x73", layout_manager_->GetMinimumSize(kWidth).ToString());
315}
316
317TEST_F(OpaqueBrowserFrameViewLayoutTest, WindowWithAvatarLabelAndButton) {
318  AddAvatarLabel();
319  root_view_->Layout();
320
321  ExpectBasicWindowBounds();
322
323  // Check the location of the avatar label relative to the avatar button.
324  // The label height and width depends on the font size and the text displayed.
325  // This may possibly change, so we don't test it here.
326  EXPECT_EQ(menu_button_->bounds().x() - 2, avatar_label_->bounds().x());
327  EXPECT_EQ(
328      menu_button_->bounds().bottom() - 3 - avatar_label_->bounds().height(),
329      avatar_label_->bounds().y());
330}
331