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. 4 5 #include "ui/app_list/views/apps_grid_view.h" 6 7 #include <string> 8 9 #include "base/basictypes.h" 10 #include "base/command_line.h" 11 #include "base/compiler_specific.h" 12 #include "base/memory/scoped_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "testing/gtest/include/gtest/gtest.h" 17 #include "ui/app_list/app_list_constants.h" 18 #include "ui/app_list/app_list_folder_item.h" 19 #include "ui/app_list/app_list_item.h" 20 #include "ui/app_list/app_list_model.h" 21 #include "ui/app_list/app_list_switches.h" 22 #include "ui/app_list/pagination_model.h" 23 #include "ui/app_list/test/app_list_test_model.h" 24 #include "ui/app_list/views/app_list_item_view.h" 25 #include "ui/app_list/views/apps_grid_view_folder_delegate.h" 26 #include "ui/app_list/views/test/apps_grid_view_test_api.h" 27 #include "ui/views/test/views_test_base.h" 28 29 namespace app_list { 30 namespace test { 31 32 namespace { 33 34 const int kCols = 2; 35 const int kRows = 2; 36 const int kTilesPerPage = kCols * kRows; 37 38 const int kWidth = 320; 39 const int kHeight = 240; 40 41 class PageFlipWaiter : public PaginationModelObserver { 42 public: 43 PageFlipWaiter(base::MessageLoopForUI* ui_loop, PaginationModel* model) 44 : ui_loop_(ui_loop), model_(model), wait_(false) { 45 model_->AddObserver(this); 46 } 47 48 virtual ~PageFlipWaiter() { 49 model_->RemoveObserver(this); 50 } 51 52 void Wait() { 53 DCHECK(!wait_); 54 wait_ = true; 55 56 ui_loop_->Run(); 57 wait_ = false; 58 } 59 60 void Reset() { selected_pages_.clear(); } 61 62 const std::string& selected_pages() const { return selected_pages_; } 63 64 private: 65 // PaginationModelObserver overrides: 66 virtual void TotalPagesChanged() OVERRIDE { 67 } 68 virtual void SelectedPageChanged(int old_selected, 69 int new_selected) OVERRIDE { 70 if (!selected_pages_.empty()) 71 selected_pages_ += ','; 72 selected_pages_ += base::IntToString(new_selected); 73 74 if (wait_) 75 ui_loop_->Quit(); 76 } 77 virtual void TransitionStarted() OVERRIDE { 78 } 79 virtual void TransitionChanged() OVERRIDE { 80 } 81 82 base::MessageLoopForUI* ui_loop_; 83 PaginationModel* model_; 84 bool wait_; 85 std::string selected_pages_; 86 87 DISALLOW_COPY_AND_ASSIGN(PageFlipWaiter); 88 }; 89 90 } // namespace 91 92 class AppsGridViewTest : public views::ViewsTestBase { 93 public: 94 AppsGridViewTest() {} 95 virtual ~AppsGridViewTest() {} 96 97 // testing::Test overrides: 98 virtual void SetUp() OVERRIDE { 99 views::ViewsTestBase::SetUp(); 100 model_.reset(new AppListTestModel); 101 model_->SetFoldersEnabled(true); 102 103 apps_grid_view_.reset(new AppsGridView(NULL)); 104 apps_grid_view_->SetLayout(kCols, kRows); 105 apps_grid_view_->SetBoundsRect(gfx::Rect(gfx::Size(kWidth, kHeight))); 106 apps_grid_view_->SetModel(model_.get()); 107 apps_grid_view_->SetItemList(model_->top_level_item_list()); 108 109 test_api_.reset(new AppsGridViewTestApi(apps_grid_view_.get())); 110 } 111 virtual void TearDown() OVERRIDE { 112 apps_grid_view_.reset(); // Release apps grid view before models. 113 views::ViewsTestBase::TearDown(); 114 } 115 116 protected: 117 void EnsureFoldersEnabled() { 118 // Folders require AppList sync to be enabled. 119 CommandLine::ForCurrentProcess()->AppendSwitch( 120 switches::kEnableSyncAppList); 121 } 122 123 AppListItemView* GetItemViewAt(int index) { 124 return static_cast<AppListItemView*>( 125 test_api_->GetViewAtModelIndex(index)); 126 } 127 128 AppListItemView* GetItemViewForPoint(const gfx::Point& point) { 129 for (size_t i = 0; i < model_->top_level_item_list()->item_count(); ++i) { 130 AppListItemView* view = GetItemViewAt(i); 131 if (view->bounds().Contains(point)) 132 return view; 133 } 134 return NULL; 135 } 136 137 gfx::Rect GetItemTileRectAt(int row, int col) { 138 DCHECK_GT(model_->top_level_item_list()->item_count(), 0u); 139 140 gfx::Insets insets(apps_grid_view_->GetInsets()); 141 gfx::Rect rect(gfx::Point(insets.left(), insets.top()), 142 GetItemViewAt(0)->bounds().size()); 143 rect.Offset(col * rect.width(), row * rect.height()); 144 return rect; 145 } 146 147 PaginationModel* GetPaginationModel() { 148 return apps_grid_view_->pagination_model(); 149 } 150 151 // Points are in |apps_grid_view_|'s coordinates. 152 AppListItemView* SimulateDrag(AppsGridView::Pointer pointer, 153 const gfx::Point& from, 154 const gfx::Point& to) { 155 AppListItemView* view = GetItemViewForPoint(from); 156 DCHECK(view); 157 158 gfx::Point translated_from = gfx::PointAtOffsetFromOrigin( 159 from - view->bounds().origin()); 160 gfx::Point translated_to = gfx::PointAtOffsetFromOrigin( 161 to - view->bounds().origin()); 162 163 ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, 164 translated_from, from, 0, 0); 165 apps_grid_view_->InitiateDrag(view, pointer, pressed_event); 166 167 ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, 168 translated_to, to, 0, 0); 169 apps_grid_view_->UpdateDragFromItem(pointer, drag_event); 170 return view; 171 } 172 173 void SimulateKeyPress(ui::KeyboardCode key_code) { 174 ui::KeyEvent key_event(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE); 175 apps_grid_view_->OnKeyPressed(key_event); 176 } 177 178 scoped_ptr<AppListTestModel> model_; 179 scoped_ptr<AppsGridView> apps_grid_view_; 180 scoped_ptr<AppsGridViewTestApi> test_api_; 181 182 private: 183 DISALLOW_COPY_AND_ASSIGN(AppsGridViewTest); 184 }; 185 186 class TestAppsGridViewFolderDelegate : public AppsGridViewFolderDelegate { 187 public: 188 TestAppsGridViewFolderDelegate() : show_bubble_(false) {} 189 virtual ~TestAppsGridViewFolderDelegate() {} 190 191 // Overridden from AppsGridViewFolderDelegate: 192 virtual void UpdateFolderViewBackground(bool show_bubble) OVERRIDE { 193 show_bubble_ = show_bubble; 194 } 195 196 virtual void ReparentItem(AppListItemView* original_drag_view, 197 const gfx::Point& drag_point_in_folder_grid) 198 OVERRIDE {} 199 200 virtual void DispatchDragEventForReparent( 201 AppsGridView::Pointer pointer, 202 const gfx::Point& drag_point_in_folder_grid) OVERRIDE {} 203 204 virtual void DispatchEndDragEventForReparent( 205 bool events_forwarded_to_drag_drop_host, 206 bool cancel_drag) OVERRIDE {} 207 208 virtual bool IsPointOutsideOfFolderBoundary(const gfx::Point& point) 209 OVERRIDE { 210 return false; 211 } 212 213 virtual bool IsOEMFolder() const OVERRIDE { return false; } 214 215 virtual void SetRootLevelDragViewVisible(bool visible) OVERRIDE {} 216 217 bool show_bubble() { return show_bubble_; } 218 219 private: 220 bool show_bubble_; 221 222 DISALLOW_COPY_AND_ASSIGN(TestAppsGridViewFolderDelegate); 223 }; 224 225 TEST_F(AppsGridViewTest, CreatePage) { 226 // Fully populates a page. 227 const int kPages = 1; 228 model_->PopulateApps(kPages * kTilesPerPage); 229 EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); 230 231 // Adds one more and gets a new page created. 232 model_->CreateAndAddItem("Extra"); 233 EXPECT_EQ(kPages + 1, GetPaginationModel()->total_pages()); 234 } 235 236 TEST_F(AppsGridViewTest, EnsureHighlightedVisible) { 237 const int kPages = 3; 238 model_->PopulateApps(kPages * kTilesPerPage); 239 EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); 240 EXPECT_EQ(0, GetPaginationModel()->selected_page()); 241 242 // Highlight first one and last one one first page and first page should be 243 // selected. 244 model_->HighlightItemAt(0); 245 EXPECT_EQ(0, GetPaginationModel()->selected_page()); 246 model_->HighlightItemAt(kTilesPerPage - 1); 247 EXPECT_EQ(0, GetPaginationModel()->selected_page()); 248 249 // Highlight first one on 2nd page and 2nd page should be selected. 250 model_->HighlightItemAt(kTilesPerPage + 1); 251 EXPECT_EQ(1, GetPaginationModel()->selected_page()); 252 253 // Highlight last one in the model and last page should be selected. 254 model_->HighlightItemAt(model_->top_level_item_list()->item_count() - 1); 255 EXPECT_EQ(kPages - 1, GetPaginationModel()->selected_page()); 256 } 257 258 TEST_F(AppsGridViewTest, RemoveSelectedLastApp) { 259 const int kTotalItems = 2; 260 const int kLastItemIndex = kTotalItems - 1; 261 262 model_->PopulateApps(kTotalItems); 263 264 AppListItemView* last_view = GetItemViewAt(kLastItemIndex); 265 apps_grid_view_->SetSelectedView(last_view); 266 model_->DeleteItem(model_->GetItemName(kLastItemIndex)); 267 268 EXPECT_FALSE(apps_grid_view_->IsSelectedView(last_view)); 269 270 // No crash happens. 271 AppListItemView* view = GetItemViewAt(0); 272 apps_grid_view_->SetSelectedView(view); 273 EXPECT_TRUE(apps_grid_view_->IsSelectedView(view)); 274 } 275 276 TEST_F(AppsGridViewTest, MouseDragWithFolderDisabled) { 277 model_->SetFoldersEnabled(false); 278 const int kTotalItems = 4; 279 model_->PopulateApps(kTotalItems); 280 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 281 model_->GetModelContent()); 282 283 gfx::Point from = GetItemTileRectAt(0, 0).CenterPoint(); 284 gfx::Point to = GetItemTileRectAt(0, 1).CenterPoint(); 285 286 // Dragging changes model order. 287 SimulateDrag(AppsGridView::MOUSE, from, to); 288 apps_grid_view_->EndDrag(false); 289 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"), 290 model_->GetModelContent()); 291 test_api_->LayoutToIdealBounds(); 292 293 // Canceling drag should keep existing order. 294 SimulateDrag(AppsGridView::MOUSE, from, to); 295 apps_grid_view_->EndDrag(true); 296 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"), 297 model_->GetModelContent()); 298 test_api_->LayoutToIdealBounds(); 299 300 // Deleting an item keeps remaining intact. 301 SimulateDrag(AppsGridView::MOUSE, from, to); 302 model_->DeleteItem(model_->GetItemName(0)); 303 apps_grid_view_->EndDrag(false); 304 EXPECT_EQ(std::string("Item 1,Item 2,Item 3"), 305 model_->GetModelContent()); 306 test_api_->LayoutToIdealBounds(); 307 308 // Adding a launcher item cancels the drag and respects the order. 309 SimulateDrag(AppsGridView::MOUSE, from, to); 310 EXPECT_TRUE(apps_grid_view_->has_dragged_view()); 311 model_->CreateAndAddItem("Extra"); 312 // No need to EndDrag explicitly - adding an item should do this. 313 EXPECT_FALSE(apps_grid_view_->has_dragged_view()); 314 // Even though cancelled, mouse move events can still arrive via the item 315 // view. Ensure that behaves sanely, and doesn't start a new drag. 316 ui::MouseEvent drag_event( 317 ui::ET_MOUSE_DRAGGED, gfx::Point(1, 1), gfx::Point(2, 2), 0, 0); 318 apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, drag_event); 319 EXPECT_FALSE(apps_grid_view_->has_dragged_view()); 320 321 EXPECT_EQ(std::string("Item 1,Item 2,Item 3,Extra"), 322 model_->GetModelContent()); 323 test_api_->LayoutToIdealBounds(); 324 } 325 326 TEST_F(AppsGridViewTest, MouseDragItemIntoFolder) { 327 EnsureFoldersEnabled(); 328 329 size_t kTotalItems = 3; 330 model_->PopulateApps(kTotalItems); 331 EXPECT_EQ(model_->top_level_item_list()->item_count(), kTotalItems); 332 EXPECT_EQ(std::string("Item 0,Item 1,Item 2"), model_->GetModelContent()); 333 334 gfx::Point from = GetItemTileRectAt(0, 1).CenterPoint(); 335 gfx::Point to = GetItemTileRectAt(0, 0).CenterPoint(); 336 337 // Dragging item_1 over item_0 creates a folder. 338 SimulateDrag(AppsGridView::MOUSE, from, to); 339 apps_grid_view_->EndDrag(false); 340 EXPECT_EQ(kTotalItems - 1, model_->top_level_item_list()->item_count()); 341 EXPECT_EQ(AppListFolderItem::kItemType, 342 model_->top_level_item_list()->item_at(0)->GetItemType()); 343 AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( 344 model_->top_level_item_list()->item_at(0)); 345 EXPECT_EQ(2u, folder_item->ChildItemCount()); 346 AppListItem* item_0 = model_->FindItem("Item 0"); 347 EXPECT_TRUE(item_0->IsInFolder()); 348 EXPECT_EQ(folder_item->id(), item_0->folder_id()); 349 AppListItem* item_1 = model_->FindItem("Item 1"); 350 EXPECT_TRUE(item_1->IsInFolder()); 351 EXPECT_EQ(folder_item->id(), item_1->folder_id()); 352 std::string expected_items = folder_item->id() + ",Item 2"; 353 EXPECT_EQ(expected_items, model_->GetModelContent()); 354 test_api_->LayoutToIdealBounds(); 355 356 // Dragging item_2 to the folder adds item_2 to the folder. 357 SimulateDrag(AppsGridView::MOUSE, from, to); 358 apps_grid_view_->EndDrag(false); 359 360 EXPECT_EQ(kTotalItems - 2, model_->top_level_item_list()->item_count()); 361 EXPECT_EQ(folder_item->id(), model_->GetModelContent()); 362 EXPECT_EQ(3u, folder_item->ChildItemCount()); 363 item_0 = model_->FindItem("Item 0"); 364 EXPECT_TRUE(item_0->IsInFolder()); 365 EXPECT_EQ(folder_item->id(), item_0->folder_id()); 366 item_1 = model_->FindItem("Item 1"); 367 EXPECT_TRUE(item_1->IsInFolder()); 368 EXPECT_EQ(folder_item->id(), item_1->folder_id()); 369 AppListItem* item_2 = model_->FindItem("Item 2"); 370 EXPECT_TRUE(item_2->IsInFolder()); 371 EXPECT_EQ(folder_item->id(), item_2->folder_id()); 372 test_api_->LayoutToIdealBounds(); 373 } 374 375 TEST_F(AppsGridViewTest, MouseDragMaxItemsInFolder) { 376 EnsureFoldersEnabled(); 377 378 // Create and add a folder with 15 items in it. 379 size_t kTotalItems = kMaxFolderItems - 1; 380 model_->CreateAndPopulateFolderWithApps(kTotalItems); 381 EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); 382 EXPECT_EQ(AppListFolderItem::kItemType, 383 model_->top_level_item_list()->item_at(0)->GetItemType()); 384 AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( 385 model_->top_level_item_list()->item_at(0)); 386 EXPECT_EQ(kTotalItems, folder_item->ChildItemCount()); 387 388 // Create and add another 2 items. 389 model_->PopulateAppWithId(kTotalItems); 390 model_->PopulateAppWithId(kTotalItems + 1); 391 EXPECT_EQ(3u, model_->top_level_item_list()->item_count()); 392 EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); 393 EXPECT_EQ(model_->GetItemName(kMaxFolderItems - 1), 394 model_->top_level_item_list()->item_at(1)->id()); 395 EXPECT_EQ(model_->GetItemName(kMaxFolderItems), 396 model_->top_level_item_list()->item_at(2)->id()); 397 398 gfx::Point from = GetItemTileRectAt(0, 1).CenterPoint(); 399 gfx::Point to = GetItemTileRectAt(0, 0).CenterPoint(); 400 401 // Dragging one item into the folder, the folder should accept the item. 402 SimulateDrag(AppsGridView::MOUSE, from, to); 403 apps_grid_view_->EndDrag(false); 404 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 405 EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); 406 EXPECT_EQ(kMaxFolderItems, folder_item->ChildItemCount()); 407 EXPECT_EQ(model_->GetItemName(kMaxFolderItems), 408 model_->top_level_item_list()->item_at(1)->id()); 409 test_api_->LayoutToIdealBounds(); 410 411 // Dragging the last item over the folder, the folder won't accept the new 412 // item. 413 SimulateDrag(AppsGridView::MOUSE, from, to); 414 apps_grid_view_->EndDrag(false); 415 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 416 EXPECT_EQ(kMaxFolderItems, folder_item->ChildItemCount()); 417 test_api_->LayoutToIdealBounds(); 418 } 419 420 // Check that moving items around doesn't allow a drop to happen into a full 421 // folder. 422 TEST_F(AppsGridViewTest, MouseDragMaxItemsInFolderWithMovement) { 423 EnsureFoldersEnabled(); 424 425 // Create and add a folder with 16 items in it. 426 size_t kTotalItems = kMaxFolderItems; 427 model_->CreateAndPopulateFolderWithApps(kTotalItems); 428 EXPECT_EQ(1u, model_->top_level_item_list()->item_count()); 429 EXPECT_EQ(AppListFolderItem::kItemType, 430 model_->top_level_item_list()->item_at(0)->GetItemType()); 431 AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( 432 model_->top_level_item_list()->item_at(0)); 433 EXPECT_EQ(kTotalItems, folder_item->ChildItemCount()); 434 435 // Create and add another item. 436 model_->PopulateAppWithId(kTotalItems); 437 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 438 EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(0)->id()); 439 EXPECT_EQ(model_->GetItemName(kMaxFolderItems), 440 model_->top_level_item_list()->item_at(1)->id()); 441 442 AppListItemView* folder_view = 443 GetItemViewForPoint(GetItemTileRectAt(0, 0).CenterPoint()); 444 445 // Drag the new item to the left so that the grid reorders. 446 gfx::Point from = GetItemTileRectAt(0, 1).CenterPoint(); 447 gfx::Point to = GetItemTileRectAt(0, 0).bottom_left(); 448 AppListItemView* dragged_view = SimulateDrag(AppsGridView::MOUSE, from, to); 449 test_api_->LayoutToIdealBounds(); 450 451 // The grid now looks like | blank | folder |. 452 EXPECT_EQ(NULL, GetItemViewForPoint(GetItemTileRectAt(0, 0).CenterPoint())); 453 EXPECT_EQ(folder_view, 454 GetItemViewForPoint(GetItemTileRectAt(0, 1).CenterPoint())); 455 456 // Move onto the folder and end the drag. 457 to = GetItemTileRectAt(0, 1).CenterPoint(); 458 gfx::Point translated_to = 459 gfx::PointAtOffsetFromOrigin(to - dragged_view->bounds().origin()); 460 ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, translated_to, to, 0, 0); 461 apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, drag_event); 462 apps_grid_view_->EndDrag(false); 463 464 // The item should not have moved into the folder. 465 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 466 EXPECT_EQ(kMaxFolderItems, folder_item->ChildItemCount()); 467 test_api_->LayoutToIdealBounds(); 468 } 469 470 TEST_F(AppsGridViewTest, MouseDragItemReorder) { 471 // This test assumes Folders are enabled. 472 EnsureFoldersEnabled(); 473 474 model_->PopulateApps(4); 475 EXPECT_EQ(4u, model_->top_level_item_list()->item_count()); 476 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 477 model_->GetModelContent()); 478 479 // Dragging an item towards its neighbours should not reorder until the drag 480 // is past the folder drop point. 481 gfx::Point top_right = GetItemTileRectAt(0, 1).CenterPoint(); 482 gfx::Vector2d drag_vector; 483 int half_tile_width = 484 (GetItemTileRectAt(0, 1).x() - GetItemTileRectAt(0, 0).x()) / 2; 485 int tile_height = GetItemTileRectAt(1, 0).y() - GetItemTileRectAt(0, 0).y(); 486 487 // Drag left but stop before the folder dropping circle. 488 drag_vector.set_x(-half_tile_width - 5); 489 SimulateDrag(AppsGridView::MOUSE, top_right, top_right + drag_vector); 490 apps_grid_view_->EndDrag(false); 491 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 492 model_->GetModelContent()); 493 494 // Drag left, past the folder dropping circle. 495 drag_vector.set_x(-3 * half_tile_width + 5); 496 SimulateDrag(AppsGridView::MOUSE, top_right, top_right + drag_vector); 497 apps_grid_view_->EndDrag(false); 498 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"), 499 model_->GetModelContent()); 500 501 // Drag down, between apps 2 and 3. The gap should open up, making space for 502 // app 0 in the bottom left. 503 drag_vector.set_x(-half_tile_width); 504 drag_vector.set_y(tile_height); 505 SimulateDrag(AppsGridView::MOUSE, top_right, top_right + drag_vector); 506 apps_grid_view_->EndDrag(false); 507 EXPECT_EQ(std::string("Item 1,Item 2,Item 0,Item 3"), 508 model_->GetModelContent()); 509 510 // Drag up, between apps 1 and 2. The gap should open up, making space for app 511 // 0 in the top right. 512 gfx::Point bottom_left = GetItemTileRectAt(1, 0).CenterPoint(); 513 drag_vector.set_x(half_tile_width); 514 drag_vector.set_y(-tile_height); 515 SimulateDrag(AppsGridView::MOUSE, bottom_left, bottom_left + drag_vector); 516 apps_grid_view_->EndDrag(false); 517 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"), 518 model_->GetModelContent()); 519 520 // Dragging down past the last app should reorder to the last position. 521 drag_vector.set_x(half_tile_width); 522 drag_vector.set_y(2 * tile_height); 523 SimulateDrag(AppsGridView::MOUSE, top_right, top_right + drag_vector); 524 apps_grid_view_->EndDrag(false); 525 EXPECT_EQ(std::string("Item 1,Item 2,Item 3,Item 0"), 526 model_->GetModelContent()); 527 } 528 529 TEST_F(AppsGridViewTest, MouseDragFolderReorder) { 530 EnsureFoldersEnabled(); 531 532 size_t kTotalItems = 2; 533 model_->CreateAndPopulateFolderWithApps(kTotalItems); 534 model_->PopulateAppWithId(kTotalItems); 535 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 536 EXPECT_EQ(AppListFolderItem::kItemType, 537 model_->top_level_item_list()->item_at(0)->GetItemType()); 538 AppListFolderItem* folder_item = static_cast<AppListFolderItem*>( 539 model_->top_level_item_list()->item_at(0)); 540 EXPECT_EQ("Item 2", model_->top_level_item_list()->item_at(1)->id()); 541 542 gfx::Point from = GetItemTileRectAt(0, 0).CenterPoint(); 543 gfx::Point to = GetItemTileRectAt(0, 1).CenterPoint(); 544 545 // Dragging folder over item_1 should leads to re-ordering these two 546 // items. 547 SimulateDrag(AppsGridView::MOUSE, from, to); 548 apps_grid_view_->EndDrag(false); 549 EXPECT_EQ(2u, model_->top_level_item_list()->item_count()); 550 EXPECT_EQ("Item 2", model_->top_level_item_list()->item_at(0)->id()); 551 EXPECT_EQ(folder_item->id(), model_->top_level_item_list()->item_at(1)->id()); 552 test_api_->LayoutToIdealBounds(); 553 } 554 555 TEST_F(AppsGridViewTest, MouseDragWithCancelDeleteAddItem) { 556 size_t kTotalItems = 4; 557 model_->PopulateApps(kTotalItems); 558 EXPECT_EQ(model_->top_level_item_list()->item_count(), kTotalItems); 559 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 560 model_->GetModelContent()); 561 562 gfx::Point from = GetItemTileRectAt(0, 0).CenterPoint(); 563 gfx::Point to = GetItemTileRectAt(0, 1).CenterPoint(); 564 565 // Canceling drag should keep existing order. 566 SimulateDrag(AppsGridView::MOUSE, from, to); 567 apps_grid_view_->EndDrag(true); 568 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 569 model_->GetModelContent()); 570 test_api_->LayoutToIdealBounds(); 571 572 // Deleting an item keeps remaining intact. 573 SimulateDrag(AppsGridView::MOUSE, from, to); 574 model_->DeleteItem(model_->GetItemName(2)); 575 apps_grid_view_->EndDrag(false); 576 EXPECT_EQ(std::string("Item 0,Item 1,Item 3"), model_->GetModelContent()); 577 test_api_->LayoutToIdealBounds(); 578 579 // Adding a launcher item cancels the drag and respects the order. 580 SimulateDrag(AppsGridView::MOUSE, from, to); 581 model_->CreateAndAddItem("Extra"); 582 apps_grid_view_->EndDrag(false); 583 EXPECT_EQ(std::string("Item 0,Item 1,Item 3,Extra"), 584 model_->GetModelContent()); 585 test_api_->LayoutToIdealBounds(); 586 } 587 588 TEST_F(AppsGridViewTest, MouseDragFlipPage) { 589 test_api_->SetPageFlipDelay(10); 590 GetPaginationModel()->SetTransitionDurations(10, 10); 591 592 PageFlipWaiter page_flip_waiter(message_loop(), GetPaginationModel()); 593 594 const int kPages = 3; 595 model_->PopulateApps(kPages * kTilesPerPage); 596 EXPECT_EQ(kPages, GetPaginationModel()->total_pages()); 597 EXPECT_EQ(0, GetPaginationModel()->selected_page()); 598 599 gfx::Point from = GetItemTileRectAt(0, 0).CenterPoint(); 600 gfx::Point to = gfx::Point(apps_grid_view_->width(), 601 apps_grid_view_->height() / 2); 602 603 // Drag to right edge. 604 page_flip_waiter.Reset(); 605 SimulateDrag(AppsGridView::MOUSE, from, to); 606 607 // Page should be flipped after sometime to hit page 1 and 2 then stop. 608 while (test_api_->HasPendingPageFlip()) { 609 page_flip_waiter.Wait(); 610 } 611 EXPECT_EQ("1,2", page_flip_waiter.selected_pages()); 612 EXPECT_EQ(2, GetPaginationModel()->selected_page()); 613 614 apps_grid_view_->EndDrag(true); 615 616 // Now drag to the left edge and test the other direction. 617 to.set_x(0); 618 619 page_flip_waiter.Reset(); 620 SimulateDrag(AppsGridView::MOUSE, from, to); 621 622 while (test_api_->HasPendingPageFlip()) { 623 page_flip_waiter.Wait(); 624 } 625 EXPECT_EQ("1,0", page_flip_waiter.selected_pages()); 626 EXPECT_EQ(0, GetPaginationModel()->selected_page()); 627 628 apps_grid_view_->EndDrag(true); 629 } 630 631 TEST_F(AppsGridViewTest, SimultaneousDragWithFolderDisabled) { 632 model_->SetFoldersEnabled(false); 633 const int kTotalItems = 4; 634 model_->PopulateApps(kTotalItems); 635 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 636 model_->GetModelContent()); 637 638 gfx::Point mouse_from = GetItemTileRectAt(0, 0).CenterPoint(); 639 gfx::Point mouse_to = GetItemTileRectAt(0, 1).CenterPoint(); 640 641 gfx::Point touch_from = GetItemTileRectAt(1, 0).CenterPoint(); 642 gfx::Point touch_to = GetItemTileRectAt(1, 1).CenterPoint(); 643 644 // Starts a mouse drag first then a touch drag. 645 SimulateDrag(AppsGridView::MOUSE, mouse_from, mouse_to); 646 SimulateDrag(AppsGridView::TOUCH, touch_from, touch_to); 647 // Finishes the drag and mouse drag wins. 648 apps_grid_view_->EndDrag(false); 649 EXPECT_EQ(std::string("Item 1,Item 0,Item 2,Item 3"), 650 model_->GetModelContent()); 651 test_api_->LayoutToIdealBounds(); 652 653 // Starts a touch drag first then a mouse drag. 654 SimulateDrag(AppsGridView::TOUCH, touch_from, touch_to); 655 SimulateDrag(AppsGridView::MOUSE, mouse_from, mouse_to); 656 // Finishes the drag and touch drag wins. 657 apps_grid_view_->EndDrag(false); 658 EXPECT_EQ(std::string("Item 1,Item 0,Item 3,Item 2"), 659 model_->GetModelContent()); 660 test_api_->LayoutToIdealBounds(); 661 } 662 663 TEST_F(AppsGridViewTest, UpdateFolderBackgroundOnCancelDrag) { 664 EnsureFoldersEnabled(); 665 666 const int kTotalItems = 4; 667 TestAppsGridViewFolderDelegate folder_delegate; 668 apps_grid_view_->set_folder_delegate(&folder_delegate); 669 model_->PopulateApps(kTotalItems); 670 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 671 model_->GetModelContent()); 672 673 gfx::Point mouse_from = GetItemTileRectAt(0, 0).CenterPoint(); 674 gfx::Point mouse_to = GetItemTileRectAt(0, 1).CenterPoint(); 675 676 // Starts a mouse drag and then cancels it. 677 SimulateDrag(AppsGridView::MOUSE, mouse_from, mouse_to); 678 EXPECT_TRUE(folder_delegate.show_bubble()); 679 apps_grid_view_->EndDrag(true); 680 EXPECT_FALSE(folder_delegate.show_bubble()); 681 EXPECT_EQ(std::string("Item 0,Item 1,Item 2,Item 3"), 682 model_->GetModelContent()); 683 } 684 685 TEST_F(AppsGridViewTest, HighlightWithKeyboard) { 686 const int kPages = 3; 687 const int kItems = (kPages - 1) * kTilesPerPage + 1; 688 model_->PopulateApps(kItems); 689 690 const int first_index = 0; 691 const int last_index = kItems - 1; 692 const int last_index_on_page1_first_row = kRows - 1; 693 const int last_index_on_page1 = kTilesPerPage - 1; 694 const int first_index_on_page2 = kTilesPerPage; 695 const int first_index_on_page2_last_row = 2 * kTilesPerPage - kRows; 696 const int last_index_on_page2_last_row = 2 * kTilesPerPage - 1; 697 698 // Try moving off the item beyond the first one. 699 apps_grid_view_->SetSelectedView(GetItemViewAt(first_index)); 700 SimulateKeyPress(ui::VKEY_UP); 701 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt(first_index))); 702 SimulateKeyPress(ui::VKEY_LEFT); 703 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt(first_index))); 704 705 // Move to the last item and try to go past it. 706 apps_grid_view_->SetSelectedView(GetItemViewAt(last_index)); 707 SimulateKeyPress(ui::VKEY_DOWN); 708 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt(last_index))); 709 SimulateKeyPress(ui::VKEY_RIGHT); 710 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt(last_index))); 711 712 // Move right on last item on page 1 should get to first item on page 2's last 713 // row and vice versa. 714 apps_grid_view_->SetSelectedView(GetItemViewAt(last_index_on_page1)); 715 SimulateKeyPress(ui::VKEY_RIGHT); 716 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 717 first_index_on_page2_last_row))); 718 SimulateKeyPress(ui::VKEY_LEFT); 719 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 720 last_index_on_page1))); 721 722 // Up/down on page boundary does nothing. 723 apps_grid_view_->SetSelectedView(GetItemViewAt(last_index_on_page1)); 724 SimulateKeyPress(ui::VKEY_DOWN); 725 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 726 last_index_on_page1))); 727 apps_grid_view_->SetSelectedView( 728 GetItemViewAt(first_index_on_page2_last_row)); 729 apps_grid_view_-> 730 SetSelectedView(GetItemViewAt(last_index_on_page1_first_row)); 731 SimulateKeyPress(ui::VKEY_UP); 732 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 733 last_index_on_page1_first_row))); 734 735 // Page up and down should go to the same item on the next and last page. 736 apps_grid_view_->SetSelectedView(GetItemViewAt(first_index_on_page2)); 737 SimulateKeyPress(ui::VKEY_PRIOR); 738 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 739 first_index))); 740 SimulateKeyPress(ui::VKEY_NEXT); 741 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 742 first_index_on_page2))); 743 744 // Moving onto a a page with too few apps to support the expected index snaps 745 // to the last available index. 746 apps_grid_view_->SetSelectedView(GetItemViewAt(last_index_on_page2_last_row)); 747 SimulateKeyPress(ui::VKEY_RIGHT); 748 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 749 last_index))); 750 apps_grid_view_->SetSelectedView(GetItemViewAt(last_index_on_page2_last_row)); 751 SimulateKeyPress(ui::VKEY_NEXT); 752 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 753 last_index))); 754 755 756 757 // After page switch, arrow keys select first item on current page. 758 apps_grid_view_->SetSelectedView(GetItemViewAt(first_index)); 759 GetPaginationModel()->SelectPage(1, false); 760 SimulateKeyPress(ui::VKEY_UP); 761 EXPECT_TRUE(apps_grid_view_->IsSelectedView(GetItemViewAt( 762 first_index_on_page2))); 763 } 764 765 TEST_F(AppsGridViewTest, ItemLabelShortNameOverride) { 766 // If the app's full name and short name differ, the title label's tooltip 767 // should always be the full name of the app. 768 std::string expected_text("xyz"); 769 std::string expected_tooltip("tooltip"); 770 AppListItem* item = model_->CreateAndAddItem("Item with short name"); 771 model_->SetItemNameAndShortName(item, expected_tooltip, expected_text); 772 773 base::string16 actual_tooltip; 774 AppListItemView* item_view = GetItemViewAt(0); 775 ASSERT_TRUE(item_view); 776 const views::Label* title_label = item_view->title(); 777 EXPECT_TRUE(title_label->GetTooltipText( 778 title_label->bounds().CenterPoint(), &actual_tooltip)); 779 EXPECT_EQ(expected_tooltip, base::UTF16ToUTF8(actual_tooltip)); 780 EXPECT_EQ(expected_text, base::UTF16ToUTF8(title_label->text())); 781 } 782 783 TEST_F(AppsGridViewTest, ItemLabelNoShortName) { 784 // If the app's full name and short name are the same, use the default tooltip 785 // behavior of the label (only show a tooltip if the title is truncated). 786 std::string title("a"); 787 AppListItem* item = model_->CreateAndAddItem(title); 788 model_->SetItemNameAndShortName(item, title, ""); 789 790 base::string16 actual_tooltip; 791 AppListItemView* item_view = GetItemViewAt(0); 792 ASSERT_TRUE(item_view); 793 const views::Label* title_label = item_view->title(); 794 EXPECT_FALSE(title_label->GetTooltipText( 795 title_label->bounds().CenterPoint(), &actual_tooltip)); 796 EXPECT_EQ(title, base::UTF16ToUTF8(title_label->text())); 797 } 798 799 } // namespace test 800 } // namespace app_list 801