apps_container_view_unittest.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Copyright 2022 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/apps_container_view.h"
  5. #include "ash/app_list/app_list_controller_impl.h"
  6. #include "ash/app_list/model/app_list_test_model.h"
  7. #include "ash/app_list/test/app_list_test_helper.h"
  8. #include "ash/app_list/views/apps_grid_view_test_api.h"
  9. #include "ash/app_list/views/continue_section_view.h"
  10. #include "ash/app_list/views/recent_apps_view.h"
  11. #include "ash/app_list/views/search_box_view.h"
  12. #include "ash/constants/ash_features.h"
  13. #include "ash/public/cpp/tablet_mode.h"
  14. #include "ash/shell.h"
  15. #include "ash/test/ash_test_base.h"
  16. #include "ash/test/layer_animation_stopped_waiter.h"
  17. #include "base/test/scoped_feature_list.h"
  18. #include "ui/compositor/layer.h"
  19. #include "ui/compositor/layer_animator.h"
  20. #include "ui/compositor/scoped_animation_duration_scale_mode.h"
  21. #include "ui/events/test/event_generator.h"
  22. #include "ui/views/accessibility/view_accessibility.h"
  23. #include "ui/views/controls/textfield/textfield.h"
  24. namespace ash {
  25. namespace {
  26. class TransitionWaiter : public PaginationModelObserver {
  27. public:
  28. explicit TransitionWaiter(PaginationModel* model) : model_(model) {
  29. model_->AddObserver(this);
  30. }
  31. TransitionWaiter(const TransitionWaiter&) = delete;
  32. TransitionWaiter& operator=(const TransitionWaiter&) = delete;
  33. ~TransitionWaiter() override { model_->RemoveObserver(this); }
  34. void Wait() {
  35. ui_run_loop_ = std::make_unique<base::RunLoop>();
  36. ui_run_loop_->Run();
  37. }
  38. private:
  39. // PaginationModelObserver:
  40. void TransitionEnded() override { ui_run_loop_->QuitWhenIdle(); }
  41. std::unique_ptr<base::RunLoop> ui_run_loop_;
  42. PaginationModel* model_ = nullptr;
  43. };
  44. } // namespace
  45. class AppsContainerViewTest : public AshTestBase {
  46. public:
  47. AppsContainerViewTest() {
  48. // These tests primarily exercise the "hide continue section" behavior.
  49. features_.InitWithFeatures({features::kProductivityLauncher,
  50. features::kLauncherHideContinueSection},
  51. {});
  52. }
  53. ~AppsContainerViewTest() override = default;
  54. // testing::Test:
  55. void SetUp() override {
  56. AshTestBase::SetUp();
  57. app_list_test_model_ = std::make_unique<test::AppListTestModel>();
  58. search_model_ = std::make_unique<SearchModel>();
  59. Shell::Get()->app_list_controller()->SetActiveModel(
  60. /*profile_id=*/1, app_list_test_model_.get(), search_model_.get());
  61. }
  62. void AddFolderWithApps(int count) {
  63. app_list_test_model_->CreateAndPopulateFolderWithApps(count);
  64. }
  65. AppListToastContainerView* GetToastContainerView() {
  66. return GetAppListTestHelper()
  67. ->GetAppsContainerView()
  68. ->GetToastContainerView();
  69. }
  70. void PressDown() {
  71. ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
  72. generator.PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN);
  73. }
  74. int GetSelectedPage() {
  75. return GetAppListTestHelper()
  76. ->GetRootPagedAppsGridView()
  77. ->pagination_model()
  78. ->selected_page();
  79. }
  80. int GetTotalPages() {
  81. return GetAppListTestHelper()
  82. ->GetRootPagedAppsGridView()
  83. ->pagination_model()
  84. ->total_pages();
  85. }
  86. bool HasGradientMask() {
  87. return GetAppListTestHelper()
  88. ->GetAppsContainerView()
  89. ->scrollable_container_for_test()
  90. ->layer()
  91. ->layer_mask_layer();
  92. }
  93. private:
  94. base::test::ScopedFeatureList features_;
  95. std::unique_ptr<test::AppListTestModel> app_list_test_model_;
  96. std::unique_ptr<SearchModel> search_model_;
  97. };
  98. TEST_F(AppsContainerViewTest, ContinueSectionVisibleByDefault) {
  99. // Show the app list with enough items to make the continue section and
  100. // recent apps visible.
  101. auto* helper = GetAppListTestHelper();
  102. helper->AddContinueSuggestionResults(4);
  103. helper->AddRecentApps(5);
  104. helper->AddAppItems(5);
  105. TabletMode::Get()->SetEnabledForTest(true);
  106. // The continue section and recent apps are visible.
  107. EXPECT_TRUE(helper->GetFullscreenContinueSectionView()->GetVisible());
  108. EXPECT_TRUE(helper->GetFullscreenRecentAppsView()->GetVisible());
  109. EXPECT_TRUE(helper->GetAppsContainerView()->separator()->GetVisible());
  110. }
  111. TEST_F(AppsContainerViewTest, CanHideContinueSection) {
  112. // Show the app list with enough items to make the continue section and
  113. // recent apps visible.
  114. auto* helper = GetAppListTestHelper();
  115. helper->AddContinueSuggestionResults(4);
  116. helper->AddRecentApps(5);
  117. helper->AddAppItems(5);
  118. TabletMode::Get()->SetEnabledForTest(true);
  119. // Hide the continue section.
  120. Shell::Get()->app_list_controller()->SetHideContinueSection(true);
  121. // Continue section and recent apps are hidden.
  122. EXPECT_FALSE(helper->GetFullscreenContinueSectionView()->GetVisible());
  123. EXPECT_FALSE(helper->GetFullscreenRecentAppsView()->GetVisible());
  124. EXPECT_FALSE(helper->GetAppsContainerView()->separator()->GetVisible());
  125. }
  126. TEST_F(AppsContainerViewTest, HideContinueSectionPlaysAnimation) {
  127. // Show the app list without animation.
  128. ASSERT_EQ(ui::ScopedAnimationDurationScaleMode::duration_multiplier(),
  129. ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
  130. auto* helper = GetAppListTestHelper();
  131. helper->AddContinueSuggestionResults(4);
  132. helper->AddRecentApps(5);
  133. const int item_count = 5;
  134. helper->AddAppItems(item_count);
  135. TabletMode::Get()->SetEnabledForTest(true);
  136. // Enable animations.
  137. ui::ScopedAnimationDurationScaleMode duration(
  138. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  139. // Hide the continue section.
  140. Shell::Get()->app_list_controller()->SetHideContinueSection(true);
  141. // Animation status is updated.
  142. auto* apps_grid_view = helper->GetRootPagedAppsGridView();
  143. EXPECT_EQ(apps_grid_view->grid_animation_status_for_test(),
  144. AppListGridAnimationStatus::kHideContinueSection);
  145. // Individial app items are animating their transforms.
  146. for (int i = 0; i < item_count; ++i) {
  147. SCOPED_TRACE(testing::Message() << "Item " << i);
  148. AppListItemView* item = apps_grid_view->GetItemViewAt(i);
  149. ASSERT_TRUE(item->layer());
  150. EXPECT_TRUE(item->layer()->GetAnimator()->is_animating());
  151. EXPECT_TRUE(item->layer()->GetAnimator()->IsAnimatingProperty(
  152. ui::LayerAnimationElement::TRANSFORM));
  153. }
  154. // Wait for the last item's animation to complete.
  155. AppListItemView* last_item = apps_grid_view->GetItemViewAt(item_count - 1);
  156. LayerAnimationStoppedWaiter().Wait(last_item->layer());
  157. // Animation status is updated.
  158. EXPECT_EQ(apps_grid_view->grid_animation_status_for_test(),
  159. AppListGridAnimationStatus::kEmpty);
  160. // Layers have been removed for all items.
  161. for (int i = 0; i < item_count; ++i) {
  162. SCOPED_TRACE(testing::Message() << "Item " << i);
  163. AppListItemView* item = apps_grid_view->GetItemViewAt(i);
  164. EXPECT_FALSE(item->layer());
  165. }
  166. }
  167. TEST_F(AppsContainerViewTest, CanShowContinueSection) {
  168. // Simulate a user with the continue section hidden on startup.
  169. Shell::Get()->app_list_controller()->SetHideContinueSection(true);
  170. // Show the app list with enough items to make the continue section and
  171. // recent apps visible.
  172. auto* helper = GetAppListTestHelper();
  173. helper->AddContinueSuggestionResults(4);
  174. helper->AddRecentApps(5);
  175. helper->AddAppItems(5);
  176. TabletMode::Get()->SetEnabledForTest(true);
  177. // Continue section and recent apps are hidden.
  178. EXPECT_FALSE(helper->GetFullscreenContinueSectionView()->GetVisible());
  179. EXPECT_FALSE(helper->GetFullscreenRecentAppsView()->GetVisible());
  180. EXPECT_FALSE(helper->GetAppsContainerView()->separator()->GetVisible());
  181. // Show the continue section.
  182. Shell::Get()->app_list_controller()->SetHideContinueSection(false);
  183. // The continue section and recent apps are visible.
  184. EXPECT_TRUE(helper->GetFullscreenContinueSectionView()->GetVisible());
  185. EXPECT_TRUE(helper->GetFullscreenRecentAppsView()->GetVisible());
  186. EXPECT_TRUE(helper->GetAppsContainerView()->separator()->GetVisible());
  187. }
  188. TEST_F(AppsContainerViewTest, ShowContinueSectionPlaysAnimation) {
  189. // Simulate a user with the continue section hidden on startup.
  190. Shell::Get()->app_list_controller()->SetHideContinueSection(true);
  191. // Show the app list with enough items to make the continue section and
  192. // recent apps visible.
  193. auto* helper = GetAppListTestHelper();
  194. helper->AddContinueSuggestionResults(4);
  195. helper->AddRecentApps(5);
  196. helper->AddAppItems(5);
  197. TabletMode::Get()->SetEnabledForTest(true);
  198. // Enable animations.
  199. ui::ScopedAnimationDurationScaleMode duration(
  200. ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  201. // Show the continue section.
  202. Shell::Get()->app_list_controller()->SetHideContinueSection(false);
  203. // Continue section is fading in.
  204. auto* continue_section = helper->GetFullscreenContinueSectionView();
  205. ASSERT_TRUE(continue_section->layer());
  206. EXPECT_TRUE(continue_section->layer()->GetAnimator()->is_animating());
  207. EXPECT_EQ(continue_section->layer()->opacity(), 0.0f);
  208. EXPECT_EQ(continue_section->layer()->GetTargetOpacity(), 1.0f);
  209. // Recent apps view is fading in.
  210. auto* recent_apps = helper->GetFullscreenRecentAppsView();
  211. ASSERT_TRUE(recent_apps->layer());
  212. EXPECT_TRUE(recent_apps->layer()->GetAnimator()->is_animating());
  213. EXPECT_EQ(recent_apps->layer()->opacity(), 0.0f);
  214. EXPECT_EQ(recent_apps->layer()->GetTargetOpacity(), 1.0f);
  215. // Separator view is fading in.
  216. auto* separator = helper->GetAppsContainerView()->separator();
  217. ASSERT_TRUE(separator->layer());
  218. EXPECT_TRUE(separator->layer()->GetAnimator()->is_animating());
  219. EXPECT_EQ(separator->layer()->opacity(), 0.0f);
  220. EXPECT_EQ(separator->layer()->GetTargetOpacity(), 1.0f);
  221. // Apps grid is animating its transform.
  222. auto* apps_grid_view = helper->GetRootPagedAppsGridView();
  223. ASSERT_TRUE(apps_grid_view->layer());
  224. EXPECT_TRUE(apps_grid_view->layer()->GetAnimator()->is_animating());
  225. EXPECT_TRUE(apps_grid_view->layer()->GetAnimator()->IsAnimatingProperty(
  226. ui::LayerAnimationElement::TRANSFORM));
  227. }
  228. TEST_F(AppsContainerViewTest, OpeningFolderRemovesOtherViewsFromAccessibility) {
  229. auto* helper = GetAppListTestHelper();
  230. helper->AddContinueSuggestionResults(4);
  231. helper->AddRecentApps(5);
  232. AddFolderWithApps(5);
  233. TabletMode::Get()->SetEnabledForTest(true);
  234. // Force the sorting toast to show.
  235. AppListController::Get()->UpdateAppListWithNewTemporarySortOrder(
  236. AppListSortOrder::kColor,
  237. /*animate=*/false, /*update_position_closure=*/base::OnceClosure());
  238. ASSERT_TRUE(GetToastContainerView()->GetToastButton());
  239. // Open the folder.
  240. AppListItemView* folder_item =
  241. helper->GetRootPagedAppsGridView()->GetItemViewAt(0);
  242. LeftClickOn(folder_item);
  243. // Note: For fullscreen app list, the search box is part of the focus cycle
  244. // when a folder is open.
  245. auto* continue_section = helper->GetFullscreenContinueSectionView();
  246. EXPECT_TRUE(continue_section->GetViewAccessibility().IsIgnored());
  247. EXPECT_TRUE(continue_section->GetViewAccessibility().IsLeaf());
  248. auto* recent_apps = helper->GetFullscreenRecentAppsView();
  249. EXPECT_TRUE(recent_apps->GetViewAccessibility().IsIgnored());
  250. EXPECT_TRUE(recent_apps->GetViewAccessibility().IsLeaf());
  251. auto* toast_container = GetToastContainerView();
  252. EXPECT_TRUE(toast_container->GetViewAccessibility().IsIgnored());
  253. EXPECT_TRUE(toast_container->GetViewAccessibility().IsLeaf());
  254. auto* apps_grid_view = helper->GetRootPagedAppsGridView();
  255. EXPECT_TRUE(apps_grid_view->GetViewAccessibility().IsIgnored());
  256. EXPECT_TRUE(apps_grid_view->GetViewAccessibility().IsLeaf());
  257. // Close the folder.
  258. PressAndReleaseKey(ui::VKEY_ESCAPE);
  259. EXPECT_FALSE(continue_section->GetViewAccessibility().IsIgnored());
  260. EXPECT_FALSE(continue_section->GetViewAccessibility().IsLeaf());
  261. EXPECT_FALSE(recent_apps->GetViewAccessibility().IsIgnored());
  262. EXPECT_FALSE(recent_apps->GetViewAccessibility().IsLeaf());
  263. EXPECT_FALSE(toast_container->GetViewAccessibility().IsIgnored());
  264. EXPECT_FALSE(toast_container->GetViewAccessibility().IsLeaf());
  265. EXPECT_FALSE(apps_grid_view->GetViewAccessibility().IsIgnored());
  266. EXPECT_FALSE(apps_grid_view->GetViewAccessibility().IsLeaf());
  267. }
  268. TEST_F(AppsContainerViewTest, UpdatesSelectedPageAfterFocusTraversal) {
  269. auto* helper = GetAppListTestHelper();
  270. helper->AddRecentApps(5);
  271. helper->AddAppItems(16);
  272. TabletMode::Get()->SetEnabledForTest(true);
  273. auto* apps_grid_view = helper->GetRootPagedAppsGridView();
  274. auto* recent_apps_view = helper->GetFullscreenRecentAppsView();
  275. auto* search_box = helper->GetSearchBoxView()->search_box();
  276. // Focus moves to the search box.
  277. PressDown();
  278. EXPECT_TRUE(search_box->HasFocus());
  279. EXPECT_EQ(GetSelectedPage(), 0);
  280. // Focus moves to the first item inside `RecentAppsView`.
  281. PressDown();
  282. EXPECT_TRUE(recent_apps_view->GetItemViewAt(0)->HasFocus());
  283. EXPECT_EQ(GetSelectedPage(), 0);
  284. // Focus moves to the first item / first row inside `PagedAppsGridView`.
  285. PressDown();
  286. EXPECT_TRUE(apps_grid_view->GetItemViewAt(0)->HasFocus());
  287. EXPECT_EQ(GetSelectedPage(), 0);
  288. // Focus moves to the first item / second row inside `PagedAppsGridView`.
  289. PressDown();
  290. EXPECT_TRUE(apps_grid_view->GetItemViewAt(5)->HasFocus());
  291. EXPECT_EQ(GetSelectedPage(), 0);
  292. // Focus moves to the first item / third row inside `PagedAppsGridView`.
  293. PressDown();
  294. EXPECT_TRUE(apps_grid_view->GetItemViewAt(10)->HasFocus());
  295. EXPECT_EQ(GetSelectedPage(), 0);
  296. // Focus moves to the first item / first row on the second page of
  297. // `PagedAppsGridView`.
  298. PressDown();
  299. EXPECT_TRUE(apps_grid_view->GetItemViewAt(15)->HasFocus());
  300. EXPECT_EQ(GetSelectedPage(), 1);
  301. // Focus moves to the search box, but second page stays active.
  302. PressDown();
  303. EXPECT_TRUE(search_box->HasFocus());
  304. EXPECT_EQ(GetSelectedPage(), 1);
  305. // Focus moves to the first item inside `RecentAppsView` and activates first
  306. // page.
  307. PressDown();
  308. EXPECT_TRUE(recent_apps_view->GetItemViewAt(0)->HasFocus());
  309. EXPECT_EQ(GetSelectedPage(), 0);
  310. }
  311. // Test that the gradient mask is created when the page drag begins, and
  312. // destroyed once the page drag has been released and completes.
  313. TEST_F(AppsContainerViewTest, StartPageDragThenRelease) {
  314. GetAppListTestHelper()->AddAppItems(23);
  315. TabletMode::Get()->SetEnabledForTest(true);
  316. auto* apps_grid_view = GetAppListTestHelper()->GetRootPagedAppsGridView();
  317. test::AppsGridViewTestApi test_api(apps_grid_view);
  318. EXPECT_FALSE(HasGradientMask());
  319. EXPECT_EQ(0, GetSelectedPage());
  320. EXPECT_EQ(2, GetTotalPages());
  321. TransitionWaiter transition_waiter(apps_grid_view->pagination_model());
  322. gfx::Point start_page_drag = test_api.GetViewAtIndex(GridIndex(0, 0))
  323. ->GetIconBoundsInScreen()
  324. .bottom_right();
  325. start_page_drag.Offset(10, 0);
  326. // Begin a touch and drag the page upward.
  327. auto* generator = GetEventGenerator();
  328. generator->set_current_screen_location(start_page_drag);
  329. generator->PressTouch();
  330. generator->MoveTouchBy(0, -20);
  331. // Move the touch down a bit so it does not register as a fling to the next
  332. // page.
  333. generator->MoveTouchBy(0, 1);
  334. // Gradient mask should exist during the page drag.
  335. EXPECT_TRUE(HasGradientMask());
  336. // End the page drag and wait for the page to animate back to the correct
  337. // position.
  338. generator->ReleaseTouch();
  339. transition_waiter.Wait();
  340. // The gradient mask should be removed after the end of the page animation.
  341. EXPECT_FALSE(HasGradientMask());
  342. EXPECT_EQ(0, GetSelectedPage());
  343. }
  344. } // namespace ash