1// Copyright 2014 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 "ash/wm/window_state.h"
6
7#include "ash/screen_util.h"
8#include "ash/shell.h"
9#include "ash/test/ash_test_base.h"
10#include "ash/wm/window_state.h"
11#include "ash/wm/wm_event.h"
12#include "ui/aura/client/aura_constants.h"
13#include "ui/aura/test/test_window_delegate.h"
14#include "ui/aura/window.h"
15
16namespace ash {
17namespace wm {
18namespace {
19
20class AlwaysMaximizeTestState : public WindowState::State {
21 public:
22  explicit AlwaysMaximizeTestState(WindowStateType initial_state_type)
23      : state_type_(initial_state_type) {}
24  virtual ~AlwaysMaximizeTestState() {}
25
26  // WindowState::State overrides:
27  virtual void OnWMEvent(WindowState* window_state,
28                         const WMEvent* event) OVERRIDE {
29    // We don't do anything here.
30  }
31  virtual WindowStateType GetType() const OVERRIDE {
32    return state_type_;
33  }
34  virtual void AttachState(
35      WindowState* window_state,
36      WindowState::State* previous_state) OVERRIDE {
37    // We always maximize.
38    if (state_type_ != WINDOW_STATE_TYPE_MAXIMIZED) {
39      window_state->Maximize();
40      state_type_ = WINDOW_STATE_TYPE_MAXIMIZED;
41    }
42  }
43  virtual void DetachState(WindowState* window_state) OVERRIDE {}
44
45 private:
46  WindowStateType state_type_;
47
48  DISALLOW_COPY_AND_ASSIGN(AlwaysMaximizeTestState);
49};
50
51}  // namespace
52
53typedef test::AshTestBase WindowStateTest;
54
55// Test that a window gets properly snapped to the display's edges in a
56// multi monitor environment.
57TEST_F(WindowStateTest, SnapWindowBasic) {
58  if (!SupportsMultipleDisplays())
59    return;
60
61  UpdateDisplay("0+0-500x400, 0+500-600x400");
62  const gfx::Rect kPrimaryDisplayWorkAreaBounds =
63      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
64  const gfx::Rect kSecondaryDisplayWorkAreaBounds =
65      ScreenUtil::GetSecondaryDisplay().work_area();
66
67  scoped_ptr<aura::Window> window(
68      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100)));
69  WindowState* window_state = GetWindowState(window.get());
70  const WMEvent snap_left(WM_EVENT_SNAP_LEFT);
71  window_state->OnWMEvent(&snap_left);
72  gfx::Rect expected = gfx::Rect(
73      kPrimaryDisplayWorkAreaBounds.x(),
74      kPrimaryDisplayWorkAreaBounds.y(),
75      kPrimaryDisplayWorkAreaBounds.width() / 2,
76      kPrimaryDisplayWorkAreaBounds.height());
77  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
78
79  const WMEvent snap_right(WM_EVENT_SNAP_RIGHT);
80  window_state->OnWMEvent(&snap_right);
81  expected.set_x(kPrimaryDisplayWorkAreaBounds.right() - expected.width());
82  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
83
84  // Move the window to the secondary display.
85  window->SetBoundsInScreen(gfx::Rect(600, 0, 100, 100),
86                            ScreenUtil::GetSecondaryDisplay());
87
88  window_state->OnWMEvent(&snap_right);
89  expected = gfx::Rect(
90      kSecondaryDisplayWorkAreaBounds.x() +
91          kSecondaryDisplayWorkAreaBounds.width() / 2,
92      kSecondaryDisplayWorkAreaBounds.y(),
93      kSecondaryDisplayWorkAreaBounds.width() / 2,
94      kSecondaryDisplayWorkAreaBounds.height());
95  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
96
97  window_state->OnWMEvent(&snap_left);
98  expected.set_x(kSecondaryDisplayWorkAreaBounds.x());
99  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
100}
101
102// Test how the minimum and maximum size specified by the aura::WindowDelegate
103// affect snapping.
104TEST_F(WindowStateTest, SnapWindowMinimumSize) {
105  if (!SupportsHostWindowResize())
106    return;
107
108  UpdateDisplay("0+0-600x900");
109  const gfx::Rect kWorkAreaBounds =
110      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
111
112  aura::test::TestWindowDelegate delegate;
113  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
114      &delegate, -1, gfx::Rect(0, 100, kWorkAreaBounds.width() - 1, 100)));
115
116  // It should be possible to snap a window with a minimum size.
117  delegate.set_minimum_size(gfx::Size(kWorkAreaBounds.width() - 1, 0));
118  WindowState* window_state = GetWindowState(window.get());
119  EXPECT_TRUE(window_state->CanSnap());
120  const WMEvent snap_right(WM_EVENT_SNAP_RIGHT);
121  window_state->OnWMEvent(&snap_right);
122  gfx::Rect expected = gfx::Rect(kWorkAreaBounds.x() + 1,
123                                 kWorkAreaBounds.y(),
124                                 kWorkAreaBounds.width() - 1,
125                                 kWorkAreaBounds.height());
126  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
127
128  // It should not be possible to snap a window with a maximum size, or if it
129  // cannot be maximized.
130  delegate.set_maximum_size(gfx::Size(kWorkAreaBounds.width() - 1, 0));
131  EXPECT_FALSE(window_state->CanSnap());
132  delegate.set_maximum_size(gfx::Size(0, kWorkAreaBounds.height() - 1));
133  EXPECT_FALSE(window_state->CanSnap());
134  delegate.set_maximum_size(gfx::Size());
135  window->SetProperty(aura::client::kCanMaximizeKey, false);
136  EXPECT_FALSE(window_state->CanSnap());
137}
138
139// Test that the minimum size specified by aura::WindowDelegate gets respected.
140TEST_F(WindowStateTest, TestRespectMinimumSize) {
141  if (!SupportsHostWindowResize())
142    return;
143
144  UpdateDisplay("0+0-1024x768");
145
146  aura::test::TestWindowDelegate delegate;
147  const gfx::Size minimum_size(gfx::Size(500, 300));
148  delegate.set_minimum_size(minimum_size);
149
150  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
151      &delegate, -1, gfx::Rect(0, 100, 100, 100)));
152
153  // Check that the window has the correct minimum size.
154  EXPECT_EQ(minimum_size.ToString(), window->bounds().size().ToString());
155
156  // Set the size to something bigger - that should work.
157  gfx::Rect bigger_bounds(700, 500, 700, 500);
158  window->SetBounds(bigger_bounds);
159  EXPECT_EQ(bigger_bounds.ToString(), window->bounds().ToString());
160
161  // Set the size to something smaller - that should only resize to the smallest
162  // possible size.
163  gfx::Rect smaller_bounds(700, 500, 100, 100);
164  window->SetBounds(smaller_bounds);
165  EXPECT_EQ(minimum_size.ToString(), window->bounds().size().ToString());
166}
167
168// Test that the minimum window size specified by aura::WindowDelegate does not
169// exceed the screen size.
170TEST_F(WindowStateTest, TestIgnoreTooBigMinimumSize) {
171  if (!SupportsHostWindowResize())
172    return;
173
174  UpdateDisplay("0+0-1024x768");
175  const gfx::Size work_area_size =
176      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area().size();
177  const gfx::Size illegal_size(1280, 960);
178  const gfx::Rect illegal_bounds(gfx::Point(0, 0), illegal_size);
179
180  aura::test::TestWindowDelegate delegate;
181  const gfx::Size minimum_size(illegal_size);
182  delegate.set_minimum_size(minimum_size);
183
184  // The creation should force the window to respect the screen size.
185  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate(
186      &delegate, -1, illegal_bounds));
187  EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString());
188
189  // Trying to set the size to something bigger then the screen size should be
190  // ignored.
191  window->SetBounds(illegal_bounds);
192  EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString());
193
194  // Maximizing the window should not allow it to go bigger than that either.
195  WindowState* window_state = GetWindowState(window.get());
196  window_state->Maximize();
197  EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString());
198}
199
200// Test that setting the bounds of a snapped window keeps its snapped.
201TEST_F(WindowStateTest, SnapWindowSetBounds) {
202  if (!SupportsHostWindowResize())
203    return;
204
205  UpdateDisplay("0+0-900x600");
206  const gfx::Rect kWorkAreaBounds =
207      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
208
209  scoped_ptr<aura::Window> window(
210      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100)));
211  WindowState* window_state = GetWindowState(window.get());
212  const WMEvent snap_left(WM_EVENT_SNAP_LEFT);
213  window_state->OnWMEvent(&snap_left);
214  EXPECT_EQ(WINDOW_STATE_TYPE_LEFT_SNAPPED, window_state->GetStateType());
215  gfx::Rect expected = gfx::Rect(kWorkAreaBounds.x(),
216                                 kWorkAreaBounds.y(),
217                                 kWorkAreaBounds.width() / 2,
218                                 kWorkAreaBounds.height());
219  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
220
221  // Snapped windows can have any width.
222  expected.set_width(500);
223  window->SetBounds(gfx::Rect(10, 10, 500, 300));
224  EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString());
225  EXPECT_EQ(WINDOW_STATE_TYPE_LEFT_SNAPPED, window_state->GetStateType());
226}
227
228// Test that snapping left/right preserves the restore bounds.
229TEST_F(WindowStateTest, RestoreBounds) {
230  scoped_ptr<aura::Window> window(
231      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100)));
232  WindowState* window_state = GetWindowState(window.get());
233
234  EXPECT_TRUE(window_state->IsNormalStateType());
235
236  // 1) Start with restored window with restore bounds set.
237  gfx::Rect restore_bounds = window->GetBoundsInScreen();
238  restore_bounds.set_width(restore_bounds.width() + 1);
239  window_state->SetRestoreBoundsInScreen(restore_bounds);
240  const WMEvent snap_left(WM_EVENT_SNAP_LEFT);
241  window_state->OnWMEvent(&snap_left);
242  const WMEvent snap_right(WM_EVENT_SNAP_RIGHT);
243  window_state->OnWMEvent(&snap_right);
244  EXPECT_NE(restore_bounds.ToString(), window->GetBoundsInScreen().ToString());
245  EXPECT_EQ(restore_bounds.ToString(),
246            window_state->GetRestoreBoundsInScreen().ToString());
247  window_state->Restore();
248  EXPECT_EQ(restore_bounds.ToString(), window->GetBoundsInScreen().ToString());
249
250  // 2) Start with restored bounds set as a result of maximizing the window.
251  window_state->Maximize();
252  gfx::Rect maximized_bounds = window->GetBoundsInScreen();
253  EXPECT_NE(maximized_bounds.ToString(), restore_bounds.ToString());
254  EXPECT_EQ(restore_bounds.ToString(),
255            window_state->GetRestoreBoundsInScreen().ToString());
256
257  window_state->OnWMEvent(&snap_left);
258  EXPECT_NE(restore_bounds.ToString(), window->GetBoundsInScreen().ToString());
259  EXPECT_NE(maximized_bounds.ToString(),
260            window->GetBoundsInScreen().ToString());
261  EXPECT_EQ(restore_bounds.ToString(),
262            window_state->GetRestoreBoundsInScreen().ToString());
263
264  window_state->Restore();
265  EXPECT_EQ(restore_bounds.ToString(), window->GetBoundsInScreen().ToString());
266}
267
268// Test that maximizing an auto managed window, then snapping it puts the window
269// at the snapped bounds and not at the auto-managed (centered) bounds.
270TEST_F(WindowStateTest, AutoManaged) {
271  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
272  WindowState* window_state = GetWindowState(window.get());
273  window_state->set_window_position_managed(true);
274  window->Hide();
275  window->SetBounds(gfx::Rect(100, 100, 100, 100));
276  window->Show();
277
278  window_state->Maximize();
279  const WMEvent snap_right(WM_EVENT_SNAP_RIGHT);
280  window_state->OnWMEvent(&snap_right);
281
282  const gfx::Rect kWorkAreaBounds =
283      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
284  gfx::Rect expected_snapped_bounds(
285      kWorkAreaBounds.x() + kWorkAreaBounds.width() / 2,
286      kWorkAreaBounds.y(),
287      kWorkAreaBounds.width() / 2,
288      kWorkAreaBounds.height());
289  EXPECT_EQ(expected_snapped_bounds.ToString(),
290            window->GetBoundsInScreen().ToString());
291
292  // The window should still be auto managed despite being right maximized.
293  EXPECT_TRUE(window_state->window_position_managed());
294}
295
296// Test that the replacement of a State object works as expected.
297TEST_F(WindowStateTest, SimpleStateSwap) {
298  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
299  WindowState* window_state = GetWindowState(window.get());
300  EXPECT_FALSE(window_state->IsMaximized());
301  window_state->SetStateObject(
302      scoped_ptr<WindowState::State> (new AlwaysMaximizeTestState(
303          window_state->GetStateType())));
304  EXPECT_TRUE(window_state->IsMaximized());
305}
306
307// Test that the replacement of a state object, following a restore with the
308// original one restores the window to its original state.
309TEST_F(WindowStateTest, StateSwapRestore) {
310  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
311  WindowState* window_state = GetWindowState(window.get());
312  EXPECT_FALSE(window_state->IsMaximized());
313  scoped_ptr<WindowState::State> old(window_state->SetStateObject(
314      scoped_ptr<WindowState::State> (new AlwaysMaximizeTestState(
315          window_state->GetStateType()))).Pass());
316  EXPECT_TRUE(window_state->IsMaximized());
317  window_state->SetStateObject(old.Pass());
318  EXPECT_FALSE(window_state->IsMaximized());
319}
320
321// Tests that a window that had same bounds as the work area shrinks after the
322// window is maximized and then restored.
323TEST_F(WindowStateTest, RestoredWindowBoundsShrink) {
324  scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
325  WindowState* window_state = GetWindowState(window.get());
326  EXPECT_FALSE(window_state->IsMaximized());
327  gfx::Rect work_area =
328      ash::Shell::GetScreen()->GetPrimaryDisplay().work_area();
329
330  window->SetBounds(work_area);
331  window_state->Maximize();
332  EXPECT_TRUE(window_state->IsMaximized());
333  EXPECT_EQ(work_area.ToString(), window->bounds().ToString());
334
335  window_state->Restore();
336  EXPECT_FALSE(window_state->IsMaximized());
337  EXPECT_NE(work_area.ToString(), window->bounds().ToString());
338  EXPECT_TRUE(work_area.Contains(window->bounds()));
339}
340
341// TODO(skuhne): Add more unit test to verify the correctness for the restore
342// operation.
343
344}  // namespace wm
345}  // namespace ash
346