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