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