app_list_test_api.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. // Copyright 2020 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/public/cpp/test/app_list_test_api.h"
  5. #include <string>
  6. #include <utility>
  7. #include <vector>
  8. #include "ash/app_list/app_list_bubble_presenter.h"
  9. #include "ash/app_list/app_list_controller_impl.h"
  10. #include "ash/app_list/app_list_model_provider.h"
  11. #include "ash/app_list/app_list_presenter_impl.h"
  12. #include "ash/app_list/model/app_list_folder_item.h"
  13. #include "ash/app_list/model/app_list_item.h"
  14. #include "ash/app_list/model/app_list_model.h"
  15. #include "ash/app_list/views/app_list_bubble_apps_page.h"
  16. #include "ash/app_list/views/app_list_bubble_view.h"
  17. #include "ash/app_list/views/app_list_folder_view.h"
  18. #include "ash/app_list/views/app_list_item_view.h"
  19. #include "ash/app_list/views/app_list_main_view.h"
  20. #include "ash/app_list/views/app_list_menu_model_adapter.h"
  21. #include "ash/app_list/views/app_list_toast_container_view.h"
  22. #include "ash/app_list/views/app_list_toast_view.h"
  23. #include "ash/app_list/views/app_list_view.h"
  24. #include "ash/app_list/views/apps_container_view.h"
  25. #include "ash/app_list/views/apps_grid_context_menu.h"
  26. #include "ash/app_list/views/apps_grid_view.h"
  27. #include "ash/app_list/views/apps_grid_view_test_api.h"
  28. #include "ash/app_list/views/contents_view.h"
  29. #include "ash/app_list/views/paged_apps_grid_view.h"
  30. #include "ash/app_list/views/recent_apps_view.h"
  31. #include "ash/app_list/views/scrollable_apps_grid_view.h"
  32. #include "ash/app_list/views/search_box_view.h"
  33. #include "ash/constants/ash_features.h"
  34. #include "ash/public/cpp/accelerators.h"
  35. #include "ash/shell.h"
  36. #include "ash/test/layer_animation_stopped_waiter.h"
  37. #include "base/callback.h"
  38. #include "base/run_loop.h"
  39. #include "testing/gtest/include/gtest/gtest.h"
  40. #include "ui/aura/window_observer.h"
  41. #include "ui/compositor/layer.h"
  42. #include "ui/events/test/event_generator.h"
  43. #include "ui/views/controls/button/label_button.h"
  44. #include "ui/views/controls/label.h"
  45. #include "ui/views/controls/menu/menu_item_view.h"
  46. #include "ui/views/controls/menu/submenu_view.h"
  47. #include "ui/views/controls/scroll_view.h"
  48. #include "ui/views/controls/textfield/textfield.h"
  49. #include "ui/views/view_model.h"
  50. namespace ash {
  51. namespace {
  52. // A global pointer to the disabler's instance. Used to ensure at most one
  53. // disabler exists at a time.
  54. class ScopedItemMoveAnimationDisabler;
  55. ScopedItemMoveAnimationDisabler* g_disabler_ptr = nullptr;
  56. AppListView* GetAppListView() {
  57. return Shell::Get()->app_list_controller()->fullscreen_presenter()->GetView();
  58. }
  59. // An app list should be either a bubble app list or a fullscreen app list.
  60. // Returns true if a bubble app list should be used under the current mode.
  61. bool ShouldUseBubbleAppList() {
  62. // A bubble app list should be used only when:
  63. // (1) It is in clamshell mode; and
  64. // (2) The productivity launcher flag is enabled.
  65. return !Shell::Get()->IsInTabletMode() &&
  66. features::IsProductivityLauncherEnabled();
  67. }
  68. // Creates a RunLoop that waits until the context menu of app list item is
  69. // shown.
  70. void WaitUntilItemMenuShown(ash::AppListItemView* item_view) {
  71. base::RunLoop run_loop;
  72. // Set the callback that will quit the RunLoop when context menu is shown.
  73. item_view->SetContextMenuShownCallbackForTest(run_loop.QuitClosure());
  74. run_loop.Run();
  75. // Reset the callback.
  76. item_view->SetContextMenuShownCallbackForTest(base::RepeatingClosure());
  77. }
  78. // Returns the menu item indicated by `order` from a non-folder item menu.
  79. views::MenuItemView* GetReorderOptionForNonFolderItemMenu(
  80. const views::MenuItemView* root_menu,
  81. ash::AppListSortOrder order) {
  82. // Get the last menu item index where the reorder submenu is.
  83. views::MenuItemView* reorder_item_view =
  84. root_menu->GetSubmenu()->GetLastItem();
  85. DCHECK_EQ(reorder_item_view->title(), u"Sort by");
  86. return reorder_item_view;
  87. }
  88. ash::AppListItemView* FindFolderItemView(ash::AppsGridView* apps_grid_view) {
  89. auto* model = apps_grid_view->view_model();
  90. for (size_t index = 0; index < model->view_size(); ++index) {
  91. ash::AppListItemView* current_view = model->view_at(index);
  92. if (current_view->is_folder())
  93. return current_view;
  94. }
  95. return nullptr;
  96. }
  97. ash::AppListItemView* FindNonFolderItemView(ash::AppsGridView* apps_grid_view) {
  98. auto* model = apps_grid_view->view_model();
  99. for (size_t index = 0; index < model->view_size(); ++index) {
  100. ash::AppListItemView* current_view = model->view_at(index);
  101. if (!current_view->is_folder())
  102. return current_view;
  103. }
  104. return nullptr;
  105. }
  106. // Returns the index of the specified sorting option.
  107. size_t GetMenuIndexOfSortingOrder(ash::AppListSortOrder order) {
  108. switch (order) {
  109. case ash::AppListSortOrder::kNameAlphabetical:
  110. return 0;
  111. case ash::AppListSortOrder::kColor:
  112. return 1;
  113. case ash::AppListSortOrder::kNameReverseAlphabetical:
  114. case ash::AppListSortOrder::kCustom:
  115. NOTREACHED();
  116. return 0;
  117. }
  118. }
  119. views::MenuItemView* GetReorderOptionForAppListOrFolderItemMenu(
  120. const views::MenuItemView* root_menu,
  121. const ash::AppListSortOrder order) {
  122. views::MenuItemView* reorder_option = nullptr;
  123. switch (order) {
  124. case ash::AppListSortOrder::kNameAlphabetical:
  125. reorder_option = root_menu->GetSubmenu()->GetMenuItemAt(1);
  126. EXPECT_TRUE(reorder_option->title() == u"Name");
  127. break;
  128. case ash::AppListSortOrder::kColor:
  129. reorder_option = root_menu->GetSubmenu()->GetMenuItemAt(2);
  130. EXPECT_TRUE(reorder_option->title() == u"Color");
  131. break;
  132. case ash::AppListSortOrder::kNameReverseAlphabetical:
  133. case ash::AppListSortOrder::kCustom:
  134. NOTREACHED();
  135. return nullptr;
  136. }
  137. return reorder_option;
  138. }
  139. views::MenuItemView* ShowRootMenuAndReturn(
  140. ash::AppsGridView* apps_grid_view,
  141. AppListTestApi::MenuType menu_type,
  142. ui::test::EventGenerator* event_generator) {
  143. views::MenuItemView* root_menu = nullptr;
  144. EXPECT_GT(apps_grid_view->view_model()->view_size(), 0u);
  145. switch (menu_type) {
  146. case AppListTestApi::MenuType::kAppListPageMenu:
  147. event_generator->MoveMouseTo(
  148. apps_grid_view->GetBoundsInScreen().CenterPoint());
  149. event_generator->ClickRightButton();
  150. root_menu =
  151. apps_grid_view->context_menu_for_test()->root_menu_item_view();
  152. break;
  153. case AppListTestApi::MenuType::kAppListNonFolderItemMenu:
  154. case AppListTestApi::MenuType::kAppListFolderItemMenu:
  155. const bool is_folder_item =
  156. (menu_type == AppListTestApi::MenuType::kAppListFolderItemMenu);
  157. ash::AppListItemView* item_view =
  158. is_folder_item ? FindFolderItemView(apps_grid_view)
  159. : FindNonFolderItemView(apps_grid_view);
  160. EXPECT_TRUE(item_view);
  161. event_generator->MoveMouseTo(
  162. item_view->GetBoundsInScreen().CenterPoint());
  163. event_generator->ClickRightButton();
  164. if (is_folder_item) {
  165. root_menu = item_view->context_menu_for_folder()->root_menu_item_view();
  166. } else {
  167. WaitUntilItemMenuShown(item_view);
  168. ash::AppListMenuModelAdapter* menu_model_adapter =
  169. item_view->item_menu_model_adapter();
  170. root_menu = menu_model_adapter->root_for_testing();
  171. }
  172. break;
  173. }
  174. EXPECT_TRUE(root_menu->SubmenuIsShowing());
  175. return root_menu;
  176. }
  177. PagedAppsGridView* GetPagedAppsGridView() {
  178. // This view only exists for tablet launcher and legacy peeking launcher.
  179. DCHECK(!ShouldUseBubbleAppList());
  180. return AppListView::TestApi(GetAppListView()).GetRootAppsGridView();
  181. }
  182. AppListBubbleView* GetAppListBubbleView() {
  183. AppListBubbleView* bubble_view = Shell::Get()
  184. ->app_list_controller()
  185. ->bubble_presenter_for_test()
  186. ->bubble_view_for_test();
  187. DCHECK(bubble_view) << "Bubble launcher view not yet created. Tests must "
  188. "show the launcher and may need to call "
  189. "WaitForBubbleWindow() if animations are enabled.";
  190. return bubble_view;
  191. }
  192. AppsContainerView* GetAppsContainerView() {
  193. return GetAppListView()
  194. ->app_list_main_view()
  195. ->contents_view()
  196. ->apps_container_view();
  197. }
  198. AppListFolderView* GetAppListFolderView() {
  199. // Handle the case that the app list bubble view is effective.
  200. if (ShouldUseBubbleAppList())
  201. return GetAppListBubbleView()->folder_view_for_test();
  202. return GetAppsContainerView()->app_list_folder_view();
  203. }
  204. AppListToastContainerView* GetToastContainerViewFromBubble() {
  205. DCHECK(features::IsLauncherAppSortEnabled());
  206. return GetAppListBubbleView()
  207. ->apps_page_for_test()
  208. ->toast_container_for_test();
  209. }
  210. AppListToastContainerView* GetToastContainerViewFromFullscreenAppList() {
  211. DCHECK(features::IsLauncherAppSortEnabled());
  212. return GetAppsContainerView()->toast_container();
  213. }
  214. RecentAppsView* GetRecentAppsView() {
  215. if (ShouldUseBubbleAppList())
  216. return GetAppListBubbleView()->apps_page_for_test()->recent_apps_for_test();
  217. return GetAppsContainerView()->GetRecentAppsView();
  218. }
  219. SearchBoxView* GetSearchBoxView() {
  220. if (ShouldUseBubbleAppList())
  221. return GetAppListBubbleView()->search_box_view_for_test();
  222. return GetAppListView()->app_list_main_view()->search_box_view();
  223. }
  224. // AppListVisibilityChangedWaiter ----------------------------------------------
  225. // Waits until the app list visibility changes.
  226. class AppListVisibilityChangedWaiter : public AppListControllerObserver {
  227. public:
  228. AppListVisibilityChangedWaiter() = default;
  229. AppListVisibilityChangedWaiter(const AppListVisibilityChangedWaiter&) =
  230. delete;
  231. AppListVisibilityChangedWaiter& operator=(
  232. const AppListVisibilityChangedWaiter&) = delete;
  233. ~AppListVisibilityChangedWaiter() override {
  234. AppListController::Get()->RemoveObserver(this);
  235. }
  236. void Wait() {
  237. AppListController::Get()->AddObserver(this);
  238. run_loop_.Run();
  239. }
  240. // AppListControllerObserver:
  241. void OnAppListVisibilityChanged(bool shown, int64_t display_id) override {
  242. run_loop_.Quit();
  243. }
  244. private:
  245. base::RunLoop run_loop_;
  246. };
  247. // WindowAddedWaiter -----------------------------------------------------------
  248. // Waits until a child window is added to a container window.
  249. class WindowAddedWaiter : public aura::WindowObserver {
  250. public:
  251. explicit WindowAddedWaiter(aura::Window* container) : container_(container) {
  252. container_->AddObserver(this);
  253. }
  254. WindowAddedWaiter(const WindowAddedWaiter&) = delete;
  255. WindowAddedWaiter& operator=(const WindowAddedWaiter&) = delete;
  256. ~WindowAddedWaiter() override { container_->RemoveObserver(this); }
  257. void Wait() { run_loop_.Run(); }
  258. aura::Window* added_window() { return added_window_; }
  259. private:
  260. // aura::WindowObserver:
  261. void OnWindowAdded(aura::Window* new_window) override {
  262. added_window_ = new_window;
  263. DCHECK(run_loop_.IsRunningOnCurrentThread());
  264. run_loop_.Quit();
  265. }
  266. aura::Window* const container_;
  267. aura::Window* added_window_ = nullptr;
  268. base::RunLoop run_loop_;
  269. };
  270. // ScopedItemMoveAnimationDisabler ---------------------------------------------
  271. // Disable the apps grid item move animation in scope.
  272. class ScopedItemMoveAnimationDisabler {
  273. public:
  274. explicit ScopedItemMoveAnimationDisabler(AppsGridView* apps_grid)
  275. : apps_grid_(apps_grid) {
  276. DCHECK(!g_disabler_ptr);
  277. apps_grid_->set_enable_item_move_animation_for_test(false);
  278. g_disabler_ptr = this;
  279. }
  280. ScopedItemMoveAnimationDisabler(const ScopedItemMoveAnimationDisabler&) =
  281. delete;
  282. ScopedItemMoveAnimationDisabler& operator=(
  283. const ScopedItemMoveAnimationDisabler&) = delete;
  284. ~ScopedItemMoveAnimationDisabler() {
  285. apps_grid_->set_enable_item_move_animation_for_test(true);
  286. DCHECK(g_disabler_ptr);
  287. g_disabler_ptr = nullptr;
  288. }
  289. private:
  290. AppsGridView* const apps_grid_;
  291. };
  292. } // namespace
  293. AppListTestApi::AppListTestApi() = default;
  294. AppListTestApi::~AppListTestApi() = default;
  295. AppListModel* AppListTestApi::GetAppListModel() {
  296. return AppListModelProvider::Get()->model();
  297. }
  298. void AppListTestApi::ShowBubbleAppListAndWait() {
  299. ash::AcceleratorController::Get()->PerformActionIfEnabled(
  300. ash::TOGGLE_APP_LIST_FULLSCREEN, {});
  301. WaitForBubbleWindow(
  302. /*wait_for_opening_animation=*/true);
  303. }
  304. void AppListTestApi::WaitForBubbleWindow(bool wait_for_opening_animation) {
  305. DCHECK(features::IsProductivityLauncherEnabled());
  306. DCHECK(!Shell::Get()->IsInTabletMode());
  307. // Wait for the window only when the app list window does not exist.
  308. auto* app_list_controller = Shell::Get()->app_list_controller();
  309. if (!app_list_controller->GetWindow()) {
  310. // Wait for a child window to be added to the app list container.
  311. aura::Window* container = Shell::GetContainer(
  312. Shell::GetPrimaryRootWindow(), kShellWindowId_AppListContainer);
  313. WindowAddedWaiter waiter(container);
  314. waiter.Wait();
  315. // App list window exists.
  316. aura::Window* app_list_window = app_list_controller->GetWindow();
  317. DCHECK(app_list_window);
  318. DCHECK_EQ(app_list_window, waiter.added_window());
  319. }
  320. if (wait_for_opening_animation)
  321. WaitForAppListShowAnimation(/*is_bubble_window=*/true);
  322. }
  323. void AppListTestApi::WaitForAppListShowAnimation(bool is_bubble_window) {
  324. // Ensure that the app list is visible before waiting for animations.
  325. AppListController* controller = AppListControllerImpl::Get();
  326. if (!controller->IsVisible()) {
  327. AppListVisibilityChangedWaiter waiter;
  328. waiter.Wait();
  329. if (!controller->IsVisible())
  330. ADD_FAILURE() << "Launcher is not visible.";
  331. }
  332. // Wait for the app list window animation.
  333. aura::Window* app_list_window = controller->GetWindow();
  334. DCHECK(app_list_window);
  335. LayerAnimationStoppedWaiter().Wait(app_list_window->layer());
  336. if (!is_bubble_window)
  337. return;
  338. DCHECK(features::IsProductivityLauncherEnabled());
  339. DCHECK(!Shell::Get()->IsInTabletMode());
  340. ScrollableAppsGridView* scrollable_apps_grid_view =
  341. static_cast<ScrollableAppsGridView*>(GetTopLevelAppsGridView());
  342. if (!scrollable_apps_grid_view->layer())
  343. return;
  344. // Wait for the animation to show the bubble view.
  345. LayerAnimationStoppedWaiter().Wait(GetAppListBubbleView()->layer());
  346. // Wait for the animation to show the apps page.
  347. LayerAnimationStoppedWaiter().Wait(GetAppListBubbleView()
  348. ->apps_page_for_test()
  349. ->scroll_view()
  350. ->contents()
  351. ->layer());
  352. // Wait for the apps grid slide animation.
  353. LayerAnimationStoppedWaiter().Wait(scrollable_apps_grid_view->layer());
  354. }
  355. bool AppListTestApi::HasApp(const std::string& app_id) {
  356. return GetAppListModel()->FindItem(app_id);
  357. }
  358. std::u16string AppListTestApi::GetAppListItemViewName(
  359. const std::string& item_id) {
  360. AppListItemView* item_view = GetTopLevelItemViewFromId(item_id);
  361. if (!item_view)
  362. return u"";
  363. return item_view->title()->GetText();
  364. }
  365. AppListItemView* AppListTestApi::GetTopLevelItemViewFromId(
  366. const std::string& item_id) {
  367. views::ViewModelT<AppListItemView>* view_model =
  368. GetTopLevelAppsGridView()->view_model();
  369. for (size_t i = 0; i < view_model->view_size(); ++i) {
  370. AppListItemView* app_list_item_view = view_model->view_at(i);
  371. if (app_list_item_view->item()->id() == item_id)
  372. return app_list_item_view;
  373. }
  374. return nullptr;
  375. }
  376. std::vector<std::string> AppListTestApi::GetTopLevelViewIdList() {
  377. std::vector<std::string> id_list;
  378. auto* view_model = GetTopLevelAppsGridView()->view_model();
  379. for (size_t i = 0; i < view_model->view_size(); ++i) {
  380. AppListItem* app_list_item = view_model->view_at(i)->item();
  381. if (app_list_item) {
  382. id_list.push_back(app_list_item->id());
  383. }
  384. }
  385. return id_list;
  386. }
  387. std::string AppListTestApi::CreateFolderWithApps(
  388. const std::vector<std::string>& apps) {
  389. // Only create a folder if there are two or more apps.
  390. DCHECK_GE(apps.size(), 2u);
  391. AppListModel* model = GetAppListModel();
  392. // Create a folder using the first two apps, and add the others to the
  393. // folder iteratively.
  394. std::string folder_id = model->MergeItems(apps[0], apps[1]);
  395. // Return early if MergeItems failed.
  396. if (folder_id.empty())
  397. return "";
  398. // Skip item move animations.
  399. ScopedItemMoveAnimationDisabler disabler(GetTopLevelAppsGridView());
  400. for (size_t i = 2; i < apps.size(); ++i)
  401. model->MergeItems(folder_id, apps[i]);
  402. return folder_id;
  403. }
  404. std::string AppListTestApi::GetFolderId(const std::string& app_id) {
  405. return GetAppListModel()->FindItem(app_id)->folder_id();
  406. }
  407. std::vector<std::string> AppListTestApi::GetAppIdsInFolder(
  408. const std::string& folder_id) {
  409. AppListItem* folder_item = GetAppListModel()->FindItem(folder_id);
  410. DCHECK(folder_item->is_folder());
  411. AppListItemList* folder_list =
  412. static_cast<AppListFolderItem*>(folder_item)->item_list();
  413. std::vector<std::string> id_list;
  414. for (size_t i = 0; i < folder_list->item_count(); ++i)
  415. id_list.push_back(folder_list->item_at(i)->id());
  416. return id_list;
  417. }
  418. void AppListTestApi::MoveItemToPosition(const std::string& item_id,
  419. const size_t to_index) {
  420. AppListItem* app_item = GetAppListModel()->FindItem(item_id);
  421. const std::string folder_id = app_item->folder_id();
  422. AppListItemList* item_list;
  423. std::vector<std::string> top_level_id_list = GetTopLevelViewIdList();
  424. // The app should be either at the top level or in a folder.
  425. if (folder_id.empty()) {
  426. // The app is at the top level.
  427. item_list = GetAppListModel()->top_level_item_list();
  428. } else {
  429. // The app is in the folder with |folder_id|.
  430. item_list = GetAppListModel()->FindFolderItem(folder_id)->item_list();
  431. }
  432. size_t from_index = 0;
  433. item_list->FindItemIndex(item_id, &from_index);
  434. item_list->MoveItem(from_index, to_index);
  435. }
  436. void AppListTestApi::AddPageBreakItemAfterId(const std::string& item_id) {
  437. auto* model = GetAppListModel();
  438. model->AddPageBreakItemAfter(model->FindItem(item_id));
  439. }
  440. int AppListTestApi::GetTopListItemCount() {
  441. return GetAppListModel()->top_level_item_list()->item_count();
  442. }
  443. views::View* AppListTestApi::GetLastItemInAppsGridView() {
  444. AppsGridView* grid = GetTopLevelAppsGridView();
  445. return grid->view_model()->view_at(grid->view_model()->view_size() - 1);
  446. }
  447. PaginationModel* AppListTestApi::GetPaginationModel() {
  448. return GetPagedAppsGridView()->pagination_model();
  449. }
  450. void AppListTestApi::UpdatePagedViewStructure() {
  451. GetPagedAppsGridView()->UpdatePagedViewStructure();
  452. }
  453. AppsGridView* AppListTestApi::GetTopLevelAppsGridView() {
  454. if (ShouldUseBubbleAppList()) {
  455. return GetAppListBubbleView()
  456. ->apps_page_for_test()
  457. ->scrollable_apps_grid_view();
  458. }
  459. return GetPagedAppsGridView();
  460. }
  461. const AppsGridView* AppListTestApi::GetTopLevelAppsGridView() const {
  462. if (ShouldUseBubbleAppList()) {
  463. return GetAppListBubbleView()
  464. ->apps_page_for_test()
  465. ->scrollable_apps_grid_view();
  466. }
  467. return GetPagedAppsGridView();
  468. }
  469. AppsGridView* AppListTestApi::GetFolderAppsGridView() {
  470. return GetAppListFolderView()->items_grid_view();
  471. }
  472. bool AppListTestApi::IsFolderViewAnimating() const {
  473. return GetAppListFolderView()->IsAnimationRunning();
  474. }
  475. views::View* AppListTestApi::GetBubbleReorderUndoButton() {
  476. return GetToastContainerViewFromBubble()->GetToastButton();
  477. }
  478. views::View* AppListTestApi::GetFullscreenReorderUndoButton() {
  479. return GetToastContainerViewFromFullscreenAppList()->GetToastButton();
  480. }
  481. AppListToastType AppListTestApi::GetToastType() const {
  482. AppListToastContainerView* toast_container =
  483. ShouldUseBubbleAppList() ? GetToastContainerViewFromBubble()
  484. : GetToastContainerViewFromFullscreenAppList();
  485. return toast_container->current_toast();
  486. }
  487. void AppListTestApi::SetFolderViewAnimationCallback(
  488. base::OnceClosure folder_animation_done_callback) {
  489. AppListFolderView* folder_view = GetAppListFolderView();
  490. folder_view->SetAnimationDoneTestCallback(base::BindOnce(
  491. [](AppListFolderView* folder_view,
  492. base::OnceClosure folder_animation_done_callback) {
  493. std::move(folder_animation_done_callback).Run();
  494. },
  495. folder_view, std::move(folder_animation_done_callback)));
  496. }
  497. views::View* AppListTestApi::GetToastContainerView() {
  498. if (ShouldUseBubbleAppList())
  499. return GetToastContainerViewFromBubble();
  500. return GetToastContainerViewFromFullscreenAppList();
  501. }
  502. void AppListTestApi::AddReorderAnimationCallback(
  503. AppsGridView::TestReorderDoneCallbackType callback) {
  504. DCHECK(features::IsLauncherAppSortEnabled());
  505. GetTopLevelAppsGridView()->AddReorderCallbackForTest(std::move(callback));
  506. }
  507. void AppListTestApi::AddFadeOutAnimationStartClosure(
  508. base::OnceClosure closure) {
  509. DCHECK(features::IsLauncherAppSortEnabled());
  510. GetTopLevelAppsGridView()->AddFadeOutAnimationStartClosureForTest(
  511. std::move(closure));
  512. }
  513. bool AppListTestApi::HasAnyWaitingReorderDoneCallback() const {
  514. DCHECK(features::IsLauncherAppSortEnabled());
  515. return GetTopLevelAppsGridView()->HasAnyWaitingReorderDoneCallbackForTest();
  516. }
  517. void AppListTestApi::DisableAppListNudge(bool disable) {
  518. AppListNudgeController::SetReorderNudgeDisabledForTest(disable);
  519. }
  520. void AppListTestApi::SetContinueSectionPrivacyNoticeAccepted() {
  521. AppListNudgeController::SetPrivacyNoticeAcceptedForTest(true);
  522. }
  523. void AppListTestApi::ReorderItemInRootByDragAndDrop(int source_index,
  524. int target_index) {
  525. test::AppsGridViewTestApi(GetTopLevelAppsGridView())
  526. .ReorderItemByDragAndDrop(source_index, target_index);
  527. }
  528. views::View* AppListTestApi::GetVisibleSearchResultView(int index) {
  529. views::View* app_list =
  530. ShouldUseBubbleAppList()
  531. ? static_cast<views::View*>(GetAppListBubbleView())
  532. : static_cast<views::View*>(GetAppListView());
  533. views::View::Views search_results;
  534. app_list->GetViewsInGroup(kSearchResultViewGroup, &search_results);
  535. int current_visible_index = -1;
  536. for (auto* view : search_results) {
  537. if (view->GetVisible())
  538. ++current_visible_index;
  539. if (current_visible_index == index)
  540. return view;
  541. }
  542. return nullptr;
  543. }
  544. ash::AppListItemView* AppListTestApi::FindTopLevelFolderItemView() {
  545. return FindFolderItemView(GetTopLevelAppsGridView());
  546. }
  547. void AppListTestApi::VerifyTopLevelItemVisibility() {
  548. auto* view_model = GetTopLevelAppsGridView()->view_model();
  549. std::vector<std::string> invisible_item_names;
  550. for (size_t view_index = 0; view_index < view_model->view_size();
  551. ++view_index) {
  552. auto* item_view = view_model->view_at(view_index);
  553. if (!item_view->GetVisible())
  554. invisible_item_names.push_back(item_view->item()->name());
  555. }
  556. // Invisible items should be none.
  557. EXPECT_EQ(std::vector<std::string>(), invisible_item_names);
  558. }
  559. views::View* AppListTestApi::GetRecentAppAt(int index) {
  560. return GetRecentAppsView()->GetItemViewAt(index);
  561. }
  562. void AppListTestApi::SimulateSearch(const std::u16string& query) {
  563. views::Textfield* textfield = GetSearchBoxView()->search_box();
  564. textfield->SetText(u"");
  565. textfield->InsertText(
  566. query,
  567. ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
  568. }
  569. void AppListTestApi::ReorderByMouseClickAtContextMenuInAppsGrid(
  570. ash::AppsGridView* apps_grid_view,
  571. ash::AppListSortOrder order,
  572. MenuType menu_type,
  573. ui::test::EventGenerator* event_generator,
  574. ReorderAnimationEndState target_state,
  575. ReorderAnimationEndState* actual_state) {
  576. // Ensure that the apps grid layout is refreshed before showing the
  577. // context menu.
  578. apps_grid_view->GetWidget()->LayoutRootViewIfNecessary();
  579. // Custom order is not a menu option.
  580. EXPECT_NE(order, ash::AppListSortOrder::kCustom);
  581. views::MenuItemView* root_menu =
  582. ShowRootMenuAndReturn(apps_grid_view, menu_type, event_generator);
  583. // Get the "Name" or "Color" option.
  584. views::MenuItemView* reorder_option = nullptr;
  585. switch (menu_type) {
  586. case MenuType::kAppListPageMenu:
  587. case MenuType::kAppListFolderItemMenu:
  588. reorder_option =
  589. GetReorderOptionForAppListOrFolderItemMenu(root_menu, order);
  590. break;
  591. case MenuType::kAppListNonFolderItemMenu: {
  592. // The `reorder_option` cached here is the submenu of the options.
  593. views::MenuItemView* reorder_submenu =
  594. GetReorderOptionForNonFolderItemMenu(root_menu, order);
  595. event_generator->MoveMouseTo(
  596. reorder_submenu->GetBoundsInScreen().CenterPoint());
  597. event_generator->ClickLeftButton();
  598. reorder_option = reorder_submenu->GetSubmenu()->GetMenuItemAt(
  599. GetMenuIndexOfSortingOrder(order));
  600. break;
  601. }
  602. }
  603. gfx::Point point_on_option =
  604. reorder_option->GetBoundsInScreen().CenterPoint();
  605. RegisterReorderAnimationDoneCallback(actual_state);
  606. // Click at the sorting option.
  607. event_generator->MoveMouseTo(point_on_option);
  608. event_generator->ClickLeftButton();
  609. switch (target_state) {
  610. case ReorderAnimationEndState::kCompleted:
  611. // Wait until the reorder animation is done.
  612. WaitForReorderAnimationAndVerifyItemVisibility();
  613. break;
  614. case ReorderAnimationEndState::kFadeOutAborted:
  615. // The fade out animation starts synchronously so do not wait before
  616. // animation interruption.
  617. break;
  618. case ReorderAnimationEndState::kFadeInAborted:
  619. // Wait until the fade out animation is done. It ensures that the app
  620. // list is under fade in animation when animation interruption occurs.
  621. WaitForFadeOutAnimation();
  622. break;
  623. }
  624. }
  625. void AppListTestApi::ReorderByMouseClickAtToplevelAppsGridMenu(
  626. ash::AppListSortOrder order,
  627. MenuType menu_type,
  628. ui::test::EventGenerator* event_generator,
  629. ReorderAnimationEndState target_state,
  630. ReorderAnimationEndState* actual_state) {
  631. ReorderByMouseClickAtContextMenuInAppsGrid(GetTopLevelAppsGridView(), order,
  632. menu_type, event_generator,
  633. target_state, actual_state);
  634. }
  635. void AppListTestApi::ClickOnRedoButtonAndWaitForAnimation(
  636. ui::test::EventGenerator* event_generator) {
  637. ReorderAnimationEndState actual_state;
  638. RegisterReorderAnimationDoneCallback(&actual_state);
  639. // Mouse click at the undo button.
  640. views::View* reorder_undo_toast_button = nullptr;
  641. if (ShouldUseBubbleAppList())
  642. reorder_undo_toast_button = GetBubbleReorderUndoButton();
  643. else
  644. reorder_undo_toast_button = GetFullscreenReorderUndoButton();
  645. event_generator->MoveMouseTo(
  646. reorder_undo_toast_button->GetBoundsInScreen().CenterPoint());
  647. event_generator->ClickLeftButton();
  648. // Verify that the toast is under animation.
  649. EXPECT_TRUE(GetToastContainerView()->layer()->GetAnimator()->is_animating());
  650. WaitForReorderAnimationAndVerifyItemVisibility();
  651. EXPECT_EQ(ReorderAnimationEndState::kCompleted, actual_state);
  652. }
  653. void AppListTestApi::ClickOnCloseButtonAndWaitForToastAnimation(
  654. ui::test::EventGenerator* event_generator) {
  655. AppListToastContainerView* toast_container =
  656. ShouldUseBubbleAppList() ? GetToastContainerViewFromBubble()
  657. : GetToastContainerViewFromFullscreenAppList();
  658. views::View* close_button = toast_container->GetCloseButton();
  659. event_generator->MoveMouseTo(close_button->GetBoundsInScreen().CenterPoint());
  660. event_generator->ClickLeftButton();
  661. // Wait until the toast fade out animation ends.
  662. LayerAnimationStoppedWaiter animation_waiter;
  663. animation_waiter.Wait(toast_container->toast_view()->layer());
  664. }
  665. void AppListTestApi::RegisterReorderAnimationDoneCallback(
  666. ReorderAnimationEndState* actual_state) {
  667. AddReorderAnimationCallback(
  668. base::BindRepeating(&AppListTestApi::OnReorderAnimationDone,
  669. weak_factory_.GetWeakPtr(), actual_state));
  670. }
  671. void AppListTestApi::OnReorderAnimationDone(ReorderAnimationEndState* result,
  672. bool abort,
  673. AppListGridAnimationStatus status) {
  674. DCHECK(status == AppListGridAnimationStatus::kReorderFadeOut ||
  675. status == AppListGridAnimationStatus::kReorderFadeIn);
  676. // Record the animation running result.
  677. if (abort) {
  678. if (status == AppListGridAnimationStatus::kReorderFadeOut)
  679. *result = ReorderAnimationEndState::kFadeOutAborted;
  680. else
  681. *result = ReorderAnimationEndState::kFadeInAborted;
  682. } else {
  683. EXPECT_EQ(AppListGridAnimationStatus::kReorderFadeIn, status);
  684. *result = ReorderAnimationEndState::kCompleted;
  685. // Verify that the toast container under the clamshell mode does not have
  686. // a layer after reorder animation completes.
  687. views::View* toast_container = GetToastContainerView();
  688. if (toast_container && !ash::Shell::Get()->IsInTabletMode())
  689. EXPECT_FALSE(toast_container->layer());
  690. }
  691. // Callback can be registered without a running loop.
  692. if (run_loop_for_reorder_)
  693. run_loop_for_reorder_->Quit();
  694. }
  695. void AppListTestApi::WaitForReorderAnimationAndVerifyItemVisibility() {
  696. run_loop_for_reorder_ = std::make_unique<base::RunLoop>();
  697. run_loop_for_reorder_->Run();
  698. VerifyTopLevelItemVisibility();
  699. }
  700. void AppListTestApi::WaitForFadeOutAnimation() {
  701. ash::AppsGridView* apps_grid_view = GetTopLevelAppsGridView();
  702. if (apps_grid_view->grid_animation_status_for_test() !=
  703. AppListGridAnimationStatus::kReorderFadeOut) {
  704. // The apps grid is not under fade out animation so no op.
  705. return;
  706. }
  707. ASSERT_TRUE(!run_loop_for_reorder_ || !run_loop_for_reorder_->running());
  708. run_loop_for_reorder_ = std::make_unique<base::RunLoop>();
  709. apps_grid_view->AddFadeOutAnimationDoneClosureForTest(
  710. run_loop_for_reorder_->QuitClosure());
  711. run_loop_for_reorder_->Run();
  712. }
  713. } // namespace ash