apps_grid_controller_unittest.mm revision 3551c9c881056c480085172ff9840cab31610854
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 "base/mac/foundation_util.h" 6#include "base/mac/scoped_nsobject.h" 7#include "base/strings/utf_string_conversions.h" 8#include "skia/ext/skia_utils_mac.h" 9#import "testing/gtest_mac.h" 10#include "ui/app_list/app_list_constants.h" 11#include "ui/app_list/app_list_item_model.h" 12#import "ui/app_list/cocoa/apps_collection_view_drag_manager.h" 13#import "ui/app_list/cocoa/apps_grid_controller.h" 14#import "ui/app_list/cocoa/apps_grid_view_item.h" 15#import "ui/app_list/cocoa/apps_pagination_model_observer.h" 16#import "ui/app_list/cocoa/test/apps_grid_controller_test_helper.h" 17#include "ui/app_list/test/app_list_test_model.h" 18#include "ui/app_list/test/app_list_test_view_delegate.h" 19#include "ui/base/models/simple_menu_model.h" 20#import "ui/base/test/cocoa_test_event_utils.h" 21 22@interface TestPaginationObserver : NSObject<AppsPaginationModelObserver> { 23 @private 24 NSInteger hoveredSegmentForTest_; 25 int totalPagesChangedCount_; 26 int selectedPageChangedCount_; 27 int lastNewSelectedPage_; 28 bool visibilityDidChange_; 29} 30 31@property(assign, nonatomic) NSInteger hoveredSegmentForTest; 32@property(assign, nonatomic) int totalPagesChangedCount; 33@property(assign, nonatomic) int selectedPageChangedCount; 34@property(assign, nonatomic) int lastNewSelectedPage; 35 36- (bool)readVisibilityDidChange; 37 38@end 39 40@implementation TestPaginationObserver 41 42@synthesize hoveredSegmentForTest = hoveredSegmentForTest_; 43@synthesize totalPagesChangedCount = totalPagesChangedCount_; 44@synthesize selectedPageChangedCount = selectedPageChangedCount_; 45@synthesize lastNewSelectedPage = lastNewSelectedPage_; 46 47- (id)init { 48 if ((self = [super init])) 49 hoveredSegmentForTest_ = -1; 50 51 return self; 52} 53 54- (bool)readVisibilityDidChange { 55 bool truth = visibilityDidChange_; 56 visibilityDidChange_ = false; 57 return truth; 58} 59 60- (void)totalPagesChanged { 61 ++totalPagesChangedCount_; 62} 63 64- (void)selectedPageChanged:(int)newSelected { 65 ++selectedPageChangedCount_; 66 lastNewSelectedPage_ = newSelected; 67} 68 69- (void)pageVisibilityChanged { 70 visibilityDidChange_ = true; 71} 72 73- (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow { 74 return hoveredSegmentForTest_; 75} 76 77@end 78 79namespace app_list { 80namespace test { 81 82namespace { 83 84class AppsGridControllerTest : public AppsGridControllerTestHelper { 85 public: 86 AppsGridControllerTest() {} 87 88 AppListTestViewDelegate* delegate() { 89 return owned_delegate_.get(); 90 } 91 92 NSColor* ButtonTitleColorAt(size_t index) { 93 NSDictionary* attributes = 94 [[[GetItemViewAt(index) cell] attributedTitle] attributesAtIndex:0 95 effectiveRange:NULL]; 96 return [attributes objectForKey:NSForegroundColorAttributeName]; 97 } 98 99 virtual void SetUp() OVERRIDE { 100 owned_apps_grid_controller_.reset([[AppsGridController alloc] init]); 101 owned_delegate_.reset(new AppListTestViewDelegate); 102 [owned_apps_grid_controller_ setDelegate:owned_delegate_.get()]; 103 AppsGridControllerTestHelper::SetUpWithGridController( 104 owned_apps_grid_controller_.get()); 105 106 [[test_window() contentView] addSubview:[apps_grid_controller_ view]]; 107 [test_window() makePretendKeyWindowAndSetFirstResponder: 108 [apps_grid_controller_ collectionViewAtPageIndex:0]]; 109 } 110 111 virtual void TearDown() OVERRIDE { 112 owned_apps_grid_controller_.reset(); 113 AppsGridControllerTestHelper::TearDown(); 114 } 115 116 private: 117 base::scoped_nsobject<AppsGridController> owned_apps_grid_controller_; 118 scoped_ptr<AppListTestViewDelegate> owned_delegate_; 119 120 DISALLOW_COPY_AND_ASSIGN(AppsGridControllerTest); 121}; 122 123class AppListItemWithMenu : public AppListItemModel { 124 public: 125 AppListItemWithMenu(const std::string& title) : menu_model_(NULL) { 126 SetTitle(title); 127 menu_model_.AddItem(0, UTF8ToUTF16("Menu For: " + title)); 128 } 129 130 virtual ui::MenuModel* GetContextMenuModel() OVERRIDE { 131 return &menu_model_; 132 } 133 134 private: 135 ui::SimpleMenuModel menu_model_; 136 137 DISALLOW_COPY_AND_ASSIGN(AppListItemWithMenu); 138}; 139 140// Generate a mouse event at the centre of the view in |page| with the given 141// |index_in_page| that can be used to initiate, update and complete drag 142// operations. 143NSEvent* MouseEventInCell(NSCollectionView* page, size_t index_in_page) { 144 NSRect cell_rect = [page frameForItemAtIndex:index_in_page]; 145 NSPoint point_in_view = NSMakePoint(NSMidX(cell_rect), NSMidY(cell_rect)); 146 NSPoint point_in_window = [page convertPoint:point_in_view 147 toView:nil]; 148 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window); 149} 150 151NSEvent* MouseEventForScroll(NSView* view, CGFloat relative_x) { 152 NSRect view_rect = [view frame]; 153 NSPoint point_in_view = NSMakePoint(NSMidX(view_rect), NSMidY(view_rect)); 154 point_in_view.x += point_in_view.x * relative_x; 155 NSPoint point_in_window = [view convertPoint:point_in_view 156 toView:nil]; 157 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window); 158} 159 160} // namespace 161 162TEST_VIEW(AppsGridControllerTest, [apps_grid_controller_ view]); 163 164// Test showing with an empty model. 165TEST_F(AppsGridControllerTest, EmptyModelAndShow) { 166 EXPECT_TRUE([[apps_grid_controller_ view] superview]); 167 168 // First page should always exist, even if empty. 169 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 170 EXPECT_EQ(0u, [[GetPageAt(0) content] count]); 171 EXPECT_TRUE([GetPageAt(0) superview]); // The pages container. 172 EXPECT_TRUE([[GetPageAt(0) superview] superview]); 173} 174 175// Test with a single item. 176// This test is disabled in builders until the delay to wait for the collection 177// view to load subviews can be removed, or some other solution is found. 178TEST_F(AppsGridControllerTest, DISABLED_SingleEntryModel) { 179 // We need to "wake up" the NSCollectionView, otherwise it does not 180 // immediately update its subviews later in this function. 181 // When this test is run by itself, it's enough just to send a keypress (and 182 // this delay is not needed). 183 DelayForCollectionView(); 184 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 185 EXPECT_EQ(0u, [[GetPageAt(0) content] count]); 186 187 model()->PopulateApps(1); 188 SinkEvents(); 189 EXPECT_FALSE([GetPageAt(0) animations]); 190 191 EXPECT_EQ(1u, [[GetPageAt(0) content] count]); 192 NSArray* subviews = [GetPageAt(0) subviews]; 193 EXPECT_EQ(1u, [subviews count]); 194 195 // Note that using GetItemViewAt(0) here also works, and returns non-nil even 196 // without the delay, but a "click" on it does not register without the delay. 197 NSView* subview = [subviews objectAtIndex:0]; 198 199 // Launch the item. 200 SimulateClick(subview); 201 SinkEvents(); 202 EXPECT_EQ(1, delegate()->activate_count()); 203 EXPECT_EQ(std::string("Item 0"), delegate()->last_activated()->title()); 204} 205 206// Test activating an item on the second page (the 17th item). 207TEST_F(AppsGridControllerTest, DISABLED_TwoPageModel) { 208 DelayForCollectionView(); 209 ReplaceTestModel(kItemsPerPage * 2); 210 [apps_grid_controller_ scrollToPage:1]; 211 212 // The NSScrollView animator ignores the duration configured on the 213 // NSAnimationContext (set by CocoaTest::Init), so we need to delay here. 214 DelayForCollectionView(); 215 NSArray* subviews = [GetPageAt(1) subviews]; 216 NSView* subview = [subviews objectAtIndex:0]; 217 // Launch the item. 218 SimulateClick(subview); 219 SinkEvents(); 220 EXPECT_EQ(1, delegate()->activate_count()); 221 EXPECT_EQ(std::string("Item 16"), delegate()->last_activated()->title()); 222} 223 224// Test setModel. 225TEST_F(AppsGridControllerTest, ReplaceModel) { 226 const size_t kOrigItems = 1; 227 const size_t kNewItems = 2; 228 229 model()->PopulateApps(kOrigItems); 230 EXPECT_EQ(kOrigItems, [[GetPageAt(0) content] count]); 231 232 ReplaceTestModel(kNewItems); 233 EXPECT_EQ(kNewItems, [[GetPageAt(0) content] count]); 234} 235 236// Test pagination. 237TEST_F(AppsGridControllerTest, Pagination) { 238 model()->PopulateApps(1); 239 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 240 EXPECT_EQ(1u, [[GetPageAt(0) content] count]); 241 242 ReplaceTestModel(kItemsPerPage); 243 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 244 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]); 245 246 // Test adding an item onto the next page. 247 model()->PopulateApps(1); // Now 17 items. 248 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]); 249 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]); 250 EXPECT_EQ(1u, [[GetPageAt(1) content] count]); 251 252 // Test N pages with the last page having one empty spot. 253 const size_t kPagesToTest = 3; 254 ReplaceTestModel(kPagesToTest * kItemsPerPage - 1); 255 EXPECT_EQ(kPagesToTest, [apps_grid_controller_ pageCount]); 256 for (size_t page_index = 0; page_index < kPagesToTest - 1; ++page_index) { 257 EXPECT_EQ(kItemsPerPage, [[GetPageAt(page_index) content] count]); 258 } 259 EXPECT_EQ(kItemsPerPage - 1, [[GetPageAt(kPagesToTest - 1) content] count]); 260 261 // Test removing pages. 262 ReplaceTestModel(1); 263 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 264 EXPECT_EQ(1u, [[GetPageAt(0) content] count]); 265} 266 267// Tests that selecting an item changes the text color correctly. 268TEST_F(AppsGridControllerTest, SelectionChangesTextColor) { 269 model()->PopulateApps(2); 270 [apps_grid_controller_ selectItemAtIndex:0]; 271 EXPECT_NSEQ(ButtonTitleColorAt(0), 272 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor)); 273 EXPECT_NSEQ(ButtonTitleColorAt(1), 274 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor)); 275 276 [apps_grid_controller_ selectItemAtIndex:1]; 277 EXPECT_NSEQ(ButtonTitleColorAt(0), 278 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor)); 279 EXPECT_NSEQ(ButtonTitleColorAt(1), 280 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor)); 281} 282 283// Tests basic keyboard navigation on the first page. 284TEST_F(AppsGridControllerTest, FirstPageKeyboardNavigation) { 285 model()->PopulateApps(kItemsPerPage - 2); 286 EXPECT_EQ(kItemsPerPage - 2, [[GetPageAt(0) content] count]); 287 288 SimulateKeyAction(@selector(moveRight:)); 289 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]); 290 291 SimulateKeyAction(@selector(moveRight:)); 292 EXPECT_EQ(1u, [apps_grid_controller_ selectedItemIndex]); 293 294 SimulateKeyAction(@selector(moveDown:)); 295 EXPECT_EQ(5u, [apps_grid_controller_ selectedItemIndex]); 296 297 SimulateKeyAction(@selector(moveLeft:)); 298 EXPECT_EQ(4u, [apps_grid_controller_ selectedItemIndex]); 299 300 SimulateKeyAction(@selector(moveUp:)); 301 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]); 302 303 // Go to the third item, and launch it. 304 SimulateKeyAction(@selector(moveRight:)); 305 SimulateKeyAction(@selector(moveRight:)); 306 EXPECT_EQ(2u, [apps_grid_controller_ selectedItemIndex]); 307 SimulateKeyAction(@selector(insertNewline:)); 308 EXPECT_EQ(1, delegate()->activate_count()); 309 EXPECT_EQ(std::string("Item 2"), delegate()->last_activated()->title()); 310} 311 312// Tests keyboard navigation across pages. 313TEST_F(AppsGridControllerTest, CrossPageKeyboardNavigation) { 314 model()->PopulateApps(kItemsPerPage + 10); 315 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]); 316 EXPECT_EQ(10u, [[GetPageAt(1) content] count]); 317 318 // Moving Left, Up, or PageUp from the top-left corner of the first page does 319 // nothing. 320 [apps_grid_controller_ selectItemAtIndex:0]; 321 SimulateKeyAction(@selector(moveLeft:)); 322 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]); 323 SimulateKeyAction(@selector(moveUp:)); 324 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]); 325 SimulateKeyAction(@selector(scrollPageUp:)); 326 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]); 327 328 // Moving Right from the right side goes to the next page. Moving Left goes 329 // back to the first page. 330 [apps_grid_controller_ selectItemAtIndex:3]; 331 SimulateKeyAction(@selector(moveRight:)); 332 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 333 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]); 334 SimulateKeyAction(@selector(moveLeft:)); 335 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 336 EXPECT_EQ(3u, [apps_grid_controller_ selectedItemIndex]); 337 338 // Moving Down from the bottom does nothing. 339 [apps_grid_controller_ selectItemAtIndex:13]; 340 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 341 SimulateKeyAction(@selector(moveDown:)); 342 EXPECT_EQ(13u, [apps_grid_controller_ selectedItemIndex]); 343 344 // Moving Right into a non-existent square on the next page will select the 345 // last item. 346 [apps_grid_controller_ selectItemAtIndex:15]; 347 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 348 SimulateKeyAction(@selector(moveRight:)); 349 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 350 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]); 351 352 // PageDown and PageUp switches pages while maintaining the same selection 353 // position. 354 [apps_grid_controller_ selectItemAtIndex:6]; 355 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 356 SimulateKeyAction(@selector(scrollPageDown:)); 357 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 358 EXPECT_EQ(kItemsPerPage + 6, [apps_grid_controller_ selectedItemIndex]); 359 SimulateKeyAction(@selector(scrollPageUp:)); 360 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 361 EXPECT_EQ(6u, [apps_grid_controller_ selectedItemIndex]); 362 363 // PageDown into a non-existent square on the next page will select the last 364 // item. 365 [apps_grid_controller_ selectItemAtIndex:11]; 366 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 367 SimulateKeyAction(@selector(scrollPageDown:)); 368 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 369 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]); 370 371 // Moving Right, Down, or PageDown from the bottom-right corner of the last 372 // page (not the last item) does nothing. 373 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 9]; 374 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 375 SimulateKeyAction(@selector(moveRight:)); 376 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]); 377 SimulateKeyAction(@selector(moveDown:)); 378 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]); 379 SimulateKeyAction(@selector(scrollPageDown:)); 380 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]); 381 382 // After page switch, arrow keys select first item on current page. 383 [apps_grid_controller_ scrollToPage:0]; 384 [apps_grid_controller_ scrollToPage:1]; 385 EXPECT_EQ(NSNotFound, [apps_grid_controller_ selectedItemIndex]); 386 SimulateKeyAction(@selector(moveUp:)); 387 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]); 388} 389 390// Highlighting an item should cause the page it's on to be visible. 391TEST_F(AppsGridControllerTest, EnsureHighlightedVisible) { 392 model()->PopulateApps(3 * kItemsPerPage); 393 EXPECT_EQ(kItemsPerPage, [[GetPageAt(2) content] count]); 394 395 // First and last items of first page. 396 [apps_grid_controller_ selectItemAtIndex:0]; 397 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 398 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage - 1]; 399 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 400 401 // First item of second page. 402 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 1]; 403 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 404 405 // Last item in model. 406 [apps_grid_controller_ selectItemAtIndex:3 * kItemsPerPage - 1]; 407 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 408} 409 410// Test runtime updates: adding items, removing items, and moving items (e.g. in 411// response to app install, uninstall, and chrome sync changes. Also test 412// changing titles and icons. 413TEST_F(AppsGridControllerTest, ModelUpdates) { 414 model()->PopulateApps(2); 415 EXPECT_EQ(2u, [[GetPageAt(0) content] count]); 416 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent()); 417 418 // Add an item (PopulateApps will create a duplicate "Item 0"). 419 model()->PopulateApps(1); 420 EXPECT_EQ(3u, [[GetPageAt(0) content] count]); 421 NSButton* button = GetItemViewAt(2); 422 EXPECT_NSEQ(@"Item 0", [button title]); 423 EXPECT_EQ(std::string("|Item 0,Item 1,Item 0|"), GetViewContent()); 424 425 // Update the title via the ItemModelObserver. 426 app_list::AppListItemModel* item_model = model()->apps()->GetItemAt(2); 427 item_model->SetTitle("UpdatedItem"); 428 EXPECT_NSEQ(@"UpdatedItem", [button title]); 429 EXPECT_EQ(std::string("|Item 0,Item 1,UpdatedItem|"), GetViewContent()); 430 431 // Test icon updates through the model observer by ensuring the icon changes. 432 NSSize icon_size = [[button image] size]; 433 EXPECT_EQ(0, icon_size.width); 434 EXPECT_EQ(0, icon_size.height); 435 436 SkBitmap bitmap; 437 const int kTestImageSize = 10; 438 const int kTargetImageSize = 48; 439 bitmap.setConfig(SkBitmap::kARGB_8888_Config, kTestImageSize, kTestImageSize); 440 item_model->SetIcon(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), false); 441 icon_size = [[button image] size]; 442 // Icon should always be resized to 48x48. 443 EXPECT_EQ(kTargetImageSize, icon_size.width); 444 EXPECT_EQ(kTargetImageSize, icon_size.height); 445 446 // Test removing an item at the end. 447 model()->apps()->DeleteAt(2); 448 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]); 449 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent()); 450 451 // Test removing in the middle. 452 model()->AddItem("Item 2"); 453 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]); 454 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent()); 455 model()->apps()->DeleteAt(1); 456 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]); 457 EXPECT_EQ(std::string("|Item 0,Item 2|"), GetViewContent()); 458 459 // Test inserting in the middle. 460 model()->apps()->AddAt(1, model()->CreateItem("Item One")); 461 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]); 462 EXPECT_EQ(std::string("|Item 0,Item One,Item 2|"), GetViewContent()); 463 464 // Test swapping items (e.g. rearranging via sync). 465 model()->apps()->Move(1, 2); 466 EXPECT_EQ(std::string("|Item 0,Item 2,Item One|"), GetViewContent()); 467 468 // Test removing multiple items via the model. 469 model()->apps()->DeleteAll(); 470 EXPECT_EQ(0u, [apps_grid_controller_ itemCount]); 471 EXPECT_EQ(std::string("||"), GetViewContent()); 472 473 // Test removing the last item when there is one item on the second page. 474 ReplaceTestModel(kItemsPerPage + 1); 475 EXPECT_EQ(kItemsPerPage + 1, [apps_grid_controller_ itemCount]); 476 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]); 477 model()->apps()->DeleteAt(kItemsPerPage); 478 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ itemCount]); 479 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 480} 481 482// Test install progress bars, and install flow with item highlighting. 483TEST_F(AppsGridControllerTest, ItemInstallProgress) { 484 ReplaceTestModel(kItemsPerPage + 1); 485 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]); 486 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 487 app_list::AppListItemModel* item_model = 488 model()->apps()->GetItemAt(kItemsPerPage); 489 490 // Highlighting an item should activate the page it is on. 491 item_model->SetHighlighted(true); 492 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 493 494 // Clearing a highlight stays on the current page. 495 [apps_grid_controller_ scrollToPage:0]; 496 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 497 item_model->SetHighlighted(false); 498 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 499 500 // Starting install should add a progress bar, and temporarily clear the 501 // button title. 502 NSButton* button = GetItemViewAt(kItemsPerPage); 503 NSView* containerView = [button superview]; 504 EXPECT_EQ(1u, [[containerView subviews] count]); 505 EXPECT_NSEQ(@"Item 16", [button title]); 506 item_model->SetHighlighted(true); 507 item_model->SetIsInstalling(true); 508 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 509 510 EXPECT_EQ(2u, [[containerView subviews] count]); 511 EXPECT_NSEQ(@"", [button title]); 512 NSProgressIndicator* progressIndicator = 513 [[containerView subviews] objectAtIndex:1]; 514 EXPECT_FALSE([progressIndicator isIndeterminate]); 515 EXPECT_EQ(0.0, [progressIndicator doubleValue]); 516 517 // Updating the progress in the model should update the progress bar. 518 item_model->SetPercentDownloaded(50); 519 EXPECT_EQ(50.0, [progressIndicator doubleValue]); 520 521 // Two things can be installing simultaneously. When one starts or completes 522 // the model builder will ask for the item to be highlighted. 523 app_list::AppListItemModel* alternate_item_model = 524 model()->apps()->GetItemAt(0); 525 item_model->SetHighlighted(false); 526 alternate_item_model->SetHighlighted(true); 527 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 528 529 // Update the first item (page doesn't change on updates). 530 item_model->SetPercentDownloaded(100); 531 EXPECT_EQ(100.0, [progressIndicator doubleValue]); 532 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 533 534 // A percent of -1 indicates the download is complete and the unpack/install 535 // process has started. 536 item_model->SetPercentDownloaded(-1); 537 EXPECT_TRUE([progressIndicator isIndeterminate]); 538 539 // Completing install removes the progress bar, and restores the title. 540 // AppsModelBuilder will reload the ExtensionAppItem, which also highlights. 541 // Do the same here. 542 alternate_item_model->SetHighlighted(false); 543 item_model->SetHighlighted(true); 544 item_model->SetIsInstalling(false); 545 EXPECT_EQ(1u, [[containerView subviews] count]); 546 EXPECT_NSEQ(@"Item 16", [button title]); 547 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 548 549 // Things should cleanup OK with |alternate_item_model| left installing. 550} 551 552// Test mouseover selection. 553TEST_F(AppsGridControllerTest, MouseoverSelects) { 554 model()->PopulateApps(2); 555 EXPECT_EQ(nil, GetSelectedView()); 556 557 // Test entering and exiting the first item. 558 SimulateMouseEnterItemAt(0); 559 EXPECT_EQ(GetItemViewAt(0), GetSelectedView()); 560 SimulateMouseExitItemAt(0); 561 EXPECT_EQ(nil, GetSelectedView()); 562 563 // AppKit doesn't guarantee the order, so test moving between items. 564 SimulateMouseEnterItemAt(0); 565 EXPECT_EQ(GetItemViewAt(0), GetSelectedView()); 566 SimulateMouseEnterItemAt(1); 567 EXPECT_EQ(GetItemViewAt(1), GetSelectedView()); 568 SimulateMouseExitItemAt(0); 569 EXPECT_EQ(GetItemViewAt(1), GetSelectedView()); 570 SimulateMouseExitItemAt(1); 571 EXPECT_EQ(nil, GetSelectedView()); 572} 573 574// Test AppsGridPaginationObserver totalPagesChanged(). 575TEST_F(AppsGridControllerTest, PaginationObserverPagesChanged) { 576 base::scoped_nsobject<TestPaginationObserver> observer( 577 [[TestPaginationObserver alloc] init]); 578 [apps_grid_controller_ setPaginationObserver:observer]; 579 580 // Test totalPagesChanged. 581 model()->PopulateApps(kItemsPerPage); 582 EXPECT_EQ(0, [observer totalPagesChangedCount]); 583 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 584 model()->PopulateApps(1); 585 EXPECT_EQ(1, [observer totalPagesChangedCount]); 586 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]); 587 ReplaceTestModel(0); 588 EXPECT_EQ(2, [observer totalPagesChangedCount]); 589 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]); 590 ReplaceTestModel(kItemsPerPage * 3 + 1); 591 EXPECT_EQ(3, [observer totalPagesChangedCount]); 592 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]); 593 594 EXPECT_FALSE([observer readVisibilityDidChange]); 595 EXPECT_EQ(0, [observer selectedPageChangedCount]); 596 597 [apps_grid_controller_ setPaginationObserver:nil]; 598} 599 600// Test AppsGridPaginationObserver selectedPageChanged(). 601TEST_F(AppsGridControllerTest, PaginationObserverSelectedPageChanged) { 602 base::scoped_nsobject<TestPaginationObserver> observer( 603 [[TestPaginationObserver alloc] init]); 604 [apps_grid_controller_ setPaginationObserver:observer]; 605 EXPECT_EQ(0, [[NSAnimationContext currentContext] duration]); 606 607 ReplaceTestModel(kItemsPerPage * 3 + 1); 608 EXPECT_EQ(1, [observer totalPagesChangedCount]); 609 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]); 610 611 EXPECT_FALSE([observer readVisibilityDidChange]); 612 EXPECT_EQ(0, [observer selectedPageChangedCount]); 613 614 [apps_grid_controller_ scrollToPage:1]; 615 EXPECT_EQ(1, [observer selectedPageChangedCount]); 616 EXPECT_EQ(1, [observer lastNewSelectedPage]); 617 EXPECT_TRUE([observer readVisibilityDidChange]); 618 EXPECT_FALSE([observer readVisibilityDidChange]); // Testing test behaviour. 619 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]); 620 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:1]); 621 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:2]); 622 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:3]); 623 624 [apps_grid_controller_ scrollToPage:0]; 625 EXPECT_EQ(2, [observer selectedPageChangedCount]); 626 EXPECT_EQ(0, [observer lastNewSelectedPage]); 627 EXPECT_TRUE([observer readVisibilityDidChange]); 628 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:0]); 629 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:1]); 630 631 [apps_grid_controller_ scrollToPage:3]; 632 // Note: with no animations, there is only a single page change. However, with 633 // animations we expect multiple updates depending on the rate that the scroll 634 // view updates and sends out NSViewBoundsDidChangeNotification. 635 EXPECT_EQ(3, [observer selectedPageChangedCount]); 636 EXPECT_EQ(3, [observer lastNewSelectedPage]); 637 EXPECT_TRUE([observer readVisibilityDidChange]); 638 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]); 639 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:3]); 640 641 [apps_grid_controller_ setPaginationObserver:nil]; 642} 643 644// Test basic item moves with two items; swapping them around, dragging outside 645// of the view bounds, and dragging on the background. 646TEST_F(AppsGridControllerTest, DragAndDropSimple) { 647 model()->PopulateApps(2); 648 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0]; 649 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0); 650 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1); 651 NSEvent* mouse_at_page_centre = MouseEventInCell(page, 6); 652 NSEvent* mouse_off_page = MouseEventInCell(page, kItemsPerPage * 2); 653 654 const std::string kOrdered = "Item 0,Item 1"; 655 const std::string kSwapped = "Item 1,Item 0"; 656 const std::string kOrderedView = "|Item 0,Item 1|"; 657 const std::string kSwappedView = "|Item 1,Item 0|"; 658 659 EXPECT_EQ(kOrdered, model()->GetModelContent()); 660 EXPECT_EQ(kOrderedView, GetViewContent()); 661 AppsCollectionViewDragManager* drag_manager = 662 [apps_grid_controller_ dragManager]; 663 664 // Drag first item over the second item and release. 665 [drag_manager onMouseDownInPage:page 666 withEvent:mouse_at_cell_0]; 667 [drag_manager onMouseDragged:mouse_at_cell_1]; 668 EXPECT_EQ(kOrdered, model()->GetModelContent()); 669 EXPECT_EQ(kSwappedView, GetViewContent()); // View swaps first. 670 [drag_manager onMouseUp:mouse_at_cell_1]; 671 EXPECT_EQ(kSwapped, model()->GetModelContent()); 672 EXPECT_EQ(kSwappedView, GetViewContent()); 673 674 // Drag item back. 675 [drag_manager onMouseDownInPage:page 676 withEvent:mouse_at_cell_1]; 677 [drag_manager onMouseDragged:mouse_at_cell_0]; 678 EXPECT_EQ(kSwapped, model()->GetModelContent()); 679 EXPECT_EQ(kOrderedView, GetViewContent()); 680 [drag_manager onMouseUp:mouse_at_cell_0]; 681 EXPECT_EQ(kOrdered, model()->GetModelContent()); 682 EXPECT_EQ(kOrderedView, GetViewContent()); 683 684 // Drag first item to centre of view (should put in last place). 685 [drag_manager onMouseDownInPage:page 686 withEvent:mouse_at_cell_0]; 687 [drag_manager onMouseDragged:mouse_at_page_centre]; 688 EXPECT_EQ(kOrdered, model()->GetModelContent()); 689 EXPECT_EQ(kSwappedView, GetViewContent()); 690 [drag_manager onMouseUp:mouse_at_page_centre]; 691 EXPECT_EQ(kSwapped, model()->GetModelContent()); 692 EXPECT_EQ(kSwappedView, GetViewContent()); 693 694 // Drag item to centre again (should leave it in the last place). 695 [drag_manager onMouseDownInPage:page 696 withEvent:mouse_at_cell_1]; 697 [drag_manager onMouseDragged:mouse_at_page_centre]; 698 EXPECT_EQ(kSwapped, model()->GetModelContent()); 699 EXPECT_EQ(kSwappedView, GetViewContent()); 700 [drag_manager onMouseUp:mouse_at_page_centre]; 701 EXPECT_EQ(kSwapped, model()->GetModelContent()); 702 EXPECT_EQ(kSwappedView, GetViewContent()); 703 704 // Drag starting in the centre of the view, should do nothing. 705 [drag_manager onMouseDownInPage:page 706 withEvent:mouse_at_page_centre]; 707 [drag_manager onMouseDragged:mouse_at_cell_0]; 708 EXPECT_EQ(kSwapped, model()->GetModelContent()); 709 EXPECT_EQ(kSwappedView, GetViewContent()); 710 [drag_manager onMouseUp:mouse_at_cell_0]; 711 EXPECT_EQ(kSwapped, model()->GetModelContent()); 712 EXPECT_EQ(kSwappedView, GetViewContent()); 713 714 // Click off page. 715 [drag_manager onMouseDownInPage:page 716 withEvent:mouse_off_page]; 717 [drag_manager onMouseDragged:mouse_at_cell_0]; 718 EXPECT_EQ(kSwapped, model()->GetModelContent()); 719 EXPECT_EQ(kSwappedView, GetViewContent()); 720 [drag_manager onMouseUp:mouse_at_cell_0]; 721 EXPECT_EQ(kSwapped, model()->GetModelContent()); 722 EXPECT_EQ(kSwappedView, GetViewContent()); 723 724 // Drag to first over second item, then off page. 725 [drag_manager onMouseDownInPage:page 726 withEvent:mouse_at_cell_0]; 727 [drag_manager onMouseDragged:mouse_at_cell_1]; 728 EXPECT_EQ(kSwapped, model()->GetModelContent()); 729 EXPECT_EQ(kOrderedView, GetViewContent()); 730 [drag_manager onMouseDragged:mouse_off_page]; 731 EXPECT_EQ(kSwapped, model()->GetModelContent()); 732 EXPECT_EQ(kOrderedView, GetViewContent()); 733 [drag_manager onMouseUp:mouse_off_page]; 734 EXPECT_EQ(kOrdered, model()->GetModelContent()); 735 EXPECT_EQ(kOrderedView, GetViewContent()); 736 737 // Replace with an empty model, and ensure we do not break. 738 ReplaceTestModel(0); 739 EXPECT_EQ(std::string(), model()->GetModelContent()); 740 EXPECT_EQ(std::string("||"), GetViewContent()); 741 [drag_manager onMouseDownInPage:page 742 withEvent:mouse_at_cell_0]; 743 [drag_manager onMouseDragged:mouse_at_cell_1]; 744 [drag_manager onMouseUp:mouse_at_cell_1]; 745 EXPECT_EQ(std::string(), model()->GetModelContent()); 746 EXPECT_EQ(std::string("||"), GetViewContent()); 747} 748 749// Test item moves between pages. 750TEST_F(AppsGridControllerTest, DragAndDropMultiPage) { 751 const size_t kPagesToTest = 3; 752 // Put one item on the last page to hit more edge cases. 753 ReplaceTestModel(kItemsPerPage * (kPagesToTest - 1) + 1); 754 NSCollectionView* page[kPagesToTest]; 755 for (size_t i = 0; i < kPagesToTest; ++i) 756 page[i] = [apps_grid_controller_ collectionViewAtPageIndex:i]; 757 758 const std::string kSecondItemMovedToSecondPage = 759 "|Item 0,Item 2,Item 3,Item 4,Item 5,Item 6,Item 7,Item 8," 760 "Item 9,Item 10,Item 11,Item 12,Item 13,Item 14,Item 15,Item 16|" 761 "|Item 17,Item 1,Item 18,Item 19,Item 20,Item 21,Item 22,Item 23," 762 "Item 24,Item 25,Item 26,Item 27,Item 28,Item 29,Item 30,Item 31|" 763 "|Item 32|"; 764 765 NSEvent* mouse_at_cell_0 = MouseEventInCell(page[0], 0); 766 NSEvent* mouse_at_cell_1 = MouseEventInCell(page[0], 1); 767 AppsCollectionViewDragManager* drag_manager = 768 [apps_grid_controller_ dragManager]; 769 [drag_manager onMouseDownInPage:page[0] 770 withEvent:mouse_at_cell_1]; 771 772 // Initiate dragging before changing pages. 773 [drag_manager onMouseDragged:mouse_at_cell_0]; 774 775 // Scroll to the second page. 776 [apps_grid_controller_ scrollToPage:1]; 777 [drag_manager onMouseDragged:mouse_at_cell_1]; 778 779 // Do one exhaustive check, and then spot-check corner cases. 780 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent()); 781 EXPECT_EQ(0u, GetPageIndexForItem(0)); 782 EXPECT_EQ(1u, GetPageIndexForItem(1)); 783 EXPECT_EQ(0u, GetPageIndexForItem(2)); 784 EXPECT_EQ(0u, GetPageIndexForItem(16)); 785 EXPECT_EQ(1u, GetPageIndexForItem(17)); 786 EXPECT_EQ(1u, GetPageIndexForItem(31)); 787 EXPECT_EQ(2u, GetPageIndexForItem(32)); 788 789 // Scroll to the third page and drag some more. 790 [apps_grid_controller_ scrollToPage:2]; 791 [drag_manager onMouseDragged:mouse_at_cell_1]; 792 EXPECT_EQ(2u, GetPageIndexForItem(1)); 793 EXPECT_EQ(1u, GetPageIndexForItem(31)); 794 EXPECT_EQ(1u, GetPageIndexForItem(32)); 795 796 // Scroll backwards. 797 [apps_grid_controller_ scrollToPage:1]; 798 [drag_manager onMouseDragged:mouse_at_cell_1]; 799 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent()); 800 EXPECT_EQ(1u, GetPageIndexForItem(1)); 801 EXPECT_EQ(1u, GetPageIndexForItem(31)); 802 EXPECT_EQ(2u, GetPageIndexForItem(32)); 803 804 // Simulate installing an item while dragging (or have it appear during sync). 805 model()->PopulateAppWithId(33); 806 // Item should go back to its position before the drag. 807 EXPECT_EQ(0u, GetPageIndexForItem(1)); 808 EXPECT_EQ(1u, GetPageIndexForItem(31)); 809 EXPECT_EQ(2u, GetPageIndexForItem(32)); 810 // New item should appear at end. 811 EXPECT_EQ(2u, GetPageIndexForItem(33)); 812 813 // Scroll to end again, and keep dragging (should be ignored). 814 [apps_grid_controller_ scrollToPage:2]; 815 [drag_manager onMouseDragged:mouse_at_cell_0]; 816 EXPECT_EQ(0u, GetPageIndexForItem(1)); 817 [drag_manager onMouseUp:mouse_at_cell_0]; 818 EXPECT_EQ(0u, GetPageIndexForItem(1)); 819} 820 821// Test scrolling when dragging past edge or over the pager. 822TEST_F(AppsGridControllerTest, ScrollingWhileDragging) { 823 base::scoped_nsobject<TestPaginationObserver> observer( 824 [[TestPaginationObserver alloc] init]); 825 [apps_grid_controller_ setPaginationObserver:observer]; 826 827 ReplaceTestModel(kItemsPerPage * 3); 828 // Start on the middle page. 829 [apps_grid_controller_ scrollToPage:1]; 830 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:1]; 831 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0); 832 833 NSEvent* at_center = MouseEventForScroll([apps_grid_controller_ view], 0.0); 834 NSEvent* at_left = MouseEventForScroll([apps_grid_controller_ view], -1.1); 835 NSEvent* at_right = MouseEventForScroll([apps_grid_controller_ view], 1.1); 836 837 AppsCollectionViewDragManager* drag_manager = 838 [apps_grid_controller_ dragManager]; 839 [drag_manager onMouseDownInPage:page 840 withEvent:mouse_at_cell_0]; 841 [drag_manager onMouseDragged:at_center]; 842 843 // Nothing should be scheduled: target page is visible page. 844 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 845 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]); 846 847 // Drag to the left, should go to first page and no further. 848 [drag_manager onMouseDragged:at_left]; 849 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 850 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]); 851 [apps_grid_controller_ scrollToPage:0]; // Commit without timer for testing. 852 [drag_manager onMouseDragged:at_left]; 853 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 854 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]); 855 856 // Drag to the right, should go to last page and no futher. 857 [drag_manager onMouseDragged:at_right]; 858 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]); 859 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]); 860 [apps_grid_controller_ scrollToPage:1]; 861 [drag_manager onMouseDragged:at_right]; 862 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]); 863 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]); 864 [apps_grid_controller_ scrollToPage:2]; 865 [drag_manager onMouseDragged:at_right]; 866 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 867 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]); 868 869 // Simulate a hover over the first pager segment. 870 [observer setHoveredSegmentForTest:0]; 871 [drag_manager onMouseDragged:at_center]; 872 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 873 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]); 874 875 // Drag it back, should cancel schedule. 876 [observer setHoveredSegmentForTest:-1]; 877 [drag_manager onMouseDragged:at_center]; 878 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 879 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]); 880 881 // Hover again, now over middle segment, and ensure a release also cancels. 882 [observer setHoveredSegmentForTest:1]; 883 [drag_manager onMouseDragged:at_center]; 884 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 885 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]); 886 [drag_manager onMouseUp:at_center]; 887 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]); 888 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]); 889 890 [apps_grid_controller_ setPaginationObserver:nil]; 891} 892 893TEST_F(AppsGridControllerTest, ContextMenus) { 894 model()->apps()->AddAt(0, new AppListItemWithMenu("Item One")); 895 model()->apps()->AddAt(1, new AppListItemWithMenu("Item Two")); 896 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]); 897 898 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0]; 899 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0); 900 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1); 901 902 NSMenu* menu = [page menuForEvent:mouse_at_cell_0]; 903 EXPECT_EQ(1, [menu numberOfItems]); 904 EXPECT_NSEQ(@"Menu For: Item One", [[menu itemAtIndex:0] title]); 905 906 menu = [page menuForEvent:mouse_at_cell_1]; 907 EXPECT_EQ(1, [menu numberOfItems]); 908 EXPECT_NSEQ(@"Menu For: Item Two", [[menu itemAtIndex:0] title]); 909} 910 911} // namespace test 912} // namespace app_list 913