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.
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"
22 @interface TestPaginationObserver : NSObject<AppsPaginationModelObserver> {
24 NSInteger hoveredSegmentForTest_;
25 int totalPagesChangedCount_;
26 int selectedPageChangedCount_;
27 int lastNewSelectedPage_;
28 bool visibilityDidChange_;
31 @property(assign, nonatomic) NSInteger hoveredSegmentForTest;
32 @property(assign, nonatomic) int totalPagesChangedCount;
33 @property(assign, nonatomic) int selectedPageChangedCount;
34 @property(assign, nonatomic) int lastNewSelectedPage;
36 - (bool)readVisibilityDidChange;
40 @implementation TestPaginationObserver
42 @synthesize hoveredSegmentForTest = hoveredSegmentForTest_;
43 @synthesize totalPagesChangedCount = totalPagesChangedCount_;
44 @synthesize selectedPageChangedCount = selectedPageChangedCount_;
45 @synthesize lastNewSelectedPage = lastNewSelectedPage_;
48 if ((self = [super init]))
49 hoveredSegmentForTest_ = -1;
54 - (bool)readVisibilityDidChange {
55 bool truth = visibilityDidChange_;
56 visibilityDidChange_ = false;
60 - (void)totalPagesChanged {
61 ++totalPagesChangedCount_;
64 - (void)selectedPageChanged:(int)newSelected {
65 ++selectedPageChangedCount_;
66 lastNewSelectedPage_ = newSelected;
69 - (void)pageVisibilityChanged {
70 visibilityDidChange_ = true;
73 - (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow {
74 return hoveredSegmentForTest_;
84 class AppsGridControllerTest : public AppsGridControllerTestHelper {
86 AppsGridControllerTest() {}
88 AppListTestViewDelegate* delegate() {
89 return owned_delegate_.get();
92 NSColor* ButtonTitleColorAt(size_t index) {
93 NSDictionary* attributes =
94 [[[GetItemViewAt(index) cell] attributedTitle] attributesAtIndex:0
96 return [attributes objectForKey:NSForegroundColorAttributeName];
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());
106 [[test_window() contentView] addSubview:[apps_grid_controller_ view]];
107 [test_window() makePretendKeyWindowAndSetFirstResponder:
108 [apps_grid_controller_ collectionViewAtPageIndex:0]];
111 virtual void TearDown() OVERRIDE {
112 owned_apps_grid_controller_.reset();
113 AppsGridControllerTestHelper::TearDown();
117 base::scoped_nsobject<AppsGridController> owned_apps_grid_controller_;
118 scoped_ptr<AppListTestViewDelegate> owned_delegate_;
120 DISALLOW_COPY_AND_ASSIGN(AppsGridControllerTest);
123 class AppListItemWithMenu : public AppListItemModel {
125 explicit AppListItemWithMenu(const std::string& title)
126 : AppListItemModel(title),
129 SetTitleAndFullName(title, title);
130 menu_model_.AddItem(0, UTF8ToUTF16("Menu For: " + title));
133 void SetMenuReadyForTesting(bool ready) {
137 virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
145 ui::SimpleMenuModel menu_model_;
148 DISALLOW_COPY_AND_ASSIGN(AppListItemWithMenu);
151 // Generate a mouse event at the centre of the view in |page| with the given
152 // |index_in_page| that can be used to initiate, update and complete drag
154 NSEvent* MouseEventInCell(NSCollectionView* page, size_t index_in_page) {
155 NSRect cell_rect = [page frameForItemAtIndex:index_in_page];
156 NSPoint point_in_view = NSMakePoint(NSMidX(cell_rect), NSMidY(cell_rect));
157 NSPoint point_in_window = [page convertPoint:point_in_view
159 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
162 NSEvent* MouseEventForScroll(NSView* view, CGFloat relative_x) {
163 NSRect view_rect = [view frame];
164 NSPoint point_in_view = NSMakePoint(NSMidX(view_rect), NSMidY(view_rect));
165 point_in_view.x += point_in_view.x * relative_x;
166 NSPoint point_in_window = [view convertPoint:point_in_view
168 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
173 TEST_VIEW(AppsGridControllerTest, [apps_grid_controller_ view]);
175 // Test showing with an empty model.
176 TEST_F(AppsGridControllerTest, EmptyModelAndShow) {
177 EXPECT_TRUE([[apps_grid_controller_ view] superview]);
179 // First page should always exist, even if empty.
180 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
181 EXPECT_EQ(0u, [[GetPageAt(0) content] count]);
182 EXPECT_TRUE([GetPageAt(0) superview]); // The pages container.
183 EXPECT_TRUE([[GetPageAt(0) superview] superview]);
186 // Test with a single item.
187 // This test is disabled in builders until the delay to wait for the collection
188 // view to load subviews can be removed, or some other solution is found.
189 TEST_F(AppsGridControllerTest, DISABLED_SingleEntryModel) {
190 // We need to "wake up" the NSCollectionView, otherwise it does not
191 // immediately update its subviews later in this function.
192 // When this test is run by itself, it's enough just to send a keypress (and
193 // this delay is not needed).
194 DelayForCollectionView();
195 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
196 EXPECT_EQ(0u, [[GetPageAt(0) content] count]);
198 model()->PopulateApps(1);
200 EXPECT_FALSE([GetPageAt(0) animations]);
202 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
203 NSArray* subviews = [GetPageAt(0) subviews];
204 EXPECT_EQ(1u, [subviews count]);
206 // Note that using GetItemViewAt(0) here also works, and returns non-nil even
207 // without the delay, but a "click" on it does not register without the delay.
208 NSView* subview = [subviews objectAtIndex:0];
211 SimulateClick(subview);
213 EXPECT_EQ(1, model()->activate_count());
214 ASSERT_TRUE(model()->last_activated());
215 EXPECT_EQ(std::string("Item 0"), model()->last_activated()->title());
218 // Test activating an item on the second page (the 17th item).
219 TEST_F(AppsGridControllerTest, DISABLED_TwoPageModel) {
220 DelayForCollectionView();
221 ReplaceTestModel(kItemsPerPage * 2);
222 [apps_grid_controller_ scrollToPage:1];
224 // The NSScrollView animator ignores the duration configured on the
225 // NSAnimationContext (set by CocoaTest::Init), so we need to delay here.
226 DelayForCollectionView();
227 NSArray* subviews = [GetPageAt(1) subviews];
228 NSView* subview = [subviews objectAtIndex:0];
230 SimulateClick(subview);
232 EXPECT_EQ(1, model()->activate_count());
233 ASSERT_TRUE(model()->last_activated());
234 EXPECT_EQ(std::string("Item 16"), model()->last_activated()->title());
238 TEST_F(AppsGridControllerTest, ReplaceModel) {
239 const size_t kOrigItems = 1;
240 const size_t kNewItems = 2;
242 model()->PopulateApps(kOrigItems);
243 EXPECT_EQ(kOrigItems, [[GetPageAt(0) content] count]);
245 ReplaceTestModel(kNewItems);
246 EXPECT_EQ(kNewItems, [[GetPageAt(0) content] count]);
250 TEST_F(AppsGridControllerTest, Pagination) {
251 model()->PopulateApps(1);
252 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
253 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
255 ReplaceTestModel(kItemsPerPage);
256 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
257 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
259 // Test adding an item onto the next page.
260 model()->PopulateApps(1); // Now 17 items.
261 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
262 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
263 EXPECT_EQ(1u, [[GetPageAt(1) content] count]);
265 // Test N pages with the last page having one empty spot.
266 const size_t kPagesToTest = 3;
267 ReplaceTestModel(kPagesToTest * kItemsPerPage - 1);
268 EXPECT_EQ(kPagesToTest, [apps_grid_controller_ pageCount]);
269 for (size_t page_index = 0; page_index < kPagesToTest - 1; ++page_index) {
270 EXPECT_EQ(kItemsPerPage, [[GetPageAt(page_index) content] count]);
272 EXPECT_EQ(kItemsPerPage - 1, [[GetPageAt(kPagesToTest - 1) content] count]);
274 // Test removing pages.
276 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
277 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
280 // Tests that selecting an item changes the text color correctly.
281 TEST_F(AppsGridControllerTest, SelectionChangesTextColor) {
282 model()->PopulateApps(2);
283 [apps_grid_controller_ selectItemAtIndex:0];
284 EXPECT_NSEQ(ButtonTitleColorAt(0),
285 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor));
286 EXPECT_NSEQ(ButtonTitleColorAt(1),
287 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor));
289 [apps_grid_controller_ selectItemAtIndex:1];
290 EXPECT_NSEQ(ButtonTitleColorAt(0),
291 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor));
292 EXPECT_NSEQ(ButtonTitleColorAt(1),
293 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor));
296 // Tests basic keyboard navigation on the first page.
297 TEST_F(AppsGridControllerTest, FirstPageKeyboardNavigation) {
298 model()->PopulateApps(kItemsPerPage - 2);
299 EXPECT_EQ(kItemsPerPage - 2, [[GetPageAt(0) content] count]);
301 SimulateKeyAction(@selector(moveRight:));
302 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
304 SimulateKeyAction(@selector(moveRight:));
305 EXPECT_EQ(1u, [apps_grid_controller_ selectedItemIndex]);
307 SimulateKeyAction(@selector(moveDown:));
308 EXPECT_EQ(5u, [apps_grid_controller_ selectedItemIndex]);
310 SimulateKeyAction(@selector(moveLeft:));
311 EXPECT_EQ(4u, [apps_grid_controller_ selectedItemIndex]);
313 SimulateKeyAction(@selector(moveUp:));
314 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
316 // Go to the third item, and launch it.
317 SimulateKeyAction(@selector(moveRight:));
318 SimulateKeyAction(@selector(moveRight:));
319 EXPECT_EQ(2u, [apps_grid_controller_ selectedItemIndex]);
320 SimulateKeyAction(@selector(insertNewline:));
321 EXPECT_EQ(1, model()->activate_count());
322 ASSERT_TRUE(model()->last_activated());
323 EXPECT_EQ(std::string("Item 2"), model()->last_activated()->title());
326 // Tests keyboard navigation across pages.
327 TEST_F(AppsGridControllerTest, CrossPageKeyboardNavigation) {
328 model()->PopulateApps(kItemsPerPage + 10);
329 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
330 EXPECT_EQ(10u, [[GetPageAt(1) content] count]);
332 // Moving Left, Up, or PageUp from the top-left corner of the first page does
334 [apps_grid_controller_ selectItemAtIndex:0];
335 SimulateKeyAction(@selector(moveLeft:));
336 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
337 SimulateKeyAction(@selector(moveUp:));
338 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
339 SimulateKeyAction(@selector(scrollPageUp:));
340 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
342 // Moving Right from the right side goes to the next page. Moving Left goes
343 // back to the first page.
344 [apps_grid_controller_ selectItemAtIndex:3];
345 SimulateKeyAction(@selector(moveRight:));
346 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
347 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]);
348 SimulateKeyAction(@selector(moveLeft:));
349 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
350 EXPECT_EQ(3u, [apps_grid_controller_ selectedItemIndex]);
352 // Moving Down from the bottom does nothing.
353 [apps_grid_controller_ selectItemAtIndex:13];
354 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
355 SimulateKeyAction(@selector(moveDown:));
356 EXPECT_EQ(13u, [apps_grid_controller_ selectedItemIndex]);
358 // Moving Right into a non-existent square on the next page will select the
360 [apps_grid_controller_ selectItemAtIndex:15];
361 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
362 SimulateKeyAction(@selector(moveRight:));
363 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
364 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
366 // PageDown and PageUp switches pages while maintaining the same selection
368 [apps_grid_controller_ selectItemAtIndex:6];
369 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
370 SimulateKeyAction(@selector(scrollPageDown:));
371 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
372 EXPECT_EQ(kItemsPerPage + 6, [apps_grid_controller_ selectedItemIndex]);
373 SimulateKeyAction(@selector(scrollPageUp:));
374 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
375 EXPECT_EQ(6u, [apps_grid_controller_ selectedItemIndex]);
377 // PageDown into a non-existent square on the next page will select the last
379 [apps_grid_controller_ selectItemAtIndex:11];
380 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
381 SimulateKeyAction(@selector(scrollPageDown:));
382 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
383 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
385 // Moving Right, Down, or PageDown from the bottom-right corner of the last
386 // page (not the last item) does nothing.
387 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 9];
388 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
389 SimulateKeyAction(@selector(moveRight:));
390 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
391 SimulateKeyAction(@selector(moveDown:));
392 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
393 SimulateKeyAction(@selector(scrollPageDown:));
394 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
396 // After page switch, arrow keys select first item on current page.
397 [apps_grid_controller_ scrollToPage:0];
398 [apps_grid_controller_ scrollToPage:1];
399 EXPECT_EQ(NSNotFound, [apps_grid_controller_ selectedItemIndex]);
400 SimulateKeyAction(@selector(moveUp:));
401 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]);
404 // Highlighting an item should cause the page it's on to be visible.
405 TEST_F(AppsGridControllerTest, EnsureHighlightedVisible) {
406 model()->PopulateApps(3 * kItemsPerPage);
407 EXPECT_EQ(kItemsPerPage, [[GetPageAt(2) content] count]);
409 // First and last items of first page.
410 [apps_grid_controller_ selectItemAtIndex:0];
411 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
412 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage - 1];
413 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
415 // First item of second page.
416 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 1];
417 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
419 // Last item in model.
420 [apps_grid_controller_ selectItemAtIndex:3 * kItemsPerPage - 1];
421 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
424 // Test runtime updates: adding items, removing items, and moving items (e.g. in
425 // response to app install, uninstall, and chrome sync changes. Also test
426 // changing titles and icons.
427 TEST_F(AppsGridControllerTest, ModelUpdate) {
428 model()->PopulateApps(2);
429 EXPECT_EQ(2u, [[GetPageAt(0) content] count]);
430 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
432 // Add an item (PopulateApps will create a new "Item 2").
433 model()->PopulateApps(1);
434 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
435 NSButton* button = GetItemViewAt(2);
436 EXPECT_NSEQ(@"Item 2", [button title]);
437 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
439 // Update the title via the ItemModelObserver.
440 app_list::AppListItemModel* item_model = model()->item_list()->item_at(2);
441 item_model->SetTitleAndFullName("UpdatedItem", "UpdatedItem");
442 EXPECT_NSEQ(@"UpdatedItem", [button title]);
443 EXPECT_EQ(std::string("|Item 0,Item 1,UpdatedItem|"), GetViewContent());
445 // Test icon updates through the model observer by ensuring the icon changes.
446 NSSize icon_size = [[button image] size];
447 EXPECT_EQ(0, icon_size.width);
448 EXPECT_EQ(0, icon_size.height);
451 const int kTestImageSize = 10;
452 const int kTargetImageSize = 48;
453 bitmap.setConfig(SkBitmap::kARGB_8888_Config, kTestImageSize, kTestImageSize);
454 item_model->SetIcon(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), false);
455 icon_size = [[button image] size];
456 // Icon should always be resized to 48x48.
457 EXPECT_EQ(kTargetImageSize, icon_size.width);
458 EXPECT_EQ(kTargetImageSize, icon_size.height);
461 TEST_F(AppsGridControllerTest, ModelAdd) {
462 model()->PopulateApps(2);
463 EXPECT_EQ(2u, [[GetPageAt(0) content] count]);
464 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
466 app_list::AppListItemList* item_list = model()->item_list();
468 model()->CreateAndAddItem("Item 2");
469 ASSERT_EQ(3u, item_list->item_count());
470 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]);
471 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
473 // Test adding an item whose position is in the middle.
474 app_list::AppListItemModel* item0 = item_list->item_at(0);
475 app_list::AppListItemModel* item1 = item_list->item_at(1);
476 app_list::AppListItemModel* item3 =
477 model()->CreateItem("Item Three", "Item Three");
478 item3->set_position(item0->position().CreateBetween(item1->position()));
479 item_list->AddItem(item3);
480 EXPECT_EQ(4u, [apps_grid_controller_ itemCount]);
481 EXPECT_EQ(std::string("|Item 0,Item Three,Item 1,Item 2|"), GetViewContent());
484 TEST_F(AppsGridControllerTest, ModelMove) {
485 model()->PopulateApps(3);
486 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
487 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
489 // Test swapping items (e.g. rearranging via sync).
490 model()->item_list()->MoveItem(1, 2);
491 EXPECT_EQ(std::string("|Item 0,Item 2,Item 1|"), GetViewContent());
494 TEST_F(AppsGridControllerTest, ModelRemove) {
495 model()->PopulateApps(3);
496 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
497 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
499 // Test removing an item at the end.
500 model()->item_list()->DeleteItem("Item 2");
501 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
502 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
504 // Test removing in the middle.
505 model()->CreateAndAddItem("Item 2");
506 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]);
507 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
508 model()->item_list()->DeleteItem("Item 1");
509 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
510 EXPECT_EQ(std::string("|Item 0,Item 2|"), GetViewContent());
513 TEST_F(AppsGridControllerTest, ModelRemoveAlll) {
514 model()->PopulateApps(3);
515 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
516 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
518 // Test removing multiple items via the model.
519 model()->item_list()->DeleteItemsByType(NULL /* all items */);
520 EXPECT_EQ(0u, [apps_grid_controller_ itemCount]);
521 EXPECT_EQ(std::string("||"), GetViewContent());
524 TEST_F(AppsGridControllerTest, ModelRemovePage) {
525 app_list::AppListItemList* item_list = model()->item_list();
527 model()->PopulateApps(kItemsPerPage + 1);
528 ASSERT_EQ(kItemsPerPage + 1, item_list->item_count());
529 EXPECT_EQ(kItemsPerPage + 1, [apps_grid_controller_ itemCount]);
530 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
532 // Test removing the last item when there is one item on the second page.
533 app_list::AppListItemModel* last_item = item_list->item_at(kItemsPerPage);
534 item_list->DeleteItem(last_item->id());
535 EXPECT_EQ(kItemsPerPage, item_list->item_count());
536 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ itemCount]);
537 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
540 // Test install progress bars, and install flow with item highlighting.
541 TEST_F(AppsGridControllerTest, ItemInstallProgress) {
542 ReplaceTestModel(kItemsPerPage + 1);
543 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
544 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
545 app_list::AppListItemModel* item_model =
546 model()->item_list()->item_at(kItemsPerPage);
548 // Highlighting an item should activate the page it is on.
549 item_model->SetHighlighted(true);
550 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
552 // Clearing a highlight stays on the current page.
553 [apps_grid_controller_ scrollToPage:0];
554 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
555 item_model->SetHighlighted(false);
556 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
558 // Starting install should add a progress bar, and temporarily clear the
560 NSButton* button = GetItemViewAt(kItemsPerPage);
561 NSView* containerView = [button superview];
562 EXPECT_EQ(1u, [[containerView subviews] count]);
563 EXPECT_NSEQ(@"Item 16", [button title]);
564 item_model->SetHighlighted(true);
565 item_model->SetIsInstalling(true);
566 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
568 EXPECT_EQ(2u, [[containerView subviews] count]);
569 EXPECT_NSEQ(@"", [button title]);
570 NSProgressIndicator* progressIndicator =
571 [[containerView subviews] objectAtIndex:1];
572 EXPECT_FALSE([progressIndicator isIndeterminate]);
573 EXPECT_EQ(0.0, [progressIndicator doubleValue]);
575 // Updating the progress in the model should update the progress bar.
576 item_model->SetPercentDownloaded(50);
577 EXPECT_EQ(50.0, [progressIndicator doubleValue]);
579 // Two things can be installing simultaneously. When one starts or completes
580 // the model builder will ask for the item to be highlighted.
581 app_list::AppListItemModel* alternate_item_model =
582 model()->item_list()->item_at(0);
583 item_model->SetHighlighted(false);
584 alternate_item_model->SetHighlighted(true);
585 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
587 // Update the first item (page doesn't change on updates).
588 item_model->SetPercentDownloaded(100);
589 EXPECT_EQ(100.0, [progressIndicator doubleValue]);
590 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
592 // A percent of -1 indicates the download is complete and the unpack/install
593 // process has started.
594 item_model->SetPercentDownloaded(-1);
595 EXPECT_TRUE([progressIndicator isIndeterminate]);
597 // Completing install removes the progress bar, and restores the title.
598 // ExtensionAppModelBuilder will reload the ExtensionAppItem, which also
599 // highlights. Do the same here.
600 alternate_item_model->SetHighlighted(false);
601 item_model->SetHighlighted(true);
602 item_model->SetIsInstalling(false);
603 EXPECT_EQ(1u, [[containerView subviews] count]);
604 EXPECT_NSEQ(@"Item 16", [button title]);
605 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
607 // Things should cleanup OK with |alternate_item_model| left installing.
610 // Test mouseover selection.
611 TEST_F(AppsGridControllerTest, MouseoverSelects) {
612 model()->PopulateApps(2);
613 EXPECT_EQ(nil, GetSelectedView());
615 // Test entering and exiting the first item.
616 SimulateMouseEnterItemAt(0);
617 EXPECT_EQ(GetItemViewAt(0), GetSelectedView());
618 SimulateMouseExitItemAt(0);
619 EXPECT_EQ(nil, GetSelectedView());
621 // AppKit doesn't guarantee the order, so test moving between items.
622 SimulateMouseEnterItemAt(0);
623 EXPECT_EQ(GetItemViewAt(0), GetSelectedView());
624 SimulateMouseEnterItemAt(1);
625 EXPECT_EQ(GetItemViewAt(1), GetSelectedView());
626 SimulateMouseExitItemAt(0);
627 EXPECT_EQ(GetItemViewAt(1), GetSelectedView());
628 SimulateMouseExitItemAt(1);
629 EXPECT_EQ(nil, GetSelectedView());
632 // Test AppsGridPaginationObserver totalPagesChanged().
633 TEST_F(AppsGridControllerTest, PaginationObserverPagesChanged) {
634 base::scoped_nsobject<TestPaginationObserver> observer(
635 [[TestPaginationObserver alloc] init]);
636 [apps_grid_controller_ setPaginationObserver:observer];
638 // Test totalPagesChanged.
639 model()->PopulateApps(kItemsPerPage);
640 EXPECT_EQ(0, [observer totalPagesChangedCount]);
641 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
642 model()->PopulateApps(1);
643 EXPECT_EQ(1, [observer totalPagesChangedCount]);
644 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
646 EXPECT_EQ(2, [observer totalPagesChangedCount]);
647 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
648 ReplaceTestModel(kItemsPerPage * 3 + 1);
649 EXPECT_EQ(3, [observer totalPagesChangedCount]);
650 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]);
652 EXPECT_FALSE([observer readVisibilityDidChange]);
653 EXPECT_EQ(0, [observer selectedPageChangedCount]);
655 [apps_grid_controller_ setPaginationObserver:nil];
658 // Test AppsGridPaginationObserver selectedPageChanged().
659 TEST_F(AppsGridControllerTest, PaginationObserverSelectedPageChanged) {
660 base::scoped_nsobject<TestPaginationObserver> observer(
661 [[TestPaginationObserver alloc] init]);
662 [apps_grid_controller_ setPaginationObserver:observer];
663 EXPECT_EQ(0, [[NSAnimationContext currentContext] duration]);
665 ReplaceTestModel(kItemsPerPage * 3 + 1);
666 EXPECT_EQ(1, [observer totalPagesChangedCount]);
667 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]);
669 EXPECT_FALSE([observer readVisibilityDidChange]);
670 EXPECT_EQ(0, [observer selectedPageChangedCount]);
672 [apps_grid_controller_ scrollToPage:1];
673 EXPECT_EQ(1, [observer selectedPageChangedCount]);
674 EXPECT_EQ(1, [observer lastNewSelectedPage]);
675 EXPECT_TRUE([observer readVisibilityDidChange]);
676 EXPECT_FALSE([observer readVisibilityDidChange]); // Testing test behaviour.
677 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]);
678 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:1]);
679 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:2]);
680 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:3]);
682 [apps_grid_controller_ scrollToPage:0];
683 EXPECT_EQ(2, [observer selectedPageChangedCount]);
684 EXPECT_EQ(0, [observer lastNewSelectedPage]);
685 EXPECT_TRUE([observer readVisibilityDidChange]);
686 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:0]);
687 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:1]);
689 [apps_grid_controller_ scrollToPage:3];
690 // Note: with no animations, there is only a single page change. However, with
691 // animations we expect multiple updates depending on the rate that the scroll
692 // view updates and sends out NSViewBoundsDidChangeNotification.
693 EXPECT_EQ(3, [observer selectedPageChangedCount]);
694 EXPECT_EQ(3, [observer lastNewSelectedPage]);
695 EXPECT_TRUE([observer readVisibilityDidChange]);
696 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]);
697 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:3]);
699 [apps_grid_controller_ setPaginationObserver:nil];
702 // Test basic item moves with two items; swapping them around, dragging outside
703 // of the view bounds, and dragging on the background.
704 TEST_F(AppsGridControllerTest, DragAndDropSimple) {
705 model()->PopulateApps(2);
706 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0];
707 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
708 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1);
709 NSEvent* mouse_at_page_centre = MouseEventInCell(page, 6);
710 NSEvent* mouse_off_page = MouseEventInCell(page, kItemsPerPage * 2);
712 const std::string kOrdered = "Item 0,Item 1";
713 const std::string kSwapped = "Item 1,Item 0";
714 const std::string kOrderedView = "|Item 0,Item 1|";
715 const std::string kSwappedView = "|Item 1,Item 0|";
717 EXPECT_EQ(kOrdered, model()->GetModelContent());
718 EXPECT_EQ(kOrderedView, GetViewContent());
719 AppsCollectionViewDragManager* drag_manager =
720 [apps_grid_controller_ dragManager];
722 // Drag first item over the second item and release.
723 [drag_manager onMouseDownInPage:page
724 withEvent:mouse_at_cell_0];
725 [drag_manager onMouseDragged:mouse_at_cell_1];
726 EXPECT_EQ(kOrdered, model()->GetModelContent());
727 EXPECT_EQ(kSwappedView, GetViewContent()); // View swaps first.
728 [drag_manager onMouseUp:mouse_at_cell_1];
729 EXPECT_EQ(kSwapped, model()->GetModelContent());
730 EXPECT_EQ(kSwappedView, GetViewContent());
733 [drag_manager onMouseDownInPage:page
734 withEvent:mouse_at_cell_1];
735 [drag_manager onMouseDragged:mouse_at_cell_0];
736 EXPECT_EQ(kSwapped, model()->GetModelContent());
737 EXPECT_EQ(kOrderedView, GetViewContent());
738 [drag_manager onMouseUp:mouse_at_cell_0];
739 EXPECT_EQ(kOrdered, model()->GetModelContent());
740 EXPECT_EQ(kOrderedView, GetViewContent());
742 // Drag first item to centre of view (should put in last place).
743 [drag_manager onMouseDownInPage:page
744 withEvent:mouse_at_cell_0];
745 [drag_manager onMouseDragged:mouse_at_page_centre];
746 EXPECT_EQ(kOrdered, model()->GetModelContent());
747 EXPECT_EQ(kSwappedView, GetViewContent());
748 [drag_manager onMouseUp:mouse_at_page_centre];
749 EXPECT_EQ(kSwapped, model()->GetModelContent());
750 EXPECT_EQ(kSwappedView, GetViewContent());
752 // Drag item to centre again (should leave it in the last place).
753 [drag_manager onMouseDownInPage:page
754 withEvent:mouse_at_cell_1];
755 [drag_manager onMouseDragged:mouse_at_page_centre];
756 EXPECT_EQ(kSwapped, model()->GetModelContent());
757 EXPECT_EQ(kSwappedView, GetViewContent());
758 [drag_manager onMouseUp:mouse_at_page_centre];
759 EXPECT_EQ(kSwapped, model()->GetModelContent());
760 EXPECT_EQ(kSwappedView, GetViewContent());
762 // Drag starting in the centre of the view, should do nothing.
763 [drag_manager onMouseDownInPage:page
764 withEvent:mouse_at_page_centre];
765 [drag_manager onMouseDragged:mouse_at_cell_0];
766 EXPECT_EQ(kSwapped, model()->GetModelContent());
767 EXPECT_EQ(kSwappedView, GetViewContent());
768 [drag_manager onMouseUp:mouse_at_cell_0];
769 EXPECT_EQ(kSwapped, model()->GetModelContent());
770 EXPECT_EQ(kSwappedView, GetViewContent());
773 [drag_manager onMouseDownInPage:page
774 withEvent:mouse_off_page];
775 [drag_manager onMouseDragged:mouse_at_cell_0];
776 EXPECT_EQ(kSwapped, model()->GetModelContent());
777 EXPECT_EQ(kSwappedView, GetViewContent());
778 [drag_manager onMouseUp:mouse_at_cell_0];
779 EXPECT_EQ(kSwapped, model()->GetModelContent());
780 EXPECT_EQ(kSwappedView, GetViewContent());
782 // Drag to first over second item, then off page.
783 [drag_manager onMouseDownInPage:page
784 withEvent:mouse_at_cell_0];
785 [drag_manager onMouseDragged:mouse_at_cell_1];
786 EXPECT_EQ(kSwapped, model()->GetModelContent());
787 EXPECT_EQ(kOrderedView, GetViewContent());
788 [drag_manager onMouseDragged:mouse_off_page];
789 EXPECT_EQ(kSwapped, model()->GetModelContent());
790 EXPECT_EQ(kOrderedView, GetViewContent());
791 [drag_manager onMouseUp:mouse_off_page];
792 EXPECT_EQ(kOrdered, model()->GetModelContent());
793 EXPECT_EQ(kOrderedView, GetViewContent());
795 // Replace with an empty model, and ensure we do not break.
797 EXPECT_EQ(std::string(), model()->GetModelContent());
798 EXPECT_EQ(std::string("||"), GetViewContent());
799 [drag_manager onMouseDownInPage:page
800 withEvent:mouse_at_cell_0];
801 [drag_manager onMouseDragged:mouse_at_cell_1];
802 [drag_manager onMouseUp:mouse_at_cell_1];
803 EXPECT_EQ(std::string(), model()->GetModelContent());
804 EXPECT_EQ(std::string("||"), GetViewContent());
807 // Test item moves between pages.
808 TEST_F(AppsGridControllerTest, DragAndDropMultiPage) {
809 const size_t kPagesToTest = 3;
810 // Put one item on the last page to hit more edge cases.
811 ReplaceTestModel(kItemsPerPage * (kPagesToTest - 1) + 1);
812 NSCollectionView* page[kPagesToTest];
813 for (size_t i = 0; i < kPagesToTest; ++i)
814 page[i] = [apps_grid_controller_ collectionViewAtPageIndex:i];
816 const std::string kSecondItemMovedToSecondPage =
817 "|Item 0,Item 2,Item 3,Item 4,Item 5,Item 6,Item 7,Item 8,"
818 "Item 9,Item 10,Item 11,Item 12,Item 13,Item 14,Item 15,Item 16|"
819 "|Item 17,Item 1,Item 18,Item 19,Item 20,Item 21,Item 22,Item 23,"
820 "Item 24,Item 25,Item 26,Item 27,Item 28,Item 29,Item 30,Item 31|"
823 NSEvent* mouse_at_cell_0 = MouseEventInCell(page[0], 0);
824 NSEvent* mouse_at_cell_1 = MouseEventInCell(page[0], 1);
825 AppsCollectionViewDragManager* drag_manager =
826 [apps_grid_controller_ dragManager];
827 [drag_manager onMouseDownInPage:page[0]
828 withEvent:mouse_at_cell_1];
830 // Initiate dragging before changing pages.
831 [drag_manager onMouseDragged:mouse_at_cell_0];
833 // Scroll to the second page.
834 [apps_grid_controller_ scrollToPage:1];
835 [drag_manager onMouseDragged:mouse_at_cell_1];
837 // Do one exhaustive check, and then spot-check corner cases.
838 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent());
839 EXPECT_EQ(0u, GetPageIndexForItem(0));
840 EXPECT_EQ(1u, GetPageIndexForItem(1));
841 EXPECT_EQ(0u, GetPageIndexForItem(2));
842 EXPECT_EQ(0u, GetPageIndexForItem(16));
843 EXPECT_EQ(1u, GetPageIndexForItem(17));
844 EXPECT_EQ(1u, GetPageIndexForItem(31));
845 EXPECT_EQ(2u, GetPageIndexForItem(32));
847 // Scroll to the third page and drag some more.
848 [apps_grid_controller_ scrollToPage:2];
849 [drag_manager onMouseDragged:mouse_at_cell_1];
850 EXPECT_EQ(2u, GetPageIndexForItem(1));
851 EXPECT_EQ(1u, GetPageIndexForItem(31));
852 EXPECT_EQ(1u, GetPageIndexForItem(32));
855 [apps_grid_controller_ scrollToPage:1];
856 [drag_manager onMouseDragged:mouse_at_cell_1];
857 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent());
858 EXPECT_EQ(1u, GetPageIndexForItem(1));
859 EXPECT_EQ(1u, GetPageIndexForItem(31));
860 EXPECT_EQ(2u, GetPageIndexForItem(32));
862 // Simulate installing an item while dragging (or have it appear during sync).
863 model()->PopulateAppWithId(33);
864 // Item should go back to its position before the drag.
865 EXPECT_EQ(0u, GetPageIndexForItem(1));
866 EXPECT_EQ(1u, GetPageIndexForItem(31));
867 EXPECT_EQ(2u, GetPageIndexForItem(32));
868 // New item should appear at end.
869 EXPECT_EQ(2u, GetPageIndexForItem(33));
871 // Scroll to end again, and keep dragging (should be ignored).
872 [apps_grid_controller_ scrollToPage:2];
873 [drag_manager onMouseDragged:mouse_at_cell_0];
874 EXPECT_EQ(0u, GetPageIndexForItem(1));
875 [drag_manager onMouseUp:mouse_at_cell_0];
876 EXPECT_EQ(0u, GetPageIndexForItem(1));
879 // Test scrolling when dragging past edge or over the pager.
880 TEST_F(AppsGridControllerTest, ScrollingWhileDragging) {
881 base::scoped_nsobject<TestPaginationObserver> observer(
882 [[TestPaginationObserver alloc] init]);
883 [apps_grid_controller_ setPaginationObserver:observer];
885 ReplaceTestModel(kItemsPerPage * 3);
886 // Start on the middle page.
887 [apps_grid_controller_ scrollToPage:1];
888 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:1];
889 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
891 NSEvent* at_center = MouseEventForScroll([apps_grid_controller_ view], 0.0);
892 NSEvent* at_left = MouseEventForScroll([apps_grid_controller_ view], -1.1);
893 NSEvent* at_right = MouseEventForScroll([apps_grid_controller_ view], 1.1);
895 AppsCollectionViewDragManager* drag_manager =
896 [apps_grid_controller_ dragManager];
897 [drag_manager onMouseDownInPage:page
898 withEvent:mouse_at_cell_0];
899 [drag_manager onMouseDragged:at_center];
901 // Nothing should be scheduled: target page is visible page.
902 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
903 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
905 // Drag to the left, should go to first page and no further.
906 [drag_manager onMouseDragged:at_left];
907 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
908 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
909 [apps_grid_controller_ scrollToPage:0]; // Commit without timer for testing.
910 [drag_manager onMouseDragged:at_left];
911 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
912 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
914 // Drag to the right, should go to last page and no futher.
915 [drag_manager onMouseDragged:at_right];
916 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
917 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
918 [apps_grid_controller_ scrollToPage:1];
919 [drag_manager onMouseDragged:at_right];
920 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
921 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
922 [apps_grid_controller_ scrollToPage:2];
923 [drag_manager onMouseDragged:at_right];
924 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
925 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
927 // Simulate a hover over the first pager segment.
928 [observer setHoveredSegmentForTest:0];
929 [drag_manager onMouseDragged:at_center];
930 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
931 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
933 // Drag it back, should cancel schedule.
934 [observer setHoveredSegmentForTest:-1];
935 [drag_manager onMouseDragged:at_center];
936 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
937 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
939 // Hover again, now over middle segment, and ensure a release also cancels.
940 [observer setHoveredSegmentForTest:1];
941 [drag_manager onMouseDragged:at_center];
942 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
943 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
944 [drag_manager onMouseUp:at_center];
945 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
946 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
948 [apps_grid_controller_ setPaginationObserver:nil];
951 TEST_F(AppsGridControllerTest, ContextMenus) {
952 AppListItemWithMenu* item_two_model = new AppListItemWithMenu("Item Two");
953 model()->item_list()->AddItem(new AppListItemWithMenu("Item One"));
954 model()->item_list()->AddItem(item_two_model);
955 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
957 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0];
958 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
959 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1);
961 NSMenu* menu = [page menuForEvent:mouse_at_cell_0];
962 EXPECT_EQ(1, [menu numberOfItems]);
963 EXPECT_NSEQ(@"Menu For: Item One", [[menu itemAtIndex:0] title]);
965 // Test a context menu request while the item is still installing.
966 item_two_model->SetMenuReadyForTesting(false);
967 menu = [page menuForEvent:mouse_at_cell_1];
968 EXPECT_EQ(nil, menu);
970 item_two_model->SetMenuReadyForTesting(true);
971 menu = [page menuForEvent:mouse_at_cell_1];
972 EXPECT_EQ(1, [menu numberOfItems]);
973 EXPECT_NSEQ(@"Menu For: Item Two", [[menu itemAtIndex:0] title]);
977 } // namespace app_list