chromium/ash/shelf/shelf_background_animator_unittest.cc

// Copyright 2016 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/shelf/shelf_background_animator.h"

#include <memory>

#include "ash/animation/animation_change_type.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_background_animator_observer.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/animation/slide_animation.h"

namespace ash {
namespace {

// A valid color value that is distinct from any final animation state values.
// Used to check if color values are changed during animations.
const SkColor kDummyColor = SK_ColorBLUE;

// Observer that caches color values for the last observation. This observer
// will also call a set callback when ShelfBackgroundAnimator completes an
// animation.
class TestShelfBackgroundObserver : public ShelfBackgroundAnimatorObserver {
 public:
  TestShelfBackgroundObserver() = default;

  TestShelfBackgroundObserver(const TestShelfBackgroundObserver&) = delete;
  TestShelfBackgroundObserver& operator=(const TestShelfBackgroundObserver&) =
      delete;

  ~TestShelfBackgroundObserver() override = default;

  SkColor background_color() const { return background_color_; }

  // Convenience function to get the alpha value from |background_color_|.
  int GetBackgroundAlpha() const;

  // Sets |animation_complete_callback_| to be called when animation ends.
  void SetAnimationCompleteCallback(base::OnceClosure callback);

  // ShelfBackgroundObserver:
  void UpdateShelfBackground(SkColor color) override;
  void OnShelfBackgroundAnimationEnded() override;

 private:
  int background_color_ = SK_ColorTRANSPARENT;

  base::OnceClosure animation_complete_callback_;
};

int TestShelfBackgroundObserver::GetBackgroundAlpha() const {
  return SkColorGetA(background_color_);
}

void TestShelfBackgroundObserver::UpdateShelfBackground(SkColor color) {
  background_color_ = color;
}

void TestShelfBackgroundObserver::OnShelfBackgroundAnimationEnded() {
  if (!animation_complete_callback_.is_null())
    std::move(animation_complete_callback_).Run();
}

void TestShelfBackgroundObserver::SetAnimationCompleteCallback(
    base::OnceClosure callback) {
  animation_complete_callback_ = std::move(callback);
}

}  // namespace

// Provides internal access to a ShelfBackgroundAnimator instance.
class ShelfBackgroundAnimatorTestApi {
 public:
  explicit ShelfBackgroundAnimatorTestApi(ShelfBackgroundAnimator* animator)
      : animator_(animator) {}

  ShelfBackgroundAnimatorTestApi(const ShelfBackgroundAnimatorTestApi&) =
      delete;
  ShelfBackgroundAnimatorTestApi& operator=(
      const ShelfBackgroundAnimatorTestApi&) = delete;

  ~ShelfBackgroundAnimatorTestApi() = default;

  ShelfBackgroundType previous_background_type() const {
    return animator_->previous_background_type_;
  }

  gfx::SlideAnimation* animator() const { return animator_->animator_.get(); }

  SkColor shelf_background_target_color() const {
    return animator_->shelf_background_values_.target_color();
  }

 private:
  // The instance to provide internal access to.
  raw_ptr<ShelfBackgroundAnimator, DanglingUntriaged> animator_;
};

class ShelfBackgroundAnimatorTest : public AshTestBase {
 public:
  ShelfBackgroundAnimatorTest() = default;

  ShelfBackgroundAnimatorTest(const ShelfBackgroundAnimatorTest&) = delete;
  ShelfBackgroundAnimatorTest& operator=(const ShelfBackgroundAnimatorTest&) =
      delete;

  ~ShelfBackgroundAnimatorTest() override = default;

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

 protected:
  // Convenience wrapper for |animator_|'s PaintBackground().
  void PaintBackground(
      ShelfBackgroundType background_type,
      AnimationChangeType change_type = AnimationChangeType::IMMEDIATE);

  // Set all of the color values for the |observer_|.
  void SetColorValuesOnObserver(SkColor color);

  // Waits for animation to complete.
  void WaitForAnimationCompletion();

  TestShelfBackgroundObserver observer_;

  // Test target.
  raw_ptr<ShelfBackgroundAnimator, DanglingUntriaged> animator_;

  // Provides internal access to |animator_|.
  std::unique_ptr<ShelfBackgroundAnimatorTestApi> test_api_;
};

void ShelfBackgroundAnimatorTest::SetUp() {
  AshTestBase::SetUp();

  animator_ =
      GetPrimaryShelf()->shelf_widget()->background_animator_for_testing();
  animator_->AddObserver(&observer_);

  test_api_ = std::make_unique<ShelfBackgroundAnimatorTestApi>(animator_);
}

void ShelfBackgroundAnimatorTest::PaintBackground(
    ShelfBackgroundType background_type,
    AnimationChangeType change_type) {
  animator_->PaintBackground(background_type, change_type);
}

void ShelfBackgroundAnimatorTest::SetColorValuesOnObserver(SkColor color) {
  observer_.UpdateShelfBackground(color);
}

void ShelfBackgroundAnimatorTest::WaitForAnimationCompletion() {
  base::RunLoop run_loop;

  observer_.SetAnimationCompleteCallback(run_loop.QuitWhenIdleClosure());

  run_loop.Run();
}

// Verify the |previous_background_type_| and |target_background_type_| values
// when animating to the same target type multiple times.
TEST_F(ShelfBackgroundAnimatorTest, BackgroundTypesWhenAnimatingToSameTarget) {
  PaintBackground(ShelfBackgroundType::kMaximized);
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            animator_->target_background_type());

  PaintBackground(ShelfBackgroundType::kDefaultBg);
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            animator_->target_background_type());
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            test_api_->previous_background_type());

  PaintBackground(ShelfBackgroundType::kDefaultBg);
  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            animator_->target_background_type());
  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            test_api_->previous_background_type());
}

// Verify subsequent calls to PaintBackground() using the
// AnimationChangeType::ANIMATE change type are ignored.
TEST_F(ShelfBackgroundAnimatorTest,
       MultipleAnimateCallsToSameTargetAreIgnored) {
  PaintBackground(ShelfBackgroundType::kMaximized);
  SetColorValuesOnObserver(kDummyColor);

  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  animator_->PaintBackground(ShelfBackgroundType::kDefaultBg,
                             AnimationChangeType::ANIMATE);
  WaitForAnimationCompletion();

  EXPECT_NE(observer_.background_color(), kDummyColor);

  SetColorValuesOnObserver(kDummyColor);
  animator_->PaintBackground(ShelfBackgroundType::kDefaultBg,
                             AnimationChangeType::ANIMATE);

  EXPECT_EQ(observer_.background_color(), kDummyColor);
}

// Verify observers are updated with the current values when they are added.
TEST_F(ShelfBackgroundAnimatorTest, ObserversUpdatedWhenAdded) {
  animator_->RemoveObserver(&observer_);
  SetColorValuesOnObserver(kDummyColor);

  animator_->AddObserver(&observer_);

  EXPECT_NE(observer_.background_color(), kDummyColor);
}

// Verify the alpha values for the ShelfBackgroundType::kDefaultBg state.
TEST_F(ShelfBackgroundAnimatorTest, DefaultBackground) {
  PaintBackground(ShelfBackgroundType::kDefaultBg);

  EXPECT_EQ(ShelfBackgroundType::kDefaultBg,
            animator_->target_background_type());
  EXPECT_EQ((int)SkColorGetA(ShelfConfig::Get()->GetDefaultShelfColor(
                GetPrimaryShelf()->shelf_widget())),
            observer_.GetBackgroundAlpha());
}

// Verify the alpha values for the ShelfBackgroundType::kMaximized state.
TEST_F(ShelfBackgroundAnimatorTest, MaximizedBackground) {
  PaintBackground(ShelfBackgroundType::kMaximized);

  EXPECT_EQ(ShelfBackgroundType::kMaximized,
            animator_->target_background_type());
  EXPECT_EQ((int)SkColorGetA(ShelfConfig::Get()->GetMaximizedShelfColor(
                GetPrimaryShelf()->shelf_widget())),
            observer_.GetBackgroundAlpha());
}

TEST_F(ShelfBackgroundAnimatorTest,
       AnimatorIsDetroyedWhenCompletingSuccessfully) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  PaintBackground(ShelfBackgroundType::kMaximized,
                  AnimationChangeType::ANIMATE);
  EXPECT_TRUE(test_api_->animator());
  WaitForAnimationCompletion();

  EXPECT_FALSE(test_api_->animator());
}

TEST_F(ShelfBackgroundAnimatorTest,
       AnimatorDestroyedWhenChangingBackgroundImmediately) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
  PaintBackground(ShelfBackgroundType::kMaximized,
                  AnimationChangeType::ANIMATE);
  EXPECT_TRUE(test_api_->animator());

  PaintBackground(ShelfBackgroundType::kDefaultBg,
                  AnimationChangeType::IMMEDIATE);
  EXPECT_FALSE(test_api_->animator());
}

// Verify that existing animator is used when animating to the previous state.
TEST_F(ShelfBackgroundAnimatorTest,
       ExistingAnimatorIsReusedWhenAnimatingToPreviousState) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  // First PaintBackground() must be immediate so that the
  // ShelfBackgroundAnimator has its color set correctly.
  PaintBackground(ShelfBackgroundType::kDefaultBg,
                  AnimationChangeType::IMMEDIATE);
  PaintBackground(ShelfBackgroundType::kMaximized,
                  AnimationChangeType::ANIMATE);

  const gfx::SlideAnimation* animator = test_api_->animator();
  EXPECT_TRUE(animator);

  PaintBackground(ShelfBackgroundType::kDefaultBg,
                  AnimationChangeType::ANIMATE);

  EXPECT_EQ(animator, test_api_->animator());
}

// Verify that existing animator is not re-used when the target background isn't
// the same as the previous background.
TEST_F(ShelfBackgroundAnimatorTest,
       ExistingAnimatorNotReusedWhenTargetBackgroundNotPreviousBackground) {
  ui::ScopedAnimationDurationScaleMode test_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);

  PaintBackground(ShelfBackgroundType::kHomeLauncher,
                  AnimationChangeType::ANIMATE);

  const gfx::SlideAnimation* animator = test_api_->animator();
  EXPECT_TRUE(animator);

  EXPECT_NE(ShelfBackgroundType::kMaximized,
            test_api_->previous_background_type());
  PaintBackground(ShelfBackgroundType::kMaximized,
                  AnimationChangeType::ANIMATE);

  EXPECT_NE(animator, test_api_->animator());
}

// Verify observers are always notified, even when alpha values don't change.
TEST_F(ShelfBackgroundAnimatorTest,
       ObserversAreNotifiedWhenSnappingToSameTargetBackground) {
  PaintBackground(ShelfBackgroundType::kDefaultBg);
  SetColorValuesOnObserver(kDummyColor);
  PaintBackground(ShelfBackgroundType::kDefaultBg);

  EXPECT_NE(observer_.background_color(), kDummyColor);
}

class ShelfBackgroundTargetColorTest : public NoSessionAshTestBase {
 public:
  ShelfBackgroundTargetColorTest() = default;

  ShelfBackgroundTargetColorTest(const ShelfBackgroundTargetColorTest&) =
      delete;
  ShelfBackgroundTargetColorTest& operator=(
      const ShelfBackgroundTargetColorTest&) = delete;

  ~ShelfBackgroundTargetColorTest() override = default;

 protected:
  // Helper function to notify session state changes.
  void NotifySessionStateChanged(session_manager::SessionState state) {
    GetSessionControllerClient()->SetSessionState(state);
  }
};

// Verify the target color of the shelf background is updated based on session
// state, starting from LOGIN_PRIMARY.
TEST_F(ShelfBackgroundTargetColorTest, ShelfBackgroundColorUpdatedFromLogin) {
  ShelfBackgroundAnimatorTestApi test_api(
      Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
          ->shelf_widget()
          ->background_animator_for_testing());

  NotifySessionStateChanged(session_manager::SessionState::LOGIN_PRIMARY);
  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);

  SimulateUserLogin("[email protected]");

  NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
  EXPECT_EQ(test_api.shelf_background_target_color(),
            ShelfConfig::Get()->GetDefaultShelfColor(
                GetPrimaryShelf()->shelf_widget()));
}

// Verify the target color of the shelf background is updated based on session
// state, starting from OOBE.
TEST_F(ShelfBackgroundTargetColorTest, ShelfBackgroundColorUpdatedFromOOBE) {
  ShelfBackgroundAnimatorTestApi test_api(
      Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())
          ->shelf_widget()
          ->background_animator_for_testing());

  NotifySessionStateChanged(session_manager::SessionState::OOBE);
  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);

  SimulateUserLogin("[email protected]");

  NotifySessionStateChanged(
      session_manager::SessionState::LOGGED_IN_NOT_ACTIVE);
  EXPECT_EQ(test_api.shelf_background_target_color(), SK_ColorTRANSPARENT);

  NotifySessionStateChanged(session_manager::SessionState::ACTIVE);
  EXPECT_EQ(test_api.shelf_background_target_color(),
            ShelfConfig::Get()->GetDefaultShelfColor(
                GetPrimaryShelf()->shelf_widget()));
}

}  // namespace ash