Home | History | Annotate | Download | only in views
      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     // 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);
     82 
     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);
     88 
     89     widget_->SetContentsView(main_view_);
     90   }
     91 
     92   virtual void TearDown() OVERRIDE {
     93     widget_->Close();
     94     views::ViewsTestBase::TearDown();
     95     delegate_.reset();
     96   }
     97 
     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);
    106       }
    107     }
    108 
    109     return NULL;
    110   }
    111 
    112   void SimulateClick(views::View* view) {
    113     gfx::Point center = view->GetLocalBounds().CenterPoint();
    114     view->OnMousePressed(ui::MouseEvent(ui::ET_MOUSE_PRESSED,
    115                                         center,
    116                                         center,
    117                                         ui::EF_LEFT_MOUSE_BUTTON,
    118                                         ui::EF_LEFT_MOUSE_BUTTON));
    119     view->OnMouseReleased(ui::MouseEvent(ui::ET_MOUSE_RELEASED,
    120                                          center,
    121                                          center,
    122                                          ui::EF_LEFT_MOUSE_BUTTON,
    123                                          ui::EF_LEFT_MOUSE_BUTTON));
    124   }
    125 
    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);
    131     DCHECK(view);
    132 
    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);
    137     return view;
    138   }
    139 
    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) {
    145     DCHECK(drag_view);
    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);
    150   }
    151 
    152   AppsGridView* RootGridView() {
    153     return main_view_->contents_view()->apps_container_view()->apps_grid_view();
    154   }
    155 
    156   AppListFolderView* FolderView() {
    157     return main_view_->contents_view()
    158         ->apps_container_view()
    159         ->app_list_folder_view();
    160   }
    161 
    162   AppsGridView* FolderGridView() { return FolderView()->items_grid_view(); }
    163 
    164   const views::ViewModel* RootViewModel() {
    165     return RootGridView()->view_model_for_test();
    166   }
    167 
    168   const views::ViewModel* FolderViewModel() {
    169     return FolderGridView()->view_model_for_test();
    170   }
    171 
    172   AppListItemView* CreateAndOpenSingleItemFolder() {
    173     // Prepare single folder with a single item in it.
    174     AppListFolderItem* folder_item =
    175         delegate_->GetTestModel()->CreateSingleItemFolder("single_item_folder",
    176                                                           "single");
    177     EXPECT_EQ(folder_item,
    178               delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
    179     EXPECT_EQ(AppListFolderItem::kItemType, folder_item->GetItemType());
    180 
    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);
    185 
    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());
    191 
    192 #if defined(OS_WIN)
    193     AppsGridViewTestApi folder_grid_view_test_api(FolderGridView());
    194     folder_grid_view_test_api.DisableSynchronousDrag();
    195 #endif
    196     return folder_item_view;
    197   }
    198 
    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());
    208 
    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();
    217 
    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());
    222 
    223     return dragged;
    224   }
    225 
    226  protected:
    227   views::Widget* widget_;  // Owned by native window.
    228   AppListMainView* main_view_;  // Owned by |widget_|.
    229   scoped_ptr<AppListTestViewDelegate> delegate_;
    230 
    231  private:
    232   DISALLOW_COPY_AND_ASSIGN(AppListMainViewTest);
    233 };
    234 
    235 }  // namespace
    236 
    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());
    241 
    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());
    245 
    246   const int kReplacementItems = 5;
    247   delegate_->ReplaceTestModel(kReplacementItems);
    248   main_view_->ModelChanged();
    249   EXPECT_EQ(kReplacementItems, RootViewModel()->view_size());
    250 }
    251 
    252 // Tests dragging an item out of a single item folder and drop it at the last
    253 // slot.
    254 TEST_F(AppListMainViewTest, DragLastItemFromFolderAndDropAtLastSlot) {
    255   AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
    256   const gfx::Rect first_slot_tile = folder_item_view->bounds();
    257 
    258   EXPECT_EQ(1, FolderViewModel()->view_size());
    259 
    260   AppListItemView* dragged = StartDragForReparent(0);
    261 
    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() * 2, 0);
    265   gfx::Point point = drop_target_tile.CenterPoint();
    266   SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
    267 
    268   // Drop it.
    269   FolderGridView()->EndDrag(false);
    270 
    271   // Folder icon view should be gone and there is only one item view.
    272   EXPECT_EQ(1, RootViewModel()->view_size());
    273   EXPECT_EQ(AppListItemView::kViewClassName,
    274             RootViewModel()->view_at(0)->GetClassName());
    275 
    276   // The item view should be in slot 1 instead of slot 2 where it is dropped.
    277   AppsGridViewTestApi root_grid_view_test_api(RootGridView());
    278   root_grid_view_test_api.LayoutToIdealBounds();
    279   EXPECT_EQ(first_slot_tile, RootViewModel()->view_at(0)->bounds());
    280 
    281   // Single item folder should be auto removed.
    282   EXPECT_EQ(NULL,
    283             delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
    284 }
    285 
    286 // Tests dragging an item out of a single item folder and dropping it onto the
    287 // page switcher. Regression test for http://crbug.com/415530/.
    288 TEST_F(AppListMainViewTest, DragReparentItemOntoPageSwitcher) {
    289   AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
    290   const gfx::Rect first_slot_tile = folder_item_view->bounds();
    291 
    292   delegate_->GetTestModel()->PopulateApps(20);
    293 
    294   EXPECT_EQ(1, FolderViewModel()->view_size());
    295   EXPECT_EQ(21, RootViewModel()->view_size());
    296 
    297   AppListItemView* dragged = StartDragForReparent(0);
    298 
    299   gfx::Rect main_view_bounds = main_view_->bounds();
    300   // Drag the reparent item to the page switcher.
    301   gfx::Point point =
    302       gfx::Point(main_view_bounds.width() / 2,
    303                  main_view_bounds.bottom() - first_slot_tile.height());
    304   SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
    305 
    306   // Drop it.
    307   FolderGridView()->EndDrag(false);
    308 
    309   // The folder should be destroyed.
    310   EXPECT_EQ(21, RootViewModel()->view_size());
    311   EXPECT_EQ(NULL,
    312             delegate_->GetTestModel()->FindFolderItem("single_item_folder"));
    313 }
    314 
    315 // Test that an interrupted drag while reparenting an item from a folder, when
    316 // canceled via the root grid, correctly forwards the cancelation to the drag
    317 // ocurring from the folder.
    318 TEST_F(AppListMainViewTest, MouseDragItemOutOfFolderWithCancel) {
    319   CreateAndOpenSingleItemFolder();
    320   AppListItemView* dragged = StartDragForReparent(0);
    321 
    322   // Now add an item to the model, not in any folder, e.g., as if by Sync.
    323   EXPECT_TRUE(RootGridView()->has_dragged_view());
    324   EXPECT_TRUE(FolderGridView()->has_dragged_view());
    325   delegate_->GetTestModel()->CreateAndAddItem("Extra");
    326 
    327   // The drag operation should get canceled.
    328   EXPECT_FALSE(RootGridView()->has_dragged_view());
    329   EXPECT_FALSE(FolderGridView()->has_dragged_view());
    330 
    331   // Additional mouse move operations should be ignored.
    332   gfx::Point point(1, 1);
    333   SimulateUpdateDrag(FolderGridView(), AppsGridView::MOUSE, dragged, point);
    334   EXPECT_FALSE(RootGridView()->has_dragged_view());
    335   EXPECT_FALSE(FolderGridView()->has_dragged_view());
    336 }
    337 
    338 }  // namespace test
    339 }  // namespace app_list
    340