Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / app_list / views / app_list_main_view_unittest.cc
blobaaae0b90ff8137262daeab13ff5709cba174ae16
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 "ui/app_list/views/app_list_main_view.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/run_loop.h"
9 #include "base/time/time.h"
10 #include "base/timer/timer.h"
11 #include "testing/gtest/include/gtest/gtest.h"
12 #include "ui/app_list/app_list_switches.h"
13 #include "ui/app_list/test/app_list_test_model.h"
14 #include "ui/app_list/test/app_list_test_view_delegate.h"
15 #include "ui/app_list/views/app_list_folder_view.h"
16 #include "ui/app_list/views/app_list_item_view.h"
17 #include "ui/app_list/views/apps_container_view.h"
18 #include "ui/app_list/views/apps_grid_view.h"
19 #include "ui/app_list/views/contents_view.h"
20 #include "ui/app_list/views/search_box_view.h"
21 #include "ui/app_list/views/test/apps_grid_view_test_api.h"
22 #include "ui/events/event_utils.h"
23 #include "ui/events/test/event_generator.h"
24 #include "ui/views/test/views_test_base.h"
25 #include "ui/views/view_model.h"
26 #include "ui/views/widget/widget.h"
28 namespace app_list {
29 namespace test {
31 namespace {
33 const int kInitialItems = 2;
35 class GridViewVisibleWaiter {
36 public:
37 explicit GridViewVisibleWaiter(AppsGridView* grid_view)
38 : grid_view_(grid_view) {}
39 ~GridViewVisibleWaiter() {}
41 void Wait() {
42 if (grid_view_->visible())
43 return;
45 check_timer_.Start(FROM_HERE,
46 base::TimeDelta::FromMilliseconds(50),
47 base::Bind(&GridViewVisibleWaiter::OnTimerCheck,
48 base::Unretained(this)));
49 run_loop_.reset(new base::RunLoop);
50 run_loop_->Run();
51 check_timer_.Stop();
54 private:
55 void OnTimerCheck() {
56 if (grid_view_->visible())
57 run_loop_->Quit();
60 AppsGridView* grid_view_;
61 scoped_ptr<base::RunLoop> run_loop_;
62 base::RepeatingTimer<GridViewVisibleWaiter> check_timer_;
64 DISALLOW_COPY_AND_ASSIGN(GridViewVisibleWaiter);
67 class AppListMainViewTest : public views::ViewsTestBase {
68 public:
69 AppListMainViewTest()
70 : main_widget_(nullptr),
71 main_view_(nullptr),
72 search_box_widget_(nullptr),
73 search_box_view_(nullptr) {}
75 ~AppListMainViewTest() override {}
77 // testing::Test overrides:
78 void SetUp() override {
79 views::ViewsTestBase::SetUp();
80 delegate_.reset(new AppListTestViewDelegate);
82 // In Ash, the third argument is a container aura::Window, but it is always
83 // NULL on Windows, and not needed for tests. It is only used to determine
84 // the scale factor for preloading icons.
85 main_view_ = new AppListMainView(delegate_.get());
86 main_view_->SetPaintToLayer(true);
87 main_view_->model()->SetFoldersEnabled(true);
88 search_box_view_ = new SearchBoxView(main_view_, delegate_.get());
89 main_view_->Init(nullptr, 0, search_box_view_);
91 main_widget_ = new views::Widget;
92 views::Widget::InitParams main_widget_params =
93 CreateParams(views::Widget::InitParams::TYPE_POPUP);
94 main_widget_params.bounds.set_size(main_view_->GetPreferredSize());
95 main_widget_->Init(main_widget_params);
96 main_widget_->SetContentsView(main_view_);
98 search_box_widget_ = new views::Widget;
99 views::Widget::InitParams search_box_widget_params =
100 CreateParams(views::Widget::InitParams::TYPE_CONTROL);
101 search_box_widget_params.parent = main_widget_->GetNativeView();
102 search_box_widget_params.opacity =
103 views::Widget::InitParams::TRANSLUCENT_WINDOW;
104 search_box_widget_->Init(search_box_widget_params);
105 search_box_widget_->SetContentsView(search_box_view_);
108 void TearDown() override {
109 main_widget_->Close();
110 views::ViewsTestBase::TearDown();
111 delegate_.reset();
114 // |point| is in |grid_view|'s coordinates.
115 AppListItemView* GetItemViewAtPointInGrid(AppsGridView* grid_view,
116 const gfx::Point& point) {
117 const views::ViewModelT<AppListItemView>* view_model =
118 grid_view->view_model_for_test();
119 for (int i = 0; i < view_model->view_size(); ++i) {
120 views::View* view = view_model->view_at(i);
121 if (view->bounds().Contains(point)) {
122 return static_cast<AppListItemView*>(view);
126 return NULL;
129 void SimulateClick(views::View* view) {
130 gfx::Point center = view->GetLocalBounds().CenterPoint();
131 view->OnMousePressed(ui::MouseEvent(
132 ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(),
133 ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
134 view->OnMouseReleased(ui::MouseEvent(
135 ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(),
136 ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
139 // |point| is in |grid_view|'s coordinates.
140 AppListItemView* SimulateInitiateDrag(AppsGridView* grid_view,
141 AppsGridView::Pointer pointer,
142 const gfx::Point& point) {
143 AppListItemView* view = GetItemViewAtPointInGrid(grid_view, point);
144 DCHECK(view);
146 gfx::Point translated =
147 gfx::PointAtOffsetFromOrigin(point - view->bounds().origin());
148 ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, translated, point,
149 ui::EventTimeForNow(), 0, 0);
150 grid_view->InitiateDrag(view, pointer, pressed_event);
151 return view;
154 // |point| is in |grid_view|'s coordinates.
155 void SimulateUpdateDrag(AppsGridView* grid_view,
156 AppsGridView::Pointer pointer,
157 AppListItemView* drag_view,
158 const gfx::Point& point) {
159 DCHECK(drag_view);
160 gfx::Point translated =
161 gfx::PointAtOffsetFromOrigin(point - drag_view->bounds().origin());
162 ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, translated, point,
163 ui::EventTimeForNow(), 0, 0);
164 grid_view->UpdateDragFromItem(pointer, drag_event);
167 ContentsView* GetContentsView() { return main_view_->contents_view(); }
169 AppsGridView* RootGridView() {
170 return GetContentsView()->apps_container_view()->apps_grid_view();
173 AppListFolderView* FolderView() {
174 return GetContentsView()->apps_container_view()->app_list_folder_view();
177 AppsGridView* FolderGridView() { return FolderView()->items_grid_view(); }
179 const views::ViewModelT<AppListItemView>* RootViewModel() {
180 return RootGridView()->view_model_for_test();
183 const views::ViewModelT<AppListItemView>* FolderViewModel() {
184 return FolderGridView()->view_model_for_test();
187 AppListItemView* CreateAndOpenSingleItemFolder() {
188 // Prepare single folder with a single item in it.
189 AppListFolderItem* folder_item =
190 delegate_->GetTestModel()->CreateSingleItemFolder("single_item_folder",
191 "single");
192 EXPECT_EQ(folder_item,
193 delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
194 EXPECT_EQ(AppListFolderItem::kItemType, folder_item->GetItemType());
196 EXPECT_EQ(1, RootViewModel()->view_size());
197 AppListItemView* folder_item_view =
198 static_cast<AppListItemView*>(RootViewModel()->view_at(0));
199 EXPECT_EQ(folder_item_view->item(), folder_item);
201 // Click on the folder to open it.
202 EXPECT_FALSE(FolderView()->visible());
203 SimulateClick(folder_item_view);
204 base::RunLoop().RunUntilIdle();
205 EXPECT_TRUE(FolderView()->visible());
207 #if defined(OS_WIN)
208 AppsGridViewTestApi folder_grid_view_test_api(FolderGridView());
209 folder_grid_view_test_api.DisableSynchronousDrag();
210 #endif
211 return folder_item_view;
214 AppListItemView* StartDragForReparent(int index_in_folder) {
215 // Start to drag the item in folder.
216 views::View* item_view = FolderViewModel()->view_at(index_in_folder);
217 gfx::Point point = item_view->bounds().CenterPoint();
218 AppListItemView* dragged =
219 SimulateInitiateDrag(FolderGridView(), AppsGridView::MOUSE, point);
220 EXPECT_EQ(item_view, dragged);
221 EXPECT_FALSE(RootGridView()->visible());
222 EXPECT_TRUE(FolderView()->visible());
224 // Drag it to top left corner.
225 point = gfx::Point(0, 0);
226 // Two update drags needed to actually drag the view. The first changes
227 // state and the 2nd one actually moves the view. The 2nd call can be
228 // removed when UpdateDrag is fixed.
229 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
230 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
231 base::RunLoop().RunUntilIdle();
233 // Wait until the folder view is invisible and root grid view shows up.
234 GridViewVisibleWaiter(RootGridView()).Wait();
235 EXPECT_TRUE(RootGridView()->visible());
236 EXPECT_EQ(0, FolderView()->layer()->opacity());
238 return dragged;
241 protected:
242 views::Widget* main_widget_; // Owned by native window.
243 AppListMainView* main_view_; // Owned by |main_widget_|.
244 scoped_ptr<AppListTestViewDelegate> delegate_;
245 views::Widget* search_box_widget_; // Owned by |main_widget_|.
246 SearchBoxView* search_box_view_; // Owned by |search_box_widget_|.
248 private:
249 DISALLOW_COPY_AND_ASSIGN(AppListMainViewTest);
252 } // namespace
254 // Tests changing the AppListModel when switching profiles.
255 TEST_F(AppListMainViewTest, ModelChanged) {
256 delegate_->GetTestModel()->PopulateApps(kInitialItems);
257 EXPECT_EQ(kInitialItems, RootViewModel()->view_size());
259 // The model is owned by a profile keyed service, which is never destroyed
260 // until after profile switching.
261 scoped_ptr<AppListModel> old_model(delegate_->ReleaseTestModel());
263 const int kReplacementItems = 5;
264 delegate_->ReplaceTestModel(kReplacementItems);
265 main_view_->ModelChanged();
266 EXPECT_EQ(kReplacementItems, RootViewModel()->view_size());
269 // Tests that mouse hovering over an app item highlights it
270 TEST_F(AppListMainViewTest, MouseHoverToHighlight) {
271 delegate_->GetTestModel()->PopulateApps(2);
272 main_widget_->Show();
274 ui::test::EventGenerator generator(GetContext(),
275 main_widget_->GetNativeWindow());
276 AppListItemView* item0 = RootViewModel()->view_at(0);
277 AppListItemView* item1 = RootViewModel()->view_at(1);
279 // If experimental launcher, switch to All Apps page
280 if (app_list::switches::IsExperimentalAppListEnabled()) {
281 GetContentsView()->SetActiveState(AppListModel::STATE_APPS);
282 GetContentsView()->Layout();
285 generator.MoveMouseTo(item0->GetBoundsInScreen().CenterPoint());
286 EXPECT_TRUE(item0->is_highlighted());
287 EXPECT_FALSE(item1->is_highlighted());
289 generator.MoveMouseTo(item1->GetBoundsInScreen().CenterPoint());
290 EXPECT_FALSE(item0->is_highlighted());
291 EXPECT_TRUE(item1->is_highlighted());
293 generator.MoveMouseTo(gfx::Point(-1, -1));
294 EXPECT_FALSE(item0->is_highlighted());
295 EXPECT_FALSE(item1->is_highlighted());
298 // No touch on desktop Mac. Tracked in http://crbug.com/445520.
299 #if defined(OS_MACOSX) && !defined(USE_AURA)
300 #define MAYBE_TapGestureToHighlight DISABLED_TapGestureToHighlight
301 #else
302 #define MAYBE_TapGestureToHighlight TapGestureToHighlight
303 #endif
305 // Tests that tap gesture on app item highlights it
306 TEST_F(AppListMainViewTest, MAYBE_TapGestureToHighlight) {
307 delegate_->GetTestModel()->PopulateApps(1);
308 main_widget_->Show();
310 ui::test::EventGenerator generator(GetContext(),
311 main_widget_->GetNativeWindow());
312 AppListItemView* item = RootViewModel()->view_at(0);
314 // If experimental launcher, switch to All Apps page
315 if (app_list::switches::IsExperimentalAppListEnabled()) {
316 GetContentsView()->SetActiveState(AppListModel::STATE_APPS);
317 GetContentsView()->Layout();
320 generator.set_current_location(item->GetBoundsInScreen().CenterPoint());
321 generator.PressTouch();
322 EXPECT_TRUE(item->is_highlighted());
324 generator.ReleaseTouch();
325 EXPECT_FALSE(item->is_highlighted());
328 // Tests dragging an item out of a single item folder and drop it at the last
329 // slot.
330 TEST_F(AppListMainViewTest, DragLastItemFromFolderAndDropAtLastSlot) {
331 AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
332 const gfx::Rect first_slot_tile = folder_item_view->bounds();
334 EXPECT_EQ(1, FolderViewModel()->view_size());
336 AppListItemView* dragged = StartDragForReparent(0);
338 // Drop it to the slot on the right of first slot.
339 gfx::Rect drop_target_tile(first_slot_tile);
340 drop_target_tile.Offset(first_slot_tile.width() * 2, 0);
341 gfx::Point point = drop_target_tile.CenterPoint();
342 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
344 // Drop it.
345 FolderGridView()->EndDrag(false);
347 // Folder icon view should be gone and there is only one item view.
348 EXPECT_EQ(1, RootViewModel()->view_size());
349 EXPECT_EQ(
350 AppListItemView::kViewClassName,
351 static_cast<views::View*>(RootViewModel()->view_at(0))->GetClassName());
353 // The item view should be in slot 1 instead of slot 2 where it is dropped.
354 AppsGridViewTestApi root_grid_view_test_api(RootGridView());
355 root_grid_view_test_api.LayoutToIdealBounds();
356 EXPECT_EQ(first_slot_tile, RootViewModel()->view_at(0)->bounds());
358 // Single item folder should be auto removed.
359 EXPECT_EQ(NULL,
360 delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
362 // Ensure keyboard selection works on the root grid view after a reparent.
363 // This is a regression test for https://crbug.com/466058.
364 ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_RIGHT, ui::EF_NONE);
365 GetContentsView()->apps_container_view()->OnKeyPressed(key_event);
367 EXPECT_TRUE(RootGridView()->has_selected_view());
368 EXPECT_FALSE(FolderGridView()->has_selected_view());
371 // Tests dragging an item out of a single item folder and dropping it onto the
372 // page switcher. Regression test for http://crbug.com/415530/.
373 TEST_F(AppListMainViewTest, DragReparentItemOntoPageSwitcher) {
374 // Number of apps to populate. Should provide more than 1 page of apps (6*4 =
375 // 24).
376 const int kNumApps = 30;
378 // Ensure we are on the apps grid view page.
379 app_list::ContentsView* contents_view = GetContentsView();
380 contents_view->SetActiveState(AppListModel::STATE_APPS);
381 contents_view->Layout();
383 AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
384 const gfx::Rect first_slot_tile = folder_item_view->bounds();
386 delegate_->GetTestModel()->PopulateApps(kNumApps);
388 EXPECT_EQ(1, FolderViewModel()->view_size());
389 EXPECT_EQ(kNumApps + 1, RootViewModel()->view_size());
391 AppListItemView* dragged = StartDragForReparent(0);
393 gfx::Rect grid_view_bounds = RootGridView()->bounds();
394 // Drag the reparent item to the page switcher.
395 gfx::Point point =
396 gfx::Point(grid_view_bounds.width() / 2,
397 grid_view_bounds.bottom() - first_slot_tile.height());
398 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
400 // Drop it.
401 FolderGridView()->EndDrag(false);
403 // The folder should be destroyed.
404 EXPECT_EQ(kNumApps + 1, RootViewModel()->view_size());
405 EXPECT_EQ(NULL,
406 delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
409 // Test that an interrupted drag while reparenting an item from a folder, when
410 // canceled via the root grid, correctly forwards the cancelation to the drag
411 // ocurring from the folder.
412 TEST_F(AppListMainViewTest, MouseDragItemOutOfFolderWithCancel) {
413 CreateAndOpenSingleItemFolder();
414 AppListItemView* dragged = StartDragForReparent(0);
416 // Now add an item to the model, not in any folder, e.g., as if by Sync.
417 EXPECT_TRUE(RootGridView()->has_dragged_view());
418 EXPECT_TRUE(FolderGridView()->has_dragged_view());
419 delegate_->GetTestModel()->CreateAndAddItem("Extra");
421 // The drag operation should get canceled.
422 EXPECT_FALSE(RootGridView()->has_dragged_view());
423 EXPECT_FALSE(FolderGridView()->has_dragged_view());
425 // Additional mouse move operations should be ignored.
426 gfx::Point point(1, 1);
427 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
428 EXPECT_FALSE(RootGridView()->has_dragged_view());
429 EXPECT_FALSE(FolderGridView()->has_dragged_view());
432 // Test that dragging an app out of a single item folder and reparenting it
433 // back into its original folder results in a cancelled reparent. This is a
434 // regression test for http://crbug.com/429083.
435 TEST_F(AppListMainViewTest, ReparentSingleItemOntoSelf) {
436 // Add a folder with 1 item.
437 AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
438 std::string folder_id = folder_item_view->item()->id();
440 // Add another top level app.
441 delegate_->GetTestModel()->PopulateApps(1);
442 gfx::Point drag_point = folder_item_view->bounds().CenterPoint();
444 views::View::ConvertPointToTarget(RootGridView(), FolderGridView(),
445 &drag_point);
447 AppListItemView* dragged = StartDragForReparent(0);
449 // Drag the reparent item back into its folder.
450 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged,
451 drag_point);
452 FolderGridView()->EndDrag(false);
454 // The app list model should remain unchanged.
455 EXPECT_EQ(1, FolderViewModel()->view_size());
456 EXPECT_EQ(2, RootViewModel()->view_size());
457 EXPECT_EQ(folder_id, RootGridView()->GetItemViewAt(0)->item()->id());
458 EXPECT_NE(nullptr,
459 delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
462 } // namespace test
463 } // namespace app_list