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.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/events/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_ setDelegate:NULL];
113 owned_apps_grid_controller_.reset();
114 AppsGridControllerTestHelper::TearDown();
117 void ReplaceTestModel(int item_count) {
118 // Clear the delegate before reseting and destroying the model.
119 [owned_apps_grid_controller_ setDelegate:NULL];
121 owned_delegate_->ReplaceTestModel(item_count);
122 [owned_apps_grid_controller_ setDelegate:owned_delegate_.get()];
125 AppListTestModel* model() { return owned_delegate_->GetTestModel(); }
128 base::scoped_nsobject<AppsGridController> owned_apps_grid_controller_;
129 scoped_ptr<AppListTestViewDelegate> owned_delegate_;
131 DISALLOW_COPY_AND_ASSIGN(AppsGridControllerTest);
134 class AppListItemWithMenu : public AppListItem {
136 explicit AppListItemWithMenu(const std::string& title)
137 : AppListItem(title),
140 SetTitleAndFullName(title, title);
141 menu_model_.AddItem(0, base::UTF8ToUTF16("Menu For: " + title));
144 void SetMenuReadyForTesting(bool ready) {
148 virtual ui::MenuModel* GetContextMenuModel() OVERRIDE {
156 ui::SimpleMenuModel menu_model_;
159 DISALLOW_COPY_AND_ASSIGN(AppListItemWithMenu);
162 // Generate a mouse event at the centre of the view in |page| with the given
163 // |index_in_page| that can be used to initiate, update and complete drag
165 NSEvent* MouseEventInCell(NSCollectionView* page, size_t index_in_page) {
166 NSRect cell_rect = [page frameForItemAtIndex:index_in_page];
167 NSPoint point_in_view = NSMakePoint(NSMidX(cell_rect), NSMidY(cell_rect));
168 NSPoint point_in_window = [page convertPoint:point_in_view
170 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
173 NSEvent* MouseEventForScroll(NSView* view, CGFloat relative_x) {
174 NSRect view_rect = [view frame];
175 NSPoint point_in_view = NSMakePoint(NSMidX(view_rect), NSMidY(view_rect));
176 point_in_view.x += point_in_view.x * relative_x;
177 NSPoint point_in_window = [view convertPoint:point_in_view
179 return cocoa_test_event_utils::LeftMouseDownAtPoint(point_in_window);
184 TEST_VIEW(AppsGridControllerTest, [apps_grid_controller_ view]);
186 // Test showing with an empty model.
187 TEST_F(AppsGridControllerTest, EmptyModelAndShow) {
188 EXPECT_TRUE([[apps_grid_controller_ view] superview]);
190 // First page should always exist, even if empty.
191 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
192 EXPECT_EQ(0u, [[GetPageAt(0) content] count]);
193 EXPECT_TRUE([GetPageAt(0) superview]); // The pages container.
194 EXPECT_TRUE([[GetPageAt(0) superview] superview]);
197 // Test with a single item.
198 // This test is disabled in builders until the delay to wait for the collection
199 // view to load subviews can be removed, or some other solution is found.
200 TEST_F(AppsGridControllerTest, DISABLED_SingleEntryModel) {
201 // We need to "wake up" the NSCollectionView, otherwise it does not
202 // immediately update its subviews later in this function.
203 // When this test is run by itself, it's enough just to send a keypress (and
204 // this delay is not needed).
205 DelayForCollectionView();
206 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
207 EXPECT_EQ(0u, [[GetPageAt(0) content] count]);
209 model()->PopulateApps(1);
211 EXPECT_FALSE([GetPageAt(0) animations]);
213 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
214 NSArray* subviews = [GetPageAt(0) subviews];
215 EXPECT_EQ(1u, [subviews count]);
217 // Note that using GetItemViewAt(0) here also works, and returns non-nil even
218 // without the delay, but a "click" on it does not register without the delay.
219 NSView* subview = [subviews objectAtIndex:0];
222 SimulateClick(subview);
224 EXPECT_EQ(1, model()->activate_count());
225 ASSERT_TRUE(model()->last_activated());
226 EXPECT_EQ(std::string("Item 0"), model()->last_activated()->title());
229 // Test activating an item on the second page (the 17th item).
230 TEST_F(AppsGridControllerTest, DISABLED_TwoPageModel) {
231 DelayForCollectionView();
232 ReplaceTestModel(kItemsPerPage * 2);
233 [apps_grid_controller_ scrollToPage:1];
235 // The NSScrollView animator ignores the duration configured on the
236 // NSAnimationContext (set by CocoaTest::Init), so we need to delay here.
237 DelayForCollectionView();
238 NSArray* subviews = [GetPageAt(1) subviews];
239 NSView* subview = [subviews objectAtIndex:0];
241 SimulateClick(subview);
243 EXPECT_EQ(1, model()->activate_count());
244 ASSERT_TRUE(model()->last_activated());
245 EXPECT_EQ(std::string("Item 16"), model()->last_activated()->title());
249 TEST_F(AppsGridControllerTest, ReplaceModel) {
250 const size_t kOrigItems = 1;
251 const size_t kNewItems = 2;
253 model()->PopulateApps(kOrigItems);
254 EXPECT_EQ(kOrigItems, [[GetPageAt(0) content] count]);
256 ReplaceTestModel(kNewItems);
257 EXPECT_EQ(kNewItems, [[GetPageAt(0) content] count]);
261 TEST_F(AppsGridControllerTest, Pagination) {
262 model()->PopulateApps(1);
263 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
264 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
266 ReplaceTestModel(kItemsPerPage);
267 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
268 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
270 // Test adding an item onto the next page.
271 model()->PopulateApps(1); // Now 17 items.
272 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
273 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
274 EXPECT_EQ(1u, [[GetPageAt(1) content] count]);
276 // Test N pages with the last page having one empty spot.
277 const size_t kPagesToTest = 3;
278 ReplaceTestModel(kPagesToTest * kItemsPerPage - 1);
279 EXPECT_EQ(kPagesToTest, [apps_grid_controller_ pageCount]);
280 for (size_t page_index = 0; page_index < kPagesToTest - 1; ++page_index) {
281 EXPECT_EQ(kItemsPerPage, [[GetPageAt(page_index) content] count]);
283 EXPECT_EQ(kItemsPerPage - 1, [[GetPageAt(kPagesToTest - 1) content] count]);
285 // Test removing pages.
287 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
288 EXPECT_EQ(1u, [[GetPageAt(0) content] count]);
291 // Tests that selecting an item changes the text color correctly.
292 TEST_F(AppsGridControllerTest, SelectionChangesTextColor) {
293 model()->PopulateApps(2);
294 [apps_grid_controller_ selectItemAtIndex:0];
295 EXPECT_NSEQ(ButtonTitleColorAt(0),
296 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor));
297 EXPECT_NSEQ(ButtonTitleColorAt(1),
298 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor));
300 [apps_grid_controller_ selectItemAtIndex:1];
301 EXPECT_NSEQ(ButtonTitleColorAt(0),
302 gfx::SkColorToSRGBNSColor(app_list::kGridTitleColor));
303 EXPECT_NSEQ(ButtonTitleColorAt(1),
304 gfx::SkColorToSRGBNSColor(app_list::kGridTitleHoverColor));
307 // Tests basic keyboard navigation on the first page.
308 TEST_F(AppsGridControllerTest, FirstPageKeyboardNavigation) {
309 model()->PopulateApps(kItemsPerPage - 2);
310 EXPECT_EQ(kItemsPerPage - 2, [[GetPageAt(0) content] count]);
312 SimulateKeyAction(@selector(moveRight:));
313 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
315 SimulateKeyAction(@selector(moveRight:));
316 EXPECT_EQ(1u, [apps_grid_controller_ selectedItemIndex]);
318 SimulateKeyAction(@selector(moveDown:));
319 EXPECT_EQ(5u, [apps_grid_controller_ selectedItemIndex]);
321 SimulateKeyAction(@selector(moveLeft:));
322 EXPECT_EQ(4u, [apps_grid_controller_ selectedItemIndex]);
324 SimulateKeyAction(@selector(moveUp:));
325 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
327 // Go to the third item, and launch it.
328 SimulateKeyAction(@selector(moveRight:));
329 SimulateKeyAction(@selector(moveRight:));
330 EXPECT_EQ(2u, [apps_grid_controller_ selectedItemIndex]);
331 SimulateKeyAction(@selector(insertNewline:));
332 EXPECT_EQ(1, model()->activate_count());
333 ASSERT_TRUE(model()->last_activated());
334 EXPECT_EQ(std::string("Item 2"), model()->last_activated()->title());
337 // Tests keyboard navigation across pages.
338 TEST_F(AppsGridControllerTest, CrossPageKeyboardNavigation) {
339 model()->PopulateApps(kItemsPerPage + 10);
340 EXPECT_EQ(kItemsPerPage, [[GetPageAt(0) content] count]);
341 EXPECT_EQ(10u, [[GetPageAt(1) content] count]);
343 // Moving Left, Up, or PageUp from the top-left corner of the first page does
345 [apps_grid_controller_ selectItemAtIndex:0];
346 SimulateKeyAction(@selector(moveLeft:));
347 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
348 SimulateKeyAction(@selector(moveUp:));
349 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
350 SimulateKeyAction(@selector(scrollPageUp:));
351 EXPECT_EQ(0u, [apps_grid_controller_ selectedItemIndex]);
353 // Moving Right from the right side goes to the next page. Moving Left goes
354 // back to the first page.
355 [apps_grid_controller_ selectItemAtIndex:3];
356 SimulateKeyAction(@selector(moveRight:));
357 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
358 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]);
359 SimulateKeyAction(@selector(moveLeft:));
360 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
361 EXPECT_EQ(3u, [apps_grid_controller_ selectedItemIndex]);
363 // Moving Down from the bottom does nothing.
364 [apps_grid_controller_ selectItemAtIndex:13];
365 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
366 SimulateKeyAction(@selector(moveDown:));
367 EXPECT_EQ(13u, [apps_grid_controller_ selectedItemIndex]);
369 // Moving Right into a non-existent square on the next page will select the
371 [apps_grid_controller_ selectItemAtIndex:15];
372 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
373 SimulateKeyAction(@selector(moveRight:));
374 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
375 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
377 // PageDown and PageUp switches pages while maintaining the same selection
379 [apps_grid_controller_ selectItemAtIndex:6];
380 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
381 SimulateKeyAction(@selector(scrollPageDown:));
382 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
383 EXPECT_EQ(kItemsPerPage + 6, [apps_grid_controller_ selectedItemIndex]);
384 SimulateKeyAction(@selector(scrollPageUp:));
385 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
386 EXPECT_EQ(6u, [apps_grid_controller_ selectedItemIndex]);
388 // PageDown into a non-existent square on the next page will select the last
390 [apps_grid_controller_ selectItemAtIndex:11];
391 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
392 SimulateKeyAction(@selector(scrollPageDown:));
393 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
394 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
396 // Moving Right, Down, or PageDown from the bottom-right corner of the last
397 // page (not the last item) does nothing.
398 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 9];
399 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
400 SimulateKeyAction(@selector(moveRight:));
401 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
402 SimulateKeyAction(@selector(moveDown:));
403 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
404 SimulateKeyAction(@selector(scrollPageDown:));
405 EXPECT_EQ(kItemsPerPage + 9, [apps_grid_controller_ selectedItemIndex]);
407 // After page switch, arrow keys select first item on current page.
408 [apps_grid_controller_ scrollToPage:0];
409 [apps_grid_controller_ scrollToPage:1];
410 EXPECT_EQ(NSNotFound, [apps_grid_controller_ selectedItemIndex]);
411 SimulateKeyAction(@selector(moveUp:));
412 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ selectedItemIndex]);
415 // Highlighting an item should cause the page it's on to be visible.
416 TEST_F(AppsGridControllerTest, EnsureHighlightedVisible) {
417 model()->PopulateApps(3 * kItemsPerPage);
418 EXPECT_EQ(kItemsPerPage, [[GetPageAt(2) content] count]);
420 // First and last items of first page.
421 [apps_grid_controller_ selectItemAtIndex:0];
422 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
423 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage - 1];
424 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
426 // First item of second page.
427 [apps_grid_controller_ selectItemAtIndex:kItemsPerPage + 1];
428 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
430 // Last item in model.
431 [apps_grid_controller_ selectItemAtIndex:3 * kItemsPerPage - 1];
432 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
435 // Test runtime updates: adding items, removing items, and moving items (e.g. in
436 // response to app install, uninstall, and chrome sync changes. Also test
437 // changing titles and icons.
438 TEST_F(AppsGridControllerTest, ModelUpdate) {
439 model()->PopulateApps(2);
440 EXPECT_EQ(2u, [[GetPageAt(0) content] count]);
441 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
443 // Add an item (PopulateApps will create a new "Item 2").
444 model()->PopulateApps(1);
445 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
446 NSButton* button = GetItemViewAt(2);
447 EXPECT_NSEQ(@"Item 2", [button title]);
448 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
450 // Update the title via the ItemModelObserver.
451 app_list::AppListItem* item_model = model()->item_list()->item_at(2);
452 item_model->SetTitleAndFullName("UpdatedItem", "UpdatedItem");
453 EXPECT_NSEQ(@"UpdatedItem", [button title]);
454 EXPECT_EQ(std::string("|Item 0,Item 1,UpdatedItem|"), GetViewContent());
456 // Test icon updates through the model observer by ensuring the icon changes.
457 NSSize icon_size = [[button image] size];
458 EXPECT_EQ(0, icon_size.width);
459 EXPECT_EQ(0, icon_size.height);
462 const int kTestImageSize = 10;
463 const int kTargetImageSize = 48;
464 bitmap.setConfig(SkBitmap::kARGB_8888_Config, kTestImageSize, kTestImageSize);
465 item_model->SetIcon(gfx::ImageSkia::CreateFrom1xBitmap(bitmap), false);
466 icon_size = [[button image] size];
467 // Icon should always be resized to 48x48.
468 EXPECT_EQ(kTargetImageSize, icon_size.width);
469 EXPECT_EQ(kTargetImageSize, icon_size.height);
472 TEST_F(AppsGridControllerTest, ModelAdd) {
473 model()->PopulateApps(2);
474 EXPECT_EQ(2u, [[GetPageAt(0) content] count]);
475 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
477 app_list::AppListItemList* item_list = model()->item_list();
479 model()->CreateAndAddItem("Item 2");
480 ASSERT_EQ(3u, item_list->item_count());
481 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]);
482 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
484 // Test adding an item whose position is in the middle.
485 app_list::AppListItem* item0 = item_list->item_at(0);
486 app_list::AppListItem* item1 = item_list->item_at(1);
487 app_list::AppListItem* item3 =
488 model()->CreateItem("Item Three", "Item Three");
489 model()->AddItem(item3);
490 item_list->SetItemPosition(
491 item3, item0->position().CreateBetween(item1->position()));
492 EXPECT_EQ(4u, [apps_grid_controller_ itemCount]);
493 EXPECT_EQ(std::string("|Item 0,Item Three,Item 1,Item 2|"), GetViewContent());
496 TEST_F(AppsGridControllerTest, ModelMove) {
497 model()->PopulateApps(3);
498 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
499 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
501 // Test swapping items (e.g. rearranging via sync).
502 model()->item_list()->MoveItem(1, 2);
503 EXPECT_EQ(std::string("|Item 0,Item 2,Item 1|"), GetViewContent());
506 TEST_F(AppsGridControllerTest, ModelRemove) {
507 model()->PopulateApps(3);
508 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
509 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
511 // Test removing an item at the end.
512 model()->DeleteItem("Item 2");
513 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
514 EXPECT_EQ(std::string("|Item 0,Item 1|"), GetViewContent());
516 // Test removing in the middle.
517 model()->CreateAndAddItem("Item 2", "");
518 EXPECT_EQ(3u, [apps_grid_controller_ itemCount]);
519 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
520 model()->DeleteItem("Item 1");
521 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
522 EXPECT_EQ(std::string("|Item 0,Item 2|"), GetViewContent());
525 TEST_F(AppsGridControllerTest, ModelRemoveSeveral) {
526 model()->PopulateApps(3);
527 EXPECT_EQ(3u, [[GetPageAt(0) content] count]);
528 EXPECT_EQ(std::string("|Item 0,Item 1,Item 2|"), GetViewContent());
530 // Test removing multiple items via the model.
531 model()->DeleteItem("Item 0");
532 model()->DeleteItem("Item 1");
533 model()->DeleteItem("Item 2");
534 EXPECT_EQ(0u, [apps_grid_controller_ itemCount]);
535 EXPECT_EQ(std::string("||"), GetViewContent());
538 TEST_F(AppsGridControllerTest, ModelRemovePage) {
539 app_list::AppListItemList* item_list = model()->item_list();
541 model()->PopulateApps(kItemsPerPage + 1);
542 ASSERT_EQ(kItemsPerPage + 1, item_list->item_count());
543 EXPECT_EQ(kItemsPerPage + 1, [apps_grid_controller_ itemCount]);
544 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
546 // Test removing the last item when there is one item on the second page.
547 app_list::AppListItem* last_item = item_list->item_at(kItemsPerPage);
548 model()->DeleteItem(last_item->id());
549 EXPECT_EQ(kItemsPerPage, item_list->item_count());
550 EXPECT_EQ(kItemsPerPage, [apps_grid_controller_ itemCount]);
551 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
554 // Test install progress bars, and install flow with item highlighting.
555 TEST_F(AppsGridControllerTest, ItemInstallProgress) {
556 ReplaceTestModel(kItemsPerPage + 1);
557 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
558 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
559 app_list::AppListItem* item_model =
560 model()->item_list()->item_at(kItemsPerPage);
562 // Highlighting an item should activate the page it is on.
563 item_model->SetHighlighted(true);
564 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
566 // Clearing a highlight stays on the current page.
567 [apps_grid_controller_ scrollToPage:0];
568 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
569 item_model->SetHighlighted(false);
570 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
572 // Starting install should add a progress bar, and temporarily clear the
574 NSButton* button = GetItemViewAt(kItemsPerPage);
575 NSView* containerView = [button superview];
576 EXPECT_EQ(1u, [[containerView subviews] count]);
577 EXPECT_NSEQ(@"Item 16", [button title]);
578 item_model->SetHighlighted(true);
579 item_model->SetIsInstalling(true);
580 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
582 EXPECT_EQ(2u, [[containerView subviews] count]);
583 EXPECT_NSEQ(@"", [button title]);
584 NSProgressIndicator* progressIndicator =
585 [[containerView subviews] objectAtIndex:1];
586 EXPECT_FALSE([progressIndicator isIndeterminate]);
587 EXPECT_EQ(0.0, [progressIndicator doubleValue]);
589 // Updating the progress in the model should update the progress bar.
590 item_model->SetPercentDownloaded(50);
591 EXPECT_EQ(50.0, [progressIndicator doubleValue]);
593 // Two things can be installing simultaneously. When one starts or completes
594 // the model builder will ask for the item to be highlighted.
595 app_list::AppListItem* alternate_item_model =
596 model()->item_list()->item_at(0);
597 item_model->SetHighlighted(false);
598 alternate_item_model->SetHighlighted(true);
599 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
601 // Update the first item (page doesn't change on updates).
602 item_model->SetPercentDownloaded(100);
603 EXPECT_EQ(100.0, [progressIndicator doubleValue]);
604 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
606 // A percent of -1 indicates the download is complete and the unpack/install
607 // process has started.
608 item_model->SetPercentDownloaded(-1);
609 EXPECT_TRUE([progressIndicator isIndeterminate]);
611 // Completing install removes the progress bar, and restores the title.
612 // ExtensionAppModelBuilder will reload the ExtensionAppItem, which also
613 // highlights. Do the same here.
614 alternate_item_model->SetHighlighted(false);
615 item_model->SetHighlighted(true);
616 item_model->SetIsInstalling(false);
617 EXPECT_EQ(1u, [[containerView subviews] count]);
618 EXPECT_NSEQ(@"Item 16", [button title]);
619 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
621 // Things should cleanup OK with |alternate_item_model| left installing.
624 // Test mouseover selection.
625 TEST_F(AppsGridControllerTest, MouseoverSelects) {
626 model()->PopulateApps(2);
627 EXPECT_EQ(nil, GetSelectedView());
629 // Test entering and exiting the first item.
630 SimulateMouseEnterItemAt(0);
631 EXPECT_EQ(GetItemViewAt(0), GetSelectedView());
632 SimulateMouseExitItemAt(0);
633 EXPECT_EQ(nil, GetSelectedView());
635 // AppKit doesn't guarantee the order, so test moving between items.
636 SimulateMouseEnterItemAt(0);
637 EXPECT_EQ(GetItemViewAt(0), GetSelectedView());
638 SimulateMouseEnterItemAt(1);
639 EXPECT_EQ(GetItemViewAt(1), GetSelectedView());
640 SimulateMouseExitItemAt(0);
641 EXPECT_EQ(GetItemViewAt(1), GetSelectedView());
642 SimulateMouseExitItemAt(1);
643 EXPECT_EQ(nil, GetSelectedView());
646 // Test AppsGridPaginationObserver totalPagesChanged().
647 TEST_F(AppsGridControllerTest, PaginationObserverPagesChanged) {
648 base::scoped_nsobject<TestPaginationObserver> observer(
649 [[TestPaginationObserver alloc] init]);
650 [apps_grid_controller_ setPaginationObserver:observer];
652 // Test totalPagesChanged.
653 model()->PopulateApps(kItemsPerPage);
654 EXPECT_EQ(0, [observer totalPagesChangedCount]);
655 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
656 model()->PopulateApps(1);
657 EXPECT_EQ(1, [observer totalPagesChangedCount]);
658 EXPECT_EQ(2u, [apps_grid_controller_ pageCount]);
660 EXPECT_EQ(2, [observer totalPagesChangedCount]);
661 EXPECT_EQ(1u, [apps_grid_controller_ pageCount]);
662 ReplaceTestModel(kItemsPerPage * 3 + 1);
663 EXPECT_EQ(3, [observer totalPagesChangedCount]);
664 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]);
666 EXPECT_FALSE([observer readVisibilityDidChange]);
667 EXPECT_EQ(0, [observer selectedPageChangedCount]);
669 [apps_grid_controller_ setPaginationObserver:nil];
672 // Test AppsGridPaginationObserver selectedPageChanged().
673 TEST_F(AppsGridControllerTest, PaginationObserverSelectedPageChanged) {
674 base::scoped_nsobject<TestPaginationObserver> observer(
675 [[TestPaginationObserver alloc] init]);
676 [apps_grid_controller_ setPaginationObserver:observer];
677 EXPECT_EQ(0, [[NSAnimationContext currentContext] duration]);
679 ReplaceTestModel(kItemsPerPage * 3 + 1);
680 EXPECT_EQ(1, [observer totalPagesChangedCount]);
681 EXPECT_EQ(4u, [apps_grid_controller_ pageCount]);
683 EXPECT_FALSE([observer readVisibilityDidChange]);
684 EXPECT_EQ(0, [observer selectedPageChangedCount]);
686 [apps_grid_controller_ scrollToPage:1];
687 EXPECT_EQ(1, [observer selectedPageChangedCount]);
688 EXPECT_EQ(1, [observer lastNewSelectedPage]);
689 EXPECT_TRUE([observer readVisibilityDidChange]);
690 EXPECT_FALSE([observer readVisibilityDidChange]); // Testing test behaviour.
691 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]);
692 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:1]);
693 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:2]);
694 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:3]);
696 [apps_grid_controller_ scrollToPage:0];
697 EXPECT_EQ(2, [observer selectedPageChangedCount]);
698 EXPECT_EQ(0, [observer lastNewSelectedPage]);
699 EXPECT_TRUE([observer readVisibilityDidChange]);
700 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:0]);
701 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:1]);
703 [apps_grid_controller_ scrollToPage:3];
704 // Note: with no animations, there is only a single page change. However, with
705 // animations we expect multiple updates depending on the rate that the scroll
706 // view updates and sends out NSViewBoundsDidChangeNotification.
707 EXPECT_EQ(3, [observer selectedPageChangedCount]);
708 EXPECT_EQ(3, [observer lastNewSelectedPage]);
709 EXPECT_TRUE([observer readVisibilityDidChange]);
710 EXPECT_EQ(0.0, [apps_grid_controller_ visiblePortionOfPage:0]);
711 EXPECT_EQ(1.0, [apps_grid_controller_ visiblePortionOfPage:3]);
713 [apps_grid_controller_ setPaginationObserver:nil];
716 // Test basic item moves with two items; swapping them around, dragging outside
717 // of the view bounds, and dragging on the background.
718 TEST_F(AppsGridControllerTest, DragAndDropSimple) {
719 model()->PopulateApps(2);
720 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0];
721 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
722 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1);
723 NSEvent* mouse_at_page_centre = MouseEventInCell(page, 6);
724 NSEvent* mouse_off_page = MouseEventInCell(page, kItemsPerPage * 2);
726 const std::string kOrdered = "Item 0,Item 1";
727 const std::string kSwapped = "Item 1,Item 0";
728 const std::string kOrderedView = "|Item 0,Item 1|";
729 const std::string kSwappedView = "|Item 1,Item 0|";
731 EXPECT_EQ(kOrdered, model()->GetModelContent());
732 EXPECT_EQ(kOrderedView, GetViewContent());
733 AppsCollectionViewDragManager* drag_manager =
734 [apps_grid_controller_ dragManager];
736 // Drag first item over the second item and release.
737 [drag_manager onMouseDownInPage:page
738 withEvent:mouse_at_cell_0];
739 [drag_manager onMouseDragged:mouse_at_cell_1];
740 EXPECT_EQ(kOrdered, model()->GetModelContent());
741 EXPECT_EQ(kSwappedView, GetViewContent()); // View swaps first.
742 [drag_manager onMouseUp:mouse_at_cell_1];
743 EXPECT_EQ(kSwapped, model()->GetModelContent());
744 EXPECT_EQ(kSwappedView, GetViewContent());
747 [drag_manager onMouseDownInPage:page
748 withEvent:mouse_at_cell_1];
749 [drag_manager onMouseDragged:mouse_at_cell_0];
750 EXPECT_EQ(kSwapped, model()->GetModelContent());
751 EXPECT_EQ(kOrderedView, GetViewContent());
752 [drag_manager onMouseUp:mouse_at_cell_0];
753 EXPECT_EQ(kOrdered, model()->GetModelContent());
754 EXPECT_EQ(kOrderedView, GetViewContent());
756 // Drag first item to centre of view (should put in last place).
757 [drag_manager onMouseDownInPage:page
758 withEvent:mouse_at_cell_0];
759 [drag_manager onMouseDragged:mouse_at_page_centre];
760 EXPECT_EQ(kOrdered, model()->GetModelContent());
761 EXPECT_EQ(kSwappedView, GetViewContent());
762 [drag_manager onMouseUp:mouse_at_page_centre];
763 EXPECT_EQ(kSwapped, model()->GetModelContent());
764 EXPECT_EQ(kSwappedView, GetViewContent());
766 // Drag item to centre again (should leave it in the last place).
767 [drag_manager onMouseDownInPage:page
768 withEvent:mouse_at_cell_1];
769 [drag_manager onMouseDragged:mouse_at_page_centre];
770 EXPECT_EQ(kSwapped, model()->GetModelContent());
771 EXPECT_EQ(kSwappedView, GetViewContent());
772 [drag_manager onMouseUp:mouse_at_page_centre];
773 EXPECT_EQ(kSwapped, model()->GetModelContent());
774 EXPECT_EQ(kSwappedView, GetViewContent());
776 // Drag starting in the centre of the view, should do nothing.
777 [drag_manager onMouseDownInPage:page
778 withEvent:mouse_at_page_centre];
779 [drag_manager onMouseDragged:mouse_at_cell_0];
780 EXPECT_EQ(kSwapped, model()->GetModelContent());
781 EXPECT_EQ(kSwappedView, GetViewContent());
782 [drag_manager onMouseUp:mouse_at_cell_0];
783 EXPECT_EQ(kSwapped, model()->GetModelContent());
784 EXPECT_EQ(kSwappedView, GetViewContent());
787 [drag_manager onMouseDownInPage:page
788 withEvent:mouse_off_page];
789 [drag_manager onMouseDragged:mouse_at_cell_0];
790 EXPECT_EQ(kSwapped, model()->GetModelContent());
791 EXPECT_EQ(kSwappedView, GetViewContent());
792 [drag_manager onMouseUp:mouse_at_cell_0];
793 EXPECT_EQ(kSwapped, model()->GetModelContent());
794 EXPECT_EQ(kSwappedView, GetViewContent());
796 // Drag to first over second item, then off page.
797 [drag_manager onMouseDownInPage:page
798 withEvent:mouse_at_cell_0];
799 [drag_manager onMouseDragged:mouse_at_cell_1];
800 EXPECT_EQ(kSwapped, model()->GetModelContent());
801 EXPECT_EQ(kOrderedView, GetViewContent());
802 [drag_manager onMouseDragged:mouse_off_page];
803 EXPECT_EQ(kSwapped, model()->GetModelContent());
804 EXPECT_EQ(kOrderedView, GetViewContent());
805 [drag_manager onMouseUp:mouse_off_page];
806 EXPECT_EQ(kOrdered, model()->GetModelContent());
807 EXPECT_EQ(kOrderedView, GetViewContent());
809 // Replace with an empty model, and ensure we do not break.
811 EXPECT_EQ(std::string(), model()->GetModelContent());
812 EXPECT_EQ(std::string("||"), GetViewContent());
813 [drag_manager onMouseDownInPage:page
814 withEvent:mouse_at_cell_0];
815 [drag_manager onMouseDragged:mouse_at_cell_1];
816 [drag_manager onMouseUp:mouse_at_cell_1];
817 EXPECT_EQ(std::string(), model()->GetModelContent());
818 EXPECT_EQ(std::string("||"), GetViewContent());
821 // Test item moves between pages.
822 TEST_F(AppsGridControllerTest, DragAndDropMultiPage) {
823 const size_t kPagesToTest = 3;
824 // Put one item on the last page to hit more edge cases.
825 ReplaceTestModel(kItemsPerPage * (kPagesToTest - 1) + 1);
826 NSCollectionView* page[kPagesToTest];
827 for (size_t i = 0; i < kPagesToTest; ++i)
828 page[i] = [apps_grid_controller_ collectionViewAtPageIndex:i];
830 const std::string kSecondItemMovedToSecondPage =
831 "|Item 0,Item 2,Item 3,Item 4,Item 5,Item 6,Item 7,Item 8,"
832 "Item 9,Item 10,Item 11,Item 12,Item 13,Item 14,Item 15,Item 16|"
833 "|Item 17,Item 1,Item 18,Item 19,Item 20,Item 21,Item 22,Item 23,"
834 "Item 24,Item 25,Item 26,Item 27,Item 28,Item 29,Item 30,Item 31|"
837 NSEvent* mouse_at_cell_0 = MouseEventInCell(page[0], 0);
838 NSEvent* mouse_at_cell_1 = MouseEventInCell(page[0], 1);
839 AppsCollectionViewDragManager* drag_manager =
840 [apps_grid_controller_ dragManager];
841 [drag_manager onMouseDownInPage:page[0]
842 withEvent:mouse_at_cell_1];
844 // Initiate dragging before changing pages.
845 [drag_manager onMouseDragged:mouse_at_cell_0];
847 // Scroll to the second page.
848 [apps_grid_controller_ scrollToPage:1];
849 [drag_manager onMouseDragged:mouse_at_cell_1];
851 // Do one exhaustive check, and then spot-check corner cases.
852 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent());
853 EXPECT_EQ(0u, GetPageIndexForItem(0));
854 EXPECT_EQ(1u, GetPageIndexForItem(1));
855 EXPECT_EQ(0u, GetPageIndexForItem(2));
856 EXPECT_EQ(0u, GetPageIndexForItem(16));
857 EXPECT_EQ(1u, GetPageIndexForItem(17));
858 EXPECT_EQ(1u, GetPageIndexForItem(31));
859 EXPECT_EQ(2u, GetPageIndexForItem(32));
861 // Scroll to the third page and drag some more.
862 [apps_grid_controller_ scrollToPage:2];
863 [drag_manager onMouseDragged:mouse_at_cell_1];
864 EXPECT_EQ(2u, GetPageIndexForItem(1));
865 EXPECT_EQ(1u, GetPageIndexForItem(31));
866 EXPECT_EQ(1u, GetPageIndexForItem(32));
869 [apps_grid_controller_ scrollToPage:1];
870 [drag_manager onMouseDragged:mouse_at_cell_1];
871 EXPECT_EQ(kSecondItemMovedToSecondPage, GetViewContent());
872 EXPECT_EQ(1u, GetPageIndexForItem(1));
873 EXPECT_EQ(1u, GetPageIndexForItem(31));
874 EXPECT_EQ(2u, GetPageIndexForItem(32));
876 // Simulate installing an item while dragging (or have it appear during sync).
877 model()->PopulateAppWithId(33);
878 // Item should go back to its position before the drag.
879 EXPECT_EQ(0u, GetPageIndexForItem(1));
880 EXPECT_EQ(1u, GetPageIndexForItem(31));
881 EXPECT_EQ(2u, GetPageIndexForItem(32));
882 // New item should appear at end.
883 EXPECT_EQ(2u, GetPageIndexForItem(33));
885 // Scroll to end again, and keep dragging (should be ignored).
886 [apps_grid_controller_ scrollToPage:2];
887 [drag_manager onMouseDragged:mouse_at_cell_0];
888 EXPECT_EQ(0u, GetPageIndexForItem(1));
889 [drag_manager onMouseUp:mouse_at_cell_0];
890 EXPECT_EQ(0u, GetPageIndexForItem(1));
893 // Test scrolling when dragging past edge or over the pager.
894 TEST_F(AppsGridControllerTest, ScrollingWhileDragging) {
895 base::scoped_nsobject<TestPaginationObserver> observer(
896 [[TestPaginationObserver alloc] init]);
897 [apps_grid_controller_ setPaginationObserver:observer];
899 ReplaceTestModel(kItemsPerPage * 3);
900 // Start on the middle page.
901 [apps_grid_controller_ scrollToPage:1];
902 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:1];
903 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
905 NSEvent* at_center = MouseEventForScroll([apps_grid_controller_ view], 0.0);
906 NSEvent* at_left = MouseEventForScroll([apps_grid_controller_ view], -1.1);
907 NSEvent* at_right = MouseEventForScroll([apps_grid_controller_ view], 1.1);
909 AppsCollectionViewDragManager* drag_manager =
910 [apps_grid_controller_ dragManager];
911 [drag_manager onMouseDownInPage:page
912 withEvent:mouse_at_cell_0];
913 [drag_manager onMouseDragged:at_center];
915 // Nothing should be scheduled: target page is visible page.
916 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
917 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
919 // Drag to the left, should go to first page and no further.
920 [drag_manager onMouseDragged:at_left];
921 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
922 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
923 [apps_grid_controller_ scrollToPage:0]; // Commit without timer for testing.
924 [drag_manager onMouseDragged:at_left];
925 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
926 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
928 // Drag to the right, should go to last page and no futher.
929 [drag_manager onMouseDragged:at_right];
930 EXPECT_EQ(0u, [apps_grid_controller_ visiblePage]);
931 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
932 [apps_grid_controller_ scrollToPage:1];
933 [drag_manager onMouseDragged:at_right];
934 EXPECT_EQ(1u, [apps_grid_controller_ visiblePage]);
935 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
936 [apps_grid_controller_ scrollToPage:2];
937 [drag_manager onMouseDragged:at_right];
938 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
939 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
941 // Simulate a hover over the first pager segment.
942 [observer setHoveredSegmentForTest:0];
943 [drag_manager onMouseDragged:at_center];
944 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
945 EXPECT_EQ(0u, [apps_grid_controller_ scheduledScrollPage]);
947 // Drag it back, should cancel schedule.
948 [observer setHoveredSegmentForTest:-1];
949 [drag_manager onMouseDragged:at_center];
950 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
951 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
953 // Hover again, now over middle segment, and ensure a release also cancels.
954 [observer setHoveredSegmentForTest:1];
955 [drag_manager onMouseDragged:at_center];
956 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
957 EXPECT_EQ(1u, [apps_grid_controller_ scheduledScrollPage]);
958 [drag_manager onMouseUp:at_center];
959 EXPECT_EQ(2u, [apps_grid_controller_ visiblePage]);
960 EXPECT_EQ(2u, [apps_grid_controller_ scheduledScrollPage]);
962 [apps_grid_controller_ setPaginationObserver:nil];
965 TEST_F(AppsGridControllerTest, ContextMenus) {
966 AppListItemWithMenu* item_two_model = new AppListItemWithMenu("Item Two");
967 model()->AddItem(new AppListItemWithMenu("Item One"));
968 model()->AddItem(item_two_model);
969 EXPECT_EQ(2u, [apps_grid_controller_ itemCount]);
971 NSCollectionView* page = [apps_grid_controller_ collectionViewAtPageIndex:0];
972 NSEvent* mouse_at_cell_0 = MouseEventInCell(page, 0);
973 NSEvent* mouse_at_cell_1 = MouseEventInCell(page, 1);
975 NSMenu* menu = [page menuForEvent:mouse_at_cell_0];
976 EXPECT_EQ(1, [menu numberOfItems]);
977 EXPECT_NSEQ(@"Menu For: Item One", [[menu itemAtIndex:0] title]);
979 // Test a context menu request while the item is still installing.
980 item_two_model->SetMenuReadyForTesting(false);
981 menu = [page menuForEvent:mouse_at_cell_1];
982 EXPECT_EQ(nil, menu);
984 item_two_model->SetMenuReadyForTesting(true);
985 menu = [page menuForEvent:mouse_at_cell_1];
986 EXPECT_EQ(1, [menu numberOfItems]);
987 EXPECT_NSEQ(@"Menu For: Item Two", [[menu itemAtIndex:0] title]);
989 // Test that a button being held down with the left button does not also show
991 [GetItemViewAt(0) highlight:YES];
992 EXPECT_FALSE([page menuForEvent:mouse_at_cell_0]);
993 [GetItemViewAt(0) highlight:NO];
994 EXPECT_TRUE([page menuForEvent:mouse_at_cell_0]);
998 } // namespace app_list