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