chromium/ash/wm/overview/cleanup_animation_observer_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/wm/overview/cleanup_animation_observer.h"

#include <utility>
#include <vector>

#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/overview_delegate.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"

namespace ash {
namespace {

class TestOverviewDelegate : public OverviewDelegate {
 public:
  TestOverviewDelegate() = default;

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

  ~TestOverviewDelegate() override {
    // Destroy widgets that may be still animating if shell shuts down soon
    // after exiting overview mode.
    for (std::unique_ptr<DelayedAnimationObserver>& observer : observers_)
      observer->Shutdown();
  }

  // OverviewDelegate:
  void AddExitAnimationObserver(
      std::unique_ptr<DelayedAnimationObserver> animation_observer) override {
    animation_observer->SetOwner(this);
    observers_.push_back(std::move(animation_observer));
  }
  void RemoveAndDestroyExitAnimationObserver(
      DelayedAnimationObserver* animation_observer) override {
    std::erase_if(observers_, base::MatchesUniquePtr(animation_observer));
  }
  void AddEnterAnimationObserver(
      std::unique_ptr<DelayedAnimationObserver> animation_observer) override {}
  void RemoveAndDestroyEnterAnimationObserver(
      DelayedAnimationObserver* animation_observer) override {}

 private:
  std::vector<std::unique_ptr<DelayedAnimationObserver>> observers_;
};

class CleanupAnimationObserverTest : public AshTestBase,
                                     public views::WidgetObserver {
 public:
  CleanupAnimationObserverTest() = default;

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

  ~CleanupAnimationObserverTest() override {
    if (widget_)
      widget_->RemoveObserver(this);
  }

  // Creates a Widget containing a Window with the given |bounds|. This should
  // be used when the test requires a Widget. For example any test that will
  // cause a window to be closed via
  // views::Widget::GetWidgetForNativeView(window)->Close().
  std::unique_ptr<views::Widget> CreateWindowWidget(const gfx::Rect& bounds) {
    auto widget = std::make_unique<views::Widget>();
    views::Widget::InitParams params(
        views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
        views::Widget::InitParams::TYPE_WINDOW);
    params.bounds = bounds;
    params.context = GetContext();
    widget->Init(std::move(params));
    widget->Show();
    widget->AddObserver(this);
    widget_ = widget.get();
    return widget;
  }

 protected:
  bool widget_destroyed() { return !widget_; }

 private:
  void OnWidgetDestroyed(views::Widget* widget) override {
    if (widget_ == widget)
      widget_ = nullptr;
  }

  raw_ptr<views::Widget> widget_ = nullptr;
};

}  // namespace

// Tests that basic create-destroy sequence does not crash.
TEST_F(CleanupAnimationObserverTest, CreateDestroy) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  auto observer = std::make_unique<CleanupAnimationObserver>(std::move(widget));
  delegate.AddExitAnimationObserver(std::move(observer));
}

// Tests that completing animation deletes the animation observer and the
// test widget and that deleting the OverviewDelegate instance which
// owns the observer does not crash.
TEST_F(CleanupAnimationObserverTest, CreateAnimateComplete) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  aura::Window* widget_window = widget->GetNativeWindow();
  {
    ui::ScopedLayerAnimationSettings animation_settings(
        widget_window->layer()->GetAnimator());
    animation_settings.SetTransitionDuration(base::Milliseconds(1000));
    animation_settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

    auto observer =
        std::make_unique<CleanupAnimationObserver>(std::move(widget));
    animation_settings.AddObserver(observer.get());
    delegate.AddExitAnimationObserver(std::move(observer));

    widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
  }
  // The widget should be destroyed when |animation_settings| gets out of scope
  // which in absence of NON_ZERO_DURATION animation duration mode completes
  // the animation and calls OnImplicitAnimationsCompleted() on the cleanup
  // observer and auto-deletes the owned widget.
  EXPECT_TRUE(widget_destroyed());
  // TestOverviewDelegate going out of scope should not crash.
}

// Tests that starting an animation and exiting doesn't crash. If not for
// TestOverviewDelegate calling Shutdown() on a CleanupAnimationObserver
// instance in destructor, this test would have crashed.
TEST_F(CleanupAnimationObserverTest, CreateAnimateShutdown) {
  TestOverviewDelegate delegate;
  std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
  aura::Window* widget_window = widget->GetNativeWindow();
  {
    // Normal animations for tests have ZERO_DURATION, make sure we are actually
    // animating the movement.
    ui::ScopedAnimationDurationScaleMode animation_scale_mode(
        ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
    ui::ScopedLayerAnimationSettings animation_settings(
        widget_window->layer()->GetAnimator());
    animation_settings.SetTransitionDuration(base::Milliseconds(1000));
    animation_settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);

    auto observer =
        std::make_unique<CleanupAnimationObserver>(std::move(widget));
    animation_settings.AddObserver(observer.get());
    delegate.AddExitAnimationObserver(std::move(observer));

    widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
  }
  // The widget still exists.
  EXPECT_FALSE(widget_destroyed());
  // The test widget is auto-deleted when |delegate| that owns it goes out of
  // scope. The animation is still active when this happens which should not
  // crash.
}

}  // namespace ash