app_list_main_view_unittest.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. #include "ash/app_list/views/app_list_main_view.h"
  5. #include <memory>
  6. #include <string>
  7. #include "ash/app_list/app_list_model_provider.h"
  8. #include "ash/app_list/model/app_list_test_model.h"
  9. #include "ash/app_list/test/app_list_test_helper.h"
  10. #include "ash/app_list/views/app_list_folder_view.h"
  11. #include "ash/app_list/views/app_list_item_view.h"
  12. #include "ash/app_list/views/app_list_view.h"
  13. #include "ash/app_list/views/apps_container_view.h"
  14. #include "ash/app_list/views/apps_grid_view.h"
  15. #include "ash/app_list/views/contents_view.h"
  16. #include "ash/app_list/views/page_switcher.h"
  17. #include "ash/app_list/views/paged_apps_grid_view.h"
  18. #include "ash/app_list/views/search_box_view.h"
  19. #include "ash/constants/ash_features.h"
  20. #include "ash/shell.h"
  21. #include "ash/test/ash_test_base.h"
  22. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  23. #include "base/test/scoped_feature_list.h"
  24. #include "ui/compositor/layer.h"
  25. #include "ui/events/base_event_utils.h"
  26. #include "ui/events/keycodes/keyboard_codes_posix.h"
  27. #include "ui/events/types/event_type.h"
  28. #include "ui/views/controls/textfield/textfield.h"
  29. #include "ui/views/test/button_test_api.h"
  30. #include "ui/views/view_model.h"
  31. namespace ash {
  32. // Parameterized by ProductivityLauncher.
  33. class AppListMainViewTest : public AshTestBase,
  34. public testing::WithParamInterface<bool> {
  35. public:
  36. AppListMainViewTest() {
  37. feature_list_.InitWithFeatureState(features::kProductivityLauncher,
  38. GetParam());
  39. }
  40. AppListMainViewTest(const AppListMainViewTest& other) = delete;
  41. AppListMainViewTest& operator=(const AppListMainViewTest& other) = delete;
  42. ~AppListMainViewTest() override = default;
  43. // testing::Test overrides:
  44. void SetUp() override {
  45. AshTestBase::SetUp();
  46. // Create and show the app list in fullscreen apps grid state.
  47. auto* helper = GetAppListTestHelper();
  48. if (features::IsProductivityLauncherEnabled()) {
  49. // Tablet mode uses a fullscreen AppListMainView.
  50. Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  51. } else {
  52. helper->Show(GetPrimaryDisplay().id());
  53. helper->GetAppListView()->SetState(AppListViewState::kFullscreenAllApps);
  54. }
  55. app_list_view_ = helper->GetAppListView();
  56. }
  57. test::AppListTestModel* GetTestModel() {
  58. return GetAppListTestHelper()->model();
  59. }
  60. // |point| is in |grid_view|'s coordinates.
  61. AppListItemView* GetItemViewAtPointInGrid(AppsGridView* grid_view,
  62. const gfx::Point& point) {
  63. const auto& entries = grid_view->view_model()->entries();
  64. const auto iter = std::find_if(
  65. entries.begin(), entries.end(), [&point](const auto& entry) {
  66. return entry.view->bounds().Contains(point);
  67. });
  68. return iter == entries.end() ? nullptr
  69. : static_cast<AppListItemView*>(iter->view);
  70. }
  71. // |point| is in |grid_view|'s coordinates.
  72. AppListItemView* SimulateInitiateDrag(AppsGridView* grid_view,
  73. const gfx::Point& point) {
  74. AppListItemView* view = GetItemViewAtPointInGrid(grid_view, point);
  75. DCHECK(view);
  76. // NOTE: Assumes that the app list view window bounds match the root window
  77. // bounds.
  78. gfx::Point root_window_point = point;
  79. views::View::ConvertPointToWidget(grid_view, &root_window_point);
  80. view->InitiateDrag(point, root_window_point);
  81. return view;
  82. }
  83. // |point| is in |grid_view|'s coordinates.
  84. void SimulateUpdateDrag(AppsGridView* grid_view,
  85. AppsGridView::Pointer pointer,
  86. AppListItemView* drag_view,
  87. const gfx::Point& point) {
  88. DCHECK(drag_view);
  89. // NOTE: Assumes that the app list view window bounds match the root window
  90. // bounds.
  91. gfx::Point root_window_point = point;
  92. views::View::ConvertPointToWidget(grid_view, &root_window_point);
  93. ui::MouseEvent drag_event(ui::ET_MOUSE_DRAGGED, point, root_window_point,
  94. ui::EventTimeForNow(), 0, 0);
  95. grid_view->UpdateDragFromItem(pointer, drag_event);
  96. }
  97. AppListMainView* main_view() { return app_list_view_->app_list_main_view(); }
  98. ContentsView* contents_view() { return main_view()->contents_view(); }
  99. SearchBoxView* search_box_view() { return main_view()->search_box_view(); }
  100. AppsGridView* GetRootGridView() {
  101. return contents_view()->apps_container_view()->apps_grid_view();
  102. }
  103. AppListFolderView* GetFolderView() {
  104. return contents_view()->apps_container_view()->app_list_folder_view();
  105. }
  106. PageSwitcher* GetPageSwitcherView() {
  107. return contents_view()->apps_container_view()->page_switcher();
  108. }
  109. AppsGridView* GetFolderGridView() {
  110. return GetFolderView()->items_grid_view();
  111. }
  112. const views::ViewModelT<AppListItemView>* GetRootViewModel() {
  113. return GetRootGridView()->view_model();
  114. }
  115. const views::ViewModelT<AppListItemView>* GetFolderViewModel() {
  116. return GetFolderGridView()->view_model();
  117. }
  118. AppListItemView* CreateAndOpenSingleItemFolder() {
  119. // Prepare single folder with a single item in it.
  120. AppListFolderItem* folder_item =
  121. GetTestModel()->CreateSingleItemFolder("single_item_folder", "single");
  122. GetRootGridView()->Layout();
  123. EXPECT_EQ(folder_item,
  124. GetTestModel()->FindFolderItem("single_item_folder"));
  125. EXPECT_EQ(AppListFolderItem::kItemType, folder_item->GetItemType());
  126. EXPECT_EQ(1u, GetRootViewModel()->view_size());
  127. AppListItemView* folder_item_view =
  128. static_cast<AppListItemView*>(GetRootViewModel()->view_at(0));
  129. EXPECT_EQ(folder_item_view->item(), folder_item);
  130. // Click on the folder to open it.
  131. EXPECT_FALSE(GetFolderView()->GetVisible());
  132. LeftClickOn(folder_item_view);
  133. EXPECT_TRUE(GetFolderView()->GetVisible());
  134. return folder_item_view;
  135. }
  136. AppListItemView* StartDragForReparent(int index_in_folder) {
  137. // Start to drag the item in folder.
  138. views::View* item_view = GetFolderViewModel()->view_at(index_in_folder);
  139. AppListItemView* dragged = SimulateInitiateDrag(
  140. GetFolderGridView(), item_view->bounds().CenterPoint());
  141. EXPECT_EQ(item_view, dragged);
  142. EXPECT_TRUE(GetRootGridView()->GetVisible());
  143. EXPECT_TRUE(GetFolderView()->GetVisible());
  144. // Drag the item completely outside the folder bounds.
  145. gfx::Point drag_target = gfx::Point(-(item_view->width() + 1) / 2,
  146. -(item_view->height() + 1) / 2);
  147. // Two update drags needed to actually drag the view. The first changes
  148. // state and the 2nd one actually moves the view. The 2nd call can be
  149. // removed when UpdateDrag is fixed.
  150. SimulateUpdateDrag(GetFolderGridView(), AppsGridView::MOUSE, dragged,
  151. drag_target);
  152. SimulateUpdateDrag(GetFolderGridView(), AppsGridView::MOUSE, dragged,
  153. drag_target);
  154. // Fire reparent timer, which should start when the item exits the folder
  155. // bounds. The timer closes the folder view.
  156. EXPECT_TRUE(GetFolderGridView()->FireFolderItemReparentTimerForTest());
  157. // Note: the folder item is expected to remain visible so it keeps getting
  158. // drag events, but it should become completely transparent.
  159. EXPECT_TRUE(GetFolderView()->GetVisible());
  160. EXPECT_EQ(0.0f, GetFolderGridView()->layer()->opacity());
  161. return dragged;
  162. }
  163. void ClickButton(views::Button* button) {
  164. views::test::ButtonTestApi(button).NotifyClick(ui::MouseEvent(
  165. ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), base::TimeTicks(),
  166. ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
  167. }
  168. protected:
  169. base::test::ScopedFeatureList feature_list_;
  170. AppListView* app_list_view_ = nullptr; // Owned by native widget.
  171. };
  172. INSTANTIATE_TEST_SUITE_P(ProductivityLauncher,
  173. AppListMainViewTest,
  174. testing::Bool());
  175. // Tests that the close button becomes invisible after close button is clicked.
  176. TEST_P(AppListMainViewTest, CloseButtonInvisibleAfterCloseButtonClicked) {
  177. PressAndReleaseKey(ui::VKEY_A);
  178. ClickButton(search_box_view()->close_button());
  179. EXPECT_FALSE(search_box_view()->close_button()->GetVisible());
  180. }
  181. // Tests that the search box becomes empty after close button is clicked.
  182. TEST_P(AppListMainViewTest, SearchBoxEmptyAfterCloseButtonClicked) {
  183. PressAndReleaseKey(ui::VKEY_A);
  184. ClickButton(search_box_view()->close_button());
  185. EXPECT_TRUE(search_box_view()->search_box()->GetText().empty());
  186. }
  187. // Tests that the search box is no longer active after close button is clicked.
  188. TEST_P(AppListMainViewTest, SearchBoxActiveAfterCloseButtonClicked) {
  189. PressAndReleaseKey(ui::VKEY_A);
  190. ClickButton(search_box_view()->close_button());
  191. EXPECT_FALSE(search_box_view()->is_search_box_active());
  192. }
  193. // Tests changing the AppListModel when switching profiles.
  194. TEST_P(AppListMainViewTest, ModelChanged) {
  195. const size_t kInitialItems = 2;
  196. GetTestModel()->PopulateApps(kInitialItems);
  197. EXPECT_EQ(kInitialItems, GetRootViewModel()->view_size());
  198. AppListModel* old_model = GetAppListTestHelper()->model();
  199. SearchModel* old_search_model = GetAppListTestHelper()->search_model();
  200. // Simulate a profile switch (which switches the app list models).
  201. auto search_model = std::make_unique<SearchModel>();
  202. auto model = std::make_unique<test::AppListTestModel>();
  203. const size_t kReplacementItems = 5;
  204. model->PopulateApps(kReplacementItems);
  205. AppListModelProvider::Get()->SetActiveModel(model.get(), search_model.get());
  206. EXPECT_EQ(kReplacementItems, GetRootViewModel()->view_size());
  207. // Replace the old model so observers on `model` are removed before test
  208. // shutdown.
  209. AppListModelProvider::Get()->SetActiveModel(old_model, old_search_model);
  210. }
  211. // Tests dragging an item out of a single item folder and dropping it onto the
  212. // page switcher. Regression test for http://crbug.com/415530/.
  213. TEST_P(AppListMainViewTest, DragReparentItemOntoPageSwitcher) {
  214. // This test is flaky when ProductivityLauncher is false. Skip the test in
  215. // that case. http://crbug.com/1351834
  216. if (!features::IsProductivityLauncherEnabled())
  217. return;
  218. AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
  219. ASSERT_TRUE(folder_item_view);
  220. // Number of apps to populate. Should provide more than 1 page of apps (5*4 =
  221. // 20).
  222. const size_t kNumApps = 30;
  223. GetTestModel()->PopulateApps(kNumApps);
  224. GetRootGridView()->Layout();
  225. EXPECT_EQ(1u, GetFolderViewModel()->view_size());
  226. EXPECT_EQ(kNumApps + 1, GetRootViewModel()->view_size());
  227. AppListItemView* dragged = StartDragForReparent(0);
  228. // Drag the reparent item to the page switcher.
  229. gfx::Point point = GetPageSwitcherView()->GetLocalBounds().CenterPoint();
  230. views::View::ConvertPointToTarget(GetPageSwitcherView(), GetFolderGridView(),
  231. &point);
  232. SimulateUpdateDrag(GetFolderGridView(), AppsGridView::MOUSE, dragged, point);
  233. // Drop it.
  234. GetFolderGridView()->EndDrag(false);
  235. // The folder should not be destroyed.
  236. EXPECT_EQ(kNumApps + 1, GetRootViewModel()->view_size());
  237. AppListFolderItem* const folder_item =
  238. GetTestModel()->FindFolderItem("single_item_folder");
  239. ASSERT_TRUE(folder_item);
  240. EXPECT_EQ(1u, folder_item->item_list()->item_count());
  241. }
  242. // Test that an interrupted drag while reparenting an item from a folder, when
  243. // canceled via the root grid, correctly forwards the cancelation to the drag
  244. // ocurring from the folder.
  245. TEST_P(AppListMainViewTest, MouseDragItemOutOfFolderWithCancel) {
  246. CreateAndOpenSingleItemFolder();
  247. AppListItemView* dragged = StartDragForReparent(0);
  248. // Now add an item to the model, not in any folder, e.g., as if by Sync.
  249. EXPECT_TRUE(GetRootGridView()->has_dragged_item());
  250. EXPECT_TRUE(GetFolderGridView()->has_dragged_item());
  251. GetTestModel()->CreateAndAddItem("Extra");
  252. // The drag operation should get canceled.
  253. EXPECT_FALSE(GetRootGridView()->has_dragged_item());
  254. EXPECT_FALSE(GetFolderGridView()->has_dragged_item());
  255. // Additional mouse move operations should be ignored.
  256. gfx::Point point(1, 1);
  257. SimulateUpdateDrag(GetFolderGridView(), AppsGridView::MOUSE, dragged, point);
  258. EXPECT_FALSE(GetRootGridView()->has_dragged_item());
  259. EXPECT_FALSE(GetFolderGridView()->has_dragged_item());
  260. }
  261. // Test that dragging an app out of a single item folder and reparenting it
  262. // back into its original folder results in a cancelled reparent. This is a
  263. // regression test for http://crbug.com/429083.
  264. TEST_P(AppListMainViewTest, ReparentSingleItemOntoSelf) {
  265. // Add a folder with 1 item.
  266. AppListItemView* folder_item_view = CreateAndOpenSingleItemFolder();
  267. std::string folder_id = folder_item_view->item()->id();
  268. // Add another top level app.
  269. GetTestModel()->PopulateApps(1);
  270. gfx::Point drag_point = folder_item_view->bounds().CenterPoint();
  271. views::View::ConvertPointToTarget(GetRootGridView(), GetFolderGridView(),
  272. &drag_point);
  273. AppListItemView* dragged = StartDragForReparent(0);
  274. // Drag the reparent item back into its folder.
  275. SimulateUpdateDrag(GetFolderGridView(), AppsGridView::MOUSE, dragged,
  276. drag_point);
  277. GetFolderGridView()->EndDrag(false);
  278. // The app list model should remain unchanged.
  279. EXPECT_EQ(2u, GetRootViewModel()->view_size());
  280. EXPECT_EQ(folder_id, GetRootGridView()->GetItemViewAt(0)->item()->id());
  281. AppListFolderItem* const folder_item =
  282. GetTestModel()->FindFolderItem("single_item_folder");
  283. ASSERT_TRUE(folder_item);
  284. EXPECT_EQ(1u, folder_item->item_list()->item_count());
  285. }
  286. } // namespace ash