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/test/app_list_test_model.h"
13 #include "ui/app_list/test/app_list_test_view_delegate.h"
14 #include "ui/app_list/views/app_list_folder_view.h"
15 #include "ui/app_list/views/app_list_item_view.h"
16 #include "ui/app_list/views/apps_container_view.h"
17 #include "ui/app_list/views/apps_grid_view.h"
18 #include "ui/app_list/views/contents_view.h"
19 #include "ui/app_list/views/test/apps_grid_view_test_api.h"
20 #include "ui/views/test/views_test_base.h"
21 #include "ui/views/view_model.h"
22 #include "ui/views/widget/widget.h"
29 const int kInitialItems
= 2;
31 class GridViewVisibleWaiter
{
33 explicit GridViewVisibleWaiter(AppsGridView
* grid_view
)
34 : grid_view_(grid_view
) {}
35 ~GridViewVisibleWaiter() {}
38 if (grid_view_
->visible())
41 check_timer_
.Start(FROM_HERE
,
42 base::TimeDelta::FromMilliseconds(50),
43 base::Bind(&GridViewVisibleWaiter::OnTimerCheck
,
44 base::Unretained(this)));
45 run_loop_
.reset(new base::RunLoop
);
52 if (grid_view_
->visible())
56 AppsGridView
* grid_view_
;
57 scoped_ptr
<base::RunLoop
> run_loop_
;
58 base::RepeatingTimer
<GridViewVisibleWaiter
> check_timer_
;
60 DISALLOW_COPY_AND_ASSIGN(GridViewVisibleWaiter
);
63 class AppListMainViewTest
: public views::ViewsTestBase
{
69 virtual ~AppListMainViewTest() {}
71 // testing::Test overrides:
72 virtual void SetUp() OVERRIDE
{
73 views::ViewsTestBase::SetUp();
74 delegate_
.reset(new AppListTestViewDelegate
);
76 // In Ash, the third argument is a container aura::Window, but it is always
77 // NULL on Windows, and not needed for tests. It is only used to determine
78 // the scale factor for preloading icons.
79 main_view_
= new AppListMainView(delegate_
.get(), 0, NULL
);
80 main_view_
->SetPaintToLayer(true);
81 main_view_
->model()->SetFoldersEnabled(true);
83 widget_
= new views::Widget
;
84 views::Widget::InitParams params
=
85 CreateParams(views::Widget::InitParams::TYPE_POPUP
);
86 params
.bounds
.set_size(main_view_
->GetPreferredSize());
87 widget_
->Init(params
);
89 widget_
->SetContentsView(main_view_
);
92 virtual void TearDown() OVERRIDE
{
94 views::ViewsTestBase::TearDown();
98 // |point| is in |grid_view|'s coordinates.
99 AppListItemView
* GetItemViewAtPointInGrid(AppsGridView
* grid_view
,
100 const gfx::Point
& point
) {
101 const views::ViewModel
* view_model
= grid_view
->view_model_for_test();
102 for (int i
= 0; i
< view_model
->view_size(); ++i
) {
103 views::View
* view
= view_model
->view_at(i
);
104 if (view
->bounds().Contains(point
)) {
105 return static_cast<AppListItemView
*>(view
);
112 void SimulateClick(views::View
* view
) {
113 gfx::Point center
= view
->GetLocalBounds().CenterPoint();
114 view
->OnMousePressed(ui::MouseEvent(ui::ET_MOUSE_PRESSED
,
117 ui::EF_LEFT_MOUSE_BUTTON
,
118 ui::EF_LEFT_MOUSE_BUTTON
));
119 view
->OnMouseReleased(ui::MouseEvent(ui::ET_MOUSE_RELEASED
,
122 ui::EF_LEFT_MOUSE_BUTTON
,
123 ui::EF_LEFT_MOUSE_BUTTON
));
126 // |point| is in |grid_view|'s coordinates.
127 AppListItemView
* SimulateInitiateDrag(AppsGridView
* grid_view
,
128 AppsGridView::Pointer pointer
,
129 const gfx::Point
& point
) {
130 AppListItemView
* view
= GetItemViewAtPointInGrid(grid_view
, point
);
133 gfx::Point translated
=
134 gfx::PointAtOffsetFromOrigin(point
- view
->bounds().origin());
135 ui::MouseEvent
pressed_event(ui::ET_MOUSE_PRESSED
, translated
, point
, 0, 0);
136 grid_view
->InitiateDrag(view
, pointer
, pressed_event
);
140 // |point| is in |grid_view|'s coordinates.
141 void SimulateUpdateDrag(AppsGridView
* grid_view
,
142 AppsGridView::Pointer pointer
,
143 AppListItemView
* drag_view
,
144 const gfx::Point
& point
) {
146 gfx::Point translated
=
147 gfx::PointAtOffsetFromOrigin(point
- drag_view
->bounds().origin());
148 ui::MouseEvent
drag_event(ui::ET_MOUSE_DRAGGED
, translated
, point
, 0, 0);
149 grid_view
->UpdateDragFromItem(pointer
, drag_event
);
152 AppsGridView
* RootGridView() {
153 return main_view_
->contents_view()->apps_container_view()->apps_grid_view();
156 AppListFolderView
* FolderView() {
157 return main_view_
->contents_view()
158 ->apps_container_view()
159 ->app_list_folder_view();
162 AppsGridView
* FolderGridView() { return FolderView()->items_grid_view(); }
164 const views::ViewModel
* RootViewModel() {
165 return RootGridView()->view_model_for_test();
168 const views::ViewModel
* FolderViewModel() {
169 return FolderGridView()->view_model_for_test();
172 AppListItemView
* CreateAndOpenSingleItemFolder() {
173 // Prepare single folder with a single item in it.
174 AppListFolderItem
* folder_item
=
175 delegate_
->GetTestModel()->CreateSingleItemFolder("single_item_folder",
177 EXPECT_EQ(folder_item
,
178 delegate_
->GetTestModel()->FindFolderItem("single_item_folder"));
179 EXPECT_EQ(AppListFolderItem::kItemType
, folder_item
->GetItemType());
181 EXPECT_EQ(1, RootViewModel()->view_size());
182 AppListItemView
* folder_item_view
=
183 static_cast<AppListItemView
*>(RootViewModel()->view_at(0));
184 EXPECT_EQ(folder_item_view
->item(), folder_item
);
186 // Click on the folder to open it.
187 EXPECT_FALSE(FolderView()->visible());
188 SimulateClick(folder_item_view
);
189 base::RunLoop().RunUntilIdle();
190 EXPECT_TRUE(FolderView()->visible());
193 AppsGridViewTestApi
folder_grid_view_test_api(FolderGridView());
194 folder_grid_view_test_api
.DisableSynchronousDrag();
196 return folder_item_view
;
199 AppListItemView
* StartDragForReparent(int index_in_folder
) {
200 // Start to drag the item in folder.
201 views::View
* item_view
= FolderViewModel()->view_at(index_in_folder
);
202 gfx::Point point
= item_view
->bounds().CenterPoint();
203 AppListItemView
* dragged
=
204 SimulateInitiateDrag(FolderGridView(), AppsGridView::MOUSE
, point
);
205 EXPECT_EQ(item_view
, dragged
);
206 EXPECT_FALSE(RootGridView()->visible());
207 EXPECT_TRUE(FolderView()->visible());
209 // Drag it to top left corner.
210 point
= gfx::Point(0, 0);
211 // Two update drags needed to actually drag the view. The first changes
212 // state and the 2nd one actually moves the view. The 2nd call can be
213 // removed when UpdateDrag is fixed.
214 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE
, dragged
, point
);
215 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE
, dragged
, point
);
216 base::RunLoop().RunUntilIdle();
218 // Wait until the folder view is invisible and root grid view shows up.
219 GridViewVisibleWaiter(RootGridView()).Wait();
220 EXPECT_TRUE(RootGridView()->visible());
221 EXPECT_EQ(0, FolderView()->layer()->opacity());
227 views::Widget
* widget_
; // Owned by native window.
228 AppListMainView
* main_view_
; // Owned by |widget_|.
229 scoped_ptr
<AppListTestViewDelegate
> delegate_
;
232 DISALLOW_COPY_AND_ASSIGN(AppListMainViewTest
);
237 // Tests changing the AppListModel when switching profiles.
238 TEST_F(AppListMainViewTest
, ModelChanged
) {
239 delegate_
->GetTestModel()->PopulateApps(kInitialItems
);
240 EXPECT_EQ(kInitialItems
, RootViewModel()->view_size());
242 // The model is owned by a profile keyed service, which is never destroyed
243 // until after profile switching.
244 scoped_ptr
<AppListModel
> old_model(delegate_
->ReleaseTestModel());
246 const int kReplacementItems
= 5;
247 delegate_
->ReplaceTestModel(kReplacementItems
);
248 main_view_
->ModelChanged();
249 EXPECT_EQ(kReplacementItems
, RootViewModel()->view_size());
252 // Tests dragging an item out of a single item folder and drop it at the last
254 TEST_F(AppListMainViewTest
, DragLastItemFromFolderAndDropAtLastSlot
) {
255 AppListItemView
* folder_item_view
= CreateAndOpenSingleItemFolder();
256 const gfx::Rect first_slot_tile
= folder_item_view
->bounds();
258 EXPECT_EQ(1, FolderViewModel()->view_size());
260 AppListItemView
* dragged
= StartDragForReparent(0);
262 // Drop it to the slot on the right of first slot.
263 gfx::Rect
drop_target_tile(first_slot_tile
);
264 drop_target_tile
.Offset(first_slot_tile
.width(), 0);
265 gfx::Point point
= drop_target_tile
.CenterPoint();
266 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE
, dragged
, point
);
267 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE
, dragged
, point
);
268 base::RunLoop().RunUntilIdle();
271 FolderGridView()->EndDrag(false);
272 base::RunLoop().RunUntilIdle();
274 // Folder icon view should be gone and there is only one item view.
275 EXPECT_EQ(1, RootViewModel()->view_size());
276 EXPECT_EQ(AppListItemView::kViewClassName
,
277 RootViewModel()->view_at(0)->GetClassName());
279 // The item view should be in slot 1 instead of slot 2 where it is dropped.
280 AppsGridViewTestApi
root_grid_view_test_api(RootGridView());
281 root_grid_view_test_api
.LayoutToIdealBounds();
282 EXPECT_EQ(first_slot_tile
, RootViewModel()->view_at(0)->bounds());
284 // Single item folder should be auto removed.
286 delegate_
->GetTestModel()->FindFolderItem("single_item_folder"));
289 // Test that an interrupted drag while reparenting an item from a folder, when
290 // canceled via the root grid, correctly forwards the cancelation to the drag
291 // ocurring from the folder.
292 TEST_F(AppListMainViewTest
, MouseDragItemOutOfFolderWithCancel
) {
293 CreateAndOpenSingleItemFolder();
294 AppListItemView
* dragged
= StartDragForReparent(0);
296 // Now add an item to the model, not in any folder, e.g., as if by Sync.
297 EXPECT_TRUE(RootGridView()->has_dragged_view());
298 EXPECT_TRUE(FolderGridView()->has_dragged_view());
299 delegate_
->GetTestModel()->CreateAndAddItem("Extra");
301 // The drag operation should get canceled.
302 EXPECT_FALSE(RootGridView()->has_dragged_view());
303 EXPECT_FALSE(FolderGridView()->has_dragged_view());
305 // Additional mouse move operations should be ignored.
306 gfx::Point
point(1, 1);
307 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE
, dragged
, point
);
308 EXPECT_FALSE(RootGridView()->has_dragged_view());
309 EXPECT_FALSE(FolderGridView()->has_dragged_view());
313 } // namespace app_list