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.
5 #include "ui/app_list/apps_grid_view.h"
9 #include "base/basictypes.h"
10 #include "base/compiler_specific.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop.h"
13 #include "base/stringprintf.h"
14 #include "base/timer.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "ui/app_list/app_list_item_model.h"
17 #include "ui/app_list/app_list_item_view.h"
18 #include "ui/app_list/app_list_model.h"
19 #include "ui/app_list/pagination_model.h"
20 #include "ui/app_list/test/apps_grid_view_test_api.h"
27 const int kIconDimension
= 48;
30 const int kTilesPerPage
= kCols
* kRows
;
32 const int kWidth
= 320;
33 const int kHeight
= 240;
35 class PageFlipWaiter
: public PaginationModelObserver
{
37 PageFlipWaiter(MessageLoopForUI
* ui_loop
,
38 PaginationModel
* model
)
42 page_changed_(false) {
43 model_
->AddObserver(this);
46 virtual ~PageFlipWaiter() {
47 model_
->RemoveObserver(this);
50 bool Wait(int time_out_ms
) {
53 page_changed_
= false;
55 wait_timer_
.Start(FROM_HERE
,
56 base::TimeDelta::FromMilliseconds(time_out_ms
),
57 this, &PageFlipWaiter::OnWaitTimeOut
);
64 void OnWaitTimeOut() {
68 // PaginationModelObserver overrides:
69 virtual void TotalPagesChanged() OVERRIDE
{
71 virtual void SelectedPageChanged(int old_selected
,
72 int new_selected
) OVERRIDE
{
77 virtual void TransitionChanged() OVERRIDE
{
80 MessageLoopForUI
* ui_loop_
;
81 PaginationModel
* model_
;
84 base::OneShotTimer
<PageFlipWaiter
> wait_timer_
;
86 DISALLOW_COPY_AND_ASSIGN(PageFlipWaiter
);
91 class AppsGridViewTest
: public testing::Test
{
94 virtual ~AppsGridViewTest() {}
96 // testing::Test overrides:
97 virtual void SetUp() OVERRIDE
{
98 model_
.reset(new AppListModel
);
99 pagination_model_
.reset(new PaginationModel
);
101 apps_grid_view_
.reset(new AppsGridView(NULL
, pagination_model_
.get()));
102 apps_grid_view_
->SetLayout(kIconDimension
, kCols
, kRows
);
103 apps_grid_view_
->SetBoundsRect(gfx::Rect(gfx::Size(kWidth
, kHeight
)));
104 apps_grid_view_
->SetModel(model_
.get());
106 test_api_
.reset(new AppsGridViewTestApi(apps_grid_view_
.get()));
108 virtual void TearDown() OVERRIDE
{
109 apps_grid_view_
.reset(); // Release apps grid view before models.
113 void PopulateApps(int n
) {
114 for (int i
= 0; i
< n
; ++i
) {
115 std::string title
= base::StringPrintf("Item %d", i
);
116 model_
->apps()->Add(CreateItem(title
));
120 // Get a string of all apps in |model| joined with ','.
121 std::string
GetModelContent() {
123 for (size_t i
= 0; i
< model_
->apps()->item_count(); ++i
) {
126 content
+= model_
->apps()->GetItemAt(i
)->title();
131 AppListItemModel
* CreateItem(const std::string
& title
) {
132 AppListItemModel
* item
= new AppListItemModel
;
133 item
->SetTitle(title
);
137 void HighlightItemAt(int index
) {
138 AppListItemModel
* item
= model_
->apps()->GetItemAt(index
);
139 item
->SetHighlighted(true);
142 AppListItemView
* GetItemViewAt(int index
) {
143 return static_cast<AppListItemView
*>(
144 test_api_
->GetViewAtModelIndex(index
));
147 AppListItemView
* GetItemViewForPoint(const gfx::Point
& point
) {
148 for (size_t i
= 0; i
< model_
->apps()->item_count(); ++i
) {
149 AppListItemView
* view
= GetItemViewAt(i
);
150 if (view
->bounds().Contains(point
))
156 gfx::Rect
GetItemTileRectAt(int row
, int col
) {
157 DCHECK_GT(model_
->apps()->item_count(), 0u);
159 gfx::Insets
insets(apps_grid_view_
->GetInsets());
160 gfx::Rect
rect(gfx::Point(insets
.left(), insets
.top()),
161 GetItemViewAt(0)->bounds().size());
162 rect
.Offset(col
* rect
.width(), row
* rect
.height());
166 // Points are in |apps_grid_view_|'s coordinates.
167 void SimulateDrag(AppsGridView::Pointer pointer
,
168 const gfx::Point
& from
,
169 const gfx::Point
& to
) {
170 AppListItemView
* view
= GetItemViewForPoint(from
);
173 gfx::Point translated_from
= gfx::PointAtOffsetFromOrigin(
174 from
- view
->bounds().origin());
175 gfx::Point translated_to
= gfx::PointAtOffsetFromOrigin(
176 to
- view
->bounds().origin());
178 ui::MouseEvent
pressed_event(ui::ET_MOUSE_PRESSED
,
179 translated_from
, from
, 0);
180 apps_grid_view_
->InitiateDrag(view
, pointer
, pressed_event
);
182 ui::MouseEvent
drag_event(ui::ET_MOUSE_DRAGGED
,
183 translated_to
, to
, 0);
184 apps_grid_view_
->UpdateDrag(view
, pointer
, drag_event
);
187 void SimulateKeyPress(ui::KeyboardCode key_code
) {
188 ui::KeyEvent
key_event(ui::ET_KEY_PRESSED
, key_code
, 0, false);
189 apps_grid_view_
->OnKeyPressed(key_event
);
192 scoped_ptr
<AppListModel
> model_
;
193 scoped_ptr
<PaginationModel
> pagination_model_
;
194 scoped_ptr
<AppsGridView
> apps_grid_view_
;
195 scoped_ptr
<AppsGridViewTestApi
> test_api_
;
197 MessageLoopForUI message_loop_
;
200 DISALLOW_COPY_AND_ASSIGN(AppsGridViewTest
);
203 TEST_F(AppsGridViewTest
, CreatePage
) {
204 // Fully populates a page.
205 const int kPages
= 1;
206 PopulateApps(kPages
* kTilesPerPage
);
207 EXPECT_EQ(kPages
, pagination_model_
->total_pages());
209 // Adds one more and gets a new page created.
210 model_
->apps()->Add(CreateItem(std::string("Extra")));
211 EXPECT_EQ(kPages
+ 1, pagination_model_
->total_pages());
214 TEST_F(AppsGridViewTest
, EnsureHighlightedVisible
) {
215 const int kPages
= 3;
216 PopulateApps(kPages
* kTilesPerPage
);
217 EXPECT_EQ(kPages
, pagination_model_
->total_pages());
218 EXPECT_EQ(0, pagination_model_
->selected_page());
220 // Highlight first one and last one one first page and first page should be
223 EXPECT_EQ(0, pagination_model_
->selected_page());
224 HighlightItemAt(kTilesPerPage
- 1);
225 EXPECT_EQ(0, pagination_model_
->selected_page());
227 // Highlight first one on 2nd page and 2nd page should be selected.
228 HighlightItemAt(kTilesPerPage
+ 1);
229 EXPECT_EQ(1, pagination_model_
->selected_page());
231 // Highlight last one in the model and last page should be selected.
232 HighlightItemAt(model_
->apps()->item_count() - 1);
233 EXPECT_EQ(kPages
- 1, pagination_model_
->selected_page());
236 TEST_F(AppsGridViewTest
, RemoveSelectedLastApp
) {
237 const int kTotalItems
= 2;
238 const int kLastItemIndex
= kTotalItems
- 1;
240 PopulateApps(kTotalItems
);
242 AppListItemView
* last_view
= GetItemViewAt(kLastItemIndex
);
243 apps_grid_view_
->SetSelectedView(last_view
);
244 model_
->apps()->DeleteAt(kLastItemIndex
);
246 EXPECT_FALSE(apps_grid_view_
->IsSelectedView(last_view
));
249 AppListItemView
* view
= GetItemViewAt(0);
250 apps_grid_view_
->SetSelectedView(view
);
251 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(view
));
254 TEST_F(AppsGridViewTest
, MouseDrag
) {
255 const int kTotalItems
= 4;
256 PopulateApps(kTotalItems
);
257 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"),
260 gfx::Point from
= GetItemTileRectAt(0, 0).CenterPoint();
261 gfx::Point to
= GetItemTileRectAt(0, 1).CenterPoint();
263 // Dragging changes model order.
264 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
265 apps_grid_view_
->EndDrag(false);
266 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"),
268 test_api_
->LayoutToIdealBounds();
270 // Canceling drag should keep existing order.
271 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
272 apps_grid_view_
->EndDrag(true);
273 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"),
275 test_api_
->LayoutToIdealBounds();
277 // Deleting an item keeps remaining intact.
278 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
279 model_
->apps()->DeleteAt(1);
280 apps_grid_view_
->EndDrag(false);
281 EXPECT_EQ(std::string("Item 1,Item 2,Item 3"),
283 test_api_
->LayoutToIdealBounds();
285 // Adding a launcher item cancels the drag and respects the order.
286 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
287 model_
->apps()->Add(CreateItem(std::string("Extra")));
288 apps_grid_view_
->EndDrag(false);
289 EXPECT_EQ(std::string("Item 1,Item 2,Item 3,Extra"),
291 test_api_
->LayoutToIdealBounds();
294 TEST_F(AppsGridViewTest
, MouseDragFlipPage
) {
295 test_api_
->SetPageFlipDelay(10);
296 pagination_model_
->SetTransitionDurations(10, 10);
298 PageFlipWaiter
page_flip_waiter(&message_loop_
,
299 pagination_model_
.get());
301 const int kPages
= 3;
302 PopulateApps(kPages
* kTilesPerPage
);
303 EXPECT_EQ(kPages
, pagination_model_
->total_pages());
304 EXPECT_EQ(0, pagination_model_
->selected_page());
306 gfx::Point from
= GetItemTileRectAt(0, 0).CenterPoint();
307 gfx::Point to
= gfx::Point(apps_grid_view_
->width(),
308 apps_grid_view_
->height() / 2);
310 // Drag to right edge.
311 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
313 // Page should be flipped after sometime.
314 EXPECT_TRUE(page_flip_waiter
.Wait(100));
315 EXPECT_EQ(1, pagination_model_
->selected_page());
317 // Stay there and page should be flipped again.
318 EXPECT_TRUE(page_flip_waiter
.Wait(100));
319 EXPECT_EQ(2, pagination_model_
->selected_page());
321 // Stay there longer and no page flip happen since we are at the last page.
322 EXPECT_FALSE(page_flip_waiter
.Wait(100));
323 EXPECT_EQ(2, pagination_model_
->selected_page());
325 apps_grid_view_
->EndDrag(true);
327 // Now drag to the left edge and test the other direction.
330 SimulateDrag(AppsGridView::MOUSE
, from
, to
);
332 EXPECT_TRUE(page_flip_waiter
.Wait(100));
333 EXPECT_EQ(1, pagination_model_
->selected_page());
335 EXPECT_TRUE(page_flip_waiter
.Wait(100));
336 EXPECT_EQ(0, pagination_model_
->selected_page());
338 EXPECT_FALSE(page_flip_waiter
.Wait(100));
339 EXPECT_EQ(0, pagination_model_
->selected_page());
340 apps_grid_view_
->EndDrag(true);
343 TEST_F(AppsGridViewTest
, SimultaneousDrag
) {
344 const int kTotalItems
= 4;
345 PopulateApps(kTotalItems
);
346 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"),
349 gfx::Point mouse_from
= GetItemTileRectAt(0, 0).CenterPoint();
350 gfx::Point mouse_to
= GetItemTileRectAt(0, 1).CenterPoint();
352 gfx::Point touch_from
= GetItemTileRectAt(1, 0).CenterPoint();
353 gfx::Point touch_to
= GetItemTileRectAt(1, 1).CenterPoint();
355 // Starts a mouse drag first then a touch drag.
356 SimulateDrag(AppsGridView::MOUSE
, mouse_from
, mouse_to
);
357 SimulateDrag(AppsGridView::TOUCH
, touch_from
, touch_to
);
358 // Finishes the drag and mouse drag wins.
359 apps_grid_view_
->EndDrag(false);
360 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"),
362 test_api_
->LayoutToIdealBounds();
364 // Starts a touch drag first then a mouse drag.
365 SimulateDrag(AppsGridView::TOUCH
, touch_from
, touch_to
);
366 SimulateDrag(AppsGridView::MOUSE
, mouse_from
, mouse_to
);
367 // Finishes the drag and touch drag wins.
368 apps_grid_view_
->EndDrag(false);
369 EXPECT_EQ(std::string("Item 1,Item 0,Item 3,Item 2"),
371 test_api_
->LayoutToIdealBounds();
374 TEST_F(AppsGridViewTest
, HighlightWithKeyboard
) {
375 const int kTotalitems
= 2;
376 PopulateApps(kTotalitems
);
377 apps_grid_view_
->SetSelectedView(GetItemViewAt(0));
379 // Try moving off the item to an invalid location.
380 SimulateKeyPress(ui::VKEY_UP
);
381 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(GetItemViewAt(0)));
382 SimulateKeyPress(ui::VKEY_DOWN
);
383 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(GetItemViewAt(0)));
384 SimulateKeyPress(ui::VKEY_LEFT
);
385 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(GetItemViewAt(0)));
387 // Move to the second item and try to go past it.
388 SimulateKeyPress(ui::VKEY_RIGHT
);
389 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(GetItemViewAt(1)));
390 SimulateKeyPress(ui::VKEY_RIGHT
);
391 EXPECT_TRUE(apps_grid_view_
->IsSelectedView(GetItemViewAt(1)));
395 } // namespace app_list