chromium/ash/system/holding_space/holding_space_animation_registry_unittest.cc

// Copyright 2022 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/system/holding_space/holding_space_animation_registry.h"

#include <array>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/holding_space/holding_space_client.h"
#include "ash/public/cpp/holding_space/holding_space_controller.h"
#include "ash/public/cpp/holding_space/holding_space_file.h"
#include "ash/public/cpp/holding_space/holding_space_image.h"
#include "ash/public/cpp/holding_space/holding_space_item.h"
#include "ash/public/cpp/holding_space/holding_space_model.h"
#include "ash/public/cpp/holding_space/holding_space_prefs.h"
#include "ash/public/cpp/holding_space/holding_space_util.h"
#include "ash/public/cpp/holding_space/mock_holding_space_client.h"
#include "ash/system/progress_indicator/progress_icon_animation.h"
#include "ash/system/progress_indicator/progress_indicator_animation.h"
#include "ash/system/progress_indicator/progress_indicator_animation_registry.h"
#include "ash/system/progress_indicator/progress_ring_animation.h"
#include "ash/test/ash_test_base.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/repeating_test_future.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace ash {
namespace {

constexpr char kTestUser[] = "user@test";

// HoldingSpaceAnimationRegistryTest -------------------------------------------

class HoldingSpaceAnimationRegistryTest : public AshTestBase {
 public:
  // AshTestBase:
  void SetUp() override {
    AshTestBase::SetUp();

    // Initialize holding space for `kTestUser`.
    AccountId user_account = AccountId::FromUserEmail(kTestUser);
    HoldingSpaceController::Get()->RegisterClientAndModelForUser(
        user_account, client(), model());
    GetSessionControllerClient()->AddUserSession(kTestUser);
    holding_space_prefs::MarkTimeOfFirstAvailability(
        GetSessionControllerClient()->GetUserPrefService(user_account));
  }

  HoldingSpaceItem* AddItem(
      HoldingSpaceItem::Type type,
      const base::FilePath& path,
      const HoldingSpaceProgress& progress = HoldingSpaceProgress()) {
    std::unique_ptr<HoldingSpaceItem> item =
        HoldingSpaceItem::CreateFileBackedItem(
            type,
            HoldingSpaceFile(
                path, HoldingSpaceFile::FileSystemType::kTest,
                GURL(base::StrCat({"filesystem:", path.BaseName().value()}))),
            progress,
            base::BindOnce(
                [](HoldingSpaceItem::Type type, const base::FilePath& path) {
                  return std::make_unique<HoldingSpaceImage>(
                      holding_space_util::GetMaxImageSizeForType(type), path,
                      /*async_bitmap_resolver=*/base::DoNothing());
                }));
    HoldingSpaceItem* item_ptr = item.get();
    model()->AddItem(std::move(item));
    return item_ptr;
  }

  void ExpectProgressIconAnimationExistsForKey(
      ProgressIndicatorAnimationRegistry::AnimationKey key,
      bool exists) {
    auto* animation = registry()->GetProgressIconAnimationForKey(key);
    EXPECT_EQ(!!animation, exists);
  }

  void ExpectProgressIconAnimationHasAnimatedForKey(
      ProgressIndicatorAnimationRegistry::AnimationKey key,
      bool has_animated) {
    auto* animation = registry()->GetProgressIconAnimationForKey(key);
    EXPECT_EQ(animation && animation->HasAnimated(), has_animated);
  }

  void ExpectProgressRingAnimationOfTypeForKey(
      ProgressIndicatorAnimationRegistry::AnimationKey key,
      const std::optional<ProgressRingAnimation::Type>& type) {
    auto* animation = registry()->GetProgressRingAnimationForKey(key);
    EXPECT_EQ(!!animation, type.has_value());
    if (animation && type.has_value())
      EXPECT_EQ(animation->type(), type.value());
  }

  void StartSession() {
    AccountId user_account = AccountId::FromUserEmail(kTestUser);
    GetSessionControllerClient()->SwitchActiveUser(user_account);
  }

  void EnableTrayIconPreviews() {
    AccountId account_id = AccountId::FromUserEmail(kTestUser);
    auto* prefs = GetSessionControllerClient()->GetUserPrefService(account_id);
    ASSERT_TRUE(prefs);
    holding_space_prefs::SetPreviewsEnabled(prefs, true);
  }

  HoldingSpaceController* controller() { return HoldingSpaceController::Get(); }

  testing::NiceMock<MockHoldingSpaceClient>* client() {
    return &holding_space_client_;
  }

  HoldingSpaceModel* model() { return &holding_space_model_; }

  HoldingSpaceAnimationRegistry* registry() {
    return HoldingSpaceAnimationRegistry::GetInstance();
  }

 private:
  testing::NiceMock<MockHoldingSpaceClient> holding_space_client_;
  HoldingSpaceModel holding_space_model_;
};

}  // namespace

// Tests -----------------------------------------------------------------------

TEST_F(HoldingSpaceAnimationRegistryTest, ProgressIndicatorAnimations) {
  using Type = ProgressRingAnimation::Type;

  StartSession();
  EnableTrayIconPreviews();

  const auto controller_key =
      ProgressIndicatorAnimationRegistry::AsAnimationKey(controller());

  // Verify initial animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, false);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, std::nullopt);

  // Add a completed item to the `model()`.
  HoldingSpaceItem* item_0 =
      AddItem(HoldingSpaceItem::Type::kDownload, base::FilePath("/tmp/0"));
  const auto item_0_key =
      ProgressIndicatorAnimationRegistry::AsAnimationKey(item_0);

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, false);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);

  // Add an indeterminately in-progress item to the `model()`.
  HoldingSpaceItem* item_1 =
      AddItem(HoldingSpaceItem::Type::kDownload, base::FilePath("/tmp/1"),
              HoldingSpaceProgress(0, std::nullopt));
  const auto item_1_key =
      ProgressIndicatorAnimationRegistry::AsAnimationKey(item_1);

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(controller_key, true);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, Type::kIndeterminate);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_1_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(item_1_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_1_key, Type::kIndeterminate);

  // Add a determinately in-progress item to the `model()`.
  HoldingSpaceItem* item_2 =
      AddItem(HoldingSpaceItem::Type::kDownload, base::FilePath("/tmp/2"),
              HoldingSpaceProgress(0, 10));
  const auto item_2_key =
      ProgressIndicatorAnimationRegistry::AsAnimationKey(item_2);

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(controller_key, true);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, Type::kIndeterminate);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_1_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(item_1_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_1_key, Type::kIndeterminate);
  ExpectProgressIconAnimationExistsForKey(item_2_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(item_2_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_2_key, std::nullopt);

  // Complete the first in-progress item.
  model()->UpdateItem(item_1->id())->SetProgress(HoldingSpaceProgress(10, 10));

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(controller_key, true);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_1_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_1_key, Type::kPulse);
  ExpectProgressIconAnimationExistsForKey(item_2_key, true);
  ExpectProgressIconAnimationHasAnimatedForKey(item_2_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_2_key, std::nullopt);

  // Complete the second in-progress item.
  model()->UpdateItem(item_2->id())->SetProgress(HoldingSpaceProgress(10, 10));

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, false);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, Type::kPulse);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_1_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_1_key, Type::kPulse);
  ExpectProgressIconAnimationExistsForKey(item_2_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_2_key, Type::kPulse);

  // Wait for `kPulse` animations to complete.
  base::test::RepeatingTestFuture<ProgressRingAnimation*> future;
  std::array<base::CallbackListSubscription, 3u> subscriptions = {
      registry()->AddProgressRingAnimationChangedCallbackForKey(
          controller_key, future.GetCallback()),
      registry()->AddProgressRingAnimationChangedCallbackForKey(
          item_1_key, future.GetCallback()),
      registry()->AddProgressRingAnimationChangedCallbackForKey(
          item_2_key, future.GetCallback())};
  for (size_t i = 0u; i < std::size(subscriptions); ++i)
    EXPECT_EQ(future.Take(), nullptr);

  // Verify animation `registry()` state.
  ExpectProgressIconAnimationExistsForKey(controller_key, false);
  ExpectProgressRingAnimationOfTypeForKey(controller_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_0_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_0_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_1_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_1_key, std::nullopt);
  ExpectProgressIconAnimationExistsForKey(item_2_key, false);
  ExpectProgressRingAnimationOfTypeForKey(item_2_key, std::nullopt);
}

}  // namespace ash