chromium/ash/app_list/app_list_presenter_unittest.cc

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include "ash/accessibility/accessibility_controller.h"
#include "ash/app_list/app_list_bubble_presenter.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_model_provider.h"
#include "ash/app_list/app_list_presenter_impl.h"
#include "ash/app_list/model/app_list_item.h"
#include "ash/app_list/model/app_list_test_model.h"
#include "ash/app_list/model/search/test_search_result.h"
#include "ash/app_list/quick_app_access_model.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_bubble_search_page.h"
#include "ash/app_list/views/app_list_bubble_view.h"
#include "ash/app_list/views/app_list_folder_view.h"
#include "ash/app_list/views/app_list_item_view.h"
#include "ash/app_list/views/app_list_main_view.h"
#include "ash/app_list/views/app_list_search_view.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/app_list/views/apps_container_view.h"
#include "ash/app_list/views/apps_grid_view.h"
#include "ash/app_list/views/apps_grid_view_test_api.h"
#include "ash/app_list/views/contents_view.h"
#include "ash/app_list/views/continue_section_view.h"
#include "ash/app_list/views/paged_apps_grid_view.h"
#include "ash/app_list/views/recent_apps_view.h"
#include "ash/app_list/views/remove_query_confirmation_dialog.h"
#include "ash/app_list/views/result_selection_controller.h"
#include "ash/app_list/views/scrollable_apps_grid_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/app_list/views/search_result_actions_view.h"
#include "ash/app_list/views/search_result_base_view.h"
#include "ash/app_list/views/search_result_list_view.h"
#include "ash/app_list/views/search_result_page_anchored_dialog.h"
#include "ash/app_list/views/search_result_page_view.h"
#include "ash/assistant/ui/assistant_ui_constants.h"
#include "ash/display/display_configuration_controller.h"
#include "ash/display/display_configuration_controller_test_api.h"
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/test/keyboard_test_util.h"
#include "ash/public/cpp/app_list/app_list_controller_observer.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/root_window_controller.h"
#include "ash/rotator/screen_rotation_animator.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_app_button.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_test_util.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shell.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/test/ash_test_base.h"
#include "ash/wallpaper/wallpaper_controller_test_api.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_types.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace_controller_test_api.h"
#include "base/command_line.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/display/display.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/events/event_constants.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/touch_selection/touch_selection_menu_runner.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/touchui/touch_selection_controller_impl.h"
#include "ui/views/touchui/touch_selection_menu_runner_views.h"
#include "ui/views/view_class_properties.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

constexpr int kBestMatchContainerIndex = 1;

SearchModel* GetSearchModel() {
  return AppListModelProvider::Get()->search_model();
}

int64_t GetPrimaryDisplayId() {
  return display::Screen::GetScreen()->GetPrimaryDisplay().id();
}

void EnableTabletMode(bool enable) {
  // Avoid |TabletModeController::OnGetSwitchStates| from disabling tablet mode
  // again at the end of |TabletModeController::TabletModeController|.
  base::RunLoop().RunUntilIdle();

  if (enable) {
    ash::TabletModeControllerTestApi().EnterTabletMode();
  } else {
    ash::TabletModeControllerTestApi().LeaveTabletMode();
  }

  // The app list will be shown automatically when tablet mode is enabled (Home
  // launcher flag is enabled). Wait here for the animation complete.
  base::RunLoop().RunUntilIdle();
}

std::unique_ptr<TestSearchResult> CreateOmniboxSuggestionResult(
    const std::string& result_id,
    bool support_removal) {
  auto suggestion_result = std::make_unique<TestSearchResult>();
  suggestion_result->set_result_id(result_id);
  suggestion_result->set_best_match(true);
  suggestion_result->set_display_type(SearchResultDisplayType::kList);
  if (support_removal) {
    SearchResultActions actions;
    actions.emplace_back(SearchResultActionType::kRemove, u"Remove");
    suggestion_result->SetActions(actions);
  }
  // Give this item a name so that the accessibility paint checks pass.
  // (Focusable items should have accessible names.)
  suggestion_result->SetAccessibleName(base::UTF8ToUTF16(result_id));

  return suggestion_result;
}

// Verifies the current search result page anchored dialog bounds.
// The dialog is expected to be positioned horizontally centered within the
// search box bounds.
void SanityCheckSearchResultsAnchoredDialogBounds(
    const views::Widget* dialog,
    const SearchBoxView* search_box_view) {
  auto horizontal_center_offset = [](const gfx::Rect& inner,
                                     const gfx::Rect& outer) -> int {
    return outer.CenterPoint().x() - inner.CenterPoint().x();
  };

  const gfx::Rect dialog_bounds = dialog->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds = search_box_view->GetBoundsInScreen();
  // The dialog should be horizontally centered within the search box.
  EXPECT_EQ(0, horizontal_center_offset(dialog_bounds, search_box_bounds));
  // Verify the confirmation dialog is positioned with the top within search
  // box bounds.
  EXPECT_GT(dialog_bounds.y(), search_box_bounds.y());
  EXPECT_LT(dialog_bounds.y(), search_box_bounds.bottom());
}

// Returns the search box view from either the clamshell bubble or the tablet
// mode fullscreen launcher.
SearchBoxView* GetSearchBoxViewFromHelper(AppListTestHelper* helper) {
  if (!Shell::Get()->IsInTabletMode()) {
    DCHECK(Shell::Get()->app_list_controller()->IsVisible());
    return helper->GetBubbleSearchBoxView();
  }
  return helper->GetSearchBoxView();
}

// Test observer to verify that `AppListView` / its presenter do not call
// `OnVisibilityChanged(false)` during **aborted** hide animation.
class TestAppListControllerObserver : public AppListControllerObserver {
 public:
  TestAppListControllerObserver() = default;
  TestAppListControllerObserver(const TestAppListControllerObserver&) = delete;
  TestAppListControllerObserver& operator=(
      const TestAppListControllerObserver&) = delete;
  ~TestAppListControllerObserver() override {
    Shell::Get()->app_list_controller()->RemoveObserver(this);
  }

  void OnAppListVisibilityChanged(bool shown, int64_t display_id) override {
    if (!shown)
      ++visibility_changed_to_hidden_times_;
  }

  int visibility_changed_to_hidden_times() const {
    return visibility_changed_to_hidden_times_;
  }

 private:
  int visibility_changed_to_hidden_times_ = 0;
};

}  // namespace

// This suite used to be called AppListPresenterDelegateTest. It's not called
// AppListPresenterImplTest because that name was already taken. The two test
// suite were not merged in order to maintain git blame history for this file.
class AppListPresenterTest : public AshTestBase,
                             public testing::WithParamInterface<bool> {
 public:
  AppListPresenterTest() = default;
  AppListPresenterTest(const AppListPresenterTest&) = delete;
  AppListPresenterTest& operator=(const AppListPresenterTest&) = delete;
  ~AppListPresenterTest() override = default;

  // testing::Test:
  void SetUp() override {
    AshTestBase::SetUp();

    // Make the display big enough to hold the app list.
    UpdateDisplay("1024x768");
  }

  // Ensures the launcher is visible and showing the apps grid.
  void EnsureLauncherWithVisibleAppsGrid() {
    auto* helper = GetAppListTestHelper();
    helper->ShowAndRunLoop(GetPrimaryDisplayId());
    helper->WaitUntilIdle();
  }

  void SetAppListStateAndWait(AppListViewState new_state) {
    GetAppListView()->SetState(new_state);
    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckState(new_state);
  }

  // Whether to run the test with mouse or gesture events.
  bool TestMouseEventParam() const { return GetParam(); }

  // Whether to run the test with fullscreen or not.
  bool TestFullscreenParam() const { return GetParam(); }

  test::AppListTestModel* GetAppListModel() {
    return GetAppListTestHelper()->model();
  }

  SearchBoxView* GetSearchBoxView() {
    return GetSearchBoxViewFromHelper(GetAppListTestHelper());
  }

  gfx::Point GetPointOutsideSearchbox() {
    // Ensures that the point satisfies the following conditions:
    // (1) The point is within AppListView.
    // (2) The point is outside of the search box.
    // (3) The touch event on the point should not be consumed by the handler
    // for back gesture.
    return GetSearchBoxView()->GetBoundsInScreen().bottom_right();
  }

  gfx::Point GetPointInsideSearchbox() {
    return GetSearchBoxView()->GetBoundsInScreen().CenterPoint();
  }

  AppListView* GetAppListView() {
    return GetAppListTestHelper()->GetAppListView();
  }

  SearchResultPageView* search_result_page() {
    return GetAppListView()
        ->app_list_main_view()
        ->contents_view()
        ->search_result_page_view();
  }

  SearchResultContainerView* GetDefaultSearchResultListView() {
    return search_result_page()
        ->search_view()
        ->result_container_views_for_test()[kBestMatchContainerIndex];
  }

  AppsGridView* apps_grid_view() {
    return GetAppListTestHelper()->GetScrollableAppsGridView();
  }

  void ClickMouseAt(const gfx::Point& point) {
    ui::test::EventGenerator* generator = GetEventGenerator();
    generator->MoveMouseTo(point);
    generator->PressLeftButton();
    generator->ReleaseLeftButton();
  }

  void LongPressAt(const gfx::Point& point) {
    ui::GestureEvent long_press(
        point.x(), point.y(), 0, base::TimeTicks::Now(),
        ui::GestureEventDetails(ui::EventType::kGestureLongPress));
    GetEventGenerator()->Dispatch(&long_press);
  }
};

// Instantiate the values in the parameterized tests. Used to
// toggle mouse and touch events and in some tests to toggle fullscreen mode
// tests.
INSTANTIATE_TEST_SUITE_P(All, AppListPresenterTest, testing::Bool());

// Tests tablet and clamshell mode combinations.
class AppListBubbleAndTabletTestBase : public AshTestBase {
 public:
  explicit AppListBubbleAndTabletTestBase(bool tablet_mode)
      : tablet_mode_(tablet_mode) {}
  AppListBubbleAndTabletTestBase(const AppListBubbleAndTabletTestBase&) =
      delete;
  AppListBubbleAndTabletTestBase& operator=(
      const AppListBubbleAndTabletTestBase&) = delete;
  ~AppListBubbleAndTabletTestBase() override = default;

  // testing::Test:
  void SetUp() override {
    AshTestBase::SetUp();

    // Make the display big enough to hold the app list.
    UpdateDisplay("1024x768");
  }

  AppsGridView* GetAppsGridView() {
    if (tablet_mode_param())
      return GetAppListTestHelper()->GetRootPagedAppsGridView();
    return GetAppListTestHelper()->GetScrollableAppsGridView();
  }

  void SetupGridTestApi() {
    grid_test_api_ =
        std::make_unique<test::AppsGridViewTestApi>(GetAppsGridView());
  }

  void OnReorderAnimationDone(base::OnceClosure closure,
                              bool aborted,
                              AppListGridAnimationStatus status) {
    EXPECT_FALSE(aborted);
    EXPECT_EQ(AppListGridAnimationStatus::kReorderFadeIn, status);
    std::move(closure).Run();
  }

  void SortAppList(AppListSortOrder order) {
    tablet_mode_param()
        ? GetAppListTestHelper()
              ->GetAppsContainerView()
              ->UpdateForNewSortingOrder(
                  order,
                  /*animate=*/true,
                  /*update_position_closure=*/base::DoNothing(),
                  /*animation_done_closure=*/base::DoNothing())
        : GetAppListTestHelper()->GetBubbleView()->UpdateForNewSortingOrder(
              order,
              /*animate=*/true, /*update_position_closure=*/base::DoNothing());

    base::RunLoop run_loop;
    GetAppsGridView()->AddReorderCallbackForTest(base::BindRepeating(
        &AppListBubbleAndTabletTestBase::OnReorderAnimationDone,
        base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  // Whether we should run the test in tablet mode.
  bool tablet_mode_param() { return tablet_mode_; }

  // Bubble launcher is visible in clamshell mode.
  bool should_show_bubble_launcher() { return !tablet_mode_param(); }

  test::AppListTestModel* GetAppListModel() {
    return GetAppListTestHelper()->model();
  }

  SearchBoxView* GetSearchBoxView() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleSearchBoxView()
               : GetAppListTestHelper()->GetAppListView()->search_box_view();
  }

  SearchResultPageView* GetFullscreenSearchPage() {
    return GetAppListTestHelper()
        ->GetAppListView()
        ->app_list_main_view()
        ->contents_view()
        ->search_result_page_view();
  }

  bool AppListSearchResultPageVisible() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleSearchPage()->GetVisible()
               : GetFullscreenSearchPage()->GetVisible();
  }

  SearchResultContainerView* GetDefaultSearchResultListView() {
    if (should_show_bubble_launcher()) {
      return GetAppListTestHelper()
          ->GetBubbleAppListSearchView()
          ->result_container_views_for_test()[kBestMatchContainerIndex];
    }
    return GetFullscreenSearchPage()
        ->search_view()
        ->result_container_views_for_test()[kBestMatchContainerIndex];
  }

  ResultSelectionController* GetResultSelectionController() {
    if (should_show_bubble_launcher()) {
      return GetAppListTestHelper()
          ->GetBubbleAppListSearchView()
          ->result_selection_controller_for_test();
    }

    return GetFullscreenSearchPage()
        ->search_view()
        ->result_selection_controller_for_test();
  }

  SearchResultPageAnchoredDialog* GetSearchResultPageDialog() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleSearchPageDialog()
               : GetAppListTestHelper()->GetFullscreenSearchPageDialog();
  }

  void CancelSearchResultPageDialog() {
    views::Widget* widget = GetSearchResultPageDialog()->widget();
    views::WidgetDelegate* widget_delegate = widget->widget_delegate();
    views::test::WidgetDestroyedWaiter widget_waiter(widget);
    GestureTapOn(static_cast<RemoveQueryConfirmationDialog*>(widget_delegate)
                     ->GetCancelButtonForTesting());
    widget_waiter.Wait();
  }

  void AcceptSearchResultPageDialog() {
    views::Widget* widget = GetSearchResultPageDialog()->widget();
    views::WidgetDelegate* widget_delegate = widget->widget_delegate();
    views::test::WidgetDestroyedWaiter widget_waiter(widget);
    GestureTapOn(static_cast<RemoveQueryConfirmationDialog*>(widget_delegate)
                     ->GetAcceptButtonForTesting());
    widget_waiter.Wait();
  }

  ContinueSectionView* GetContinueSectionView() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleContinueSectionView()
               : GetAppListTestHelper()->GetFullscreenContinueSectionView();
  }

  RecentAppsView* GetRecentAppsView() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleRecentAppsView()
               : GetAppListTestHelper()->GetFullscreenRecentAppsView();
  }

  views::View* GetAppsSeparator() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleLauncherAppsSeparatorView()
               : GetAppListTestHelper()
                     ->GetFullscreenLauncherAppsSeparatorView();
  }

  AppListFolderView* GetFolderView() {
    return should_show_bubble_launcher()
               ? GetAppListTestHelper()->GetBubbleFolderView()
               : GetAppListTestHelper()->GetFullscreenFolderView();
  }

  void DeleteFolderItemChildren(AppListFolderItem* item) {
    std::vector<std::string> items_to_delete;
    for (size_t i = 0; i < item->ChildItemCount(); ++i) {
      items_to_delete.push_back(item->GetChildItemAt(i)->id());
    }
    for (auto& item_to_delete : items_to_delete)
      GetAppListModel()->DeleteItem(item_to_delete);
  }

  void LongPressAt(const gfx::Point& point) {
    ui::GestureEvent long_press(
        point.x(), point.y(), 0, base::TimeTicks::Now(),
        ui::GestureEventDetails(ui::EventType::kGestureLongPress));
    GetEventGenerator()->Dispatch(&long_press);
  }

  void EnsureBubbleLauncherShown() {
    Shell::Get()->app_list_controller()->bubble_presenter_for_test()->Show(
        GetPrimaryDisplay().id());
  }

  void EnsureLauncherShown() {
    const bool in_tablet_mode = Shell::Get()->IsInTabletMode();

    // App list always visible in tablet mode, so launcher needs to explicitly
    // be shown only when in clamshell mode.
    if (!in_tablet_mode)
      EnsureBubbleLauncherShown();

    auto* helper = GetAppListTestHelper();
    if (!in_tablet_mode) {
      apps_grid_view_ = helper->GetScrollableAppsGridView();
    } else {
      apps_grid_view_ = helper->GetRootPagedAppsGridView();
    }
    DCHECK(apps_grid_view_);
  }

  gfx::Point SearchBoxCenterPoint() {
    SearchBoxView* search_box_view =
        should_show_bubble_launcher()
            ? GetAppListTestHelper()->GetBubbleSearchBoxView()
            : GetAppListTestHelper()->GetAppListView()->search_box_view();
    return search_box_view->GetBoundsInScreen().CenterPoint();
  }

  AppListFolderView* folder_view() {
    auto* helper = GetAppListTestHelper();
    return should_show_bubble_launcher() ? helper->GetBubbleFolderView()
                                         : helper->GetFullscreenFolderView();
  }

  bool AppListIsInFolderView() {
    return GetAppListTestHelper()->IsInFolderView();
  }

 protected:
  const bool tablet_mode_;

  std::unique_ptr<test::AppsGridViewTestApi> grid_test_api_;
  raw_ptr<AppsGridView, DanglingUntriaged> apps_grid_view_ = nullptr;
};

// Parameterized by tablet/clamshell mode.
class AppListBubbleAndTabletTest : public AppListBubbleAndTabletTestBase,
                                   public testing::WithParamInterface<bool> {
 public:
  AppListBubbleAndTabletTest()
      : AppListBubbleAndTabletTestBase(/*tablet_mode=*/GetParam()) {}
  AppListBubbleAndTabletTest(const AppListBubbleAndTabletTest&) = delete;
  AppListBubbleAndTabletTest& operator=(const AppListBubbleAndTabletTest&) =
      delete;
  ~AppListBubbleAndTabletTest() override = default;
};

// Instantiate the values in the parameterized tests. The boolean
// determines whether to run the test in tablet mode.
INSTANTIATE_TEST_SUITE_P(TabletMode,
                         AppListBubbleAndTabletTest,
                         testing::Bool());

// Subclass suite to test drag specific behavior, parameterized by
// tablet/clamshell mode and drag and whether drag and drop refactor is enabled.
class AppListBubbleAndTabletDragTest
    : public AppListBubbleAndTabletTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  AppListBubbleAndTabletDragTest()
      : AppListBubbleAndTabletTestBase(
            /*tablet_mode=*/std::get<0>(GetParam())) {
    scoped_feature_list_.InitWithFeatureState(
        app_list_features::kDragAndDropRefactor,
        is_drag_drop_refactor_enabled());
  }
  AppListBubbleAndTabletDragTest(const AppListBubbleAndTabletDragTest&) =
      delete;
  AppListBubbleAndTabletDragTest& operator=(
      const AppListBubbleAndTabletDragTest&) = delete;
  ~AppListBubbleAndTabletDragTest() override = default;

  bool is_drag_drop_refactor_enabled() { return std::get<1>(GetParam()); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Instantiate the values in the parameterized tests. The boolean
// determines whether to run the test in tablet mode with or without drag and
// drop refactor feature enabled.
INSTANTIATE_TEST_SUITE_P(TabletMode,
                         AppListBubbleAndTabletDragTest,
                         testing::Combine(testing::Bool(), testing::Bool()));

// Tests only tablet mode.
class AppListTabletTest : public AppListBubbleAndTabletTestBase {
 public:
  AppListTabletTest() : AppListBubbleAndTabletTestBase(/*tablet_mode=*/true) {}
  AppListTabletTest(const AppListTabletTest&) = delete;
  AppListTabletTest& operator=(const AppListTabletTest&) = delete;
  ~AppListTabletTest() override = default;
};

// Used to test app_list behavior with a populated apps_grid.
class PopulatedAppListTest : public AshTestBase,
                             public testing::WithParamInterface<bool> {
 public:
  PopulatedAppListTest() : is_drag_drop_refactor_enabled_(GetParam()) {}
  ~PopulatedAppListTest() override = default;

  void SetUp() override {
    AppListConfigProvider::Get().ResetForTesting();
    scoped_feature_list_.InitWithFeatureState(
        app_list_features::kDragAndDropRefactor,
        is_drag_drop_refactor_enabled_);
    AshTestBase::SetUp();

    // Make the display big enough to hold the app list.
    UpdateDisplay("1024x768");

    // Fullscreen launcher is used only in tablet mode, so enable tablet mode.
    EnableTabletMode(true);
  }

 protected:
  void OpenAppListInFullscreen() {
    AppListPresenterImpl* presenter =
        Shell::Get()->app_list_controller()->fullscreen_presenter();
    presenter->Show(AppListViewState::kFullscreenAllApps,
                    GetPrimaryDisplay().id(), base::TimeTicks::Now(),
                    /*show_source=*/std::nullopt);
    app_list_view_ = presenter->GetView();
  }

  void InitializeAppsGrid() {
    if (!app_list_view_)
      OpenAppListInFullscreen();
    apps_grid_view_ = app_list_view_->app_list_main_view()
                          ->contents_view()
                          ->apps_container_view()
                          ->apps_grid_view();
    apps_grid_test_api_ =
        std::make_unique<test::AppsGridViewTestApi>(apps_grid_view_);
  }

  void PopulateApps(int n) {
    GetAppListModel()->PopulateApps(n);
    app_list_view_->GetWidget()->LayoutRootViewIfNecessary();
  }

  AppListFolderItem* CreateAndPopulateFolderWithApps(int n) {
    auto* folder = GetAppListModel()->CreateAndPopulateFolderWithApps(n);
    app_list_view_->GetWidget()->LayoutRootViewIfNecessary();
    return folder;
  }

  gfx::Rect GetItemRectOnCurrentPageAt(int row, int col) {
    DCHECK_GT(GetAppListModel()->top_level_item_list()->item_count(), 0u);
    return apps_grid_test_api_->GetItemTileRectOnCurrentPageAt(row, col);
  }

  bool AppListIsInFolderView() const {
    return app_list_view_->app_list_main_view()
        ->contents_view()
        ->apps_container_view()
        ->IsInFolderView();
  }

  AppListFolderView* folder_view() {
    return app_list_view_->app_list_main_view()
        ->contents_view()
        ->apps_container_view()
        ->app_list_folder_view();
  }

  void UpdateFolderName(const std::string& name) {
    std::u16string folder_name = base::UTF8ToUTF16(name);
    folder_view()->folder_header_view()->SetFolderNameForTest(folder_name);
    folder_view()->folder_header_view()->ContentsChanged(
        folder_view()->folder_header_view()->GetFolderNameViewForTest(),
        folder_name);
  }

  test::AppListTestModel* GetAppListModel() {
    return GetAppListTestHelper()->model();
  }

  const std::string GetFolderName() {
    return base::UTF16ToUTF8(
        folder_view()->folder_header_view()->GetFolderNameForTest());
  }

  void RefreshFolderName() {
    folder_view()->folder_header_view()->ItemNameChanged();
  }

  void RotateScreen() {
    display::Display display =
        display::Screen::GetScreen()->GetPrimaryDisplay();
    display_manager()->SetDisplayRotation(
        display.id(), display::Display::ROTATE_90,
        display::Display::RotationSource::ACTIVE);
    // AppListView is usually notified of display bounds changes by
    // AppListPresenter, though the test delegate implementation does not
    // track display metrics changes, so OnParentWindowBoundsChanged() has to be
    // explicitly called here.
    app_list_view_->OnParentWindowBoundsChanged();
  }

  bool is_drag_drop_refactor_enabled() {
    return is_drag_drop_refactor_enabled_;
  }

  std::unique_ptr<test::AppsGridViewTestApi> apps_grid_test_api_;
  raw_ptr<AppListView, DanglingUntriaged> app_list_view_ =
      nullptr;  // Owned by native widget.
  raw_ptr<PagedAppsGridView, DanglingUntriaged> apps_grid_view_ =
      nullptr;  // Owned by |app_list_view_|.
  base::test::ScopedFeatureList scoped_feature_list_;
  const bool is_drag_drop_refactor_enabled_;
};

INSTANTIATE_TEST_SUITE_P(All, PopulatedAppListTest, testing::Bool());

// Subclass of PopulatedAppListTest which enables the virtual keyboard.
class PopulatedAppListWithVKEnabledTest : public PopulatedAppListTest {
 public:
  PopulatedAppListWithVKEnabledTest() = default;
  ~PopulatedAppListWithVKEnabledTest() override = default;

  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        keyboard::switches::kEnableVirtualKeyboard);
    PopulatedAppListTest::SetUp();
  }
};
INSTANTIATE_TEST_SUITE_P(All,
                         PopulatedAppListWithVKEnabledTest,
                         testing::Values(true));

// Subclass of PopulatedAppListTest which tests legacy behavior for the apps
// grid without the drag and drop refactoring enabled.
class PopulatedAppListLegacyBehaviourTest : public PopulatedAppListTest {
 public:
  PopulatedAppListLegacyBehaviourTest() = default;
  ~PopulatedAppListLegacyBehaviourTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All,
                         PopulatedAppListLegacyBehaviourTest,
                         testing::Values(false));

// Subclass of PopulatedAppListTest which tests the apps grid drag behavior
// interrumpted during a screen rotation. Enables drag and drop refactor by
// default.
class PopulatedAppListScreenRotationTest : public PopulatedAppListTest {
 public:
  PopulatedAppListScreenRotationTest() = default;
  ~PopulatedAppListScreenRotationTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(All,
                         PopulatedAppListScreenRotationTest,
                         testing::Values(true));

// Verify that open folders are closed after sorting apps grid.
TEST_P(AppListBubbleAndTabletTest, SortingClosesOpenFolderView) {
  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  GetAppListModel()->CreateAndPopulateFolderWithApps(4);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  SetupGridTestApi();

  grid_test_api_->PressItemAt(0);
  EXPECT_TRUE(AppListIsInFolderView());

  SortAppList(AppListSortOrder::kNameAlphabetical);
  EXPECT_FALSE(AppListIsInFolderView());
}

// Tests that folder item view does not animate out and in after folder is
// closed (and the folder item location in apps grid did not change while the
// folder was shown).
TEST_P(AppListBubbleAndTabletTest,
       FolderItemViewNotAnimatingAfterClosingFolder) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // Cache the initial folder item bounds.
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* const folder_view = GetFolderView();

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(2);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      GetFolderView()->GetBoundsInScreen().right_center() +
      gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  base::RunLoop folder_animation_waiter;
  // Once folder completes hiding, the folder item view should be moved to
  // target location.
  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
    folder_animation_waiter.Quit();

    EXPECT_EQ(original_folder_item_bounds,
              folder_item_view->GetBoundsInScreen());

    // The folder item position did not change, so the item view should not
    // start fading out when the folder view hides.
    EXPECT_FALSE(folder_item_view->layer());
  }));
  folder_animation_waiter.Run();

  EXPECT_FALSE(AppListIsInFolderView());

  // Verify that the folder is visible, and positioned in its final bounds.
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // No item layers are expected to be created.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder view bounds do not change if an item gets added to app list
// model while the folder view is visible (even if it changes the folder item
// view position in the root apps grid).
TEST_P(AppListBubbleAndTabletTest,
       FolderViewRemainsInPlaceWhenAddingItemToModel) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* const folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect final_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Add a new item.
  test::AppListTestModel::AppListTestItem* new_item =
      model->CreateItem("new_test_item");
  new_item->SetPosition(GetAppListTestHelper()
                            ->model()
                            ->top_level_item_list()
                            ->item_at(0)
                            ->position()
                            .CreateBefore());
  model->AddItem(new_item);
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
  grid_test_api_->WaitForItemMoveAnimationDone();

  // Verify that the folder view location did not change.
  EXPECT_EQ(folder_bounds, GetFolderView()->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(3);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 2 should be laid out right of the folder while the folder
  // is shown.
  EXPECT_LT(original_folder_item_bounds.right(),
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().x());

  // The item at slot 1 should be laid out left of the folder.
  EXPECT_GT(original_folder_item_bounds.x(),
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().right());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      GetFolderView()->GetBoundsInScreen().right_center() +
      gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  base::RunLoop folder_animation_waiter;
  // Once folder completes hiding, the folder item view should be moved to
  // target location.
  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
    folder_animation_waiter.Quit();

    EXPECT_EQ(original_folder_item_bounds,
              folder_item_view->GetBoundsInScreen());

    // The folder item should start fading out in it's current position.
    ASSERT_TRUE(folder_item_view->layer());
    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
  }));

  folder_animation_waiter.Run();

  EXPECT_FALSE(AppListIsInFolderView());

  // Wait for the folder item to fade out.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  grid_test_api_->WaitForItemMoveAnimationDone();

  // Make sure the folder item view fade in animation is done.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  // Verify that the folder is visible, and positioned in its final bounds.
  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
  EXPECT_FALSE(folder_item_view->layer());

  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder view bounds do not change if position of the original
// folder item view changes in the model (as long as the folder is open).
TEST_P(AppListBubbleAndTabletTest,
       FolderViewRemainsInPlaceWhenItemMovedToEndInModel) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* const folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
  const gfx::Rect final_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(5)->GetBoundsInScreen();

  // Move the folder item to the last position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(5)
                                   ->position()
                                   .CreateAfter(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(5);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 2 in the model should remain at slot 3 (where it was
  // before folder item moved in the model).
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // The item at slot 1 should be remain in place.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
                               gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  base::RunLoop folder_animation_waiter;
  // Once folder completes hiding, the folder item view should be moved to
  // target location.
  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
    folder_animation_waiter.Quit();

    EXPECT_EQ(original_folder_item_bounds,
              folder_item_view->GetBoundsInScreen());

    // The folder item should start fading out in it's current position.
    ASSERT_TRUE(folder_item_view->layer());
    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
  }));

  folder_animation_waiter.Run();

  EXPECT_FALSE(AppListIsInFolderView());

  // Wait for the folder item to fade out.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  grid_test_api_->WaitForItemMoveAnimationDone();

  // Make sure the folder item view fade in animation is done.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  // Verify that the folder is visible, and positioned in its final bounds.
  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
  EXPECT_FALSE(folder_item_view->layer());

  // The item at slot 1 should be remain in place.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  // The item at slot 2 in the model should move into original folder item slot.
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder view bounds do not change if position of the original
// folder item view changes in the model (as long as the folder is open).
TEST_P(AppListBubbleAndTabletTest,
       FolderViewRemainsInPlaceWhenItemMovedToStartInModel) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* const folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();
  const gfx::Rect final_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(0)->GetBoundsInScreen();

  // Move the folder item to the last position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(0)
                                   ->position()
                                   .CreateBefore(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 3 in the model did not change, so it should remain in
  // place.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());

  // The item at slot 2 in the model should remain in the old position (slot 1).
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
                               gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  base::RunLoop folder_animation_waiter;
  // Once folder completes hiding, the folder item view should be moved to
  // target location.
  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
    folder_animation_waiter.Quit();

    EXPECT_EQ(original_folder_item_bounds,
              folder_item_view->GetBoundsInScreen());

    // The folder item should start fading out in it's current position.
    ASSERT_TRUE(folder_item_view->layer());
    EXPECT_EQ(0.0f, folder_item_view->layer()->GetTargetOpacity());
  }));

  folder_animation_waiter.Run();

  EXPECT_FALSE(AppListIsInFolderView());

  // Wait for the folder item to fade out.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  grid_test_api_->WaitForItemMoveAnimationDone();

  // Make sure the folder item view fade in animation is done.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  // Verify that the folder is visible, and positioned in its final bounds.
  EXPECT_EQ(final_folder_item_bounds, folder_item_view->GetBoundsInScreen());
  EXPECT_FALSE(folder_item_view->layer());

  // The item at slot 2 in the model should move into original folder item slot.
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  // The item at slot 3 in the model should move into new position.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder item deletion during folder view hide animation is handled
// well.
TEST_P(AppListBubbleAndTabletTest,
       ReorderedFolderItemDeletionDuringFolderClose) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* const folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Move the folder item to the first position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(0)
                                   ->position()
                                   .CreateBefore(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 3 in the model did not change, so it should remain in
  // place.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
  // The item at slot 2 in the model should remain in the old position (slot 1).
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
                               gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  // Delete the folder item while the folder is animating out.
  DeleteFolderItemChildren(folder_item);
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
  grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_FALSE(AppListIsInFolderView());

  // Verify remaining items are moved into correct slots.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder item deletion just after folder gets hidden (while item
// bounds are still animating to final positions) gets handled well.
TEST_P(AppListBubbleAndTabletTest,
       ReorderedFolderItemDeletionDuringFolderItemFadeOut) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Move the folder item to the last position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(0)
                                   ->position()
                                   .CreateBefore(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 3 in the model did not change, so it should remain in
  // place.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
  // The item at slot 2 in the model should remain in the old position (slot 1).
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
                               gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  base::RunLoop folder_animation_waiter;
  // Once folder completes hiding, the folder item view should be moved to
  // target location.
  folder_view->SetAnimationDoneTestCallback(base::BindLambdaForTesting([&]() {
    folder_animation_waiter.Quit();

    // Delete the folder item while items are animating into their final
    // positions.
    DeleteFolderItemChildren(folder_item);
  }));

  folder_animation_waiter.Run();

  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
  grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_FALSE(AppListIsInFolderView());

  // Verify remaining items are moved into correct slots.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder item deletion just after folder gets hidden (while item
// bounds are still animating to final positions) gets handled well.
TEST_P(AppListBubbleAndTabletTest,
       ReorderedFolderItemDeletionAfterFolderItemFadeOut) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Move the folder item to the last position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(0)
                                   ->position()
                                   .CreateBefore(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 3 in the model did not change, so it should remain in
  // place.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
  // The item at slot 2 in the model should remain in the old position (slot 1).
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(folder_view->GetBoundsInScreen().right_center() +
                               gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();

  EXPECT_TRUE(folder_view->IsAnimationRunning());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  GetAppListTestHelper()->WaitForFolderAnimation();

  // Wait for the folder item to fade out.
  if (folder_item_view->layer()) {
    ui::LayerAnimationStoppedWaiter animation_waiter;
    animation_waiter.Wait(folder_item_view->layer());
  }

  // Delete the folder item while items are animating into their final
  // positions.
  DeleteFolderItemChildren(folder_item);
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_FALSE(AppListIsInFolderView());

  // Verify remaining items are moved into correct slots.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());

  // Verify that item view layers have been deleted.
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that folder item deletion while the folder is shown gets handled well.
TEST_P(AppListBubbleAndTabletTest,
       ReorderedFolderItemDeletionWhileFolderShown) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());
  GetAppListTestHelper()->WaitForFolderAnimation();
  AppListFolderView* folder_view = GetFolderView();

  // Cache the initial folder bounds.
  const gfx::Rect folder_bounds = folder_view->GetBoundsInScreen();
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Move the folder item to the last position in the model.
  model->RequestPositionUpdate(folder_id,
                               GetAppListTestHelper()
                                   ->model()
                                   ->top_level_item_list()
                                   ->item_at(0)
                                   ->position()
                                   .CreateBefore(),
                               RequestPositionUpdateReason::kMoveItem);

  // Verify that the folder view location did not actually change.
  EXPECT_EQ(folder_bounds, folder_view->GetBoundsInScreen());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_TRUE(folder_item_view);
  ASSERT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(original_folder_item_bounds, folder_item_view->GetBoundsInScreen());

  // The item at slot 3 in the model did not change, so it should remain in
  // place.
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
  // The item at slot 2 in the model should remain in the old position (slot 1).
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());

  // Delete the folder item while it's still shown.
  DeleteFolderItemChildren(folder_item);

  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
  grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_FALSE(AppListIsInFolderView());

  // Verify remaining items are moved into correct slots.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
}

// Tests that folder item deletion while the folder view is still animating into
// shown state gets handled well.
TEST_P(AppListBubbleAndTabletTest, ReorderedFolderItemDeletionDuringShow) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* const folder_item =
      model->CreateAndPopulateFolderWithApps(3);
  const std::string folder_id = folder_item->id();
  model->PopulateApps(3);

  // Setup tablet/clamshell mode and show launcher.
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  SetupGridTestApi();

  ui::ScopedAnimationDurationScaleMode scope_duration(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  grid_test_api_->PressItemAt(2);
  EXPECT_TRUE(AppListIsInFolderView());

  // Cache the initial folder bounds.
  const gfx::Rect original_folder_item_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();
  const gfx::Rect original_item_1_bounds =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen();
  const gfx::Rect original_item_3_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Delete the folder item while the folder is still showing.
  DeleteFolderItemChildren(folder_item);
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
  grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_FALSE(AppListIsInFolderView());

  // Verify remaining items are moved into correct slots.
  EXPECT_EQ(original_item_1_bounds,
            apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen());
  EXPECT_EQ(original_folder_item_bounds,
            apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen());
  EXPECT_EQ(original_item_3_bounds,
            apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen());
}

// Tests that Zero State Search is only shown when needed.
TEST_P(AppListBubbleAndTabletTest, LauncherSearchZeroState) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  ui::test::EventGenerator* generator = GetEventGenerator();

  // Tap Search Box to activate it and check search result view visibility.
  generator->GestureTapAt(SearchBoxCenterPoint());
  EXPECT_FALSE(AppListSearchResultPageVisible());

  // Type a character into the textfield and check visibility.
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Delete the character in the textfield and check visibility.
  generator->PressKey(ui::VKEY_BACK, 0);
  EXPECT_FALSE(AppListSearchResultPageVisible());
}

// Verifies that changes in launcher search box do not cause duplicate search
// requests if both clamshell and tablet app list views exist (and one of them
// is hidden).
TEST_P(AppListBubbleAndTabletTest, NoDuplicateSearchRequests) {
  // Toggle tablet mode to ensure the app list view for tablet mode state
  // opposite to the one used in test is created.
  EnableTabletMode(!tablet_mode_param());
  EnsureLauncherShown();
  EnableTabletMode(tablet_mode_param());

  EnsureLauncherShown();

  // Type a character into the textfield and verify this issues a single search
  // request.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  TestAppListClient* const client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ(std::vector<std::u16string>({u"a"}),
            client->GetAndResetPastSearchQueries());
  generator->PressKey(ui::VKEY_B, 0);
  EXPECT_EQ(std::vector<std::u16string>({u"ab"}),
            client->GetAndResetPastSearchQueries());
  generator->PressKey(ui::VKEY_BACK, 0);
  EXPECT_EQ(std::vector<std::u16string>({u"a"}),
            client->GetAndResetPastSearchQueries());
  generator->PressKey(ui::VKEY_BACK, 0);
  EXPECT_EQ(std::vector<std::u16string>({u""}),
            client->GetAndResetPastSearchQueries());
}

TEST_P(AppListBubbleAndTabletTest, ClearSearchButtonClearsSearch) {
  // Toggle tablet mode to ensure the app list view for tablet mode state
  // opposite to the one used in test is created.
  EnableTabletMode(!tablet_mode_param());
  EnsureLauncherShown();
  EnableTabletMode(tablet_mode_param());

  EnsureLauncherShown();

  // Type a character into the textfield and verify this issues a single search
  // request.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  TestAppListClient* const client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ(std::vector<std::u16string>({u"a"}),
            client->GetAndResetPastSearchQueries());
  generator->PressKey(ui::VKEY_B, 0);
  EXPECT_EQ(std::vector<std::u16string>({u"ab"}),
            client->GetAndResetPastSearchQueries());

  SearchBoxView* search_box_view = GetSearchBoxView();
  search_box_view->GetWidget()->LayoutRootViewIfNecessary();
  EXPECT_TRUE(
      search_box_view->filter_and_close_button_container()->GetVisible());
  LeftClickOn(search_box_view->close_button());

  EXPECT_EQ(std::vector<std::u16string>({u""}),
            client->GetAndResetPastSearchQueries());
}

// Regression test for b/204482740.
TEST_P(AppListBubbleAndTabletTest, AppListEventTargeterForAssistantScrolling) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // A custom event targeter is installed.
  aura::Window* window = Shell::Get()->app_list_controller()->GetWindow();
  ASSERT_TRUE(window);
  aura::WindowTargeter* targeter = window->targeter();
  ASSERT_TRUE(targeter);

  // Simulate an assistant card with a webview being shown, which sets a window
  // property on its window. See AssistantCardElementView::AddedToWidget().
  aura::Window* child =
      aura::test::CreateTestWindowWithBounds(gfx::Rect(100, 100), window);
  child->SetProperty(assistant::ui::kOnlyAllowMouseClickEvents, true);

  // Scroll events are blocked for that window.
  constexpr int offset = 10;
  ui::ScrollEvent scroll_down(ui::EventType::kScroll, gfx::Point(),
                              base::TimeTicks::Now(), ui::EF_NONE, 0, offset, 0,
                              offset, /*finger_count=*/2);
  EXPECT_FALSE(targeter->SubtreeShouldBeExploredForEvent(child, scroll_down));

  // Click events are not blocked.
  ui::MouseEvent press(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                       base::TimeTicks::Now(), ui::EF_NONE,
                       ui::EF_LEFT_MOUSE_BUTTON);
  ui::MouseEvent release(ui::EventType::kMouseReleased, gfx::Point(),
                         gfx::Point(), base::TimeTicks::Now(), ui::EF_NONE,
                         ui::EF_LEFT_MOUSE_BUTTON);
  EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(child, press));
  EXPECT_TRUE(targeter->SubtreeShouldBeExploredForEvent(child, press));
}

// Tests that apps container/page does not have a separator between apps grid
// and recent apps/continue section if neither continue section nor recent apps
// are shown.
TEST_P(AppListBubbleAndTabletTest,
       NoSeparatorWithoutRecentAppsOrContinueSection) {
  GetAppListTestHelper()->AddAppItems(5);
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  views::View* separator = GetAppsSeparator();
  EXPECT_TRUE(separator);

  RecentAppsView* recent_apps = GetRecentAppsView();
  EXPECT_TRUE(recent_apps);

  ContinueSectionView* continue_section = GetContinueSectionView();
  EXPECT_TRUE(continue_section);

  EXPECT_FALSE(separator->GetVisible());
  EXPECT_FALSE(recent_apps->GetVisible());
  EXPECT_FALSE(continue_section->GetVisible());

  // If some content gets added to continue section, separator is expected to
  // show.
  GetAppListTestHelper()->AddContinueSuggestionResults(3);
  // Run loop, as continue section content is updated asynchronously.
  base::RunLoop().RunUntilIdle();

  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  EXPECT_TRUE(separator->GetVisible());
  EXPECT_TRUE(continue_section->GetVisible());
  EXPECT_FALSE(recent_apps->GetVisible());
}

// Tests that apps container/page has a separator between apps grid
// and recent apps/continue section if recent apps are shown.
TEST_P(AppListBubbleAndTabletTest, SeparatorShownWithRecentApps) {
  GetAppListTestHelper()->AddAppItems(5);
  GetAppListTestHelper()->AddRecentApps(4);
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  views::View* separator = GetAppsSeparator();
  EXPECT_TRUE(separator);

  RecentAppsView* recent_apps = GetRecentAppsView();
  EXPECT_TRUE(recent_apps);

  ContinueSectionView* continue_section = GetContinueSectionView();
  EXPECT_TRUE(continue_section);

  EXPECT_TRUE(separator->GetVisible());
  EXPECT_TRUE(recent_apps->GetVisible());
  EXPECT_FALSE(continue_section->GetVisible());
}

// Tests that apps container/page has a separator between apps grid
// and recent apps/continue section if continue section is shown.
TEST_P(AppListBubbleAndTabletTest, SeparatorShownWithContinueSection) {
  GetAppListTestHelper()->AddAppItems(5);
  GetAppListTestHelper()->AddContinueSuggestionResults(4);
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  views::View* separator = GetAppsSeparator();
  EXPECT_TRUE(separator);

  RecentAppsView* recent_apps = GetRecentAppsView();
  EXPECT_TRUE(recent_apps);

  ContinueSectionView* continue_section = GetContinueSectionView();
  EXPECT_TRUE(continue_section);

  EXPECT_TRUE(separator->GetVisible());
  EXPECT_TRUE(continue_section->GetVisible());
  EXPECT_FALSE(recent_apps->GetVisible());
}

// Tests that apps container/page has a separator between apps grid
// and recent apps/continue section if recent apps and continue section are
// shown.
TEST_P(AppListBubbleAndTabletTest,
       SeparatorShownWithContinueSectionAndRecentApps) {
  GetAppListTestHelper()->AddAppItems(5);
  GetAppListTestHelper()->AddContinueSuggestionResults(4);
  GetAppListTestHelper()->AddRecentApps(4);
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  views::View* separator = GetAppsSeparator();
  EXPECT_TRUE(separator);

  RecentAppsView* recent_apps = GetRecentAppsView();
  EXPECT_TRUE(recent_apps);

  ContinueSectionView* continue_section = GetContinueSectionView();
  EXPECT_TRUE(continue_section);

  EXPECT_TRUE(separator->GetVisible());
  EXPECT_TRUE(recent_apps->GetVisible());
  EXPECT_TRUE(continue_section->GetVisible());
}

// Test that the separator is centered between recent apps and the first row
// of the apps grid, when recent apps are shown.
TEST_F(AppListTabletTest, SeparatorCenteredBetweenRecentAppsAndAppsGrid) {
  GetAppListTestHelper()->AddAppItems(5);
  GetAppListTestHelper()->AddContinueSuggestionResults(3);
  EnableTabletMode(true);

  views::View* separator = GetAppsSeparator();
  AppsContainerView* apps_container =
      GetAppListTestHelper()->GetAppsContainerView();

  // Separator is not centered and should be positioned below the continue
  // section, because recent apps are not shown.
  EXPECT_EQ(separator->GetBoundsInScreen().y(),
            GetContinueSectionView()->GetBoundsInScreen().bottom() +
                separator->GetProperty(views::kMarginsKey)->height() / 2);

  // Add recent apps and layout to update the separator's bounds.
  GetAppListTestHelper()->AddRecentApps(4);
  apps_container->ResetForShowApps();
  GetAppsGridView()->GetWidget()->LayoutRootViewIfNecessary();
  views::View* recent_apps = GetRecentAppsView();

  SetupGridTestApi();
  const AppListItemView* first_row_item =
      grid_test_api_->GetViewAtIndex(GridIndex(0, 0));

  // Separator should be centered between the bottom of recent apps and the
  // top of the first row of the apps grid.
  const int centered_y = recent_apps->GetBoundsInScreen().bottom() +
                         (first_row_item->GetBoundsInScreen().y() -
                          recent_apps->GetBoundsInScreen().bottom()) /
                             2;
  EXPECT_EQ(centered_y, separator->GetBoundsInScreen().y());
}

// Verifies that tapping on the search box in tablet mode with animation and
// zero state enabled should not bring Chrome crash (https://crbug.com/958267).
TEST_P(AppListPresenterTest, ClickSearchBoxInTabletMode) {
  EnableTabletMode(true);
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  // Necessary for AppListView::StateAnimationMetricsReporter::Report being
  // called when animation ends.
  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  ui::test::EventGenerator* generator = GetEventGenerator();

  // Gesture tap on the search box.
  generator->GestureTapAt(GetPointInsideSearchbox());

  // Wait until animation finishes. Verifies AppListView's state.
  base::RunLoop().RunUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Gesture tap on the area out of search box.
  generator->GestureTapAt(GetPointOutsideSearchbox());

  // Wait until animation finishes. Verifies AppListView's state.
  base::RunLoop().RunUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
}

TEST_P(AppListBubbleAndTabletTest, RemoveSuggestionShowsConfirmDialog) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add suggestion results - the result that will be tested is in
  // the second place.
  GetSearchModel()->results()->Add(CreateOmniboxSuggestionResult(
      "Another suggestion", /*support_removal=*/true));
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  // The result list is updated asynchronously.
  base::RunLoop().RunUntilIdle();

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(1);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());

  // Make sure the search results page is laid out after adding result action
  // buttons.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  ASSERT_TRUE(result_view->actions_view());
  EXPECT_EQ(1u, result_view->actions_view()->children().size());
  views::View* const action_view = result_view->actions_view()->children()[0];

  // The remove action button is visible on hover only.
  EXPECT_FALSE(action_view->GetVisible());
  generator->MoveMouseTo(result_view->GetBoundsInScreen().CenterPoint());
  EXPECT_TRUE(action_view->GetVisible());

  // Record the current result selection before clicking the remove action
  // button.
  ResultSelectionController* result_selection_controller =
      GetResultSelectionController();
  EXPECT_TRUE(result_selection_controller->selected_result()->selected());
  ResultLocationDetails* result_location =
      result_selection_controller->selected_location_details();

  // Ensure layout after the action view visibility has been updated.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  // Click the remove action button, this should surface a confirmation dialog.
  LeftClickOn(action_view);

  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());
  ASSERT_TRUE(GetSearchResultPageDialog());

  // Cancel the dialog - the app list should remain in the search result page,
  // the suggestion removal dialog should be hidden, and no result action should
  // be invoked.
  CancelSearchResultPageDialog();

  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_FALSE(GetSearchResultPageDialog());
  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());

  // The result selection should be at the same position.
  EXPECT_TRUE(result_selection_controller->selected_result()->selected());
  EXPECT_EQ(*result_location,
            *result_selection_controller->selected_location_details());

  // Make sure that the action view is shown.
  generator->MoveMouseTo(action_view->GetBoundsInScreen().left_center());
  EXPECT_TRUE(action_view->GetVisible());

  // Ensure layout after the action view visibility has been updated.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  // Click remove suggestion action button again.
  generator->MoveMouseTo(action_view->GetBoundsInScreen().CenterPoint());
  generator->ClickLeftButton();

  // Expect the removal confirmation dialog - this time, accept it.
  ASSERT_TRUE(GetSearchResultPageDialog());
  AcceptSearchResultPageDialog();

  // The app list should remain showing search results, the dialog should be
  // closed, and result removal action should be invoked.
  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_FALSE(GetSearchResultPageDialog());

  // A result should still be selected.
  EXPECT_TRUE(result_selection_controller->selected_result()->selected());

  std::vector<TestAppListClient::SearchResultActionId> expected_actions = {
      {kTestResultId, SearchResultActionType::kRemove}};
  std::vector<TestAppListClient::SearchResultActionId> invoked_actions =
      GetAppListTestHelper()
          ->app_list_client()
          ->GetAndResetInvokedResultActions();
  EXPECT_EQ(expected_actions, invoked_actions);
}

TEST_P(AppListBubbleAndTabletTest, RemoveSuggestionUsingLongTap) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add suggestion results - the result that will be tested is in
  // the second place.
  GetSearchModel()->results()->Add(CreateOmniboxSuggestionResult(
      "Another suggestion", /*support_removal=*/true));
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  GetAppListTestHelper()->WaitUntilIdle();

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(1);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());

  // Make sure the search results page is laid out after adding result action
  // buttons.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  ASSERT_TRUE(result_view->actions_view());
  EXPECT_EQ(1u, result_view->actions_view()->children().size());
  views::View* const action_view = result_view->actions_view()->children()[0];

  EXPECT_FALSE(action_view->GetVisible());
  LongPressAt(result_view->GetBoundsInScreen().CenterPoint());
  EXPECT_TRUE(action_view->GetVisible());

  // Ensure layout after the action view visibility has been updated.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  // Click the remove action button, this should surface a confirmation dialog.
  LeftClickOn(action_view);

  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());
  ASSERT_TRUE(GetSearchResultPageDialog());

  // Cancel the dialog - the app list should remain in the search result page,
  // the suggestion removal dialog should be hidden, and no result action should
  // be invoked.
  CancelSearchResultPageDialog();

  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_FALSE(GetSearchResultPageDialog());

  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());
  EXPECT_FALSE(result_view->selected());

  // Long tap on the result again.
  LongPressAt(result_view->GetBoundsInScreen().CenterPoint());

  // Ensure layout after the action view visibility has been updated.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  // Click the remove action button, this should surface a confirmation dialog.
  LeftClickOn(action_view);

  // Expect the removal confirmation dialog - this time, accept it.
  ASSERT_TRUE(GetSearchResultPageDialog());
  AcceptSearchResultPageDialog();

  // The app list should remain showing search results, the dialog should be
  // closed, and result removal action should be invoked.
  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_FALSE(GetSearchResultPageDialog());
  EXPECT_FALSE(result_view->selected());

  std::vector<TestAppListClient::SearchResultActionId> expected_actions = {
      {kTestResultId, SearchResultActionType::kRemove}};

  std::vector<TestAppListClient::SearchResultActionId> invoked_actions =
      GetAppListTestHelper()
          ->app_list_client()
          ->GetAndResetInvokedResultActions();
  EXPECT_EQ(expected_actions, invoked_actions);
}

TEST_P(AppListBubbleAndTabletTest, RemoveSuggestionUsingKeyboard) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add suggestion results - the result that will be tested is in
  // the second place.
  GetSearchModel()->results()->Add(CreateOmniboxSuggestionResult(
      "Another suggestion", /*support_removal=*/true));
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  GetAppListTestHelper()->WaitUntilIdle();

  // Select a removable suggestion.
  generator->PressKey(ui::VKEY_DOWN, 0);

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(1);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());
  ASSERT_TRUE(result_view->selected());
  ASSERT_TRUE(result_view->actions_view());
  EXPECT_EQ(1u, result_view->actions_view()->children().size());

  // Press shortcut to delete the result.
  generator->PressKey(ui::VKEY_BROWSER_BACK, ui::EF_ALT_DOWN);

  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());
  ASSERT_TRUE(GetSearchResultPageDialog());

  // Expect the removal confirmation dialog - accept it.
  ASSERT_TRUE(GetSearchResultPageDialog());
  AcceptSearchResultPageDialog();

  // The app list should remain showing search results, the dialog should be
  // closed, and result removal action should be invoked.
  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_FALSE(GetSearchResultPageDialog());
  EXPECT_FALSE(result_view->selected());

  std::vector<TestAppListClient::SearchResultActionId> expected_actions = {
      {kTestResultId, SearchResultActionType::kRemove}};

  std::vector<TestAppListClient::SearchResultActionId> invoked_actions =
      GetAppListTestHelper()
          ->app_list_client()
          ->GetAndResetInvokedResultActions();
  EXPECT_EQ(expected_actions, invoked_actions);
}

TEST_P(AppListBubbleAndTabletTest,
       SuggestionRemoveShortcutOnViewWithNoRemovalAction) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add suggestion results.
  GetSearchModel()->results()->Add(CreateOmniboxSuggestionResult(
      "Another suggestion", /*support_removal=*/false));
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/false));
  GetAppListTestHelper()->WaitUntilIdle();

  generator->PressKey(ui::VKEY_DOWN, 0);

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(1);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());
  ASSERT_TRUE(result_view->selected());

  // Press shortcut to delete the result.
  generator->PressKey(ui::VKEY_BROWSER_BACK, ui::EF_ALT_DOWN);

  EXPECT_TRUE(GetAppListTestHelper()
                  ->app_list_client()
                  ->GetAndResetInvokedResultActions()
                  .empty());
  EXPECT_FALSE(GetSearchResultPageDialog());

  EXPECT_TRUE(AppListSearchResultPageVisible());
  EXPECT_EQ(u"a", GetSearchBoxView()->search_box()->GetText());

  result_view = GetDefaultSearchResultListView()->GetResultViewAt(1);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  EXPECT_EQ(kTestResultId, result_view->result()->id());
  EXPECT_TRUE(result_view->selected());
}

TEST_P(AppListBubbleAndTabletTest,
       TransitionToAppsContainerClosesRemoveSuggestionDialog) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add a zero state suggestion result.
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  GetAppListTestHelper()->WaitUntilIdle();

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(0);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());

  // Show remove suggestion dialog.
  result_view->GetWidget()->LayoutRootViewIfNecessary();
  LongPressAt(result_view->GetBoundsInScreen().CenterPoint());
  result_view->GetWidget()->LayoutRootViewIfNecessary();
  LeftClickOn(result_view->actions_view()->children()[0]);

  ASSERT_TRUE(GetSearchResultPageDialog());

  views::Widget* const confirmation_dialog =
      GetSearchResultPageDialog()->widget();
  ASSERT_TRUE(confirmation_dialog);

  SanityCheckSearchResultsAnchoredDialogBounds(confirmation_dialog,
                                               GetSearchBoxView());

  // Verify that transition to apps page hides the removal confirmation dialog.
  views::test::WidgetDestroyedWaiter widget_close_waiter(confirmation_dialog);
  GetSearchBoxView()->ClearSearchAndDeactivateSearchBox();
  EXPECT_FALSE(AppListSearchResultPageVisible());

  widget_close_waiter.Wait();
}

TEST_P(AppListBubbleAndTabletTest,
       RemoveSuggestionDialogBoundsUpdateWhenVKHidden) {
  // Enable virtual keyboard for this test.
  KeyboardController* const keyboard_controller =
      Shell::Get()->keyboard_controller();
  keyboard_controller->SetEnableFlag(
      keyboard::KeyboardEnableFlag::kCommandLineEnabled);

  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add a suggestion result.
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  GetAppListTestHelper()->WaitUntilIdle();

  SearchResultBaseView* result_view =
      GetDefaultSearchResultListView()->GetResultViewAt(0);
  ASSERT_TRUE(result_view);
  ASSERT_TRUE(result_view->result());
  ASSERT_EQ(kTestResultId, result_view->result()->id());

  auto* const keyboard_ui_controller = keyboard::KeyboardUIController::Get();
  keyboard_ui_controller->ShowKeyboard(false /* locked */);
  ASSERT_TRUE(keyboard::test::WaitUntilShown());

  // Show remove suggestion dialog.
  result_view->GetWidget()->LayoutRootViewIfNecessary();
  LongPressAt(result_view->GetBoundsInScreen().CenterPoint());

  // Ensure layout after the action view visibility has been updated.
  result_view->GetWidget()->LayoutRootViewIfNecessary();

  // Click the remove action button, this should surface a confirmation dialog.
  LeftClickOn(result_view->actions_view()->children()[0]);

  ASSERT_TRUE(GetSearchResultPageDialog());

  // The search box should have lost the focus, which should have hidden the
  // keyboard.
  EXPECT_FALSE(keyboard_ui_controller->IsKeyboardVisible());

  // Sanity check the confirmation dialog bounds (hiding the keyboard might have
  // changed the position of the search box - the confirmation dialog should
  // have followed it).
  views::Widget* const confirmation_dialog =
      GetSearchResultPageDialog()->widget();
  SanityCheckSearchResultsAnchoredDialogBounds(confirmation_dialog,
                                               GetSearchBoxView());

  views::test::WidgetDestroyedWaiter widget_close_waiter(confirmation_dialog);
  GetSearchBoxView()->ClearSearchAndDeactivateSearchBox();
  EXPECT_FALSE(AppListSearchResultPageVisible());
  if (tablet_mode_param())
    EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());

  // Exiting the search results page should close the dialog.
  widget_close_waiter.Wait();
}

// Verifies that rotating the screen when launcher is shown does not crash.
TEST_P(AppListBubbleAndTabletTest, RotationAnimationSmoke) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(15);
  model->CreateAndPopulateFolderWithApps(3);
  model->PopulateApps(15);

  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ScreenRotationAnimator* animator =
      DisplayConfigurationControllerTestApi(
          Shell::Get()->display_configuration_controller())
          .GetScreenRotationAnimatorForDisplay(display.id());
  animator->Rotate(display::Display::ROTATE_90,
                   display::Display::RotationSource::USER,
                   DisplayConfigurationController::ANIMATION_SYNC);
}

// Verifies that rotating the screen and shutting down when the launcher is
// shown does not crash.
TEST_P(AppListBubbleAndTabletTest, ShutdownDuringRotationAnimationSmoke) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(1);
  model->CreateAndPopulateFolderWithApps(3);
  model->PopulateApps(1);

  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ScreenRotationAnimator* animator =
      DisplayConfigurationControllerTestApi(
          Shell::Get()->display_configuration_controller())
          .GetScreenRotationAnimatorForDisplay(display.id());
  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  animator->Rotate(display::Display::ROTATE_90,
                   display::Display::RotationSource::USER,
                   DisplayConfigurationController::ANIMATION_SYNC);
}

// Verifies that rotating the screen when launcher is shown does not crash.
TEST_P(AppListBubbleAndTabletTest, RotationAnimationWithFolderSmoke) {
  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(1);
  model->CreateAndPopulateFolderWithApps(3);
  model->PopulateApps(1);

  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(1));
  ASSERT_TRUE(AppListIsInFolderView());

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ScreenRotationAnimator* animator =
      DisplayConfigurationControllerTestApi(
          Shell::Get()->display_configuration_controller())
          .GetScreenRotationAnimatorForDisplay(display.id());
  animator->Rotate(display::Display::ROTATE_90,
                   display::Display::RotationSource::USER,
                   DisplayConfigurationController::ANIMATION_SYNC);

  // Close the folder view.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(
      GetFolderView()->GetBoundsInScreen().right_center() +
      gfx::Vector2d(10, 0));
  event_generator->ClickLeftButton();
  ASSERT_FALSE(AppListIsInFolderView());

  EXPECT_FALSE(GetFolderView()->shadow()->GetLayer()->visible());
}

TEST_P(AppListBubbleAndTabletTest, RotationAnimationInSearchSmoke) {
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();

  // Show search page.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);
  EXPECT_TRUE(AppListSearchResultPageVisible());

  // Add suggestion results - the result that will be tested is in
  // the second place.
  GetSearchModel()->results()->Add(CreateOmniboxSuggestionResult(
      "Another suggestion", /*support_removal=*/true));
  const std::string kTestResultId = "Test suggestion";
  GetSearchModel()->results()->Add(
      CreateOmniboxSuggestionResult(kTestResultId, /*support_removal=*/true));
  // The result list is updated asynchronously.
  base::RunLoop().RunUntilIdle();

  display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
  ScreenRotationAnimator* animator =
      DisplayConfigurationControllerTestApi(
          Shell::Get()->display_configuration_controller())
          .GetScreenRotationAnimatorForDisplay(display.id());
  animator->Rotate(display::Display::ROTATE_90,
                   display::Display::RotationSource::USER,
                   DisplayConfigurationController::ANIMATION_SYNC);
}

// Tests that mouse app list item drag is cancelled when mouse capture is lost
// (e.g. on screen rotation).
TEST_P(PopulatedAppListTest, CancelItemDragOnMouseCaptureLoss) {
  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) + 1);

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);

  // Start dragging the first item - move it in between items 1 and 2.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  dragged_view->FireMouseDragTimerForTest();

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    event_generator->MoveMouseTo(
        apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().left_center());
    EXPECT_TRUE(apps_grid_view_->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    UpdateDisplay("600x1200");
    // AppListView is usually notified of display bounds changes by
    // AppListPresenter, though the test delegate implementation does not
    // track display metrics changes, so OnParentWindowBoundsChanged() has to be
    // explicitly called here.
    app_list_view_->OnParentWindowBoundsChanged();
    // Verify that mouse drag has been canceled due to mouse capture loss.
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
    EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
    EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);
}

// Tests that app list item drag gets canceled if the dragged app list item gets
// deleted.
TEST_P(PopulatedAppListTest, CancelItemDragOnDragItemDeletion) {
  InitializeAppsGrid();
  PopulateApps(4);

  // Start dragging a view.
  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  dragged_view->FireMouseDragTimerForTest();

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    event_generator->MoveMouseTo(
        apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().left_center());
    EXPECT_TRUE(apps_grid_view_->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Delete the dragged item.
    GetAppListModel()->DeleteItem(dragged_view->item()->id());
    // Verify that mouse drag has been canceled.
    EXPECT_FALSE(apps_grid_view_->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Required by the DragDropController to finalize drag sequence.
    // TODO(b/261985897): Investigate the crash that occurs on these tests if
    // they are not properly releasing the drag.
    event_generator->ReleaseLeftButton();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);

  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 3", apps_grid_view_->GetItemViewAt(2)->item()->id());

  // Hide and show the app list again to verify checks done when resetting the
  // apps grid for show pass (e.g. verification that size of the app list views
  // model matches the size of app list data model).
  AppListTestHelper* helper = GetAppListTestHelper();
  helper->ShowAndRunLoop(GetPrimaryDisplay().id());
  helper->DismissAndRunLoop();
}

// Tests that app list item drag in folder gets canceled if the dragged app list
// item gets deleted.
TEST_P(PopulatedAppListTest, CancelFolderItemDragOnDragItemDeletion) {
  InitializeAppsGrid();
  PopulateApps(2);
  AppListFolderItem* folder = CreateAndPopulateFolderWithApps(3);
  PopulateApps(3);

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(2));
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* const dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());
  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    event_generator->MoveTouchBy(5, 5);
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_TRUE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Delete the dragged item.
    GetAppListModel()->DeleteItem(dragged_view->item()->id());
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Required by the DragDropController to finalize drag sequence.
    // TODO(b/261985897): Investigate the crash that occurs on these tests if
    // they are not properly releasing the drag.
    event_generator->ReleaseTouch();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ("Item 3",
            folder_view()->items_grid_view()->GetItemViewAt(0)->item()->id());

  // Hide and show the app list again to verify checks done when resetting the
  // apps grid for show pass (e.g. verification that size of the app list views
  // model matches the size of app list data model).
  AppListTestHelper* helper = GetAppListTestHelper();
  helper->ShowAndRunLoop(GetPrimaryDisplay().id());
  helper->DismissAndRunLoop();
}

// Tests that app list item drag from folder to root apps grid gets canceled if
// the dragged app list item gets deleted.
TEST_P(PopulatedAppListTest, CancelFolderItemReparentDragOnDragItemDeletion) {
  InitializeAppsGrid();
  PopulateApps(2);
  AppListFolderItem* folder = CreateAndPopulateFolderWithApps(3);
  PopulateApps(3);

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(2));
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* const dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  const std::string dragged_app_id = dragged_view->item()->id();
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Generate another mouse event to properly start the drag and drop sequence
    // with DragUpdate().
    event_generator->MoveTouchBy(5, 5);
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_TRUE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Drag the item outside the folder bounds and fire reparenting timer.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().CenterPoint());
    event_generator->MoveTouchBy(2, 2);
    EXPECT_TRUE(
        folder_view()->items_grid_view()->FireFolderItemReparentTimerForTest());
    EXPECT_FALSE(AppListIsInFolderView());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move item again to generate OnDragEnter()/OnDragExit() event.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());
    EXPECT_TRUE(apps_grid_view_->IsDragging());
    EXPECT_EQ(!is_drag_drop_refactor_enabled(),
              folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Delete the dragged item.
    GetAppListModel()->DeleteItem(dragged_app_id);
    // Verify that drag has been canceled.
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Required by the DragDropController to finalize drag sequence.
    // TODO(b/261985897): Investigate the crash that occurs on these tests if
    // they are not properly releasing the drag.
    event_generator->ReleaseTouch();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ("Item 5", apps_grid_view_->GetItemViewAt(3)->item()->id());

  // Hide and show the app list again to verify checks done when resetting the
  // apps grid for show pass (e.g. verification that size of the app list views
  // model matches the size of app list data model).
  AppListTestHelper* helper = GetAppListTestHelper();
  helper->ShowAndRunLoop(GetPrimaryDisplay().id());
  helper->DismissAndRunLoop();
}

TEST_P(PopulatedAppListTest,
       CancelFolderItemReparentDragOnDragItemAndFolderDeletion) {
  InitializeAppsGrid();
  PopulateApps(2);
  CreateAndPopulateFolderWithApps(2);
  PopulateApps(3);

  // Tap the folder item to show it.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->GestureTapAt(
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().CenterPoint());
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* const dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  const std::string dragged_app_id = dragged_view->item()->id();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    event_generator->MoveTouchBy(5, 5);
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_TRUE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Drag the item outside the folder bounds.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().CenterPoint());
    event_generator->MoveTouchBy(2, 2);

    // Fire reparenting timer.
    EXPECT_TRUE(
        folder_view()->items_grid_view()->FireFolderItemReparentTimerForTest());
    EXPECT_FALSE(AppListIsInFolderView());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move item within the main grid.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());

    EXPECT_TRUE(apps_grid_view_->IsDragging());
    EXPECT_EQ(!is_drag_drop_refactor_enabled(),
              folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Leave the dragged item as it's folder only child, and then delete it,
    // which should also delete the folder.
    GetAppListModel()->DeleteItem("Item 3");
    GetAppListModel()->DeleteItem(dragged_app_id);

    // Verify that drag has been canceled.
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Required by the DragDropController to finalize drag sequence.
    // TODO(b/261985897): Investigate the crash that occurs on these tests if
    // they are not properly releasing the drag.
    event_generator->ReleaseTouch();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 4", apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ("Item 5", apps_grid_view_->GetItemViewAt(3)->item()->id());

  // Hide and show the app list again to verify checks done when resetting the
  // apps grid for show pass (e.g. verification that size of the app list views
  // model matches the size of app list data model).
  AppListTestHelper* helper = GetAppListTestHelper();
  helper->ShowAndRunLoop(GetPrimaryDisplay().id());
  helper->DismissAndRunLoop();
}

// Tests that apps grid item layers are not destroyed immediately after item
// drag ends.
TEST_P(PopulatedAppListTest,
       ItemLayersNotDestroyedDuringBoundsAnimationAfterDrag) {
  InitializeAppsGrid();
  const int kItemCount = 5;
  PopulateApps(kItemCount);

  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);

  // Drag the first item between items 1 and 2.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  dragged_view->FireMouseDragTimerForTest();

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    event_generator->MoveMouseTo(
        apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().left_center());

    // Items should have layers during app list item drag.
    for (int i = 0; i < kItemCount; ++i) {
      views::View* item_view = apps_grid_view_->view_model()->view_at(i);
      EXPECT_TRUE(item_view->layer()) << "at " << i;
    }

    EXPECT_TRUE(apps_grid_view_->IsDragging());
  }));
  tasks.push_back(base::BindLambdaForTesting(
      [&]() { GetEventGenerator()->ReleaseLeftButton(); }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);

  // After the drag is released, the item bounds should animate to their final
  // bounds.
  EXPECT_TRUE(apps_grid_view_->IsItemAnimationRunning());
  for (int i = 0; i < kItemCount; ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_TRUE(item_view->layer()) << "at " << i;
  }

  // Wait for each item's layer animation to complete.
  ui::LayerAnimationStoppedWaiter animation_waiter;
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); i++) {
    if (apps_grid_view_->view_model()->view_at(i)->layer())
      animation_waiter.Wait(apps_grid_view_->view_model()->view_at(i)->layer());
  }

  // Layers should be destroyed once the item animations complete.
  for (int i = 0; i < kItemCount; ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }
}

// Tests that apps grid item drag operation can continue normally after display
// rotation (and app list config change).
TEST_P(PopulatedAppListLegacyBehaviourTest,
       ScreenRotationDuringAppsGridItemDrag) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) + 1);

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);

  // Start dragging the first item.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  event_generator->MoveTouch(
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().CenterPoint());

  UpdateDisplay("600x1200");
  // AppListView is usually notified of display bounds changes by
  // AppListPresenter, though the test delegate implementation does not
  // track display metrics changes, so OnParentWindowBoundsChanged() has to be
  // explicitly called here.
  app_list_view_->OnParentWindowBoundsChanged();

  // End drag at the in between items 1 and 2 - note that these have been
  // translated one slot left to fill in space left by the dragged view, so the
  // expected drop slot is actually slot 1.
  gfx::Point target =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().left_center();
  event_generator->MoveTouch(target);
  event_generator->ReleaseTouch();

  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
}

// Tests screen rotation during apps grid item drag where the drag gets
// canceled.
TEST_P(PopulatedAppListScreenRotationTest,
       ScreenRotationDuringAppsGridItemDragCancelsOperation) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) +
               apps_grid_test_api_->TilesPerPageInPagedGrid(1));

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);

  // Start dragging the first item.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // While the drag is running, rotate screen.
    RotateScreen();
    EXPECT_FALSE(apps_grid_view_->IsDragging());
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  // The model state should not have been changed.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
}

// Tests screen rotation during a folder apps grid item reparent drag where the
// drag gets canceled.
TEST_P(PopulatedAppListScreenRotationTest,
       ScreenRotationDuringFolderAppsGridItemDragCancelsOperation) {
  InitializeAppsGrid();
  PopulateApps(2);
  AppListFolderItem* folder = CreateAndPopulateFolderWithApps(3);
  PopulateApps(10);

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(2));
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* const dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Drag the item within the folder bounds.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().CenterPoint());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // While the drag is running, rotate screen.
    RotateScreen();
    EXPECT_FALSE(AppListIsInFolderView());
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  // The model state should not have been changed.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ("Item 5", apps_grid_view_->GetItemViewAt(3)->item()->id());
}

// Tests screen rotation during apps grid item drag where the drag gets
// canceled after a page change. Verifies the correct page is selected for the
// item that started the drag.
TEST_P(PopulatedAppListScreenRotationTest,
       ScreenRotationDuringAppsGridItemWithPageChange) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) +
               apps_grid_test_api_->TilesPerPageInPagedGrid(1));

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_EQ(2, apps_grid_view_->pagination_model()->total_pages());

  // Start dragging the first item.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move the item close to apps grid edge, to flip to the next page.
    event_generator->MoveTouch(
        apps_grid_view_->GetBoundsInScreen().bottom_center() +
        gfx::Vector2d(0, 5));
    EXPECT_TRUE(apps_grid_view_->FirePageFlipTimerForTest());
    apps_grid_view_->pagination_model()->FinishAnimation();
    EXPECT_EQ(1, apps_grid_view_->pagination_model()->selected_page());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // While the drag is running, rotate screen.
    RotateScreen();
    EXPECT_FALSE(AppListIsInFolderView());
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  // Make sure that the correct page displays, with the selected app.
  EXPECT_EQ(2, apps_grid_view_->pagination_model()->total_pages());
  EXPECT_EQ(0, apps_grid_view_->pagination_model()->selected_page());

  // The model state should not have been changed.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
}

// Tests screen rotation during apps grid item drag where the drag gets
// canceled after a page change. Verifies that the correct page is selected for
// them item that started the drag even if the item ends up in a different page.
TEST_P(PopulatedAppListScreenRotationTest,
       ScreenRotationDuringAppsGridItemWithPageNumberChange) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  // Initialize an apps grid with enough apps to have two pages on landscape
  // mode but only one page on portrait mode.
  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) + 2);

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);
  ASSERT_EQ(2, apps_grid_view_->pagination_model()->total_pages());

  // Start dragging the first item.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move the item close to apps grid edge, to flip to the next page.
    event_generator->MoveTouch(
        apps_grid_view_->GetBoundsInScreen().bottom_center() +
        gfx::Vector2d(0, 5));
    EXPECT_TRUE(apps_grid_view_->FirePageFlipTimerForTest());
    apps_grid_view_->pagination_model()->FinishAnimation();
    EXPECT_EQ(1, apps_grid_view_->pagination_model()->selected_page());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // While the drag is running, rotate screen.
    RotateScreen();
    EXPECT_FALSE(AppListIsInFolderView());
    EXPECT_FALSE(apps_grid_view_->IsDragging());
    EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  // Make sure that the correct page displays, with the selected app.
  EXPECT_EQ(1, apps_grid_view_->pagination_model()->total_pages());
  EXPECT_EQ(0, apps_grid_view_->pagination_model()->selected_page());

  // The model state should not have been changed.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
}

// Tests screen rotation during apps grid item drag where the drag item ends up
// in page-scroll area. Tests that the apps grid page scrolls without a crash,
// and that releasing drag does not change the item position in the model.
TEST_P(PopulatedAppListLegacyBehaviourTest,
       ScreenRotationDuringAppsGridItemDragWithPageScroll) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(apps_grid_test_api_->TilesPerPageInPagedGrid(0) +
               apps_grid_test_api_->TilesPerPageInPagedGrid(1));

  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(0);

  // Start dragging the first item.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  // Move the item close to screen edge, so it ends up in area that triggers
  // page scroll after rotation.
  event_generator->MoveTouch(app_list_view_->GetBoundsInScreen().left_center() +
                             gfx::Vector2d(64, 0));

  RotateScreen();

  ASSERT_EQ(2, apps_grid_view_->pagination_model()->total_pages());
  event_generator->MoveTouchBy(0, 10);
  EXPECT_TRUE(apps_grid_view_->FirePageFlipTimerForTest());
  // Move the pointer away from the grid horizontally for it to get out ouf apps
  // grid drag buffer, so the release results in a canceled drag
  // The grid is spread out vertically so there is no area under the grid
  // that's: in page flip area, outside of apps grid drag buffer, and outside of
  // shelf bounds.
  event_generator->MoveTouchBy(0, 270);
  event_generator->ReleaseTouch();

  // The model state should not have been changed.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ("Item 2", apps_grid_view_->GetItemViewAt(2)->item()->id());
}

// Tests screen rotation while app list folder item is in progress, and the item
// remains in the folder bounds during the drag.
TEST_P(PopulatedAppListLegacyBehaviourTest,
       ScreenRotationDuringFolderItemDrag) {
  // Set the display dimensions so rotation also changes the app list config.
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(2);
  AppListFolderItem* folder = CreateAndPopulateFolderWithApps(3);
  PopulateApps(10);

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(2));
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* const dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  // Drag the item within the folder bounds.
  event_generator->MoveTouch(
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen().CenterPoint());

  UpdateDisplay("600x1200");
  // AppListView is usually notified of display bounds changes by
  // AppListPresenter, though the test delegate implementation does not
  // track display metrics changes, so OnParentWindowBoundsChanged() has to be
  // explicitly called here.
  app_list_view_->OnParentWindowBoundsChanged();

  // The current behavior on app list bounds change is to close the active
  // folder, canceling the drag.
  EXPECT_FALSE(AppListIsInFolderView());
  EXPECT_FALSE(apps_grid_view_->IsDragging());
  EXPECT_FALSE(folder_view()->items_grid_view()->IsDragging());

  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ("Item 5", apps_grid_view_->GetItemViewAt(3)->item()->id());
}

// Tests that app list folder item reparenting drag (where a folder item is
// dragged outside the folder bounds, and dropped within the apps grid) can
// continue normally after screen rotation.
TEST_P(PopulatedAppListLegacyBehaviourTest,
       ScreenRotationDuringAppsGridItemReparentDrag) {
  UpdateDisplay("1200x600");

  InitializeAppsGrid();
  PopulateApps(2);
  AppListFolderItem* folder = CreateAndPopulateFolderWithApps(3);
  PopulateApps(10);

  // Tap the folder item to show it.
  GestureTapOn(apps_grid_view_->GetItemViewAt(2));
  ASSERT_TRUE(AppListIsInFolderView());

  // Start dragging the first item in the active folder.
  AppListItemView* dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  AppListItem* dragged_item = dragged_view->item();
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  // Drag the item outside the folder bounds.
  event_generator->MoveTouch(
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().CenterPoint());
  event_generator->MoveTouchBy(2, 2);

  // Fire reparenting timer.
  EXPECT_TRUE(
      folder_view()->items_grid_view()->FireFolderItemReparentTimerForTest());
  EXPECT_FALSE(AppListIsInFolderView());

  UpdateDisplay("600x1200");
  // AppListView is usually notified of display bounds changes by
  // AppListPresenter, though the test delegate implementation does not
  // track display metrics changes, so OnParentWindowBoundsChanged() has to be
  // explicitly called here.
  app_list_view_->OnParentWindowBoundsChanged();

  gfx::Point target =
      apps_grid_view_->GetItemViewAt(1)->GetBoundsInScreen().right_center();
  // End drag at the in between items 1 and 2.
  event_generator->MoveTouch(target);
  event_generator->ReleaseTouch();

  // Verify the new item location within the apps grid.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(dragged_item->id(),
            apps_grid_view_->GetItemViewAt(2)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(3)->item()->id());
}

// Tests that app list folder item reparenting drag to another folder.
TEST_P(AppListBubbleAndTabletDragTest, AppsGridItemReparentToFolderDrag) {
  UpdateDisplay("1200x600");

  test::AppListTestModel* model = GetAppListModel();
  model->PopulateApps(2);
  AppListFolderItem* folder = model->CreateAndPopulateFolderWithApps(3);
  model->PopulateApps(10);
  EnableTabletMode(tablet_mode_param());
  EnsureLauncherShown();
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  // Tap the folder item to show it.
  AppListItemView* folder_item = apps_grid_view_->GetItemViewAt(2);
  ASSERT_TRUE(folder_item);
  GestureTapOn(folder_item);
  ASSERT_TRUE(AppListIsInFolderView());
  ui::test::EventGenerator* event_generator = GetEventGenerator();

  // Start dragging the first item in the active folder.
  AppListItemView* dragged_view =
      folder_view()->items_grid_view()->GetItemViewAt(0);
  ASSERT_TRUE(dragged_view);
  AppListItem* dragged_item = dragged_view->item();
  event_generator->MoveTouch(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressTouch();
  ASSERT_TRUE(dragged_view->FireTouchDragTimerForTest());

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Drag the item outside the folder bounds.
    event_generator->MoveTouch(
        apps_grid_view_->GetItemViewAt(0)->GetBoundsInScreen().CenterPoint());
    event_generator->MoveTouchBy(2, 2);

    EXPECT_TRUE(
        folder_view()->items_grid_view()->FireFolderItemReparentTimerForTest());
    EXPECT_FALSE(AppListIsInFolderView());
  }));
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move the pointer over the item 3, and drop the dragged item.
    gfx::Point target =
        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint();
    event_generator->MoveTouch(target);
    event_generator->ReleaseTouch();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/true);

  // Verify the new item location within the apps grid.
  EXPECT_EQ("Item 0", apps_grid_view_->GetItemViewAt(0)->item()->id());
  EXPECT_EQ("Item 1", apps_grid_view_->GetItemViewAt(1)->item()->id());
  EXPECT_EQ(folder->id(), apps_grid_view_->GetItemViewAt(2)->item()->id());

  EXPECT_TRUE(apps_grid_view_->GetItemViewAt(3)->item()->is_folder());
  EXPECT_EQ(dragged_item->folder_id(),
            apps_grid_view_->GetItemViewAt(3)->item()->id());

  // Newly created folder should open and have the name input focused.
  EXPECT_TRUE(GetAppListTestHelper()->IsInFolderView());
  EXPECT_EQ(dragged_item->folder_id(), folder_view()->folder_item()->id());
  EXPECT_TRUE(folder_view()
                  ->folder_header_view()
                  ->GetFolderNameViewForTest()
                  ->HasFocus());
}

// Tests that an item can be removed just after creating a folder that contains
// that item. See https://crbug.com/1083942
TEST_P(PopulatedAppListTest, RemoveFolderItemAfterFolderCreation) {
  InitializeAppsGrid();
  const int kItemCount = 6;
  PopulateApps(kItemCount);

  // Dragging the item with index 2.
  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(2);
  AppListItem* const dragged_item = dragged_view->item();
  AppListItem* const merged_item = apps_grid_view_->GetItemViewAt(3)->item();

  const gfx::Rect expected_folder_item_view_bounds =
      apps_grid_view_->GetItemViewAt(2)->GetBoundsInScreen();

  // Drag the item on top of the item with index 3.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  dragged_view->FireMouseDragTimerForTest();

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move mouse to switch to cardified state -the cardified state starts only
    // once the drag distance exceeds a drag threshold, so the pointer has to
    // sufficiently move from the original position.
    event_generator->MoveMouseBy(10, 10);
    event_generator->MoveMouseTo(
        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());
    event_generator->ReleaseLeftButton();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);

  EXPECT_FALSE(apps_grid_view_->IsDragging());

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(2);
  EXPECT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(expected_folder_item_view_bounds,
            folder_item_view->GetBoundsInScreen());
  EXPECT_EQ(dragged_item->folder_id(), folder_item_view->item()->id());

  // Verify that item layers have been destroyed after the drag operation ended.
  apps_grid_test_api_->WaitForItemMoveAnimationDone();

  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }

  // Verify that item views have no layers after the folder has been opened.
  apps_grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_EQ(expected_folder_item_view_bounds,
            folder_item_view->GetBoundsInScreen());
  EXPECT_TRUE(AppListIsInFolderView());
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }

  // Verify that a pending layout, if any, does not cause a crash.
  apps_grid_view_->InvalidateLayout();
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  // Remove an item from the folder, and leave it as a single item folder.
  GetAppListModel()->DeleteItem(merged_item->id());
  EXPECT_TRUE(AppListIsInFolderView());
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  // Remove the original drag view item.
  GetAppListModel()->DeleteItem(dragged_item->id());
  apps_grid_test_api_->WaitForItemMoveAnimationDone();

  EXPECT_FALSE(AppListIsInFolderView());
  EXPECT_FALSE(apps_grid_view_->GetItemViewAt(2)->item()->is_folder());

  // Verify that a pending layout, if any, does not cause a crash.
  apps_grid_view_->InvalidateLayout();
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
}

TEST_P(PopulatedAppListTest, ReparentLastFolderItemAfterFolderCreation) {
  InitializeAppsGrid();
  const int kItemCount = 5;
  PopulateApps(kItemCount);

  // Dragging the item with index 4.
  AppListItemView* const dragged_view = apps_grid_view_->GetItemViewAt(4);
  AppListItem* const dragged_item = dragged_view->item();
  AppListItem* const merged_item = apps_grid_view_->GetItemViewAt(3)->item();
  const gfx::Rect expected_folder_item_view_bounds =
      apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen();

  // Drag the item on top of the item with index 3.
  ui::test::EventGenerator* event_generator = GetEventGenerator();
  event_generator->MoveMouseTo(dragged_view->GetBoundsInScreen().CenterPoint());
  event_generator->PressLeftButton();
  dragged_view->FireMouseDragTimerForTest();

  std::list<base::OnceClosure> tasks;
  tasks.push_back(base::BindLambdaForTesting([&]() {
    // Move mouse to switch to cardified state -the cardified state starts only
    // once the drag distance exceeds a drag threshold, so the pointer has to
    // sufficiently move from the original position.
    event_generator->MoveMouseBy(10, 10);
    event_generator->MoveMouseTo(
        apps_grid_view_->GetItemViewAt(3)->GetBoundsInScreen().CenterPoint());
    event_generator->ReleaseLeftButton();
  }));
  MaybeRunDragAndDropSequenceForAppList(&tasks, /*is_touch =*/false);
  EXPECT_FALSE(apps_grid_view_->IsDragging());

  AppListItem* folder_item = apps_grid_view_->GetItemViewAt(3)->item();
  EXPECT_TRUE(folder_item->is_folder());
  EXPECT_EQ(dragged_item->folder_id(), folder_item->id());

  // Verify that item layers have been destroyed after the drag operation ended.
  apps_grid_test_api_->WaitForItemMoveAnimationDone();

  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }

  AppListItemView* const folder_item_view = apps_grid_view_->GetItemViewAt(3);
  EXPECT_TRUE(folder_item_view->is_folder());
  EXPECT_EQ(expected_folder_item_view_bounds,
            folder_item_view->GetBoundsInScreen());

  // Verify that item views have no layers after the folder has been opened.
  apps_grid_test_api_->WaitForItemMoveAnimationDone();
  EXPECT_TRUE(AppListIsInFolderView());
  for (size_t i = 0; i < apps_grid_view_->view_model()->view_size(); ++i) {
    views::View* item_view = apps_grid_view_->view_model()->view_at(i);
    EXPECT_FALSE(item_view->layer()) << "at " << i;
  }

  // Verify that a pending layout, if any, does not cause a crash.
  apps_grid_view_->InvalidateLayout();
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();

  // Remove the original drag view item.
  GetAppListModel()->DeleteItem(dragged_item->id());
  // Reparent the remaining folder item to the root apps grid (as it's done by
  // Chrome when cleaning up single-item folders).
  GetAppListModel()->MoveItemToRootAt(merged_item, folder_item->position());
  apps_grid_test_api_->WaitForItemMoveAnimationDone();

  EXPECT_FALSE(AppListIsInFolderView());
  EXPECT_FALSE(apps_grid_view_->GetItemViewAt(3)->item()->is_folder());

  // Verify that a pending layout, if any, does not cause a crash.
  apps_grid_view_->InvalidateLayout();
  apps_grid_view_->GetWidget()->LayoutRootViewIfNecessary();
}

TEST_P(PopulatedAppListWithVKEnabledTest,
       TappingAppsGridClosesVirtualKeyboard) {
  InitializeAppsGrid();
  PopulateApps(2);
  gfx::Point between_apps = GetItemRectOnCurrentPageAt(0, 0).right_center();
  views::View::ConvertPointToScreen(apps_grid_view_, &between_apps);
  gfx::Point empty_space = GetItemRectOnCurrentPageAt(0, 2).CenterPoint();
  views::View::ConvertPointToScreen(apps_grid_view_, &empty_space);

  // Manually show the virtual keyboard.
  auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
  keyboard_controller->ShowKeyboard(true /* locked */);
  ASSERT_TRUE(keyboard::test::WaitUntilShown());

  // Touch the apps_grid outside of any apps. Expect that the keyboard is
  // closed.
  GetEventGenerator()->GestureTapAt(empty_space);
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());

  // Reshow the VKeyboard
  keyboard_controller->ShowKeyboard(true);
  ASSERT_TRUE(keyboard::test::WaitUntilShown());

  // Touch the apps_grid between two apps. Expect that the keyboard is closed.
  GetEventGenerator()->GestureTapAt(between_apps);
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
}

// Tests that app list hides when focus moves to a normal window.
TEST_P(AppListPresenterTest, HideOnFocusOut) {
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);

  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  wm::ActivateWindow(window.get());
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);
}

// Tests that app list remains visible when focus is moved to a different
// window in kShellWindowId_AppListContainer.
TEST_P(AppListPresenterTest, RemainVisibleWhenFocusingToApplistContainer) {
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);

  aura::Window* applist_container = Shell::GetContainer(
      Shell::GetPrimaryRootWindow(), kShellWindowId_AppListContainer);
  std::unique_ptr<aura::Window> window(
      aura::test::CreateTestWindowWithId(0, applist_container));
  wm::ActivateWindow(window.get());
  GetAppListTestHelper()->WaitUntilIdle();

  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests opening the app list on a secondary display, then deleting the display.
TEST_P(AppListPresenterTest, NonPrimaryDisplay) {
  // Set up a screen with two displays (horizontally adjacent).
  UpdateDisplay("1024x768,1024x768");

  aura::Window::Windows root_windows = Shell::GetAllRootWindows();
  ASSERT_EQ(2u, root_windows.size());
  ASSERT_EQ("1024,0 1024x768", root_windows[1]->GetBoundsInScreen().ToString());

  GetAppListTestHelper()->ShowAndRunLoop(GetSecondaryDisplay().id());
  GetAppListTestHelper()->CheckVisibility(true);

  // Remove the secondary display. Shouldn't crash (http://crbug.com/368990).
  UpdateDisplay("1024x768");

  // Updating the displays should close the app list.
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);
}

// Tests updating display should not close the app list.
TEST_P(AppListPresenterTest, UpdateDisplayNotCloseAppList) {
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);

  // Change display bounds.
  UpdateDisplay("1024x768");

  // Updating the display should not close the app list.
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that the app list window's bounds and the search box bounds are updated
// when the display bounds change.
TEST_F(AppListPresenterTest, AppListBoundsChangeForDisplayChange) {
  UpdateDisplay("1024x768");
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  const gfx::Rect app_list_bounds =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds = GetSearchBoxView()->GetBoundsInScreen();

  UpdateDisplay("800x600");
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(true);
  const gfx::Rect app_list_bounds2 =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds2 = GetSearchBoxView()->GetBoundsInScreen();
  EXPECT_GT(app_list_bounds.size().GetArea(),
            app_list_bounds2.size().GetArea());
  EXPECT_NE(search_box_bounds, search_box_bounds2);
  EXPECT_EQ(400, search_box_bounds2.CenterPoint().x());
}

// Tests that the app list window's bounds and the search box bounds in the
// fullscreen state are updated when the display bounds change.
TEST_F(AppListPresenterTest, AppListBoundsChangeForDisplayChangeFullscreen) {
  EnableTabletMode(true);
  UpdateDisplay("1024x768");
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  SetAppListStateAndWait(AppListViewState::kFullscreenAllApps);

  const gfx::Rect app_list_bounds =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds = GetSearchBoxView()->GetBoundsInScreen();

  UpdateDisplay("800x600");
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(true);
  const gfx::Rect app_list_bounds2 =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds2 = GetSearchBoxView()->GetBoundsInScreen();
  EXPECT_GT(app_list_bounds.size().GetArea(),
            app_list_bounds2.size().GetArea());
  EXPECT_NE(search_box_bounds, search_box_bounds2);
  EXPECT_EQ(400, search_box_bounds2.CenterPoint().x());
}

// Tests that the app list window's bounds and the search box bounds in the
// fullscreen search state are updated when the display bounds change.
TEST_F(AppListPresenterTest,
       AppListBoundsChangeForDisplayChangeFullscreenSearch) {
  EnableTabletMode(true);
  UpdateDisplay("1024x768");
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  SetAppListStateAndWait(AppListViewState::kFullscreenAllApps);
  SetAppListStateAndWait(AppListViewState::kFullscreenSearch);

  const gfx::Rect app_list_bounds =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds = GetSearchBoxView()->GetBoundsInScreen();

  UpdateDisplay("800x600");
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(true);
  const gfx::Rect app_list_bounds2 =
      GetAppListView()->GetWidget()->GetWindowBoundsInScreen();
  const gfx::Rect search_box_bounds2 = GetSearchBoxView()->GetBoundsInScreen();
  EXPECT_GT(app_list_bounds.size().GetArea(),
            app_list_bounds2.size().GetArea());
  EXPECT_NE(search_box_bounds, search_box_bounds2);
  EXPECT_EQ(400, search_box_bounds2.CenterPoint().x());
}

// Tests that the app list initializes in fullscreen with tablet mode active
// and that the state transitions via text input act properly.
TEST_P(AppListPresenterTest, TabletModeTextStateTransitions) {
  EnableTabletMode(true);
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  // Enter text in the searchbox, the app list should transition to fullscreen
  // search.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_0);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Pressing the escape key should transition the app list to the fullscreen
  // all apps state.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
}

// Tests that the app list closes when tablet mode deactivates.
TEST_P(AppListPresenterTest, AppListClosesWhenLeavingTabletMode) {
  EnableTabletMode(true);
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  EnableTabletMode(false);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kClosed);

  EnableTabletMode(true);
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  // Enter text in the searchbox, the app list should transition to fullscreen
  // search.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_0);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  EnableTabletMode(false);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kClosed);
}

// Tests the shelf background type is as expected when a window is created after
// going to tablet mode.
TEST_F(AppListPresenterTest, ShelfBackgroundWithHomeLauncher) {
  // Enter tablet mode to display the home launcher.
  EnableTabletMode(true);
  ShelfLayoutManager* shelf_layout_manager =
      Shelf::ForWindow(Shell::GetRootWindowForDisplayId(GetPrimaryDisplayId()))
          ->shelf_layout_manager();
  EXPECT_EQ(ShelfBackgroundType::kHomeLauncher,
            shelf_layout_manager->shelf_background_type());

  // Add a window. It should be in-app because it is in tablet mode.
  auto window = CreateTestWindow();
  wm::ActivateWindow(window.get());
  EXPECT_EQ(ShelfBackgroundType::kInApp,
            shelf_layout_manager->shelf_background_type());
}

// Tests that the bottom shelf is auto hidden when a window is fullscreened in
// tablet mode (home launcher is shown behind).
TEST_F(AppListPresenterTest, ShelfAutoHiddenWhenFullscreen) {
  EnableTabletMode(true);
  Shelf* shelf =
      Shelf::ForWindow(Shell::GetRootWindowForDisplayId(GetPrimaryDisplayId()));
  EXPECT_EQ(ShelfVisibilityState::SHELF_VISIBLE, shelf->GetVisibilityState());

  // Create and fullscreen a window. The shelf should be auto hidden.
  auto window = CreateTestWindow();
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(ShelfVisibilityState::SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(ShelfAutoHideState::SHELF_AUTO_HIDE_HIDDEN,
            shelf->GetAutoHideState());
}

// Tests that a keypress activates the searchbox and that clearing the
// searchbox, the searchbox remains active. Does not apply to bubble launcher,
// where the search box is always active.
TEST_F(AppListPresenterTest, KeyPressEnablesSearchBox) {
  EnableTabletMode(true);

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  SearchBoxView* search_box_view = GetAppListView()->search_box_view();
  EXPECT_FALSE(search_box_view->is_search_box_active());

  // Press any key, the search box should be active.
  PressAndReleaseKey(ui::VKEY_0);
  EXPECT_TRUE(search_box_view->is_search_box_active());

  // Delete the text, the search box should be inactive.
  search_box_view->ClearSearch();
  EXPECT_TRUE(search_box_view->is_search_box_active());
}

// Tests that search box gets deactivated if the active search model gets
// switched. Does not apply to bubble launcher, where the search box is
// always active.
TEST_P(AppListPresenterTest, SearchBoxDeactivatedOnModelChange) {
  EnableTabletMode(true);

  const bool test_mouse_event = TestMouseEventParam();
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  SearchBoxView* search_box_view = GetAppListView()->search_box_view();

  // Tap/Click the search box, it should activate.
  ui::test::EventGenerator* generator = GetEventGenerator();
  if (test_mouse_event) {
    generator->MoveMouseTo(GetPointInsideSearchbox());
    generator->PressLeftButton();
    generator->ReleaseLeftButton();
  } else {
    generator->GestureTapAt(GetPointInsideSearchbox());
  }

  EXPECT_TRUE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Switch the active app list and search model, and verify the search box is
  // deactivated.
  auto model_override = std::make_unique<test::AppListTestModel>();
  auto search_model_override = std::make_unique<SearchModel>();
  auto quick_app_access_model_override =
      std::make_unique<QuickAppAccessModel>();
  Shell::Get()->app_list_controller()->SetActiveModel(
      /*profile_id=*/1, model_override.get(), search_model_override.get(),
      quick_app_access_model_override.get());

  EXPECT_FALSE(search_box_view->is_search_box_active());

  GetAppListTestHelper()->CheckVisibility(true);
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  Shell::Get()->app_list_controller()->ClearActiveModel();
}

// Tests that search UI gets closed if search model changes.
TEST_F(AppListPresenterTest, SearchClearedOnModelChange) {
  EnableTabletMode(true);

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  SearchBoxView* search_box_view = GetAppListView()->search_box_view();

  // Press a key to start search, and activate the search box.
  PressAndReleaseKey(ui::VKEY_A);

  SearchModel* search_model = GetSearchModel();
  auto test_result = std::make_unique<TestSearchResult>();
  test_result->set_result_id("test");
  // Give this item a name so that the accessibility paint checks pass.
  // (Focusable items should have accessible names.)
  test_result->SetAccessibleName(u"test");
  test_result->set_display_type(SearchResultDisplayType::kList);
  search_model->results()->Add(std::move(test_result));

  auto test_list_result = std::make_unique<TestSearchResult>();
  test_list_result->set_result_id("test_list");
  // Give this item a name so that the accessibility paint checks pass.
  // (Focusable items should have accessible names.)
  test_list_result->SetAccessibleName(u"test_list");
  test_list_result->set_best_match(true);
  test_list_result->set_display_type(SearchResultDisplayType::kList);
  search_model->results()->Add(std::move(test_list_result));

  // The results are updated asynchronously. Wait until the update is finished.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  SearchResultContainerView* item_list_container =
      GetDefaultSearchResultListView();
  ASSERT_EQ(1u, item_list_container->num_results());
  EXPECT_EQ("test_list",
            item_list_container->GetResultViewAt(0)->result()->id());

  // Switch the active app list and search model, and verify the search UI gets
  // cleared.
  auto model_override = std::make_unique<test::AppListTestModel>();
  auto search_model_override = std::make_unique<SearchModel>();
  auto quick_app_access_model_override =
      std::make_unique<QuickAppAccessModel>();
  Shell::Get()->app_list_controller()->SetActiveModel(
      /*profile_id=*/1, model_override.get(), search_model_override.get(),
      quick_app_access_model_override.get());

  EXPECT_FALSE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  // Verify that the search UI shows results from the current active search
  // model.
  PressAndReleaseKey(ui::VKEY_A);

  auto test_list_result_override = std::make_unique<TestSearchResult>();
  test_list_result_override->set_result_id("test_list_override");
  // Give this item a name so that the accessibility paint checks pass.
  // (Focusable items should have accessible names.)
  test_list_result_override->SetAccessibleName(u"test_list_override");
  test_list_result_override->set_display_type(SearchResultDisplayType::kList);
  test_list_result_override->set_best_match(true);
  search_model_override->results()->Add(std::move(test_list_result_override));

  // The results are updated asynchronously. Wait until the update is finished.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  item_list_container = GetDefaultSearchResultListView();
  ASSERT_EQ(1u, item_list_container->num_results());
  EXPECT_EQ("test_list_override",
            item_list_container->GetResultViewAt(0)->result()->id());

  Shell::Get()->app_list_controller()->ClearActiveModel();

  EXPECT_FALSE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
}

// Tests that the result selection will reset after closing the search box by
// clicking the close button.
TEST_F(AppListTabletTest,
       ClosingSearchBoxByClickingCloseButtonResetsResultSelection) {
  EnableTabletMode(true);

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  SearchBoxView* search_box_view = GetSearchBoxView();
  ResultSelectionController* result_selection_controller =
      GetResultSelectionController();

  // Add search results to the search model.
  // Click on the search box to activate search.
  GetEventGenerator()->GestureTapAt(SearchBoxCenterPoint());
  EXPECT_TRUE(search_box_view->is_search_box_active());
  ASSERT_FALSE(result_selection_controller->selected_result());

  // Start search.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->PressKey(ui::VKEY_A, 0);

  // Add some search results to the search model.
  SearchModel* search_model = GetSearchModel();
  search_model->results()->Add(
      CreateOmniboxSuggestionResult("Suggestion1", /*support_removal=*/true));
  search_model->results()->Add(
      CreateOmniboxSuggestionResult("Suggestion2", /*support_removal=*/true));

  EXPECT_TRUE(AppListSearchResultPageVisible());

  // The results are updated asynchronously. Wait until the update is finished.
  base::RunLoop().RunUntilIdle();

  // Click the search box, the result selection should be the first one in
  // default.
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  EXPECT_TRUE(search_box_view->is_search_box_active());
  ASSERT_TRUE(result_selection_controller->selected_result());
  EXPECT_TRUE(result_selection_controller->selected_result()->selected());
  EXPECT_TRUE(result_selection_controller->selected_location_details()
                  ->is_first_result());

  // Move the selection to the second result.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN);
  ASSERT_TRUE(result_selection_controller->selected_result());
  EXPECT_TRUE(result_selection_controller->selected_result()->selected());
  EXPECT_FALSE(result_selection_controller->selected_location_details()
                   ->is_first_result());

  // Use the close button in search_box_view to close the search box.
  const views::View* close_button = GetSearchBoxView()->close_button();
  GestureTapOn(close_button);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(search_box_view->is_search_box_active());

  // Delete all search result after closing the search box.
  search_model->DeleteAllResults();

  // Tap the search box again, the result selection should be reset to the first
  // one.
  GestureTapOn(search_box_view);

  // Verify that there is no existing result.
  EXPECT_TRUE(search_box_view->is_search_box_active());
  EXPECT_FALSE(result_selection_controller->selected_result());
}

// Regression test for crash due to use-after-free. https://crbug.com/1163332
TEST_P(AppListPresenterTest, ShouldNotCrashOnItemClickAfterMonitorDisconnect) {
  // Set up two displays.
  UpdateDisplay("1024x768,1200x900");
  test::AppListTestModel* model = GetAppListModel();
  AppListItem* item0 = model->AddItem(new AppListItem("item 0"));
  AppListItem* item1 = model->AddItem(new AppListItem("item 1"));

  // Give each item a name so that the accessibility paint checks pass.
  // (Focusable items should have accessible names.)
  model->SetItemName(item0, item0->id());
  model->SetItemName(item1, item1->id());

  // Open and close app list on secondary display.
  AppListTestHelper* helper = GetAppListTestHelper();
  helper->ShowAndRunLoop(GetSecondaryDisplay().id());
  helper->DismissAndRunLoop();

  // Open and close app list on primary display.
  helper->ShowAndRunLoop(GetPrimaryDisplayId());
  helper->DismissAndRunLoop();

  // Disconnect secondary display.
  UpdateDisplay("1024x768");

  // Open app list to show app items.
  EnsureLauncherWithVisibleAppsGrid();

  // Click on an item.
  AppListItemView* item_view = apps_grid_view()->GetItemViewAt(0);
  EXPECT_EQ(item_view->GetViewAccessibility().GetCachedName(),
            base::UTF8ToUTF16(item0->id()));
  LeftClickOn(item_view);

  // No crash. No use-after-free detected by ASAN.
}

// Tests that no crash occurs after an attempt to show app list in an invalid
// display.
TEST_P(AppListPresenterTest, ShowInInvalidDisplay) {
  GetAppListTestHelper()->ShowAndRunLoop(display::kInvalidDisplayId);
  GetAppListTestHelper()->CheckState(AppListViewState::kClosed);
}

TEST_F(AppListPresenterTest, TapAppListThenSystemTrayShowsAutoHiddenShelf) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Create a normal unmaximized window; the shelf should be hidden.
  std::unique_ptr<views::Widget> window =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Tap the system tray should open system tray bubble and keep shelf visible.
  GestureTapOn(GetPrimaryUnifiedSystemTray());
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Tap to dismiss the app list and the auto-hide shelf.
  GetEventGenerator()->GestureTapAt(gfx::Point(0, 0));
  EXPECT_FALSE(GetPrimaryUnifiedSystemTray()->IsBubbleShown());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
  GetAppListTestHelper()->CheckVisibility(false);
}

TEST_F(AppListPresenterTest, TapAppListThenShelfHidesAutoHiddenShelf) {
  Shelf* shelf = GetPrimaryShelf();
  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  // Create a normal unmaximized window; the shelf should be hidden.
  std::unique_ptr<views::Widget> window =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  window->SetBounds(gfx::Rect(0, 0, 100, 100));
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Show the AppList.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Make sure the shelf has at least one item.
  ShelfItem item =
      ShelfTestUtil::AddAppShortcut(base::NumberToString(1), TYPE_PINNED_APP);

  // Wait for shelf view's bounds animation to end. Otherwise the scrollable
  // shelf's bounds are not updated yet.
  ShelfView* const shelf_view = shelf->GetShelfViewForTesting();
  ShelfViewTestAPI shelf_view_test_api(shelf_view);
  shelf_view_test_api.RunMessageLoopUntilAnimationsDone();

  // Test that tapping the auto-hidden shelf dismisses the app list when tapping
  // part of the shelf that does not contain the apps.
  GetEventGenerator()->GestureTapAt(
      shelf_view->GetBoundsInScreen().left_center() + gfx::Vector2d(10, 0));
  base::RunLoop().RunUntilIdle();  // Wait for autohide to be recomputed.
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());

  // Show the AppList again.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // App list should remain visible when tapping on a shelf app button.
  ASSERT_TRUE(shelf_view_test_api.GetButton(0));
  GestureTapOn(shelf_view_test_api.GetButton(0));
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

TEST_F(AppListPresenterTest, ClickingShelfArrowDoesNotHideAppList) {
  SetShelfAnimationDuration(base::Milliseconds(1));

  // Add enough shelf items for the shelf to enter overflow.
  Shelf* const shelf = GetPrimaryShelf();
  ScrollableShelfView* const scrollable_shelf_view =
      shelf->hotseat_widget()->scrollable_shelf_view();
  int index = 0;
  while (scrollable_shelf_view->layout_strategy_for_test() ==
         ScrollableShelfView::kNotShowArrowButtons) {
    ShelfItem item = ShelfTestUtil::AddAppShortcut(
        base::NumberToString(index++), TYPE_PINNED_APP);
  }

  WaitForShelfAnimation();

  shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);

  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click right scrollable shelf arrow - verify the the app list remains
  // visible.
  const views::View* right_arrow = scrollable_shelf_view->right_arrow();
  ASSERT_TRUE(right_arrow->GetVisible());
  LeftClickOn(right_arrow);

  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click left button - verify the app list stays visible.
  const views::View* left_arrow = scrollable_shelf_view->left_arrow();
  ASSERT_TRUE(left_arrow->GetVisible());
  LeftClickOn(left_arrow);

  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());

  // Click right of the right arrow - verify the app list gets dismissed.
  ASSERT_TRUE(right_arrow->GetVisible());
  GetEventGenerator()->MoveMouseTo(
      right_arrow->GetBoundsInScreen().right_center() + gfx::Vector2d(10, 0));
  GetEventGenerator()->ClickLeftButton();

  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
}

// Tests that the touch selection menu created when tapping an open folder's
// folder name view be interacted with.
TEST_P(PopulatedAppListTest, TouchSelectionMenu) {
  InitializeAppsGrid();

  AppListFolderItem* folder_item = CreateAndPopulateFolderWithApps(4);
  EXPECT_TRUE(folder_item->is_folder());
  EXPECT_EQ(1u, GetAppListModel()->top_level_item_list()->item_count());
  EXPECT_EQ(AppListFolderItem::kItemType, GetAppListTestHelper()
                                              ->model()
                                              ->top_level_item_list()
                                              ->item_at(0)
                                              ->GetItemType());

  // Open the folder.
  ASSERT_FALSE(AppListIsInFolderView());
  GestureTapOn(apps_grid_view_->GetItemViewAt(0));
  ASSERT_TRUE(AppListIsInFolderView());

  // Check that the touch selection menu runner is not running.
  EXPECT_TRUE(ui::TouchSelectionMenuRunner::GetInstance());
  EXPECT_FALSE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning());

  // Set the folder name and simulate tap on the folder name view.
  views::View* folder_name_view =
      folder_view()->folder_header_view()->GetFolderNameViewForTest();
  UpdateFolderName("folder_name");
  GestureTapOn(folder_name_view);

  // Fire the timer to show the textfield quick menu.
  views::TextfieldTestApi textfield_test_api(
      folder_view()->folder_header_view()->GetFolderNameViewForTest());
  static_cast<views::TouchSelectionControllerImpl*>(
      textfield_test_api.touch_selection_controller())
      ->ShowQuickMenuImmediatelyForTesting();
  EXPECT_TRUE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning());

  // Tap the leftmost button in the touch_menu_container and check that the
  // folder name has been cut.
  views::TouchSelectionMenuRunnerViews::TestApi test_api(
      static_cast<views::TouchSelectionMenuRunnerViews*>(
          ui::TouchSelectionMenuRunner::GetInstance()));
  views::LabelButton* button = test_api.GetFirstButton();
  ASSERT_TRUE(button);
  GestureTapOn(button);
  EXPECT_FALSE(ui::TouchSelectionMenuRunner::GetInstance()->IsRunning());

  // Refresh the folder item name.
  RefreshFolderName();
  // Check folder_name_view's name.
  ASSERT_EQ("", GetFolderName());
}

// Test a variety of behaviors for home launcher (app list in tablet mode).
// Parameterized by Mouse or touch parameter.
class AppListPresenterHomeLauncherTest
    : public AshTestBase,
      public testing::WithParamInterface<bool> {
 public:
  AppListPresenterHomeLauncherTest() = default;
  AppListPresenterHomeLauncherTest(const AppListPresenterHomeLauncherTest&) =
      delete;
  AppListPresenterHomeLauncherTest& operator=(
      const AppListPresenterHomeLauncherTest&) = delete;
  ~AppListPresenterHomeLauncherTest() override = default;

  // testing::Test:
  void SetUp() override {
    AshTestBase::SetUp();
    GetAppListTestHelper()->WaitUntilIdle();
    wallpaper_test_api_ = std::make_unique<WallpaperControllerTestApi>(
        Shell::Get()->wallpaper_controller());
  }

  void TearDown() override {
    wallpaper_test_api_.reset();
    AshTestBase::TearDown();
  }

  bool TestMouseEventParam() const { return GetParam(); }

  void TapHomeButton(int64_t display_id) {
    HomeButton* const home_button =
        Shell::GetRootWindowControllerWithDisplayId(display_id)
            ->shelf()
            ->navigation_widget()
            ->GetHomeButton();
    gfx::Point tap_point = home_button->GetBoundsInScreen().CenterPoint();
    GetEventGenerator()->GestureTapDownAndUp(tap_point);
    GetAppListTestHelper()->WaitUntilIdle();
  }

  // Ensures transition to home screen in tablet mode (where home button is not
  // always shown).
  void GoHome() {
    const int64_t primary_display_id = GetPrimaryDisplay().id();
    // If home button is not expected to be shown, use
    // AppListControllerImpl::GoHome() directly, otherwise tap on the primary
    // screen home button.
    if (!Shell::Get()->shelf_config()->shelf_controls_shown()) {
      Shell::Get()->app_list_controller()->GoHome(primary_display_id);
      return;
    }
    TapHomeButton(primary_display_id);
  }

  SplitViewController* split_view_controller() {
    return SplitViewController::Get(Shell::GetPrimaryRootWindow());
  }

  AppListView* GetAppListView() {
    return GetAppListTestHelper()->GetAppListView();
  }

  gfx::Point GetPointOutsideSearchbox() {
    // Ensures that the point satisfies the following conditions:
    // (1) The point is within AppListView.
    // (2) The point is outside of the search box.
    // (3) The touch event on the point should not be consumed by the handler
    // for back gesture.
    return GetSearchBoxViewFromHelper(GetAppListTestHelper())
        ->GetBoundsInScreen()
        .bottom_right();
  }

  gfx::Point GetPointInsideSearchbox() {
    return GetSearchBoxViewFromHelper(GetAppListTestHelper())
        ->GetBoundsInScreen()
        .CenterPoint();
  }

  void ShowAppList() {
    GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  }

  bool IsAppListVisible() {
    auto* app_list_controller = Shell::Get()->app_list_controller();
    return app_list_controller->IsVisible() &&
           app_list_controller->GetTargetVisibility(std::nullopt);
  }

 protected:
  std::unique_ptr<WallpaperControllerTestApi> wallpaper_test_api_;
};

INSTANTIATE_TEST_SUITE_P(MouseTouchEvent,
                         AppListPresenterHomeLauncherTest,
                         testing::Bool());

// Tests that the app list is shown automatically when the tablet mode is on.
// The app list is dismissed when the tablet mode is off.
TEST_F(AppListPresenterHomeLauncherTest, ShowAppListForTabletMode) {
  GetAppListTestHelper()->CheckVisibility(false);

  // Turns on tablet mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  // Turns off tablet mode.
  EnableTabletMode(false);
  GetAppListTestHelper()->CheckVisibility(false);
}

TEST_F(AppListPresenterHomeLauncherTest,
       RunZeroStateSearchWhenShownOnTabletModeTransition) {
  EXPECT_EQ(0, GetTestAppListClient()->start_zero_state_search_count());
  GetAppListTestHelper()->CheckVisibility(false);

  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(1, GetTestAppListClient()->start_zero_state_search_count());
}

TEST_F(AppListPresenterHomeLauncherTest,
       RunZeroStateSearchWhenShownAfterMinimizingWindows) {
  EXPECT_EQ(0, GetTestAppListClient()->start_zero_state_search_count());
  GetAppListTestHelper()->CheckVisibility(false);
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));

  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(0, GetTestAppListClient()->start_zero_state_search_count());

  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(1, GetTestAppListClient()->start_zero_state_search_count());
}

// Tests that the app list window's parent is changed after entering tablet
// mode.
TEST_F(AppListPresenterHomeLauncherTest, ParentWindowContainer) {
  // Show app list in non-tablet mode. The window container should be
  // kShellWindowId_AppListContainer.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  aura::Window* window = Shell::Get()->app_list_controller()->GetWindow();
  aura::Window* root_window = window->GetRootWindow();
  EXPECT_TRUE(root_window->GetChildById(kShellWindowId_AppListContainer)
                  ->Contains(window));

  // Turn on tablet mode. The window container should be
  // kShellWindowId_HomeScreenContainer.
  EnableTabletMode(true);
  aura::Window* window2 = Shell::Get()->app_list_controller()->GetWindow();
  EXPECT_TRUE(root_window->GetChildById(kShellWindowId_HomeScreenContainer)
                  ->Contains(window2));
}

// Tests that tapping or clicking on background cannot dismiss the app list.
TEST_F(AppListPresenterHomeLauncherTest, TapOrClickToDismiss) {
  // Show app list in non-tablet mode. Click outside search box.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  ui::test::EventGenerator* generator = GetEventGenerator();
  const gfx::Point origin;
  generator->MoveMouseTo(origin);
  generator->ClickLeftButton();
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_FALSE(IsAppListVisible());

  // Show app list in non-tablet mode. Tap outside search box.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  generator->GestureTapDownAndUp(origin);
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_FALSE(IsAppListVisible());

  // Show app list in tablet mode. Click outside search box.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  generator->MoveMouseTo(origin);
  generator->PressLeftButton();
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_TRUE(IsAppListVisible());

  // Tap outside search box.
  generator->GestureTapDownAndUp(origin);
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_TRUE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest,
       EscapeKeyInNonTabletModeClosesLauncher) {
  ShowAppList();
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest, BackKeyInNonTabletModeClosesLauncher) {
  ShowAppList();
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_BACK);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest,
       SearchKeyInNonTabletModeClosesLauncher) {
  ShowAppList();
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_SEARCH);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest,
       EscapeKeyInTabletModeDoesNotCloseLauncher) {
  EnableTabletMode(true);
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest,
       BackKeyInTabletModeDoesNotCloseLauncher) {
  EnableTabletMode(true);
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_BACK);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsAppListVisible());
}

TEST_F(AppListPresenterHomeLauncherTest,
       SearchKeyInTabletModeDoesNotCloseLauncher) {
  EnableTabletMode(true);
  EXPECT_TRUE(IsAppListVisible());

  PressAndReleaseKey(ui::KeyboardCode::VKEY_BROWSER_SEARCH);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(IsAppListVisible());
}

// Tests that moving focus outside app list window can dismiss it.
TEST_F(AppListPresenterHomeLauncherTest, FocusOutToDismiss) {
  // Show app list in non-tablet mode. Move focus to another window.
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckVisibility(true);
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  wm::ActivateWindow(window.get());
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);

  // Go to tablet mode with a focused window, the AppList should not be visible.
  EnableTabletMode(true);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);

  // Refocusing the already focused window should change nothing.
  wm::ActivateWindow(window.get());

  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(false);

  // Minimizing the focused window with no remaining windows should result in a
  // shown applist.
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);

  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that tapping home button while home screen is visible and showing
// search results moves the home screen to apps container page.
TEST_F(AppListPresenterHomeLauncherTest, HomeButtonDismissesSearchResults) {
  // Show app list in tablet mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  // Enable accessibility feature that forces home button to be shown even with
  // kHideShelfControlsInTabletMode enabled.
  // TODO(crbug.com/40673209) Use the a11y feature specific to showing
  // navigation buttons in tablet mode once it lands.
  Shell::Get()->accessibility_controller()->autoclick().SetEnabled(true);

  // Enter text in the searchbox, the app list should transition to fullscreen
  // search.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_0);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Tap home button - verify that home goes back to showing the apps page.
  TapHomeButton(GetPrimaryDisplay().id());

  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
}

// Tests the app list opacity in overview mode.
TEST_F(AppListPresenterHomeLauncherTest, OpacityInOverviewMode) {
  // Show app list in tablet mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  // Enable overview mode.
  OverviewController* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  ui::Layer* layer = GetAppListView()->GetWidget()->GetNativeWindow()->layer();
  EXPECT_EQ(0.0f, layer->opacity());

  // Disable overview mode.
  ExitOverview();
  EXPECT_FALSE(overview_controller->InOverviewSession());
  EXPECT_EQ(1.0f, layer->opacity());
}

TEST_F(AppListPresenterHomeLauncherTest, AppListHiddenDuringWallpaperPreview) {
  EnableTabletMode(true);
  wallpaper_test_api_->StartWallpaperPreview();
  GetAppListTestHelper()->CheckVisibility(false);
}

TEST_F(AppListPresenterHomeLauncherTest,
       AppListShownAfterWallpaperPreviewConfirmed) {
  EnableTabletMode(true);
  wallpaper_test_api_->StartWallpaperPreview();
  wallpaper_test_api_->EndWallpaperPreview(/*confirm_preview_wallpaper=*/true);
  GetAppListTestHelper()->CheckVisibility(true);
}

TEST_F(AppListPresenterHomeLauncherTest,
       AppListShownAfterWallpaperPreviewCanceled) {
  EnableTabletMode(true);
  wallpaper_test_api_->StartWallpaperPreview();
  wallpaper_test_api_->EndWallpaperPreview(/*confirm_preview_wallpaper=*/false);
  GetAppListTestHelper()->CheckVisibility(true);
}

TEST_F(AppListPresenterHomeLauncherTest,
       AppListShownAfterWallpaperPreviewAndExitOverviewMode) {
  EnableTabletMode(true);
  wallpaper_test_api_->StartWallpaperPreview();
  EnterOverview();
  EXPECT_FALSE(IsAppListVisible());

  // Disable overview mode.
  ExitOverview();
  EXPECT_TRUE(IsAppListVisible());
}

// Tests that going home will minimize all windows.
TEST_F(AppListPresenterHomeLauncherTest, GoingHomeMinimizesAllWindows) {
  // Show app list in tablet mode. Maximize all windows.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)),
      window2(CreateTestWindowInShellWithId(1)),
      window3(CreateTestWindowInShellWithId(2));
  WindowState* state1 = WindowState::Get(window1.get());
  WindowState* state2 = WindowState::Get(window2.get());
  WindowState* state3 = WindowState::Get(window3.get());
  state1->Maximize();
  state2->Maximize();
  state3->Maximize();
  EXPECT_TRUE(state1->IsMaximized());
  EXPECT_TRUE(state2->IsMaximized());
  EXPECT_TRUE(state3->IsMaximized());

  // The windows need to be activated for the mru window tracker.
  wm::ActivateWindow(window1.get());
  wm::ActivateWindow(window2.get());
  wm::ActivateWindow(window3.get());
  auto ordering =
      Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);

  GoHome();
  EXPECT_TRUE(state1->IsMinimized());
  EXPECT_TRUE(state2->IsMinimized());
  EXPECT_TRUE(state3->IsMinimized());
  GetAppListTestHelper()->CheckVisibility(true);

  // Tests that the window ordering remains the same as before we minimize.
  auto new_order =
      Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
  EXPECT_TRUE(base::ranges::equal(ordering, new_order));
}

// Tests that going home will end split view mode.
TEST_F(AppListPresenterHomeLauncherTest, GoingHomeEndsSplitViewMode) {
  // Show app list in tablet mode. Enter split view mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());

  GoHome();
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that going home will end overview mode.
TEST_F(AppListPresenterHomeLauncherTest, GoingHomeEndOverviewMode) {
  // Show app list in tablet mode. Enter overview mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  OverviewController* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());

  GoHome();
  EXPECT_FALSE(overview_controller->InOverviewSession());
  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that going home will end overview and split view mode if both are
// active (e.g. one side of the split view contains overview).
TEST_F(AppListPresenterHomeLauncherTest,
       GoingHomeEndsSplitViewModeWithOverview) {
  // Show app list in tablet mode. Enter split view mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  std::unique_ptr<aura::Window> dummy_window(CreateTestWindowInShellWithId(1));

  OverviewController* overview_controller = OverviewController::Get();
  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());

  split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
  EXPECT_TRUE(split_view_controller()->InSplitViewMode());
  EXPECT_TRUE(overview_controller->InOverviewSession());

  GoHome();
  EXPECT_FALSE(split_view_controller()->InSplitViewMode());
  EXPECT_FALSE(overview_controller->InOverviewSession());

  GetAppListTestHelper()->CheckVisibility(true);
}

// Tests that the context menu is triggered in the same way as if we are on
// the wallpaper.
TEST_F(AppListPresenterHomeLauncherTest, WallpaperContextMenu) {
  // Show app list in tablet mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  // Long press on the app list to open the context menu.
  // TODO(ginko) look into a way to populate an apps grid, then get a point
  // between these apps so that clicks/taps between apps can be tested
  const gfx::Point onscreen_point(GetPointOutsideSearchbox());
  ui::test::EventGenerator* generator = GetEventGenerator();
  ui::GestureEvent long_press(
      onscreen_point.x(), onscreen_point.y(), 0, base::TimeTicks(),
      ui::GestureEventDetails(ui::EventType::kGestureLongPress));
  generator->Dispatch(&long_press);
  GetAppListTestHelper()->WaitUntilIdle();
  const aura::Window* root = window_util::GetRootWindowAt(onscreen_point);
  const RootWindowController* root_window_controller =
      RootWindowController::ForWindow(root);
  EXPECT_TRUE(root_window_controller->IsContextMenuShownForTest());

  // Tap down to close the context menu.
  ui::GestureEvent tap_down(
      onscreen_point.x(), onscreen_point.y(), 0, base::TimeTicks(),
      ui::GestureEventDetails(ui::EventType::kGestureTapDown));
  generator->Dispatch(&tap_down);
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_FALSE(root_window_controller->IsContextMenuShownForTest());

  // Right click to open the context menu.
  generator->MoveMouseTo(onscreen_point);
  generator->ClickRightButton();
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_TRUE(root_window_controller->IsContextMenuShownForTest());

  // Left click to close the context menu.
  generator->MoveMouseTo(onscreen_point);
  generator->ClickLeftButton();
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_FALSE(root_window_controller->IsContextMenuShownForTest());
}

// Test backdrop exists for active non-fullscreen window in tablet mode.
TEST_F(AppListPresenterHomeLauncherTest, BackdropTest) {
  WorkspaceControllerTestApi test_helper(ShellTestApi().workspace_controller());
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_FALSE(test_helper.GetBackdropWindow());

  std::unique_ptr<aura::Window> non_fullscreen_window(
      CreateTestWindow(gfx::Rect(0, 0, 100, 100)));
  non_fullscreen_window->Show();
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_TRUE(test_helper.GetBackdropWindow());
}

// Tests that app list is not active when switching to tablet mode if an active
// window exists.
TEST_F(AppListPresenterHomeLauncherTest,
       NotActivateAppListWindowWhenActiveWindowExists) {
  // No window is active.
  EXPECT_EQ(nullptr, window_util::GetActiveWindow());

  // Show app list in tablet mode. It should become active.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);
  EXPECT_EQ(GetAppListView()->GetWidget()->GetNativeWindow(),
            window_util::GetActiveWindow());

  // End tablet mode.
  EnableTabletMode(false);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(nullptr, window_util::GetActiveWindow());

  // Activate a window.
  std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
  WindowState::Get(window.get())->Activate();
  EXPECT_EQ(window.get(), window_util::GetActiveWindow());

  // Show app list in tablet mode. It should not be active.
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(false);
  EXPECT_EQ(window.get(), window_util::GetActiveWindow());
}

// Tests that involve the virtual keyboard.
class AppListPresenterVirtualKeyboardTest : public AppListPresenterTest {
 public:
  AppListPresenterVirtualKeyboardTest() = default;
  ~AppListPresenterVirtualKeyboardTest() override = default;

  // AppListPresenterTest:
  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        keyboard::switches::kEnableVirtualKeyboard);
    AppListPresenterTest::SetUp();
  }

  // Performs mouse click or tap gesture on the provided point, depending on
  // whether the test is parameterized to use mouse clicks or tap gestures.
  void ClickOrTap(const gfx::Point& point) {
    if (TestMouseEventParam())
      ClickMouseAt(point);
    else
      GetEventGenerator()->GestureTapAt(point);
  }
};

// Instantiate the values in the parameterized tests. Used to toggle mouse and
// touch events.
INSTANTIATE_TEST_SUITE_P(Mouse,
                         AppListPresenterVirtualKeyboardTest,
                         testing::Bool());

// Tests that tapping or clicking the body of the applist with an active virtual
// keyboard when there exists text in the searchbox results in the virtual
// keyboard closing with no side effects.
TEST_P(AppListPresenterVirtualKeyboardTest,
       TapAppListWithVirtualKeyboardDismissesVirtualKeyboardWithSearchText) {
  EnableTabletMode(true);

  // Tap to activate the searchbox.
  ClickOrTap(GetPointInsideSearchbox());

  // Enter some text in the searchbox, the applist should transition to
  // fullscreen search.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_0);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Manually show the virtual keyboard.
  auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
  keyboard_controller->ShowKeyboard(true);
  ASSERT_TRUE(keyboard::test::WaitUntilShown());

  // Tap or click outside the searchbox, the virtual keyboard should hide.
  ClickOrTap(GetPointOutsideSearchbox());
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());

  // The searchbox should still be active and the AppListView should still be in
  // FULLSCREEN_SEARCH.
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);
  EXPECT_TRUE(GetSearchBoxView()->is_search_box_active());

  // Tap or click the body of the AppList again, the searchbox should deactivate
  // and the applist should be in FULLSCREEN_ALL_APPS.
  ClickOrTap(GetPointOutsideSearchbox());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
  EXPECT_FALSE(GetSearchBoxView()->is_search_box_active());
}

// Tests that tapping or clicking the body of the applist with an active virtual
// keyboard when there is no text in the searchbox results in both the virtual
// keyboard and searchbox closing with no side effects.
TEST_P(AppListPresenterVirtualKeyboardTest,
       TapAppListWithVirtualKeyboardDismissesVirtualKeyboardWithoutSearchText) {
  EnableTabletMode(true);

  // Tap to activate the searchbox.
  ClickOrTap(GetPointInsideSearchbox());
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);

  // Manually show the virtual keyboard.
  auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
  keyboard_controller->ShowKeyboard(true);
  ASSERT_TRUE(keyboard::test::WaitUntilShown());

  // Tap or click outside the searchbox, the virtual keyboard should hide and
  // the searchbox should be inactive when there is no text in the searchbox.
  ClickOrTap(GetPointOutsideSearchbox());
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
  EXPECT_FALSE(keyboard_controller->IsKeyboardVisible());
  EXPECT_FALSE(GetSearchBoxView()->is_search_box_active());
}

TEST_F(AppListPresenterHomeLauncherTest, TapHomeButtonOnExternalDisplay) {
  UpdateDisplay("800x600,1000x768");

  TapHomeButton(GetSecondaryDisplay().id());
  {
    SCOPED_TRACE("1st tap");
    GetAppListTestHelper()->CheckVisibility(true);
  }

  TapHomeButton(GetSecondaryDisplay().id());
  {
    SCOPED_TRACE("2nd tap");
    GetAppListTestHelper()->CheckVisibility(false);
  }
}

// Tests that a tap/click on the AppListView from Fullscreen search returns
// the AppListView to fullscreen all apps, and that a tap/click on the
// AppListView from fullscreen all apps closes the app list.
TEST_P(AppListPresenterHomeLauncherTest,
       StateTransitionsByTappingAppListBodyFromFullscreen) {
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  SearchBoxView* search_box_view = GetAppListView()->search_box_view();
  ui::test::EventGenerator* generator = GetEventGenerator();
  const bool test_mouse_event = TestMouseEventParam();

  // Press a key, this should activate the searchbox and transition to
  // fullscreen search.
  PressAndReleaseKey(ui::KeyboardCode::VKEY_0);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenSearch);
  EXPECT_TRUE(search_box_view->is_search_box_active());

  // Tap outside the searchbox, this should deactivate the searchbox and the
  // applistview should return to fullscreen all apps.
  if (test_mouse_event) {
    generator->MoveMouseTo(GetPointOutsideSearchbox());
    generator->ClickLeftButton();
  } else {
    generator->GestureTapDownAndUp(GetPointOutsideSearchbox());
  }
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);
  EXPECT_FALSE(search_box_view->is_search_box_active());
}

// Tests that the searchbox activates when it is tapped and that the widget is
// closed after tapping outside the searchbox.
TEST_P(AppListPresenterHomeLauncherTest, TapAndClickEnablesSearchBox) {
  EnableTabletMode(true);
  GetAppListTestHelper()->CheckVisibility(true);

  SearchBoxView* search_box_view = GetAppListView()->search_box_view();
  ui::test::EventGenerator* generator = GetEventGenerator();
  const bool test_mouse_event = TestMouseEventParam();

  if (test_mouse_event) {
    generator->MoveMouseTo(GetPointInsideSearchbox());
    generator->PressLeftButton();
    generator->ReleaseLeftButton();
  } else {
    generator->GestureTapAt(GetPointInsideSearchbox());
  }

  EXPECT_TRUE(search_box_view->is_search_box_active());

  // Tap on the body of the app list, the search box should deactivate.
  if (test_mouse_event) {
    generator->MoveMouseTo(GetPointOutsideSearchbox());
    generator->PressLeftButton();
    generator->ReleaseLeftButton();
  } else {
    generator->GestureTapAt(GetPointOutsideSearchbox());
  }
  GetAppListTestHelper()->WaitUntilIdle();
  EXPECT_FALSE(search_box_view->is_search_box_active());
  GetAppListTestHelper()->CheckVisibility(true);
}

// Test that gesture tapping the app list search box correctly handles the event
// by moving the textfield's cursor to the tapped position within the text.
TEST_P(AppListPresenterTest, SearchBoxTextfieldGestureTap) {
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());

  // Tap on the search box to focus and open it.
  ui::test::EventGenerator* generator = GetEventGenerator();
  generator->GestureTapAt(GetPointInsideSearchbox());

  // Set the text of the search box textfield.
  views::Textfield* textfield = GetSearchBoxView()->search_box();
  textfield->SetText(u"Test search box string");

  // The textfield's cursor position should start out at the end of the string.
  size_t initial_cursor_position = textfield->GetCursorPosition();

  // Gesture tap to move the cursor to the middle of the string.
  gfx::Rect textfield_bounds = textfield->GetBoundsInScreen();
  gfx::Rect cursor_bounds =
      views::TextfieldTestApi(textfield).GetCursorViewRect();
  EXPECT_GT(cursor_bounds.x(), 0);
  gfx::Point touch_location(textfield_bounds.x() + cursor_bounds.x() / 2,
                            textfield_bounds.y());
  generator->GestureTapAt(touch_location);

  // Cursor position should have changed after the gesture tap.
  EXPECT_LT(textfield->GetCursorPosition(), initial_cursor_position);
}

// Tests tablet <-> clamshell mode transition.
class AppListPresenterWithScaleAnimationOnTabletModeTransitionTest
    : public AppListPresenterTest {
 protected:
  void EnsureAppListViewIsCached() {
    ASSERT_FALSE(GetAppListTestHelper()->GetAppListView());

    EnableTabletMode(true);
    GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
    GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

    EnableTabletMode(false);
    GetAppListTestHelper()->WaitUntilIdle();
    GetAppListTestHelper()->CheckState(AppListViewState::kClosed);

    // Entering and exiting from tablet mode should keep `AppListView` cached.
    ASSERT_TRUE(GetAppListTestHelper()->GetAppListView());
  }
};

TEST_F(AppListPresenterWithScaleAnimationOnTabletModeTransitionTest,
       UpdatesScaleAndOpacity) {
  // Enter tablet mode.
  EnableTabletMode(true);
  GetAppListTestHelper()->ShowAndRunLoop(GetPrimaryDisplayId());
  GetAppListTestHelper()->CheckState(AppListViewState::kFullscreenAllApps);

  auto* const window =
      GetAppListTestHelper()->GetAppListView()->GetWidget()->GetNativeWindow();
  auto* const layer = window->layer();
  const auto center_point = gfx::Rect(layer->size()).CenterPoint();
  const auto no_transform = gfx::GetScaleTransform(center_point, 1.0f);
  const auto scaled_down_transform =
      gfx::GetScaleTransform(center_point, 0.92f);

  // The layer is fully visible and without applied transform.
  EXPECT_EQ(layer->opacity(), 1.0f);
  EXPECT_EQ(layer->transform(), no_transform);
  EXPECT_TRUE(window->IsVisible());

  // Exit tablet mode.
  EnableTabletMode(false);
  GetAppListTestHelper()->WaitUntilIdle();
  GetAppListTestHelper()->CheckState(AppListViewState::kClosed);

  // The layer is fully transparent, scaled down and the window is hidden.
  EXPECT_EQ(layer->opacity(), 0.00f);
  EXPECT_EQ(layer->transform(), scaled_down_transform);
  EXPECT_FALSE(window->IsVisible());
}

TEST_F(AppListPresenterWithScaleAnimationOnTabletModeTransitionTest,
       ExitingFromOverviewInClamshellModeShouldNotAffectFullscreenLauncher) {
  EnsureAppListViewIsCached();

  auto* const layer = GetAppListTestHelper()
                          ->GetAppListView()
                          ->GetWidget()
                          ->GetNativeWindow()
                          ->layer();
  const auto expected_opacity = layer->opacity();
  const auto expected_transform = layer->transform();

  OverviewController* overview_controller = OverviewController::Get();

  EnterOverview();
  EXPECT_TRUE(overview_controller->InOverviewSession());
  ExitOverview();
  EXPECT_FALSE(overview_controller->InOverviewSession());

  // Layer's opacity and transform should stay the same.
  EXPECT_EQ(layer->opacity(), expected_opacity);
  EXPECT_EQ(layer->transform(), expected_transform);
}

// Tests that dismiss animation while animating to fullscreen state and open
// animation while animating to closed state continue from the same
// opacity/scale values where it was interrupted.
TEST_F(AppListPresenterWithScaleAnimationOnTabletModeTransitionTest,
       TransitionContinuesWhereItWasInterrupted) {
  EnsureAppListViewIsCached();
  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  auto* const layer = GetAppListTestHelper()
                          ->GetAppListView()
                          ->GetWidget()
                          ->GetNativeWindow()
                          ->layer();
  const auto center_point = gfx::Rect(layer->size()).CenterPoint();
  const auto no_transform = gfx::GetScaleTransform(center_point, 1.0f);
  const auto scaled_down_transform =
      gfx::GetScaleTransform(center_point, 0.92f);

  const auto initial_opacity = 0.01f;
  const auto initial_transform = scaled_down_transform;

  ash::TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(layer->opacity(), initial_opacity);
  EXPECT_EQ(layer->GetTargetOpacity(), 1.0f);
  EXPECT_EQ(layer->transform(), initial_transform);
  EXPECT_EQ(layer->GetTargetTransform(), no_transform);

  // Interrupt clamshell -> tablet transition by switching back to clamshell
  // mode. Current transform and opacity stay at initial values.
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  EXPECT_EQ(layer->opacity(), initial_opacity);
  EXPECT_EQ(layer->GetTargetOpacity(), 0.0f);
  EXPECT_EQ(layer->transform(), initial_transform);
  EXPECT_EQ(layer->GetTargetTransform(), scaled_down_transform);

  // Interrupt tablet -> clamshell transition by switching back to tablet mode.
  // Current transform and opacity stay at initial values.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(layer->opacity(), initial_opacity);
  EXPECT_EQ(layer->GetTargetOpacity(), 1.0f);
  EXPECT_EQ(layer->transform(), initial_transform);
  EXPECT_EQ(layer->GetTargetTransform(), no_transform);
}

TEST_F(AppListPresenterWithScaleAnimationOnTabletModeTransitionTest,
       AbortedHideAnimationDoesNotChangeVisibility) {
  // Configure test observer.
  auto visibility_observer = std::make_unique<TestAppListControllerObserver>();
  auto* const app_list_controller = Shell::Get()->app_list_controller();
  app_list_controller->AddObserver(visibility_observer.get());

  // Switch to tablet mode and set normal animation duration.
  ash::TabletModeControllerTestApi().EnterTabletMode();
  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);

  EXPECT_EQ(visibility_observer->visibility_changed_to_hidden_times(), 0);
  ash::TabletModeControllerTestApi().LeaveTabletMode();
  ash::TabletModeControllerTestApi().EnterTabletMode();
  EXPECT_EQ(visibility_observer->visibility_changed_to_hidden_times(), 0);
}

}  // namespace ash