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