chromium/ash/ambient/ui/ambient_animation_progress_tracker_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/ambient/ui/ambient_animation_progress_tracker.h"

#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "cc/paint/skottie_wrapper.h"
#include "cc/test/skia_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/size.h"
#include "ui/lottie/animation.h"

namespace ash {
namespace {

constexpr float kTimestampEpsilon = .001f;

using ::testing::AllOf;
using ::testing::Eq;
using ::testing::FloatEq;
using ::testing::Ge;
using ::testing::Le;
using ::testing::SizeIs;

class AmbientAnimationProgressTrackerTest : public ::testing::Test {
 protected:
  static constexpr gfx::Size kAnimationSize = gfx::Size(100, 100);

  AmbientAnimationProgressTrackerTest()
      : clock_(base::TimeTicks::Now()),
        canvas_(kAnimationSize, /*image_scale=*/1.f, /*is_opaque=*/false) {}

  scoped_refptr<cc::SkottieWrapper> CreateSkottie(int duration_secs) {
    return cc::CreateSkottie(kAnimationSize, duration_secs);
  }

  void Paint(lottie::Animation& animation) {
    animation.Paint(&canvas_, clock_, kAnimationSize);
  }

  base::TimeTicks clock_;
  gfx::Canvas canvas_;
  AmbientAnimationProgressTracker tracker_;
};

TEST_F(AmbientAnimationProgressTrackerTest, SingleAnimation) {
  AmbientAnimationProgressTracker tracker;
  lottie::Animation animation(CreateSkottie(/*duration_secs=*/10));
  tracker.RegisterAnimation(&animation);
  ASSERT_FALSE(tracker.HasActiveAnimations());
  animation.Start();
  Paint(animation);
  ASSERT_TRUE(tracker.HasActiveAnimations());
  AmbientAnimationProgressTracker::ImmutableParams immutable_params =
      tracker.GetImmutableParams();
  AmbientAnimationProgressTracker::Progress global_progress =
      tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));
  EXPECT_THAT(immutable_params.total_duration, Eq(base::Seconds(10)));
  ASSERT_THAT(immutable_params.scheduled_cycles, SizeIs(1));
  EXPECT_THAT(immutable_params.scheduled_cycles.front().start_offset,
              Eq(base::TimeDelta()));
  EXPECT_THAT(immutable_params.scheduled_cycles.front().end_offset,
              Eq(base::Seconds(10)));
  EXPECT_THAT(immutable_params.style, Eq(lottie::Animation::Style::kLoop));

  clock_ += base::Seconds(5);
  Paint(animation);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(.5f));

  clock_ += base::Seconds(5);
  Paint(animation);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));

  clock_ += base::Seconds(5);
  Paint(animation);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(.5f));
}

TEST_F(AmbientAnimationProgressTrackerTest, MultipleAnimations) {
  AmbientAnimationProgressTracker tracker;
  lottie::Animation animation_1(CreateSkottie(/*duration_secs=*/10));
  lottie::Animation animation_2(CreateSkottie(/*duration_secs=*/10));
  tracker.RegisterAnimation(&animation_1);
  tracker.RegisterAnimation(&animation_2);
  ASSERT_FALSE(tracker.HasActiveAnimations());
  animation_1.Start();
  animation_2.Start();
  Paint(animation_1);
  Paint(animation_2);
  ASSERT_TRUE(tracker.HasActiveAnimations());
  AmbientAnimationProgressTracker::ImmutableParams immutable_params =
      tracker.GetImmutableParams();
  AmbientAnimationProgressTracker::Progress global_progress =
      tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(0.f));
  EXPECT_THAT(immutable_params.total_duration, Eq(base::Seconds(10)));
  ASSERT_THAT(immutable_params.scheduled_cycles, SizeIs(1));
  EXPECT_THAT(immutable_params.scheduled_cycles.front().start_offset,
              Eq(base::TimeDelta()));
  EXPECT_THAT(immutable_params.scheduled_cycles.front().end_offset,
              Eq(base::Seconds(10)));
  EXPECT_THAT(immutable_params.style, Eq(lottie::Animation::Style::kLoop));

  clock_ += base::Seconds(5);
  Paint(animation_1);
  clock_ += base::Milliseconds(100);
  Paint(animation_2);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp,
              AllOf(Ge(.5f - kTimestampEpsilon), Le(.51f + kTimestampEpsilon)));

  clock_ += base::Seconds(5) - base::Milliseconds(100);
  Paint(animation_1);
  Paint(animation_2);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(.0f));

  clock_ += base::Seconds(2);
  Paint(animation_2);
  clock_ += base::Seconds(1);
  Paint(animation_1);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(1));
  EXPECT_THAT(global_progress.current_timestamp,
              AllOf(Ge(.2f - kTimestampEpsilon), Le(.3f + kTimestampEpsilon)));
}

TEST_F(AmbientAnimationProgressTrackerTest, AnimationsDestroyed) {
  AmbientAnimationProgressTracker tracker;
  auto animation_1 =
      std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
  auto animation_2 =
      std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
  tracker.RegisterAnimation(animation_1.get());
  tracker.RegisterAnimation(animation_2.get());
  animation_1->Start();
  animation_2->Start();
  Paint(*animation_1);
  Paint(*animation_2);

  clock_ += base::Seconds(5);
  Paint(*animation_1);
  clock_ += base::Milliseconds(100);
  Paint(*animation_2);

  animation_1.reset();
  AmbientAnimationProgressTracker::Progress global_progress =
      tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(.51f));

  animation_2.reset();
  EXPECT_FALSE(tracker.HasActiveAnimations());

  auto animation_3 =
      std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/20));
  auto animation_4 =
      std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/20));
  tracker.RegisterAnimation(animation_3.get());
  tracker.RegisterAnimation(animation_4.get());
  animation_3->Start();
  animation_4->Start();
  Paint(*animation_3);
  Paint(*animation_4);

  clock_ += base::Seconds(5);
  Paint(*animation_3);
  Paint(*animation_4);
  global_progress = tracker.GetGlobalProgress();
  EXPECT_THAT(global_progress.num_completed_cycles, Eq(0));
  EXPECT_THAT(global_progress.current_timestamp, FloatEq(.25f));
}

TEST_F(AmbientAnimationProgressTrackerTest, AnimationRestarted) {
  AmbientAnimationProgressTracker tracker;
  auto animation =
      std::make_unique<lottie::Animation>(CreateSkottie(/*duration_secs=*/10));
  tracker.RegisterAnimation(animation.get());
  animation->Start();
  Paint(*animation);

  clock_ += base::Seconds(5);
  Paint(*animation);

  ASSERT_TRUE(tracker.HasActiveAnimations());
  EXPECT_THAT(tracker.GetGlobalProgress().current_timestamp, FloatEq(0.5f));

  animation->Stop();
  EXPECT_FALSE(tracker.HasActiveAnimations());
  // Should be a no-op.
  tracker.RegisterAnimation(animation.get());

  animation->Start();
  Paint(*animation);

  ASSERT_TRUE(tracker.HasActiveAnimations());
  EXPECT_THAT(tracker.GetGlobalProgress().current_timestamp, FloatEq(0.f));
}

}  // namespace
}  // namespace ash