910e62b5创建于 1月15日历史提交
// 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 "chrome/browser/download/bubble/download_bubble_ui_controller.h"

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/download/bubble/download_bubble_prefs.h"
#include "chrome/browser/download/bubble/download_bubble_update_service.h"
#include "chrome/browser/download/bubble/download_display_controller.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/download/download_core_service.h"
#include "chrome/browser/download/download_core_service_factory.h"
#include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/offline_item_model_manager_factory.h"
#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
#include "chrome/browser/profiles/profile_key.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/download/download_display.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_item.h"
#include "components/download/public/common/mock_download_item.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "components/offline_items_collection/core/offline_item_state.h"
#include "components/offline_items_collection/core/test_support/mock_offline_content_provider.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_download_manager.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
using ::offline_items_collection::OfflineItemState;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::ReturnRefOfCopy;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using StrictMockDownloadItem = testing::StrictMock<download::MockDownloadItem>;
using DownloadDangerType = download::DownloadDangerType;
using DownloadState = download::DownloadItem::DownloadState;
using DownloadUIModelPtr = DownloadUIModel::DownloadUIModelPtr;
using OfflineItemList =
    offline_items_collection::OfflineContentProvider::OfflineItemList;

const char kProviderNamespace[] = "mock_namespace";

class MockDownloadDisplayController : public DownloadDisplayController {
 public:
  MockDownloadDisplayController(Browser* browser,
                                DownloadBubbleUIController* bubble_controller)
      : DownloadDisplayController(nullptr, browser, bubble_controller) {
    bubble_controller->SetDownloadDisplayController(this);
  }
  void MaybeShowButtonWhenCreated() override {}
  MOCK_METHOD1(OnNewItem, void(bool));
  MOCK_METHOD2(OnUpdatedItem, void(bool, bool));
  MOCK_METHOD1(OnRemovedItem, void(const ContentId&));
};

class MockDownloadBubbleUpdateService : public DownloadBubbleUpdateService {
 public:
  enum class ModelType {
    kDownloadItem,
    kOfflineItem,
  };

  MockDownloadBubbleUpdateService(
      Profile* profile,
      const std::vector<std::unique_ptr<StrictMockDownloadItem>>&
          download_items,
      const OfflineItemList& offline_items)
      : DownloadBubbleUpdateService(profile),
        profile_(profile),
        download_items_(download_items),
        offline_items_(offline_items) {}
  MockDownloadBubbleUpdateService(const MockDownloadBubbleUpdateService&) =
      delete;
  MockDownloadBubbleUpdateService& operator=(
      const MockDownloadBubbleUpdateService&) = delete;

  ~MockDownloadBubbleUpdateService() override = default;

  bool GetAllModelsToDisplay(
      std::vector<DownloadUIModelPtr>& models,
      const webapps::AppId* web_app_id,
      bool force_backfill_download_items = true) override {
    models.clear();
    int download_item_index = 0, offline_item_index = 0;
    // Compose a list of models from the items stored in the test fixture.
    for (ModelType type : model_types_) {
      if (type == ModelType::kDownloadItem) {
        auto model = DownloadItemModel::Wrap(
            download_items_->at(download_item_index++).get());
        if (model->ShouldShowInBubble()) {
          models.push_back(std::move(model));
        }
      } else {
        auto model = OfflineItemModel::Wrap(
            OfflineItemModelManagerFactory::GetForBrowserContext(profile_),
            offline_items_->at(offline_item_index++));
        if (model->ShouldShowInBubble()) {
          models.push_back(std::move(model));
        }
      }
    }
    return true;
  }

  void AddModel(ModelType type) { model_types_.push_back(type); }

  bool IsInitialized() const override { return true; }

  MOCK_METHOD(DownloadDisplay::ProgressInfo,
              GetProgressInfo,
              (const webapps::AppId*),
              (const override));

 private:
  raw_ptr<Profile> profile_;
  std::vector<ModelType> model_types_;
  const raw_ref<const std::vector<std::unique_ptr<StrictMockDownloadItem>>>
      download_items_;
  const raw_ref<const OfflineItemList> offline_items_;
};

class DownloadBubbleUIControllerTest : public testing::Test {
 public:
  DownloadBubbleUIControllerTest()
      : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {}
  DownloadBubbleUIControllerTest(const DownloadBubbleUIControllerTest&) =
      delete;
  DownloadBubbleUIControllerTest& operator=(
      const DownloadBubbleUIControllerTest&) = delete;

  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kNoFirstRun);
    ASSERT_TRUE(testing_profile_manager_.SetUp());

    profile_ = testing_profile_manager_.CreateTestingProfile("testing_profile");
    auto manager = std::make_unique<NiceMock<content::MockDownloadManager>>();
    manager_ = manager.get();
    EXPECT_CALL(*manager, GetBrowserContext())
        .WillRepeatedly(Return(profile_.get()));
    EXPECT_CALL(*manager, RemoveObserver(_)).WillRepeatedly(Return());
    profile_->SetDownloadManagerForTesting(std::move(manager));

    // Set test delegate to get the corresponding download prefs.
    auto delegate = std::make_unique<ChromeDownloadManagerDelegate>(profile_);
    DownloadCoreServiceFactory::GetForBrowserContext(profile_)
        ->SetDownloadManagerDelegateForTesting(std::move(delegate));

    content_provider_ = std::make_unique<
        NiceMock<offline_items_collection::MockOfflineContentProvider>>();
    OfflineContentAggregatorFactory::GetForKey(profile_->GetProfileKey())
        ->RegisterProvider(kProviderNamespace, content_provider_.get());

    mock_update_service_ =
        std::make_unique<StrictMock<MockDownloadBubbleUpdateService>>(
            profile_, items_, offline_items_);
    auto window = std::make_unique<TestBrowserWindow>();
    Browser::CreateParams params(profile_, true);
    params.type = Browser::TYPE_NORMAL;
    params.window = window.release();
    browser_ = Browser::DeprecatedCreateOwnedForTesting(params);
    controller_ = std::make_unique<DownloadBubbleUIController>(
        browser_.get(), mock_update_service_.get());
    display_controller_ =
        std::make_unique<NiceMock<MockDownloadDisplayController>>(
            browser_.get(), controller_.get());
    second_controller_ = std::make_unique<DownloadBubbleUIController>(
        browser_.get(), mock_update_service_.get());
    second_display_controller_ =
        std::make_unique<NiceMock<MockDownloadDisplayController>>(
            browser_.get(), second_controller_.get());
  }

  void TearDown() override {
    DownloadCoreServiceFactory::GetForBrowserContext(profile_)
        ->SetDownloadManagerDelegateForTesting(nullptr);
    // The controller needs to be reset before download manager, because the
    // download_notifier_ will unregister itself from the manager.
    controller_.reset();
    second_controller_.reset();
    display_controller_.reset();
    second_display_controller_.reset();
    mock_update_service_.reset();
  }

 protected:
  NiceMock<content::MockDownloadManager>& manager() { return *manager_; }
  download::MockDownloadItem& item(size_t index) { return *items_[index]; }
  std::vector<std::unique_ptr<StrictMockDownloadItem>>& items() {
    return items_;
  }
  NiceMock<MockDownloadDisplayController>& display_controller() {
    return *display_controller_;
  }
  DownloadBubbleUIController& controller() { return *controller_; }
  DownloadBubbleUIController& second_controller() {
    return *second_controller_;
  }
  TestingProfile* profile() { return profile_; }
  NiceMock<offline_items_collection::MockOfflineContentProvider>&
  content_provider() {
    return *content_provider_;
  }
  MockDownloadBubbleUpdateService* mock_update_service() {
    return mock_update_service_.get();
  }

  void InitDownloadItem(
      const base::FilePath::CharType* path,
      DownloadState state,
      const std::string& id,
      bool is_transient = false,
      base::Time start_time = base::Time::Now(),
      bool may_show_animation = true,
      download::DownloadItem::TargetDisposition target_disposition =
          download::DownloadItem::TARGET_DISPOSITION_PROMPT,
      const std::string& mime_type = "",
      download::DownloadItem::DownloadCreationType creation_type =
          download::DownloadItem::DownloadCreationType::TYPE_ACTIVE_DOWNLOAD) {
    size_t index = items_.size();
    items_.push_back(std::make_unique<StrictMockDownloadItem>());
    EXPECT_CALL(item(index), GetId())
        .WillRepeatedly(Return(static_cast<uint32_t>(items_.size() + 1)));
    EXPECT_CALL(item(index), GetGuid()).WillRepeatedly(ReturnRefOfCopy(id));
    EXPECT_CALL(item(index), GetState()).WillRepeatedly(Return(state));
    EXPECT_CALL(item(index), GetStartTime()).WillRepeatedly(Return(start_time));
    EXPECT_CALL(item(index), GetTargetFilePath())
        .WillRepeatedly(
            ReturnRefOfCopy(base::FilePath(FILE_PATH_LITERAL("foo"))));
    EXPECT_CALL(item(index), GetLastReason())
        .WillRepeatedly(Return(download::DOWNLOAD_INTERRUPT_REASON_NONE));
    EXPECT_CALL(item(index), GetInsecureDownloadStatus())
        .WillRepeatedly(
            Return(download::DownloadItem::InsecureDownloadStatus::SAFE));
    int received_bytes =
        state == download::DownloadItem::IN_PROGRESS ? 50 : 100;
    EXPECT_CALL(item(index), GetReceivedBytes())
        .WillRepeatedly(Return(received_bytes));
    EXPECT_CALL(item(index), GetTotalBytes()).WillRepeatedly(Return(100));
    EXPECT_CALL(item(index), IsDone()).WillRepeatedly(Return(false));
    EXPECT_CALL(item(index), IsTransient())
        .WillRepeatedly(Return(is_transient));
    EXPECT_CALL(item(index), GetDownloadCreationType())
        .WillRepeatedly(Return(creation_type));
    EXPECT_CALL(item(index), IsPaused()).WillRepeatedly(Return(false));
    EXPECT_CALL(item(index), IsDangerous()).WillRepeatedly(Return(false));
    EXPECT_CALL(item(index), IsInsecure()).WillRepeatedly(Return(false));
    // Functions called when checking ShouldShowDownloadStartedAnimation().
    EXPECT_CALL(item(index), IsSavePackageDownload())
        .WillRepeatedly(Return(false));
    EXPECT_CALL(item(index), GetTargetDisposition())
        .WillRepeatedly(Return(target_disposition));
    EXPECT_CALL(item(index), GetMimeType()).WillRepeatedly(Return(mime_type));
    EXPECT_CALL(item(index), GetURL())
        .WillRepeatedly(ReturnRef(GURL::EmptyGURL()));
    EXPECT_CALL(item(index), GetReferrerUrl())
        .WillRepeatedly(ReturnRef(GURL::EmptyGURL()));
    EXPECT_CALL(item(index), GetDangerType())
        .WillRepeatedly(
            Return(DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
    std::vector<raw_ptr<download::DownloadItem, VectorExperimental>> items;
    for (size_t i = 0; i < items_.size(); ++i) {
      items.push_back(&item(i));
    }
    EXPECT_CALL(*manager_, GetAllDownloads(_))
        .WillRepeatedly(SetArgPointee<0>(items));
    EXPECT_CALL(*manager_, GetDownloadByGuid(id))
        .WillRepeatedly(Return(&(item(index))));
    content::DownloadItemUtils::AttachInfoForTesting(&(item(index)), profile(),
                                                     nullptr);
    mock_update_service_->AddModel(
        MockDownloadBubbleUpdateService::ModelType::kDownloadItem);
    controller().OnDownloadItemAdded(&item(index), may_show_animation);
  }

  void UpdateDownloadItem(
      int item_index,
      DownloadState state,
      bool is_paused = false,
      DownloadDangerType danger_type =
          DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS) {
    DCHECK_GT(items_.size(), static_cast<size_t>(item_index));
    EXPECT_CALL(item(item_index), GetState()).WillRepeatedly(Return(state));
    EXPECT_CALL(item(item_index), IsDone())
        .WillRepeatedly(Return(state == DownloadState::COMPLETE));
    EXPECT_CALL(item(item_index), IsDangerous())
        .WillRepeatedly(
            Return(danger_type !=
                   DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS));
    EXPECT_CALL(item(item_index), GetDangerType())
        .WillRepeatedly(Return(danger_type));
    EXPECT_CALL(item(item_index), IsPaused()).WillRepeatedly(Return(is_paused));
    controller().OnDownloadItemUpdated(&item(item_index));
  }

  void InitOfflineItem(OfflineItemState state, std::string id) {
    OfflineItem item;
    item.state = state;
    item.id.id = id;
    offline_items_.push_back(item);
    mock_update_service_->AddModel(
        MockDownloadBubbleUpdateService::ModelType::kOfflineItem);
    controller().OnOfflineItemsAdded({item});
  }

  void UpdateOfflineItem(int item_index, OfflineItemState state) {
    offline_items_[item_index].state = state;
    controller().OnOfflineItemUpdated(offline_items_[item_index]);
  }

  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  std::unique_ptr<DownloadBubbleUIController> controller_;
  std::unique_ptr<DownloadBubbleUIController> second_controller_;
  std::unique_ptr<NiceMock<MockDownloadDisplayController>> display_controller_;
  std::unique_ptr<NiceMock<MockDownloadDisplayController>>
      second_display_controller_;
  std::vector<std::unique_ptr<StrictMockDownloadItem>> items_;
  OfflineItemList offline_items_;
  raw_ptr<NiceMock<content::MockDownloadManager>, DanglingUntriaged> manager_;
  TestingProfileManager testing_profile_manager_;
  std::unique_ptr<
      NiceMock<offline_items_collection::MockOfflineContentProvider>>
      content_provider_;
  std::unique_ptr<StrictMock<MockDownloadBubbleUpdateService>>
      mock_update_service_;
  std::unique_ptr<Browser> browser_;
  raw_ptr<TestingProfile> profile_;
};

TEST_F(DownloadBubbleUIControllerTest, ProcessesNewItems) {
  std::vector<std::string> ids = {"Download 1", "Download 2", "Offline 1",
                                  "Download 3", "Offline 2"};
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(2);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[0]);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
                   download::DownloadItem::COMPLETE, ids[1]);
  EXPECT_CALL(display_controller(), OnNewItem(false)).Times(1);
  InitOfflineItem(OfflineItemState::IN_PROGRESS, ids[2]);
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[3],
                   /*is_transient=*/false, base::Time::Now());
  EXPECT_CALL(display_controller(), OnNewItem(false)).Times(1);
  InitOfflineItem(OfflineItemState::IN_PROGRESS, ids[4]);
}

TEST_F(DownloadBubbleUIControllerTest, ProcessesUpdatedItems) {
  std::vector<std::string> ids = {"Download 1", "Offline 1"};
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[0]);
  EXPECT_CALL(display_controller(), OnUpdatedItem(false, true)).Times(1);
  UpdateDownloadItem(/*item_index=*/0, DownloadState::IN_PROGRESS);
  EXPECT_CALL(display_controller(), OnUpdatedItem(true, true)).Times(1);
  UpdateDownloadItem(/*item_index=*/0, DownloadState::COMPLETE);

  EXPECT_CALL(display_controller(), OnNewItem(false)).Times(1);
  InitOfflineItem(OfflineItemState::IN_PROGRESS, ids[1]);
  EXPECT_CALL(display_controller(), OnUpdatedItem(true, true)).Times(1);
  UpdateOfflineItem(/*item_index=*/0, OfflineItemState::COMPLETE);
}

TEST_F(DownloadBubbleUIControllerTest, UpdatedItemIsPendingDeepScanning) {
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, "Download 1");
  EXPECT_CALL(display_controller(), OnUpdatedItem(true, true)).Times(1);
  UpdateDownloadItem(
      /*item_index=*/0, DownloadState::IN_PROGRESS, false,
      DownloadDangerType::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING);
}

TEST_F(DownloadBubbleUIControllerTest, TransientDownloadShouldNotShow) {
  std::vector<std::string> ids = {"Download 1", "Download 2"};
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[0],
                   /*is_transient=*/true);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[1],
                   /*is_transient=*/false);
  std::vector<DownloadUIModelPtr> models = controller().GetMainView();
  EXPECT_EQ(models.size(), 1ul);
  EXPECT_EQ(models[0]->GetContentId().id, ids[1]);
  EXPECT_FALSE(controller().last_primary_view_was_partial());
}

TEST_F(DownloadBubbleUIControllerTest,
       CompleteHistoryImportShouldNotShowInPartialView) {
  std::vector<std::string> ids = {"history_import1", "history_import2"};
  // Complete history import item.
  InitDownloadItem(
      FILE_PATH_LITERAL("/foo/bar.pdf"), download::DownloadItem::COMPLETE,
      ids[0],
      /*is_transient=*/false, /*start_time=*/base::Time::Now(),
      /*may_show_animation=*/true,
      download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
      /*mime_type=*/"",
      download::DownloadItem::DownloadCreationType::TYPE_HISTORY_IMPORT);
  // In-progress history import item.
  InitDownloadItem(
      FILE_PATH_LITERAL("/foo/bar2.pdf"), download::DownloadItem::IN_PROGRESS,
      ids[1],
      /*is_transient=*/false, /*start_time=*/base::Time::Now(),
      /*may_show_animation=*/true,
      download::DownloadItem::TARGET_DISPOSITION_OVERWRITE,
      /*mime_type=*/"",
      download::DownloadItem::DownloadCreationType::TYPE_HISTORY_IMPORT);
  std::vector<DownloadUIModelPtr> partial_view = controller().GetPartialView();
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    ASSERT_EQ(partial_view.size(), 1u);
    EXPECT_EQ(partial_view[0]->GetContentId().id, ids[1]);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(partial_view.size(), 0u);
  }
  std::vector<DownloadUIModelPtr> main_view = controller().GetMainView();
  EXPECT_EQ(main_view.size(), 2u);
  EXPECT_FALSE(controller().last_primary_view_was_partial());
}

TEST_F(DownloadBubbleUIControllerTest,
       OpeningMainViewRemovesCompletedEntryFromPartialView) {
  std::vector<std::string> ids = {"Download 1", "Offline 1"};
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[0]);
  InitOfflineItem(OfflineItemState::IN_PROGRESS, ids[1]);

  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 2ul);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
    EXPECT_EQ(second_controller().GetPartialView().size(), 2ul);
    EXPECT_TRUE(second_controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0ul);
    EXPECT_EQ(second_controller().GetPartialView().size(), 0ul);
  }

  UpdateDownloadItem(/*item_index=*/0, DownloadState::COMPLETE);
  UpdateOfflineItem(/*item_index=*/0, OfflineItemState::COMPLETE);
  EXPECT_EQ(controller().GetMainView().size(), 2ul);
  EXPECT_FALSE(controller().last_primary_view_was_partial());
  // Download was removed from partial view because it is completed.
  EXPECT_EQ(controller().GetPartialView().size(), 0ul);
  // The partial view wasn't actually shown, so this bit is not updated.
  EXPECT_FALSE(controller().last_primary_view_was_partial());
  EXPECT_EQ(second_controller().GetPartialView().size(), 0ul);
  EXPECT_EQ(second_controller().last_primary_view_was_partial(),
            download::IsDownloadBubblePartialViewEnabled(profile()));
}

TEST_F(DownloadBubbleUIControllerTest,
       OpeningMainViewDoesNotRemoveInProgressEntryFromPartialView) {
  std::vector<std::string> ids = {"Download 1", "Offline 1"};
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[0]);
  InitOfflineItem(OfflineItemState::IN_PROGRESS, ids[1]);

  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 2ul);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0ul);
  }

  // This does not remove the entries from the partial view because the items
  // are in progress.
  EXPECT_EQ(controller().GetMainView().size(), 2ul);
  EXPECT_FALSE(controller().last_primary_view_was_partial());
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 2ul);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0ul);
  }
}

// Tests that no items are returned (i.e. no partial view will be shown) if it
// is too soon since the last partial view has been shown.
TEST_F(DownloadBubbleUIControllerTest, NoItemsReturnedForPartialViewTooSoon) {
  std::vector<std::string> ids = {"Download 1", "Download 2", "Download 3",
                                  "Download 4"};

  // First time showing the partial view should work.
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar1.pdf"),
                   download::DownloadItem::COMPLETE, ids[0]);
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 1u);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0u);
  }

  // No items are returned for a partial view because it is too soon.
  task_environment_.FastForwardBy(base::Seconds(14));
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
                   download::DownloadItem::COMPLETE, ids[1]);
  EXPECT_EQ(controller().GetPartialView().size(), 0u);
  // The partial view wasn't actually shown, so this bit is not updated.
  EXPECT_EQ(controller().last_primary_view_was_partial(),
            download::IsDownloadBubblePartialViewEnabled(profile()));

  // Partial view can now be shown, and contains all the items.
  task_environment_.FastForwardBy(base::Seconds(1));
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar3.pdf"),
                   download::DownloadItem::COMPLETE, ids[1]);
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 3u);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0u);
  }

  // Showing the main view even before time is up should still work.
  task_environment_.FastForwardBy(base::Seconds(14));
  EXPECT_EQ(controller().GetPartialView().size(), 0u);
  // The partial view wasn't actually shown, so this bit is not updated.
  EXPECT_EQ(controller().last_primary_view_was_partial(),
            download::IsDownloadBubblePartialViewEnabled(profile()));
  EXPECT_EQ(controller().GetMainView().size(), 3u);
  EXPECT_FALSE(controller().last_primary_view_was_partial());

  // Main view resets the partial view time, so the partial view can now be
  // shown.
  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar4.pdf"),
                   download::DownloadItem::IN_PROGRESS, ids[3]);
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 1u);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0u);
  }
}

// Tests that the partial view timer doesn't start if the partial view was
// empty and thus not shown.
TEST_F(DownloadBubbleUIControllerTest, EmptyPartialViewDoesNotPreventOpening) {
  EXPECT_EQ(controller().GetPartialView().size(), 0u);
  EXPECT_FALSE(controller().last_primary_view_was_partial());

  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
                   download::DownloadItem::COMPLETE, "Download");
  // Partial view is returned despite previous call to GetPartialView less than
  // 15 seconds ago.
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 1u);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0u);
  }
}

// Test that the preference suppresses the partial view.
TEST_F(DownloadBubbleUIControllerTest, PrefSuppressesPartialView) {
  download::SetDownloadBubblePartialViewEnabled(profile(), false);

  EXPECT_CALL(display_controller(), OnNewItem(true)).Times(1);
  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar2.pdf"),
                   download::DownloadItem::COMPLETE, "Download");

  EXPECT_EQ(controller().GetPartialView().size(), 0u);
  EXPECT_FALSE(controller().last_primary_view_was_partial());

  download::SetDownloadBubblePartialViewEnabled(profile(), true);
  if (download::IsDownloadBubblePartialViewEnabled(profile())) {
    EXPECT_EQ(controller().GetPartialView().size(), 1u);
    EXPECT_TRUE(controller().last_primary_view_was_partial());
  } else {
    EXPECT_EQ(controller().GetPartialView().size(), 0u);
  }
}

}  // namespace