chromium/ash/app_list/views/continue_section_view_unittest.cc

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

#include "ash/app_list/views/continue_section_view.h"

#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/app_list_test_view_delegate.h"
#include "ash/app_list/model/search/search_model.h"
#include "ash/app_list/model/search/test_search_result.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_bubble_apps_page.h"
#include "ash/app_list/views/app_list_toast_container_view.h"
#include "ash/app_list/views/app_list_toast_view.h"
#include "ash/app_list/views/apps_container_view.h"
#include "ash/app_list/views/apps_grid_view_test_api.h"
#include "ash/app_list/views/continue_task_view.h"
#include "ash/app_list/views/recent_apps_view.h"
#include "ash/app_list/views/scrollable_apps_grid_view.h"
#include "ash/app_list/views/search_box_view.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/radio_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/widget/widget_delegate.h"

namespace ash {
namespace {

using test::AppListTestViewDelegate;

std::unique_ptr<TestSearchResult> CreateTestResult(const std::string& id,
                                                   AppListSearchResultType type,
                                                   const std::string& title) {
  auto result = std::make_unique<TestSearchResult>();
  result->set_result_id(id);
  result->SetTitle(base::ASCIIToUTF16(title));
  result->set_result_type(type);
  result->set_display_type(SearchResultDisplayType::kContinue);
  return result;
}

void AddSearchResultToModel(const std::string& id,
                            AppListSearchResultType type,
                            SearchModel* model,
                            const std::string& title) {
  model->results()->Add(CreateTestResult(id, type, title));
}

void WaitForAllChildrenAnimationsToComplete(views::View* view) {
  while (true) {
    bool found_animation = false;
    for (views::View* child : view->children()) {
      if (child->layer() && child->layer()->GetAnimator()->is_animating()) {
        ui::LayerAnimationStoppedWaiter waiter;
        waiter.Wait(child->layer());
        found_animation = true;
        break;
      }
    }

    if (!found_animation)
      return;
  }
}

class ContinueSectionViewTestBase : public AshTestBase {
 public:
  explicit ContinueSectionViewTestBase(bool tablet_mode)
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        tablet_mode_(tablet_mode) {}
  ~ContinueSectionViewTestBase() override = default;

  void TearDown() override {
    AshTestBase::TearDown();
    // Clean up global variables used for metrics on continue section removals.
    ContinueSectionView::ResetContinueSectionFileRemovalMetricEnabledForTest();
    ResetContinueSectionFileRemovedCountForTest();
  }

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

  // Sets up the continue section view (and app list) state for testing continue
  // section update animations. `result_count` is the number of continue tasks
  // that should initially be added to search model.
  // Returns the list of current task view layer bounds.
  std::vector<gfx::RectF> InitializeForAnimationTest(int result_count) {
    // Ensure display is large enough to display 4 chips in tablet mode.
    UpdateDisplay("1200x800");

    AddTestSearchResults(result_count);

    EnsureLauncherShown();
    base::RunLoop().RunUntilIdle();
    base::OneShotTimer* animations_timer = GetContinueSectionView()
                                               ->suggestions_container()
                                               ->animations_timer_for_test();
    if (animations_timer)
      animations_timer->FireNow();

    animation_duration_.emplace(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

    return GetCurrentLayerBoundsForAllTaskViews();
  }

  ContinueSectionView* GetContinueSectionView() {
    if (display::Screen::GetScreen()->InTabletMode()) {
      return GetAppListTestHelper()->GetFullscreenContinueSectionView();
    }
    return GetAppListTestHelper()->GetBubbleContinueSectionView();
  }

  AppListNudgeController* GetAppListNudgeController() {
    return GetContinueSectionView()->nudge_controller_for_test();
  }

  views::View* GetRecentAppsView() {
    if (display::Screen::GetScreen()->InTabletMode()) {
      return GetAppListTestHelper()->GetFullscreenRecentAppsView();
    }
    return GetAppListTestHelper()->GetBubbleRecentAppsView();
  }

  views::View* GetAppsGridView() {
    if (display::Screen::GetScreen()->InTabletMode()) {
      return GetAppListTestHelper()->GetRootPagedAppsGridView();
    }
    return GetAppListTestHelper()->GetScrollableAppsGridView();
  }

  void AddTestSearchResults(int count) {
    for (int i = 0; i < count; ++i)
      AddSearchResult(base::StringPrintf("id_%d", i),
                      AppListSearchResultType::kZeroStateFile);
  }

  void AddSearchResult(const std::string& id, AppListSearchResultType type) {
    AddSearchResultToModel(
        id, type, AppListModelProvider::Get()->search_model(), "Fake Title");
  }

  void AddSearchResultWithTitle(const std::string& id,
                                AppListSearchResultType type,
                                const std::string& title) {
    AddSearchResultToModel(id, type,
                           AppListModelProvider::Get()->search_model(), title);
  }

  void RemoveSearchResultAt(size_t index) {
    GetAppListTestHelper()->GetSearchResults()->RemoveAt(index);
  }

  SearchModel::SearchResults* GetResults() {
    return GetAppListTestHelper()->GetSearchResults();
  }

  std::vector<SearchResult*> GetContinueResults() {
    auto continue_filter = [](const SearchResult& r) -> bool {
      return r.display_type() == SearchResultDisplayType::kContinue;
    };
    std::vector<SearchResult*> continue_results;
    continue_results = SearchModel::FilterSearchResultsByFunction(
        GetResults(), base::BindRepeating(continue_filter),
        /*max_results=*/4);

    return continue_results;
  }

  SearchBoxView* GetSearchBoxView() {
    if (display::Screen::GetScreen()->InTabletMode()) {
      return GetAppListTestHelper()->GetSearchBoxView();
    }
    return GetAppListTestHelper()->GetBubbleSearchBoxView();
  }

  ContinueTaskView* GetResultViewAt(int index) {
    return GetContinueSectionView()->GetTaskViewAtForTesting(index);
  }

  std::vector<std::string> GetResultIds() {
    const size_t result_count =
        GetContinueSectionView()->GetTasksSuggestionsCount();
    std::vector<std::string> ids;
    for (size_t i = 0; i < result_count; ++i)
      ids.push_back(GetResultViewAt(i)->result()->id());
    return ids;
  }

  void VerifyResultViewsUpdated() {
    // Wait for the view to update any pending SearchResults.
    base::RunLoop().RunUntilIdle();

    std::vector<SearchResult*> results = GetContinueResults();
    for (size_t i = 0; i < results.size(); ++i)
      EXPECT_EQ(results[i], GetResultViewAt(i)->result()) << i;
  }

  void EnsureLauncherShown() {
    if (tablet_mode_param()) {
      // Convert to tablet mode to show fullscren launcher.
      ash::TabletModeControllerTestApi().EnterTabletMode();
      Shell::Get()->app_list_controller()->ShowAppList(
          AppListShowSource::kSearchKey);
      test_api_ = std::make_unique<test::AppsGridViewTestApi>(
          GetAppListTestHelper()->GetRootPagedAppsGridView());
    } else {
      Shell::Get()->app_list_controller()->ShowAppList(
          AppListShowSource::kSearchKey);
      test_api_ = std::make_unique<test::AppsGridViewTestApi>(
          GetAppListTestHelper()->GetScrollableAppsGridView());
    }
    ASSERT_TRUE(GetAppsGridView());
  }

  void HideLauncher() { Shell::Get()->app_list_controller()->DismissAppList(); }

  void ResetPrivacyNoticePref() {
    AppListNudgeController::SetPrivacyNoticeAcceptedForTest(false);
  }

  void SimulateRightClickOrLongPressOn(const views::View* view) {
    gfx::Point location = view->GetBoundsInScreen().CenterPoint();
    if (tablet_mode_param()) {
      ui::test::EventGenerator* generator = GetEventGenerator();
      generator->set_current_screen_location(location);
      generator->PressTouch();
      ui::GestureEventDetails event_details(ui::EventType::kGestureLongPress);
      ui::GestureEvent long_press(location.x(), location.y(), 0,
                                  base::TimeTicks::Now(), event_details);
      generator->Dispatch(&long_press);
      generator->ReleaseTouch();
      GetAppListTestHelper()->WaitUntilIdle();
    } else {
      RightClickOn(view);
    }
  }

  bool IsPrivacyNoticeVisible() {
    auto* privacy_notice = GetContinueSectionView()->GetPrivacyNoticeForTest();
    return privacy_notice && privacy_notice->GetVisible();
  }

  gfx::RectF GetTargetLayerBounds(views::View* view) {
    return view->layer()->GetTargetTransform().MapRect(
        gfx::RectF(view->layer()->GetTargetBounds()));
  }

  gfx::RectF GetCurrentLayerBounds(views::View* view) {
    return view->layer()->transform().MapRect(
        gfx::RectF(view->layer()->bounds()));
  }

  std::vector<gfx::RectF> GetCurrentLayerBoundsForAllTaskViews() {
    std::vector<gfx::RectF> bounds;
    const size_t count = GetContinueSectionView()->GetTasksSuggestionsCount();
    bounds.reserve(count);
    for (size_t i = 0; i < count; ++i) {
      views::View* result_view = GetResultViewAt(i);
      bounds.push_back(GetCurrentLayerBounds(result_view));
    }
    return bounds;
  }

  void RemoveSearchResultWithContextMenuAt(int index) {
    ContinueTaskView* continue_task_view = GetResultViewAt(index);

    GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
    SimulateRightClickOrLongPressOn(continue_task_view);
    EXPECT_TRUE(continue_task_view->IsMenuShowing()) << index;
    continue_task_view->ExecuteCommand(ContinueTaskCommandId::kRemoveResult,
                                       ui::EF_NONE);
    EXPECT_FALSE(continue_task_view->IsMenuShowing());
  }

  test::AppsGridViewTestApi* test_api() { return test_api_.get(); }

 private:
  bool tablet_mode_ = false;

  std::optional<ui::ScopedAnimationDurationScaleMode> animation_duration_;
  std::unique_ptr<test::AppsGridViewTestApi> test_api_;
};

class ContinueSectionViewTest : public ContinueSectionViewTestBase,
                                public testing::WithParamInterface<bool> {
 public:
  ContinueSectionViewTest()
      : ContinueSectionViewTestBase(/*tablet_mode=*/GetParam()) {}
  ~ContinueSectionViewTest() override = default;
};

class ContinueSectionViewClamshellModeTest
    : public ContinueSectionViewTestBase {
 public:
  ContinueSectionViewClamshellModeTest()
      : ContinueSectionViewTestBase(/*tablet_mode=*/false) {}
  ~ContinueSectionViewClamshellModeTest() override = default;
};

class ContinueSectionViewTabletModeTest : public ContinueSectionViewTestBase {
 public:
  ContinueSectionViewTabletModeTest()
      : ContinueSectionViewTestBase(/*tablet_mode=*/true) {}
  ~ContinueSectionViewTabletModeTest() override = default;
};

class ContinueSectionViewWithReorderNudgeTest
    : public ContinueSectionViewTestBase,
      public testing::WithParamInterface<bool> {
 public:
  ContinueSectionViewWithReorderNudgeTest()
      : ContinueSectionViewTestBase(/*tablet_mode=*/GetParam()) {}
  ~ContinueSectionViewWithReorderNudgeTest() override = default;

  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();
    GetAppListTestHelper()->DisableAppListNudge(false);
  }

  AppListToastContainerView* GetToastContainerView() {
    if (!display::Screen::GetScreen()->InTabletMode()) {
      return GetAppListTestHelper()
          ->GetBubbleAppsPage()
          ->toast_container_for_test();
    }

    return GetAppListTestHelper()->GetAppsContainerView()->toast_container();
  }
};

// Instantiate the values in the parameterized tests. Used to toggle tablet
// mode.
INSTANTIATE_TEST_SUITE_P(All, ContinueSectionViewTest, testing::Bool());
INSTANTIATE_TEST_SUITE_P(All,
                         ContinueSectionViewWithReorderNudgeTest,
                         testing::Bool());

TEST_P(ContinueSectionViewTest, CreatesViewsForTasks) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  ContinueSectionView* view = GetContinueSectionView();
  EXPECT_EQ(view->GetTasksSuggestionsCount(), 2u);
}

TEST_P(ContinueSectionViewTest, VerifyAddedViewsOrder) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueSectionView* view = GetContinueSectionView();
  ASSERT_EQ(view->GetTasksSuggestionsCount(), 3u);
  EXPECT_EQ(GetResultViewAt(0)->result()->id(), "id1");
  EXPECT_EQ(GetResultViewAt(1)->result()->id(), "id2");
  EXPECT_EQ(GetResultViewAt(2)->result()->id(), "id3");
}

// Tests that the continue section view will be visible once we have a admin
// template.
TEST_P(ContinueSectionViewTest, ShowContinueSectionWhenAdminTemplateAvailable) {
  base::test::ScopedFeatureList scoped_list;

  AddSearchResult("id", AppListSearchResultType::kDesksAdminTemplate);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueSectionView* view = GetContinueSectionView();
  ASSERT_EQ(view->GetTasksSuggestionsCount(), 1u);
  EXPECT_TRUE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, ShowsHelpAppResults) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateHelpApp);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  EXPECT_TRUE(GetContinueSectionView()->GetVisible());

  ContinueSectionView* view = GetContinueSectionView();
  ASSERT_EQ(view->GetTasksSuggestionsCount(), 4u);
  EXPECT_EQ(GetResultViewAt(0)->result()->id(), "id1");
  EXPECT_EQ(GetResultViewAt(1)->result()->id(), "id2");
  EXPECT_EQ(GetResultViewAt(2)->result()->id(), "id3");
  EXPECT_EQ(GetResultViewAt(3)->result()->id(), "id4");
}

TEST_P(ContinueSectionViewTest, HelpAppResultNotShownWithoutEnoughOtherFiles) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateHelpApp);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueSectionView* view = GetContinueSectionView();
  ASSERT_EQ(view->GetTasksSuggestionsCount(), 2u);
  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, HelpAppShownInTabletModeWith2FileResults) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateHelpApp);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateFile);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueSectionView* view = GetContinueSectionView();
  ASSERT_EQ(view->GetTasksSuggestionsCount(), 3u);
  EXPECT_EQ(GetParam(), GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, ModelObservers) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  // Remove from end.
  GetResults()->DeleteAt(2);
  VerifyResultViewsUpdated();

  // Insert a new result.
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);
  VerifyResultViewsUpdated();

  // Delete from start.
  GetResults()->DeleteAt(0);
  VerifyResultViewsUpdated();
}

TEST_P(ContinueSectionViewTest, HideContinueSectionWhenResultRemoved) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  // Minimum files for clamshell mode are 3.
  if (!tablet_mode_param())
    AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  EXPECT_TRUE(GetContinueSectionView()->GetVisible());

  RemoveSearchResultAt(1);
  VerifyResultViewsUpdated();
  ASSERT_LE(GetContinueSectionView()->GetTasksSuggestionsCount(), 2u);

  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, ShowContinueSectionWhenResultAdded) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);

  // Minimum files for clamshell mode are 3.
  if (!tablet_mode_param())
    AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  EXPECT_FALSE(GetContinueSectionView()->GetVisible());

  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  VerifyResultViewsUpdated();

  EXPECT_TRUE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, ClickOpensSearchResult) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);

  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  LeftClickOn(continue_task_view);

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id1", client->last_opened_search_result());
}

TEST_P(ContinueSectionViewTest, TapOpensSearchResult) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);

  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  GestureTapOn(continue_task_view);

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id1", client->last_opened_search_result());
}

TEST_F(ContinueSectionViewClamshellModeTest, PressEnterOpensSearchResult) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);
  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  SearchBoxView* search_box_view = GetSearchBoxView();
  EXPECT_TRUE(search_box_view->search_box()->HasFocus());

  PressAndReleaseKey(ui::VKEY_DOWN);
  EXPECT_TRUE(continue_task_view->HasFocus());

  PressAndReleaseKey(ui::VKEY_RETURN);

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id1", client->last_opened_search_result());
}

TEST_F(ContinueSectionViewClamshellModeTest, DownArrowMovesFocusVertically) {
  AddSearchResult("id0", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueTaskView* task0 = GetResultViewAt(0);
  ContinueTaskView* task1 = GetResultViewAt(1);
  ContinueTaskView* task2 = GetResultViewAt(2);
  ContinueTaskView* task3 = GetResultViewAt(3);
  ASSERT_FALSE(task0->HasFocus());
  ASSERT_FALSE(task1->HasFocus());
  ASSERT_FALSE(task2->HasFocus());
  ASSERT_FALSE(task3->HasFocus());

  // Down arrow from search box moves to first continue task.
  PressAndReleaseKey(ui::VKEY_DOWN);
  EXPECT_TRUE(task0->HasFocus());
  EXPECT_FALSE(task1->HasFocus());
  EXPECT_FALSE(task2->HasFocus());
  EXPECT_FALSE(task3->HasFocus());

  // Down arrow from first continue task moves down by one row, focusing
  // `task2` instead of the next task in tab order (`task1`).
  PressAndReleaseKey(ui::VKEY_DOWN);
  EXPECT_FALSE(task0->HasFocus());
  EXPECT_FALSE(task1->HasFocus());
  EXPECT_TRUE(task2->HasFocus());
  EXPECT_FALSE(task3->HasFocus());

  // Down again moves focus out of continue section.
  PressAndReleaseKey(ui::VKEY_DOWN);
  EXPECT_FALSE(task0->HasFocus());
  EXPECT_FALSE(task1->HasFocus());
  EXPECT_FALSE(task2->HasFocus());
  EXPECT_FALSE(task3->HasFocus());
}

TEST_F(ContinueSectionViewClamshellModeTest, UpArrowMovesFocusVertically) {
  AddSearchResult("id0", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueTaskView* task0 = GetResultViewAt(0);
  ContinueTaskView* task1 = GetResultViewAt(1);
  ContinueTaskView* task2 = GetResultViewAt(2);
  ContinueTaskView* task3 = GetResultViewAt(3);
  ASSERT_FALSE(task0->HasFocus());
  ASSERT_FALSE(task1->HasFocus());
  ASSERT_FALSE(task2->HasFocus());
  ASSERT_FALSE(task3->HasFocus());

  // Focus the last task (in the second row, second column).
  task3->RequestFocus();
  EXPECT_FALSE(task0->HasFocus());
  EXPECT_FALSE(task1->HasFocus());
  EXPECT_FALSE(task2->HasFocus());
  EXPECT_TRUE(task3->HasFocus());

  // Up arrow moves up by one row, focusing `task1` instead of the previous
  // task in tab order (`task2`).
  PressAndReleaseKey(ui::VKEY_UP);
  EXPECT_FALSE(task0->HasFocus());
  EXPECT_TRUE(task1->HasFocus());
  EXPECT_FALSE(task2->HasFocus());
  EXPECT_FALSE(task3->HasFocus());

  // Up again moves focus out of continue section.
  PressAndReleaseKey(ui::VKEY_UP);
  EXPECT_FALSE(task0->HasFocus());
  EXPECT_FALSE(task1->HasFocus());
  EXPECT_FALSE(task2->HasFocus());
  EXPECT_FALSE(task3->HasFocus());
}

TEST_F(ContinueSectionViewClamshellModeTest, FocusingChipScrollsToShowLabel) {
  auto* helper = GetAppListTestHelper();
  helper->AddContinueSuggestionResults(4);
  // Add enough apps to allow the apps page to scroll.
  helper->AddAppItems(50);
  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  // Up arrow moves focus to the last row of the apps grid, forcing the apps
  // page to scroll.
  PressAndReleaseKey(ui::VKEY_UP);
  auto* scroll_view = helper->GetBubbleAppsPage()->scroll_view();
  const int initial_scroll_offset = scroll_view->GetVisibleRect().y();
  ASSERT_GT(initial_scroll_offset, 0);

  // Down arrow twice moves focus to search box, then to continue section.
  PressAndReleaseKey(ui::VKEY_DOWN);
  PressAndReleaseKey(ui::VKEY_DOWN);
  ASSERT_TRUE(GetResultViewAt(0)->HasFocus());

  // Scroll view has scrolled to the top to reveal the label.
  const int final_scroll_offset = scroll_view->GetVisibleRect().y();
  EXPECT_EQ(final_scroll_offset, 0);
}

TEST_F(ContinueSectionViewClamshellModeTest,
       FocusingPrivacyNoticeScrollsToShowNotice) {
  auto* helper = GetAppListTestHelper();
  helper->AddContinueSuggestionResults(4);
  // Add enough apps to allow the apps page to scroll.
  helper->AddAppItems(50);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  ASSERT_TRUE(IsPrivacyNoticeVisible());

  // Up arrow moves focus to the last row of the apps grid, forcing the apps
  // page to scroll.
  PressAndReleaseKey(ui::VKEY_UP);
  auto* scroll_view = helper->GetBubbleAppsPage()->scroll_view();
  const int initial_scroll_offset = scroll_view->GetVisibleRect().y();
  ASSERT_GT(initial_scroll_offset, 0);

  // Down arrow twice moves focus to search box, then to privacy notice.
  PressAndReleaseKey(ui::VKEY_DOWN);
  PressAndReleaseKey(ui::VKEY_DOWN);
  auto* privacy_notice = GetContinueSectionView()->GetPrivacyNoticeForTest();
  ASSERT_TRUE(privacy_notice->toast_button()->HasFocus());

  // Scroll view has scrolled to the top to reveal the whole notice.
  const int final_scroll_offset = scroll_view->GetVisibleRect().y();
  EXPECT_EQ(final_scroll_offset, 0);
}

// Regression test for https://crbug.com/1273170.
TEST_F(ContinueSectionViewClamshellModeTest, SearchAndCancelDoesNotChangeSize) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  EnsureLauncherShown();

  auto* apps_grid_view = GetAppListTestHelper()->GetScrollableAppsGridView();
  const gfx::Point apps_grid_origin = apps_grid_view->origin();

  auto* continue_section_view = GetContinueSectionView();
  const gfx::Rect continue_section_bounds = continue_section_view->bounds();
  auto* widget = continue_section_view->GetWidget();

  // Start a search.
  PressAndReleaseKey(ui::VKEY_A);

  // Simulate the suggestions changing.
  GetResults()->RemoveAll();
  AddSearchResult("id3", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id5", AppListSearchResultType::kZeroStateDrive);

  // Cancel the search.
  PressAndReleaseKey(ui::VKEY_ESCAPE);
  base::RunLoop().RunUntilIdle();  // Wait for SearchResult updates to views.
  widget->LayoutRootViewIfNecessary();

  // Continue section bounds did not grow.
  EXPECT_EQ(continue_section_bounds, continue_section_view->bounds());

  // Apps grid view position did not change.
  EXPECT_EQ(apps_grid_origin, apps_grid_view->origin());
}

TEST_P(ContinueSectionViewTest, RightClickOpensContextMenu) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);
  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(continue_task_view);
  EXPECT_TRUE(continue_task_view->IsMenuShowing());
}

TEST_P(ContinueSectionViewTest, OpenWithContextMenuOption) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);
  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(continue_task_view);
  EXPECT_TRUE(continue_task_view->IsMenuShowing());
  continue_task_view->ExecuteCommand(ContinueTaskCommandId::kOpenResult,
                                     ui::EF_NONE);

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id1", client->last_opened_search_result());
}

TEST_P(ContinueSectionViewTest, RemoveWithContextMenuOption) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  EXPECT_EQ(GetResultViewAt(0)->result()->id(), "id1");
  RemoveSearchResultWithContextMenuAt(0);

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

TEST_P(ContinueSectionViewTest, ResultRemovedLogsMetricInBucket) {
  base::HistogramTester histogram_tester;
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  // Remove two results.
  EXPECT_EQ(GetResultViewAt(0)->result()->id(), "id1");
  RemoveSearchResultWithContextMenuAt(0);

  EXPECT_EQ(GetResultViewAt(1)->result()->id(), "id2");
  RemoveSearchResultWithContextMenuAt(1);

  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  std::vector<TestAppListClient::SearchResultActionId> expected_actions = {
      {"id1", SearchResultActionType::kRemove},
      {"id2", SearchResultActionType::kRemove}};
  std::vector<TestAppListClient::SearchResultActionId> invoked_actions =
      client->GetAndResetInvokedResultActions();
  EXPECT_EQ(expected_actions, invoked_actions);

  EXPECT_EQ(1, histogram_tester.GetBucketCount(
                   kContinueSectionFilesRemovedInSessionHistogram, 1));
  EXPECT_EQ(1, histogram_tester.GetBucketCount(
                   kContinueSectionFilesRemovedInSessionHistogram, 2));
  histogram_tester.ExpectTotalCount(
      kContinueSectionFilesRemovedInSessionHistogram, 2);
}

TEST_P(ContinueSectionViewTest, ResultRemovedContextMenuCloses) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(1);
  EXPECT_EQ(continue_task_view->result()->id(), "id2");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(continue_task_view);
  EXPECT_TRUE(continue_task_view->IsMenuShowing());

  RemoveSearchResultAt(1);
  VerifyResultViewsUpdated();

  ASSERT_EQ(std::vector<std::string>({"id1", "id3", "id4"}), GetResultIds());

  // Click on another result and verify it activates the item to confirm the
  // event is not consumed by a context menu.
  EXPECT_EQ(GetResultViewAt(0)->result()->id(), "id1");
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  LeftClickOn(GetResultViewAt(0));

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id1", client->last_opened_search_result());
}

TEST_P(ContinueSectionViewTest, UpdateAppsOnModelChange) {
  AddSearchResult("id11", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id12", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id13", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id14", AppListSearchResultType::kZeroStateFile);
  UpdateDisplay("1200x800");
  EnsureLauncherShown();

  EXPECT_EQ(std::vector<std::string>({"id11", "id12", "id13", "id14"}),
            GetResultIds());

  // Update active model, and make sure the shown results view get updated
  // accordingly.
  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>();

  AddSearchResultToModel("id21", AppListSearchResultType::kZeroStateFile,
                         search_model_override.get(), "Fake Title");
  AddSearchResultToModel("id22", AppListSearchResultType::kZeroStateFile,
                         search_model_override.get(), "Fake Title");
  AddSearchResultToModel("id23", AppListSearchResultType::kZeroStateFile,
                         search_model_override.get(), "Fake Title");

  Shell::Get()->app_list_controller()->SetActiveModel(
      /*profile_id=*/1, model_override.get(), search_model_override.get(),
      quick_app_access_model_override.get());
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();

  EXPECT_EQ(std::vector<std::string>({"id21", "id22", "id23"}), GetResultIds());

  // Tap a result, and verify it gets activated.
  GestureTapOn(GetResultViewAt(2));

  // The item was activated.
  TestAppListClient* client = GetAppListTestHelper()->app_list_client();
  EXPECT_EQ("id23", client->last_opened_search_result());

  // Results should be cleared if app list models get reset.
  Shell::Get()->app_list_controller()->ClearActiveModel();
  EXPECT_EQ(std::vector<std::string>{}, GetResultIds());
}

TEST_F(ContinueSectionViewTabletModeTest,
       TabletModeLayoutWithThreeSuggestions) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  UpdateDisplay("1200x800");
  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ASSERT_TRUE(GetContinueSectionView()->GetVisible());
  ASSERT_EQ(3u, GetContinueSectionView()->GetTasksSuggestionsCount());

  const gfx::Size size = GetResultViewAt(0)->size();
  const int vertical_position = GetResultViewAt(0)->y();

  for (int i = 1; i < 3; i++) {
    const ContinueTaskView* task_view = GetResultViewAt(i);
    EXPECT_TRUE(task_view->GetVisible());
    EXPECT_EQ(size, task_view->size());
    EXPECT_EQ(vertical_position, task_view->y());
    EXPECT_GT(task_view->x(), GetResultViewAt(i - 1)->bounds().right());
  }

  views::View* parent_view = GetResultViewAt(0)->parent();

  EXPECT_EQ(std::abs(parent_view->GetContentsBounds().x() -
                     GetResultViewAt(0)->bounds().x()),
            std::abs(parent_view->GetContentsBounds().right() -
                     GetResultViewAt(2)->bounds().right()));
}

TEST_F(ContinueSectionViewTabletModeTest, TabletModeLayoutWithFourSuggestions) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);

  UpdateDisplay("1200x800");
  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();

  ASSERT_TRUE(GetContinueSectionView()->GetVisible());
  ASSERT_EQ(4u, GetContinueSectionView()->GetTasksSuggestionsCount());

  const gfx::Size size = GetResultViewAt(0)->size();
  const int vertical_position = GetResultViewAt(0)->y();

  for (int i = 1; i < 4; i++) {
    const ContinueTaskView* task_view = GetResultViewAt(i);
    EXPECT_TRUE(task_view->GetVisible());
    EXPECT_EQ(size, task_view->size());
    EXPECT_EQ(vertical_position, task_view->y());
    EXPECT_GT(task_view->x(), GetResultViewAt(i - 1)->bounds().right());
  }

  views::View* parent_view = GetResultViewAt(0)->parent();

  EXPECT_EQ(std::abs(parent_view->GetContentsBounds().x() -
                     GetResultViewAt(0)->bounds().x()),
            std::abs(parent_view->GetContentsBounds().right() -
                     GetResultViewAt(3)->bounds().right()));
}

TEST_P(ContinueSectionViewTest, NoOverlapsWithRecentApps) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);
  GetAppListTestHelper()->AddRecentApps(5);
  GetAppListTestHelper()->AddAppItems(5);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();

  EXPECT_FALSE(GetContinueSectionView()->bounds().Intersects(
      GetRecentAppsView()->bounds()));
}

TEST_P(ContinueSectionViewTest, NoOverlapsWithAppsGridItems) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);
  GetAppListTestHelper()->AddAppItems(5);

  EnsureLauncherShown();

  VerifyResultViewsUpdated();
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();

  gfx::Rect continue_bounds = GetContinueSectionView()->GetBoundsInScreen();
  for (size_t i = 0; i < test_api()->GetItemList()->item_count(); i++) {
    gfx::Rect app_bounds =
        test_api()->GetViewAtModelIndex(i)->GetBoundsInScreen();
    EXPECT_FALSE(continue_bounds.Intersects(app_bounds)) << i;
  }
}

// Tests that number of shown continue section items is reduced if they don't
// all fit into the available space (while maintaining min width).
TEST_F(ContinueSectionViewTabletModeTest,
       TabletModeLayoutWithFourSuggestionsWithRestrictedSpace) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id4", AppListSearchResultType::kZeroStateFile);

  // Set the display width so only 2 continue section tasks fit into available
  // space.
  UpdateDisplay("600x800");

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ASSERT_EQ(4u, GetContinueSectionView()->GetTasksSuggestionsCount());

  // Only first two tasks are visible.
  for (int i = 0; i < 4; ++i)
    EXPECT_EQ(i <= 1, GetResultViewAt(i)->GetVisible()) << i;

  const ContinueTaskView* first_task = GetResultViewAt(0);
  const ContinueTaskView* second_task = GetResultViewAt(1);

  EXPECT_EQ(first_task->size(), second_task->size());
  EXPECT_EQ(first_task->y(), second_task->y());
  EXPECT_GT(second_task->x(), first_task->bounds().right());
}

TEST_P(ContinueSectionViewTest, AllTasksShareTheSameWidth) {
  AddSearchResultWithTitle("id1", AppListSearchResultType::kZeroStateFile,
                           "title");
  AddSearchResultWithTitle(
      "id2", AppListSearchResultType::kZeroStateDrive,
      "Really really really long title text for the label");
  AddSearchResultWithTitle("id3", AppListSearchResultType::kZeroStateDrive,
                           "-");
  AddSearchResultWithTitle("id4", AppListSearchResultType::kZeroStateFile,
                           "medium title");

  UpdateDisplay("1200x800");
  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ASSERT_EQ(4u, GetContinueSectionView()->GetTasksSuggestionsCount());

  const gfx::Size size = GetResultViewAt(0)->size();

  for (int i = 1; i < 4; i++) {
    const ContinueTaskView* task_view = GetResultViewAt(i);
    EXPECT_TRUE(task_view->GetVisible());
    EXPECT_EQ(size, task_view->size());
  }
}

TEST_P(ContinueSectionViewTest, HideContinueSectionWhithLessThanMinimumFiles) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);

  // Minimum files for clamshell mode are 3.
  if (!tablet_mode_param())
    AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  // Wait for the view to update any pending SearchResults.
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, ShowContinueSectionWhithMinimumFiles) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);

  // Minimum files for clamshell mode are 3.
  if (!tablet_mode_param())
    AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();

  // Wait for the view to update any pending SearchResults.
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, TaskViewHasRippleWithMenuOpen) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);
  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(continue_task_view);
  EXPECT_TRUE(continue_task_view->IsMenuShowing());

  EXPECT_EQ(views::InkDropState::ACTIVATED,
            views::InkDrop::Get(continue_task_view)
                ->GetInkDrop()
                ->GetTargetInkDropState());
}

TEST_P(ContinueSectionViewTest, TaskViewHidesRippleAfterMenuCloses) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ContinueTaskView* continue_task_view = GetResultViewAt(0);
  EXPECT_EQ(continue_task_view->result()->id(), "id1");

  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(continue_task_view);
  EXPECT_TRUE(continue_task_view->IsMenuShowing());

  // Click on other task view to hide context menu.
  GetContinueSectionView()->GetWidget()->LayoutRootViewIfNecessary();
  SimulateRightClickOrLongPressOn(GetResultViewAt(2));
  EXPECT_FALSE(continue_task_view->IsMenuShowing());

  // Wait for the view to update the ink drop.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(views::InkDropState::HIDDEN, views::InkDrop::Get(continue_task_view)
                                             ->GetInkDrop()
                                             ->GetTargetInkDropState());
}

TEST_P(ContinueSectionViewWithReorderNudgeTest, ShowPrivacyNotice) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest, AcceptPrivacyNotice) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  GestureTapOn(
      GetContinueSectionView()->GetPrivacyNoticeForTest()->toast_button());

  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());

  HideLauncher();
  EnsureLauncherShown();

  EXPECT_FALSE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest, TimeDismissPrivacyNotice) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  ASSERT_TRUE(GetContinueSectionView()->FirePrivacyNoticeShownTimerForTest());

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest,
       HidingContinueSectionHidesPrivacyNotice) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  ASSERT_TRUE(IsPrivacyNoticeVisible());
  ASSERT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  // Simulate the user hiding the continue section.
  Shell::Get()->app_list_controller()->SetHideContinueSection(true);
  EXPECT_FALSE(GetContinueSectionView()->GetVisible());

  // The privacy notice is suppressed.
  EXPECT_FALSE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kNone);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest,
       DoNotShowPrivacyNoticeAndReorderNudgeAlternitively) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  // Close the launcher without waiting long enough for the privacy notice to be
  // considered shown.
  HideLauncher();

  // The privacy notice should be showing instead of the reorder nudge when the
  // next time the launcher is open.
  EnsureLauncherShown();
  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest,
       DoNotShowPrivacyNoticeAndReorderNudgeAlternitively2) {
  // Open the launcher without any search result added.
  EnsureLauncherShown();

  // Reorder nudge should be showing and privacy notice should not.
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();

  // After hiding the launcher, add some search results and open the launcher
  // again.
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  // If the reorder nudge was shown and hasn't reached the shown times limit,
  // keep showing the reorder nudge instead of the privacy notice.
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();

  EnsureLauncherShown();
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();

  EnsureLauncherShown();
  // After the reorder nudge has been shown for three times, starts showing the
  // privacy notice and removes the reorder nudge.
  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
}

TEST_P(ContinueSectionViewWithReorderNudgeTest,
       SearchResultsFetchedAfterLauncherShown) {
  ResetPrivacyNoticePref();
  // Open the launcher without any search result added.
  EnsureLauncherShown();

  // Reorder nudge should be showing and privacy notice should not.
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  // Add some search results while the launcher is open.
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  VerifyResultViewsUpdated();

  // Neither the privacy notice nor the search results should show.
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowFilesSection());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kReorderNudge);
  EXPECT_EQ(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);

  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();

  // Open the launcher, wait for the reorder nudge to be shown and close it two
  // more times. After the reorder nudge has been shown for three times, starts
  // showing the privacy notice and removes the reorder nudge.

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();
  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  // Wait for long enough for the reorder nudge to be considered shown.
  task_environment()->AdvanceClock(base::Seconds(1));
  HideLauncher();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();
  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);
  EXPECT_NE(GetToastContainerView()->current_toast(),
            AppListToastType::kReorderNudge);
  HideLauncher();
}

TEST_F(ContinueSectionViewTabletModeTest, PrivacyNoticeIsShownInBackground) {
  AddSearchResult("id1", AppListSearchResultType::kZeroStateFile);
  AddSearchResult("id2", AppListSearchResultType::kZeroStateDrive);
  AddSearchResult("id3", AppListSearchResultType::kZeroStateDrive);
  ResetPrivacyNoticePref();

  EnsureLauncherShown();
  VerifyResultViewsUpdated();

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  // Activate the search box. The privacy notice will become inactive but the
  // view still exists.
  auto* search_box = GetAppListTestHelper()->GetSearchBoxView();
  search_box->SetSearchBoxActive(true, ui::EventType::kMousePressed);

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  // The privacy notice is not considered as shown when the search box is
  // active. Therefore the privacy notice timer should not be running.
  EXPECT_FALSE(GetContinueSectionView()->FirePrivacyNoticeShownTimerForTest());

  // Reopen the app list.
  HideLauncher();
  EnsureLauncherShown();

  // As the privacy notice was not active during the last shown, shows the
  // notice again.
  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  // Activate the search box and then deactivate it.
  search_box->SetSearchBoxActive(true, ui::EventType::kMousePressed);
  search_box->SetSearchBoxActive(false, ui::EventType::kMousePressed);

  // The privacy notice timer should be restarted after the search box becomes
  // inactive.
  EXPECT_TRUE(GetContinueSectionView()->FirePrivacyNoticeShownTimerForTest());

  // Reopen the app list. The privacy notice should be removed.
  HideLauncher();
  EnsureLauncherShown();
  EXPECT_FALSE(GetContinueSectionView()->ShouldShowPrivacyNotice());
  EXPECT_FALSE(GetContinueSectionView()->GetPrivacyNoticeForTest());
}

TEST_P(ContinueSectionViewTest, InitialShowDoesNotAnimate) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      GetContinueSectionView()->suggestions_container()->children();
  ASSERT_EQ(4u, container_children.size());
  for (int i = 0; i < 4; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->layer()->GetAnimator()->is_animating());
  }
}

TEST_P(ContinueSectionViewTest, UpdateWithNoChangesDoesNotAnimate) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  GetContinueSectionView()->suggestions_container()->Update();
  base::RunLoop().RunUntilIdle();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      GetContinueSectionView()->suggestions_container()->children();
  ASSERT_EQ(4u, container_children.size());
  for (int i = 0; i < 4; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->layer()->GetAnimator()->is_animating());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenLastItemReplaced) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(3);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[3];
  EXPECT_EQ(initial_bounds[3], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify views for results that are not changing do not animate.
  for (int i : std::set<int>{0, 1, 2}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->GetVisible());
    views::View* new_result_view = container_children[i + new_views_start];
    EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
    EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
  }

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenFirstItemRemoved) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(0);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[0];
  EXPECT_EQ(initial_bounds[0], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{0, 1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);

    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenSecondItemRemoved) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(1);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[0];
  EXPECT_EQ(initial_bounds[0], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(1.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify the view for the result that's not changing does not animate.
  EXPECT_FALSE(container_children[0]->GetVisible());
  views::View* stationary_view = container_children[new_views_start];
  EXPECT_FALSE(stationary_view->layer()->GetAnimator()->is_animating());
  EXPECT_EQ(initial_bounds[0], GetCurrentLayerBounds(stationary_view));
  EXPECT_EQ(1.0f, stationary_view->layer()->opacity());

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenThirdItemRemoved) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/5);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(2);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[2];
  EXPECT_EQ(initial_bounds[2], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify views for results that are not changing do not animate.
  for (int i : std::set<int>{0, 1}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->GetVisible());
    views::View* new_result_view = container_children[i + new_views_start];
    EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
    EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
  }

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenItemInserted) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/4);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->AddAt(
      1, CreateTestResult("id5", AppListSearchResultType::kZeroStateDrive,
                          "new result"));

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[3];
  EXPECT_EQ(initial_bounds[3], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify views for results that are not changing do not animate.
  for (int i : std::set<int>{0}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->GetVisible());
    views::View* new_result_view = container_children[i + new_views_start];
    EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
    EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
  }

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    if (tablet_mode_param() && i > 1) {
      EXPECT_GT(initial_bounds[i].x(), current_bounds.x());
    } else {
      EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    }
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{1, 2}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, AnimatesWhenTwoItemsRemoved) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/6);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(1);
  GetResults()->DeleteAt(1);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();

  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // Removed views should fade out.
  for (int i : std::set<int>{1, 2}) {
    views::View* view_fading_out = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(view_fading_out));
    EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());
  }

  // Verify views for results that are not changing do not animate.
  for (int i : std::set<int>{0}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->GetVisible());
    views::View* new_result_view = container_children[i + new_views_start];
    EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
    EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
  }

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 4; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, ResultRemovedMidAnimation) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/6);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(1);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  // Remove another result after update (which triggers an update animation).
  GetResults()->DeleteAt(1);
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 4, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[1];
  EXPECT_EQ(initial_bounds[1], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // Verify views for results that are not changing do not animate.
  for (int i : std::set<int>{0}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    EXPECT_FALSE(container_children[i]->GetVisible());
    views::View* new_result_view = container_children[i + new_views_start];
    EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
    EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
  }

  // Verify views that are expected to slide in.
  for (int i : std::set<int>{1, 2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i + new_views_start];
    EXPECT_EQ(initial_bounds[i], GetTargetLayerBounds(result_view));
    const gfx::RectF current_bounds = GetCurrentLayerBounds(result_view);
    EXPECT_EQ(initial_bounds[i].size(), current_bounds.size());
    EXPECT_LT(initial_bounds[i].x(), current_bounds.x());
    EXPECT_EQ(0.0f, result_view->layer()->opacity());
    EXPECT_EQ(1.0f, result_view->layer()->GetTargetOpacity());
  }

  // Verify views that are expected to slide out.
  for (int i : std::set<int>{2, 3}) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = container_children[i];
    EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(result_view));
    const gfx::RectF target_bounds = GetTargetLayerBounds(result_view);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i], target_bounds);
    } else {
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_GT(initial_bounds[i].x(), target_bounds.x());
    }
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(0.0f, result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(4u, container_children.size());

  for (int i = 0; i < 3; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
    EXPECT_EQ(gfx::Rect(), result_view->layer()->clip_rect());
  }
}

TEST_P(ContinueSectionViewTest, ContinueSectionHiddenMidAnimation) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/4);
  ASSERT_EQ(4u, initial_bounds.size());

  // Remove one item to trigger update animation.
  GetResults()->DeleteAt(1);
  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  // Remove two extra results which should hide the continue section.
  GetResults()->DeleteAt(0);
  GetResults()->DeleteAt(0);
  container_view->Update();

  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  ASSERT_EQ(1u, container_children.size());
  EXPECT_FALSE(container_children[0]->layer()->GetAnimator()->is_animating());
}

TEST_P(ContinueSectionViewTest, AnimatesWhenNumberOfChipsChanges) {
  std::vector<gfx::RectF> initial_bounds =
      InitializeForAnimationTest(/*result_count=*/4);
  ASSERT_EQ(4u, initial_bounds.size());

  GetResults()->DeleteAt(2);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  std::vector<raw_ptr<views::View, VectorExperimental>> container_children =
      container_view->children();
  const size_t new_views_start = 4;
  ASSERT_EQ(new_views_start + 3, container_children.size());

  // The removed view should fade out.
  views::View* view_fading_out = container_children[2];
  EXPECT_EQ(initial_bounds[2], GetCurrentLayerBounds(view_fading_out));
  EXPECT_EQ(0.0f, view_fading_out->layer()->GetTargetOpacity());

  // In tablet mode, all views animate when the number of items shown in the
  // container changes.
  if (tablet_mode_param()) {
    for (int i : std::set<int>{0, 1, 3}) {
      SCOPED_TRACE(testing::Message() << "Item at " << i);
      const bool result_after_removed = i > 2;
      views::View* new_result_view =
          container_children[i + new_views_start -
                             (result_after_removed ? 1 : 0)];
      const gfx::RectF current_bounds = GetCurrentLayerBounds(new_result_view);
      EXPECT_EQ(initial_bounds[i].y(), current_bounds.y());
      EXPECT_LT(initial_bounds[i].width(), current_bounds.width());
      const gfx::RectF new_view_target_bounds =
          GetTargetLayerBounds(new_result_view);
      EXPECT_EQ(current_bounds.size(), new_view_target_bounds.size());
      // Items after removed item should animate from the right, and items
      // before the removed item should animate from the left.
      if (i > 1) {
        EXPECT_LT(new_view_target_bounds.x(), current_bounds.x());
      } else {
        EXPECT_GT(new_view_target_bounds.x(), current_bounds.x());
      }
      EXPECT_EQ(0.0f, new_result_view->layer()->opacity());
      EXPECT_EQ(1.0f, new_result_view->layer()->GetTargetOpacity());

      views::View* old_result_view = container_children[i];
      EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(old_result_view));
      const gfx::RectF target_bounds = GetTargetLayerBounds(old_result_view);
      EXPECT_EQ(initial_bounds[i].size(), target_bounds.size());
      EXPECT_EQ(initial_bounds[i].x(), target_bounds.x());
      EXPECT_EQ(1.0f, old_result_view->layer()->opacity());
      EXPECT_EQ(0.0f, old_result_view->layer()->GetTargetOpacity());
    }
  } else {
    // Views before removed one should not animate.
    for (int i : std::set<int>{0, 1}) {
      SCOPED_TRACE(testing::Message() << "Item at " << i);
      EXPECT_FALSE(container_children[i]->GetVisible());
      views::View* new_result_view = container_children[new_views_start + i];
      EXPECT_FALSE(new_result_view->layer()->GetAnimator()->is_animating());
      EXPECT_EQ(initial_bounds[i], GetCurrentLayerBounds(new_result_view));
      EXPECT_EQ(1.0f, new_result_view->layer()->opacity());
    }

    // The last new view should slide in.
    views::View* last_new_result_view = container_children[new_views_start + 2];
    EXPECT_EQ(initial_bounds[2], GetTargetLayerBounds(last_new_result_view));
    const gfx::RectF last_old_current_bounds =
        GetCurrentLayerBounds(last_new_result_view);
    EXPECT_EQ(initial_bounds[2].size(), last_old_current_bounds.size());
    EXPECT_LT(initial_bounds[2].x(), last_old_current_bounds.x());
    EXPECT_EQ(0.0f, last_new_result_view->layer()->opacity());
    EXPECT_EQ(1.0f, last_new_result_view->layer()->GetTargetOpacity());

    // The last old view should slide out.
    views::View* last_old_result_view = container_children[3];
    EXPECT_EQ(initial_bounds[3], GetCurrentLayerBounds(last_old_result_view));
    const gfx::RectF last_old_target_bounds =
        GetTargetLayerBounds(last_old_result_view);
    EXPECT_EQ(initial_bounds[3].size(), last_old_target_bounds.size());
    EXPECT_GT(initial_bounds[3].x(), last_old_target_bounds.x());
    EXPECT_EQ(1.0f, last_old_result_view->layer()->opacity());
    EXPECT_EQ(0.0f, last_old_result_view->layer()->GetTargetOpacity());
  }

  WaitForAllChildrenAnimationsToComplete(container_view);
  VerifyResultViewsUpdated();
  container_children = container_view->children();
  EXPECT_EQ(3u, container_children.size());

  for (int i = 0; i < 2; ++i) {
    SCOPED_TRACE(testing::Message() << "Item at " << i);
    views::View* result_view = GetResultViewAt(i);
    if (tablet_mode_param()) {
      EXPECT_EQ(initial_bounds[i].y(), result_view->layer()->bounds().y());
    } else {
      EXPECT_EQ(initial_bounds[i], gfx::RectF(result_view->layer()->bounds()));
    }
    EXPECT_EQ(gfx::Transform(), result_view->layer()->transform());
    EXPECT_EQ(1.0f, result_view->layer()->opacity());
  }
}

TEST_F(ContinueSectionViewClamshellModeTest, AnimatesOutAfterRemovingResults) {
  ResetPrivacyNoticePref();
  InitializeForAnimationTest(/*result_count=*/3);

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  views::View* privacy_notice =
      GetContinueSectionView()->GetPrivacyNoticeForTest();

  RemoveSearchResultAt(1);

  ContinueTaskContainerView* const container_view =
      GetContinueSectionView()->suggestions_container();
  container_view->Update();

  EXPECT_EQ(1.0f, privacy_notice->layer()->opacity());
  EXPECT_EQ(0.0f, privacy_notice->layer()->GetTargetOpacity());
  EXPECT_TRUE(privacy_notice->layer()->GetAnimator()->is_animating());

  ui::LayerAnimationStoppedWaiter waiter;
  waiter.Wait(privacy_notice->layer());

  VerifyResultViewsUpdated();

  ASSERT_LE(GetContinueSectionView()->GetTasksSuggestionsCount(), 2u);
  EXPECT_FALSE(IsPrivacyNoticeVisible());

  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
}

TEST_P(ContinueSectionViewTest, AnimatesPrivacyNoticeAccept) {
  ResetPrivacyNoticePref();
  InitializeForAnimationTest(/*result_count=*/3);

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  AppListToastView* privacy_notice =
      GetContinueSectionView()->GetPrivacyNoticeForTest();

  GestureTapOn(privacy_notice->toast_button());

  EXPECT_EQ(1.0f, privacy_notice->layer()->opacity());
  EXPECT_EQ(0.0f, privacy_notice->layer()->GetTargetOpacity());
  EXPECT_TRUE(privacy_notice->layer()->GetAnimator()->is_animating());

  ui::LayerAnimationStoppedWaiter waiter;
  waiter.Wait(privacy_notice->layer());

  ContinueTaskContainerView* container_view =
      GetContinueSectionView()->suggestions_container();

  EXPECT_TRUE(container_view->layer()->GetAnimator()->is_animating());
  WaitForAllChildrenAnimationsToComplete(container_view);
  EXPECT_FALSE(IsPrivacyNoticeVisible());

  EXPECT_TRUE(GetContinueSectionView()->GetVisible());
}

// Regression test for https://crbug.com/1326237.
TEST_F(ContinueSectionViewClamshellModeTest,
       RemoveSearchResultWhileAnimatingContinueSection) {
  ResetPrivacyNoticePref();
  InitializeForAnimationTest(/*result_count=*/3);

  EXPECT_TRUE(IsPrivacyNoticeVisible());
  EXPECT_EQ(GetAppListNudgeController()->current_nudge(),
            AppListNudgeController::NudgeType::kPrivacyNotice);

  AppListToastView* privacy_notice =
      GetContinueSectionView()->GetPrivacyNoticeForTest();

  GestureTapOn(privacy_notice->toast_button());

  EXPECT_EQ(1.0f, privacy_notice->layer()->opacity());
  EXPECT_EQ(0.0f, privacy_notice->layer()->GetTargetOpacity());
  EXPECT_TRUE(privacy_notice->layer()->GetAnimator()->is_animating());

  RemoveSearchResultAt(0);

  EXPECT_EQ(0.0f, privacy_notice->layer()->GetTargetOpacity());
  EXPECT_TRUE(privacy_notice->layer()->GetAnimator()->is_animating());

  ui::LayerAnimationStoppedWaiter waiter;
  waiter.Wait(privacy_notice->layer());

  WaitForAllChildrenAnimationsToComplete(
      GetContinueSectionView()->suggestions_container());
  EXPECT_FALSE(IsPrivacyNoticeVisible());

  EXPECT_FALSE(GetContinueSectionView()->GetVisible());
}

// Regression test for https://crbug.com/1357434.
TEST_F(ContinueSectionViewClamshellModeTest,
       CloseLauncherWhileAnimatingPrivacyToastDoesNotCrash) {
  ResetPrivacyNoticePref();
  InitializeForAnimationTest(/*result_count=*/3);
  ASSERT_TRUE(IsPrivacyNoticeVisible());

  AppListToastView* privacy_toast =
      GetContinueSectionView()->GetPrivacyNoticeForTest();
  ASSERT_TRUE(privacy_toast);

  // Tap on the "OK" button to start the toast dismiss animation.
  GestureTapOn(privacy_toast->toast_button());
  EXPECT_TRUE(privacy_toast->layer()->GetAnimator()->is_animating());

  HideLauncher();
  // No crash.
}

}  // namespace
}  // namespace ash