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 "ui/app_list/views/app_list_main_view.h" 6 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" 23 24 namespace app_list { 25 namespace test { 26 27 namespace { 28 29 const int kInitialItems = 2; 30 31 class GridViewVisibleWaiter { 32 public: 33 explicit GridViewVisibleWaiter(AppsGridView* grid_view) 34 : grid_view_(grid_view) {} 35 ~GridViewVisibleWaiter() {} 36 37 void Wait() { 38 if (grid_view_->visible()) 39 return; 40 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); 46 run_loop_->Run(); 47 check_timer_.Stop(); 48 } 49 50 private: 51 void OnTimerCheck() { 52 if (grid_view_->visible()) 53 run_loop_->Quit(); 54 } 55 56 AppsGridView* grid_view_; 57 scoped_ptr<base::RunLoop> run_loop_; 58 base::RepeatingTimer<GridViewVisibleWaiter> check_timer_; 59 60 DISALLOW_COPY_AND_ASSIGN(GridViewVisibleWaiter); 61 }; 62 63 class AppListMainViewTest : public views::ViewsTestBase { 64 public: 65 AppListMainViewTest() 66 : widget_(NULL), 67 main_view_(NULL) {} 68 69 virtual ~AppListMainViewTest() {} 70 71 // testing::Test overrides: 72 virtual void SetUp() OVERRIDE { 73 views::ViewsTestBase::SetUp(); 74 delegate_.reset(new AppListTestViewDelegate); 75 76 main_view_ = new AppListMainView(delegate_.get(), 0, GetContext()); 77 main_view_->SetPaintToLayer(true); 78 main_view_->model()->SetFoldersEnabled(true); 79 80 widget_ = new views::Widget; 81 views::Widget::InitParams params = 82 CreateParams(views::Widget::InitParams::TYPE_POPUP); 83 params.bounds.set_size(main_view_->GetPreferredSize()); 84 widget_->Init(params); 85 86 widget_->SetContentsView(main_view_); 87 } 88 89 virtual void TearDown() OVERRIDE { 90 widget_->Close(); 91 views::ViewsTestBase::TearDown(); 92 delegate_.reset(); 93 } 94 95 // |point| is in |grid_view|'s coordinates. 96 AppListItemView* GetItemViewAtPointInGrid(AppsGridView* grid_view, 97 const gfx::Point& point) { 98 const views::ViewModel* view_model = grid_view->view_model_for_test(); 99 for (int i = 0; i < view_model->view_size(); ++i) { 100 views::View* view = view_model->view_at(i); 101 if (view->bounds().Contains(point)) { 102 return static_cast<AppListItemView*>(view); 103 } 104 } 105 106 return NULL; 107 } 108 109 void SimulateClick(views::View* view) { 110 gfx::Point center = view->GetLocalBounds().CenterPoint(); 111 view->OnMousePressed(ui::MouseEvent(ui::ET_MOUSE_PRESSED, 112 center, 113 center, 114 ui::EF_LEFT_MOUSE_BUTTON, 115 ui::EF_LEFT_MOUSE_BUTTON)); 116 view->OnMouseReleased(ui::MouseEvent(ui::ET_MOUSE_RELEASED, 117 center, 118 center, 119 ui::EF_LEFT_MOUSE_BUTTON, 120 ui::EF_LEFT_MOUSE_BUTTON)); 121 } 122 123 // |point| is in |grid_view|'s coordinates. 124 AppListItemView* SimulateInitiateDrag(AppsGridView* grid_view, 125 AppsGridView::Pointer pointer, 126 const gfx::Point& point) { 127 AppListItemView* view = GetItemViewAtPointInGrid(grid_view, point); 128 DCHECK(view); 129 130 gfx::Point translated = 131 gfx::PointAtOffsetFromOrigin(point - view->bounds().origin()); 132 ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, translated, point, 0, 0); 133 grid_view->InitiateDrag(view, pointer, pressed_event); 134 return view; 135 } 136 137 // |point| is in |grid_view|'s coordinates. 138 void SimulateUpdateDrag(AppsGridView* grid_view, 139 AppsGridView::Pointer pointer, 140 AppListItemView* drag_view, 141 const gfx::Point& point) { 142 DCHECK(drag_view); 143 gfx::Point translated = 144 gfx::PointAtOffsetFromOrigin(point - drag_view->bounds().origin()); 145 ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, translated, point, 0, 0); 146 grid_view->UpdateDragFromItem(pointer, drag_event); 147 } 148 149 AppsGridView* RootGridView() { 150 return main_view_->contents_view()->apps_container_view()->apps_grid_view(); 151 } 152 153 AppListFolderView* FolderView() { 154 return main_view_->contents_view() 155 ->apps_container_view() 156 ->app_list_folder_view(); 157 } 158 159 AppsGridView* FolderGridView() { return FolderView()->items_grid_view(); } 160 161 const views::ViewModel* RootViewModel() { 162 return RootGridView()->view_model_for_test(); 163 } 164 165 const views::ViewModel* FolderViewModel() { 166 return FolderGridView()->view_model_for_test(); 167 } 168 169 protected: 170 views::Widget* widget_; // Owned by native window. 171 AppListMainView* main_view_; // Owned by |widget_|. 172 scoped_ptr<AppListTestViewDelegate> delegate_; 173 174 private: 175 DISALLOW_COPY_AND_ASSIGN(AppListMainViewTest); 176 }; 177 178 } // namespace 179 180 // Tests changing the AppListModel when switching profiles. 181 TEST_F(AppListMainViewTest, ModelChanged) { 182 delegate_->GetTestModel()->PopulateApps(kInitialItems); 183 EXPECT_EQ(kInitialItems, RootViewModel()->view_size()); 184 185 // The model is owned by a profile keyed service, which is never destroyed 186 // until after profile switching. 187 scoped_ptr<AppListModel> old_model(delegate_->ReleaseTestModel()); 188 189 const int kReplacementItems = 5; 190 delegate_->ReplaceTestModel(kReplacementItems); 191 main_view_->ModelChanged(); 192 EXPECT_EQ(kReplacementItems, RootViewModel()->view_size()); 193 } 194 195 // Tests dragging an item out of a single item folder and drop it at the last 196 // slot. 197 TEST_F(AppListMainViewTest, DragLastItemFromFolderAndDropAtLastSlot) { 198 // Prepare single folder with a single item in it. 199 AppListFolderItem* folder_item = 200 delegate_->GetTestModel()->CreateSingleItemFolder("single_item_folder", 201 "single"); 202 EXPECT_EQ(folder_item, 203 delegate_->GetTestModel()->FindFolderItem("single_item_folder")); 204 EXPECT_EQ(AppListFolderItem::kItemType, folder_item->GetItemType()); 205 206 EXPECT_EQ(1, RootViewModel()->view_size()); 207 AppListItemView* folder_item_view = 208 static_cast<AppListItemView*>(RootViewModel()->view_at(0)); 209 EXPECT_EQ(folder_item_view->item(), folder_item); 210 const gfx::Rect first_slot_tile = folder_item_view->bounds(); 211 212 // Click on the folder to open it. 213 EXPECT_FALSE(FolderView()->visible()); 214 SimulateClick(folder_item_view); 215 base::RunLoop().RunUntilIdle(); 216 EXPECT_TRUE(FolderView()->visible()); 217 218 #if defined(OS_WIN) 219 AppsGridViewTestApi folder_grid_view_test_api(FolderGridView()); 220 folder_grid_view_test_api.DisableSynchronousDrag(); 221 #endif 222 223 // Start to drag the item in folder. 224 EXPECT_EQ(1, FolderViewModel()->view_size()); 225 views::View* item_view = FolderViewModel()->view_at(0); 226 gfx::Point point = item_view->bounds().CenterPoint(); 227 AppListItemView* dragged = 228 SimulateInitiateDrag(FolderGridView(), AppsGridView::MOUSE, point); 229 EXPECT_EQ(item_view, dragged); 230 EXPECT_FALSE(RootGridView()->visible()); 231 EXPECT_TRUE(FolderView()->visible()); 232 233 // Drag it to top left corner. 234 point = gfx::Point(0, 0); 235 // Two update drags needed to actually drag the view. The first changes state 236 // and the 2nd one actually moves the view. The 2nd call can be removed when 237 // UpdateDrag is fixed. 238 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point); 239 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point); 240 base::RunLoop().RunUntilIdle(); 241 242 // Wait until the folder view is invisible and root grid view shows up. 243 GridViewVisibleWaiter(RootGridView()).Wait(); 244 EXPECT_TRUE(RootGridView()->visible()); 245 EXPECT_EQ(0, FolderView()->layer()->opacity()); 246 247 // Drop it to the slot on the right of first slot. 248 gfx::Rect drop_target_tile(first_slot_tile); 249 drop_target_tile.Offset(first_slot_tile.width(), 0); 250 point = drop_target_tile.CenterPoint(); 251 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point); 252 SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point); 253 base::RunLoop().RunUntilIdle(); 254 255 // Drop it. 256 FolderGridView()->EndDrag(false); 257 base::RunLoop().RunUntilIdle(); 258 259 // Folder icon view should be gone and there is only one item view. 260 EXPECT_EQ(1, RootViewModel()->view_size()); 261 EXPECT_EQ(AppListItemView::kViewClassName, 262 RootViewModel()->view_at(0)->GetClassName()); 263 264 // The item view should be in slot 1 instead of slot 2 where it is dropped. 265 AppsGridViewTestApi root_grid_view_test_api(RootGridView()); 266 root_grid_view_test_api.LayoutToIdealBounds(); 267 EXPECT_EQ(first_slot_tile, RootViewModel()->view_at(0)->bounds()); 268 269 // Single item folder should be auto removed. 270 EXPECT_EQ(NULL, 271 delegate_->GetTestModel()->FindFolderItem("single_item_folder")); 272 } 273 274 } // namespace test 275 } // namespace app_list 276