Index: ash/common/wm/overview/cleanup_animation_observer_unittest.cc |
diff --git a/ash/common/wm/overview/cleanup_animation_observer_unittest.cc b/ash/common/wm/overview/cleanup_animation_observer_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d2efc6c4f51b02fd6d86064f11d4d489fda1de97 |
--- /dev/null |
+++ b/ash/common/wm/overview/cleanup_animation_observer_unittest.cc |
@@ -0,0 +1,186 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <vector> |
+ |
+#include "ash/common/wm/overview/cleanup_animation_observer.h" |
+ |
+#include "ash/common/wm/overview/window_selector_delegate.h" |
+#include "ash/common/wm_lookup.h" |
+#include "ash/common/wm_window.h" |
+#include "ash/test/ash_test_base.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 TestWindowSelectorDelegate : public WindowSelectorDelegate { |
+ public: |
+ TestWindowSelectorDelegate() = default; |
+ |
+ ~TestWindowSelectorDelegate() 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(); |
+ } |
+ |
+ // WindowSelectorDelegate: |
+ void OnSelectionEnded() override {} |
+ |
+ void AddDelayedAnimationObserver( |
+ std::unique_ptr<DelayedAnimationObserver> animation_observer) override { |
+ animation_observer->SetOwner(this); |
+ observers_.push_back(std::move(animation_observer)); |
+ } |
+ |
+ void RemoveAndDestroyAnimationObserver( |
+ DelayedAnimationObserver* animation_observer) override { |
+ class IsEqual { |
+ public: |
+ explicit IsEqual(DelayedAnimationObserver* animation_observer) |
+ : animation_observer_(animation_observer) {} |
+ bool operator()(const std::unique_ptr<DelayedAnimationObserver>& other) { |
+ return (other.get() == animation_observer_); |
+ } |
+ |
+ private: |
+ const DelayedAnimationObserver* animation_observer_; |
+ }; |
+ observers_.erase(std::remove_if(observers_.begin(), observers_.end(), |
+ IsEqual(animation_observer)), |
+ observers_.end()); |
+ } |
+ |
+ private: |
+ std::vector<std::unique_ptr<DelayedAnimationObserver>> observers_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestWindowSelectorDelegate); |
+}; |
+ |
+class CleanupAnimationObserverTest : public test::AshTestBase, |
+ public views::WidgetObserver { |
+ public: |
+ CleanupAnimationObserverTest() = default; |
+ |
+ ~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) { |
+ std::unique_ptr<views::Widget> widget(new views::Widget); |
+ views::Widget::InitParams params; |
+ params.bounds = bounds; |
+ params.type = views::Widget::InitParams::TYPE_WINDOW; |
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
+ widget->Init(params); |
+ widget->Show(); |
+ ParentWindowInPrimaryRootWindow(widget->GetNativeWindow()); |
+ 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; |
+ } |
+ |
+ views::Widget* widget_ = nullptr; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(CleanupAnimationObserverTest); |
+}; |
+ |
+} // namespace |
+ |
+// Tests that basic create-destroy sequence does not crash. |
+TEST_F(CleanupAnimationObserverTest, CreateDestroy) { |
+ TestWindowSelectorDelegate delegate; |
+ std::unique_ptr<views::Widget> widget( |
+ CreateWindowWidget(gfx::Rect(0, 0, 40, 40))); |
+ std::unique_ptr<CleanupAnimationObserver> observer( |
+ new CleanupAnimationObserver(std::move(widget))); |
+ delegate.AddDelayedAnimationObserver(std::move(observer)); |
+} |
+ |
+// Tests that completing animation deletes the animation observer and the |
+// test widget and that deleting the WindowSelectorDelegate instance which |
+// owns the observer does not crash. |
+TEST_F(CleanupAnimationObserverTest, CreateAnimateComplete) { |
+ TestWindowSelectorDelegate delegate; |
+ std::unique_ptr<views::Widget> widget( |
+ CreateWindowWidget(gfx::Rect(0, 0, 40, 40))); |
+ WmWindow* widget_window = WmLookup::Get()->GetWindowForWidget(widget.get()); |
+ { |
+ ui::ScopedLayerAnimationSettings animation_settings( |
+ widget_window->GetLayer()->GetAnimator()); |
+ animation_settings.SetTransitionDuration( |
+ base::TimeDelta::FromMilliseconds(1000)); |
+ animation_settings.SetPreemptionStrategy( |
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
+ |
+ std::unique_ptr<CleanupAnimationObserver> observer( |
+ new CleanupAnimationObserver(std::move(widget))); |
+ animation_settings.AddObserver(observer.get()); |
+ delegate.AddDelayedAnimationObserver(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()); |
+ // TestWindowSelectorDelegate going out of scope should not crash. |
+} |
+ |
+// Tests that starting an animation and exiting doesn't crash. If not for |
+// TestWindowSelectorDelegate calling Shutdown() on a CleanupAnimationObserver |
+// instance in destructor, this test would have crashed. |
+TEST_F(CleanupAnimationObserverTest, CreateAnimateShutdown) { |
+ TestWindowSelectorDelegate delegate; |
+ std::unique_ptr<views::Widget> widget( |
+ CreateWindowWidget(gfx::Rect(0, 0, 40, 40))); |
+ WmWindow* widget_window = WmLookup::Get()->GetWindowForWidget(widget.get()); |
+ { |
+ // 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->GetLayer()->GetAnimator()); |
+ animation_settings.SetTransitionDuration( |
+ base::TimeDelta::FromMilliseconds(1000)); |
+ animation_settings.SetPreemptionStrategy( |
+ ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET); |
+ |
+ std::unique_ptr<CleanupAnimationObserver> observer( |
+ new CleanupAnimationObserver(std::move(widget))); |
+ animation_settings.AddObserver(observer.get()); |
+ delegate.AddDelayedAnimationObserver(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 |