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