chromium/ui/views/layout/animating_layout_manager_unittest.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/layout/animating_layout_manager.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/scoped_observation.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/normalized_geometry.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"

namespace views {

namespace {

constexpr gfx::Size kChildViewSize{};

// Returns a size which is the intersection of |size| and the constraints
// provided by |bounds|, if any.
gfx::Size ConstrainSizeToBounds(const gfx::Size& size,
                                const SizeBounds& bounds) {}

// View that allows directly setting minimum size.
class TestView : public View {};

BEGIN_METADATA()

// Layout that provides a predictable target layout for an
// AnimatingLayoutManager.
class TestLayoutManager : public LayoutManagerBase {};

// Version of FillLayout that ignores invisible views.
class SmartFillLayout : public FillLayout {};

class AnimationEventLogger : public AnimatingLayoutManager::Observer {};

}  // anonymous namespace

// Test fixture which creates an AnimatingLayoutManager and instruments it so
// the animations can be directly controlled via gfx::AnimationContainerTestApi.
class AnimatingLayoutManagerTest : public testing::Test {};

const FlexSpecification AnimatingLayoutManagerTest::kDropOut =;

const FlexSpecification AnimatingLayoutManagerTest::kFlex =;

TEST_F(AnimatingLayoutManagerTest, SetLayoutManager_NoAnimation) {}

TEST_F(AnimatingLayoutManagerTest, ResetLayout_NoAnimation) {}

TEST_F(AnimatingLayoutManagerTest, HostInvalidate_TriggersAnimation) {}

TEST_F(AnimatingLayoutManagerTest,
       HostInvalidate_AnimateBounds_AnimationProgresses) {}

TEST_F(AnimatingLayoutManagerTest, HostInvalidate_NoAnimateBounds_NoAnimation) {}

TEST_F(AnimatingLayoutManagerTest,
       HostInvalidate_NoAnimateBounds_NewLayoutTriggersAnimation) {}

TEST_F(AnimatingLayoutManagerTest,
       HostInvalidate_NoAnimateBounds_AnimationProgresses) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_MiddleView_ScaleFromZero) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_MiddleView_ScaleFromMinimum) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_LeadingView_ScaleFromMinimum) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_TrailingView_ScaleFromMinimum_FadeIn) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_TrailingView_ScaleFromMinimum) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_TrailingView_ScaleFromMinimum_Vertical) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_Hide_HidesViewDuringAnimation) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_Hide_HidesViewDuringAnimation_OneFrame) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_Hide_AnimationResetDuringHide) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_SlideFromLeading_LastView) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_SlideFromLeading_Vertical) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_SlideFromLeading_MiddleView) {}

TEST_F(AnimatingLayoutManagerTest,
       FadeInOutMode_SlideFromLeading_LeadingView_SlidesFromTrailing) {}

TEST_F(AnimatingLayoutManagerTest, FadeInOutMode_SlideFromTrailing_MiddleView) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeOutOnVisibilitySet) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeInOnVisibilitySet) {}

// Regression test for issues: crbug.com/1021332, crbug.com/1003500
TEST_F(AnimatingLayoutManagerTest,
       FlexLayout_AnimateOutOnDescendentVisibilitySet) {}

// Regression test for issues: crbug.com/1021332, crbug.com/1003500
TEST_F(AnimatingLayoutManagerTest,
       FlexLayout_AnimateInOnDescendentVisibilitySet) {}

// Regression test for crbug.com/1037625: crash in SetViewVisibility() (1/2)
TEST_F(AnimatingLayoutManagerTest, FlexLayout_RemoveFadingViewDoesNotCrash) {}

// Regression test for crbug.com/1037625: crash in SetViewVisibility() (2/2)
TEST_F(AnimatingLayoutManagerTest, FlexLayout_RemoveShowingViewDoesNotCrash) {}

// Regression test for crbug.com/1037947 (1/2)
TEST_F(AnimatingLayoutManagerTest, FlexLayout_DoubleSlide) {}

// Regression test for crbug.com/1037947 (2/2) - Tests a case during sliding
// where if an animation is reversed after a fading-in and fading-out views have
// exchanged relative positions in the layout, the new fading-out view will
// slide behind the wrong view.
//
// Incorrect behavior (C slides behind B):
// [A]    [B]
// [A]C] [B]
// [A][C]B]
// [A][B[C]   <--- animation is reversed here
// [A] [B]C]
// [A]    [B]
//
// Correct behavior (C slides behind A):
// [A]    [B]
// [A]C] [B]
// [A][C]B]
// [A][B[C]   <--- animation is reversed here
// [A][C[B]
// [A]C] [B]
// [A]    [B]
//
TEST_F(AnimatingLayoutManagerTest, FlexLayout_RedirectAfterExchangePlaces) {}

// Regression test for issue crbug.com/1040173 (1/2):
// PostOrQueueAction does not delay an action after FadeIn is called.
TEST_F(AnimatingLayoutManagerTest,
       FlexLayout_PostDelayedActionAfterFadeIn_AnimateNewViewIn) {}

// Regression test for issue crbug.com/1040173 (2/2):
// PostOrQueueAction does not delay an action after FadeIn is called.
TEST_F(AnimatingLayoutManagerTest,
       FlexLayout_PostDelayedActionAfterFadeIn_SwapTwoViews) {}

// Regression test for issues crbug.com/1040618 and crbug.com/1040676:
// Views hidden due to layout constraints were not shown after a flex rule
// change and FadeIn() was called.
TEST_F(AnimatingLayoutManagerTest,
       FlexLayout_PostDelayedActionAfterFadeIn_FadeInHiddenView) {}

// Regression test for issue 1046393 (crash/use-after-free when removing view
// during animation).
TEST_F(AnimatingLayoutManagerTest, RemoveDuringAnimationDoesntCrash) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeInOnAdded) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeIn) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeOut) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeOut_NoCrashOnRemove) {}

TEST_F(AnimatingLayoutManagerTest, FlexLayout_FadeOut_IgnoreChildView) {}

// Test that when one view can flex to fill the space yielded by another view
// which is hidden, and that such a layout change triggers animation.
TEST_F(AnimatingLayoutManagerTest, FlexLayout_SlideAfterViewHidden) {}

// Test that when one view can flex to fill the space yielded by another view
// which is removed, and that such a layout change triggers animation.
TEST_F(AnimatingLayoutManagerTest, FlexLayout_SlideAfterViewRemoved) {}

// Test that when an animation starts and then the target changes mid-stream,
// the animation redirects.
TEST_F(AnimatingLayoutManagerTest, FlexLayout_RedirectAnimation) {}

// Test that when an animation starts and then the target changes near the end
// of the animation, the animation resets.
TEST_F(AnimatingLayoutManagerTest, FlexLayout_ResetAnimation) {}

TEST_F(AnimatingLayoutManagerTest, TestEvents) {}

TEST_F(AnimatingLayoutManagerTest, PostOrQueueAction) {}

TEST_F(AnimatingLayoutManagerTest, PostOrQueueAction_ContinueAnimation) {}

TEST_F(AnimatingLayoutManagerTest, PostOrQueueAction_NeverFinishes) {}

TEST_F(AnimatingLayoutManagerTest, PostOrQueueAction_MayPostImmediately) {}

TEST_F(AnimatingLayoutManagerTest, ZOrder_UnchangedWhenNotAnimating) {}

TEST_F(AnimatingLayoutManagerTest, ZOrder_UnchangedWhenNotFading) {}

TEST_F(AnimatingLayoutManagerTest, ZOrder_FadingOutViewMovedToBack) {}

TEST_F(AnimatingLayoutManagerTest, ZOrder_FadingInViewMovedToBack) {}

TEST_F(AnimatingLayoutManagerTest, ConstrainedSpace_StopsAnimation) {}

TEST_F(AnimatingLayoutManagerTest, ConstrainedSpace_TriggersDelayedAction) {}

TEST_F(AnimatingLayoutManagerTest, ConstrainedSpace_SubsequentAnimation) {}

// Test which explores an Animating Layout Manager's behavior in an
// environment where rich animation is not allowed.
class AnimatingLayoutManagerNoAnimationsTest
    : public AnimatingLayoutManagerTest {};

const std::vector<std::pair<AnimatingLayoutManager::FadeInOutMode, const char*>>
    AnimatingLayoutManagerNoAnimationsTest::kFadeModes =;

TEST_F(AnimatingLayoutManagerNoAnimationsTest, ResetNoAnimation) {}

TEST_F(AnimatingLayoutManagerNoAnimationsTest, ChangeLayoutNoAnimation) {}

TEST_F(AnimatingLayoutManagerNoAnimationsTest, HideShowViewNoAnimation) {}

TEST_F(AnimatingLayoutManagerNoAnimationsTest, FadeViewInOutNoAnimation) {}

TEST_F(AnimatingLayoutManagerNoAnimationsTest, ActionsPostedAfterLayout) {}

namespace {

constexpr base::TimeDelta kMinimumAnimationTime =;

// Layout manager which immediately lays out its child views when it is
// invalidated.
class ImmediateLayoutManager : public LayoutManagerBase {};

// Allows an AnimatingLayoutManager to be observed so that we can wait for an
// animation to complete in real time. Call WaitForAnimationToComplete() to
// pause execution until an animation (if any) is completed.
class AnimationWatcher : public AnimatingLayoutManager::Observer {};

}  // anonymous namespace

// Test which explores an animating layout manager's response to available size
// changes.
class AnimatingLayoutManagerRootViewTest : public AnimatingLayoutManagerTest {};

// Available Size Tests --------------------------------------------------------

class AnimatingLayoutManagerAvailableSizeTest
    : public AnimatingLayoutManagerRootViewTest {};

TEST_F(AnimatingLayoutManagerAvailableSizeTest, AvailableSize_LimitsExpansion) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_RestartsAnimation) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_RestartsAnimation_Vertical) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_RedirectsAnimation) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest, AvailableSize_StopsAnimation) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest, AvailableSize_ImmediateResize) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest, AvailableSize_StepDownStepUp) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_ConstraintRemovedStartsAnimation) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_LimitsExpansion_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_RestartsAnimation_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_RedirectsAnimation_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_StopsAnimation_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_ImmediateResize_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_StepDownStepUp_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AvailableSize_ConstraintRemovedStartsAnimation_WithFlex) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Horizontal_MainAxisAnimates) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Vertical_MainAxisAnimates) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Horizontal_CrossAxisSizeChangeResetsLayout) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Vertical_CrossAxisSizeChangeResetsLayout) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Horizontal_CrossAxisAlignmentWorks) {}

TEST_F(AnimatingLayoutManagerAvailableSizeTest,
       AnimateMainAxis_Vertical_CrossAxisAlignmentWorks) {}

// Flex Rule Tests -------------------------------------------------------------

class AnimatingLayoutManagerFlexRuleTest : public AnimatingLayoutManagerTest {};

const FlexSpecification
    AnimatingLayoutManagerFlexRuleTest::kScaleToMinimumSnapToZero =;

TEST_F(AnimatingLayoutManagerFlexRuleTest, ReturnsPreferredSize) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest,
       VerticalBounded_ReturnsPreferredSize) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest,
       VerticalBounded_ReturnsHeightForWidth) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, HorizontalBounded_FlexToSize) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, HorizontalBounded_DropOut) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, VerticalBounded_FlexToSize) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, VerticalBounded_DropOut) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, HorizontalDoubleBounded_DropOut) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, VerticalDoubleBounded_DropOut) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, HorizontalMinimumSize) {}

TEST_F(AnimatingLayoutManagerFlexRuleTest, VerticalMinimumSize) {}

// Animating Layout in Flex Layout ---------------------------------------------

class AnimatingLayoutManagerInFlexLayoutTest
    : public AnimatingLayoutManagerRootViewTest {};

TEST_F(AnimatingLayoutManagerInFlexLayoutTest, NoAnimation) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       AnimateFullyWithinAvailableSpace) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest, NoAnimationRestart) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest, GrowWithinConstrainedSpace) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       GrowWithinConstrainedSpace_NoAnimationRestart) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       GrowWithinConstrainedSpace_AnimationRedirected) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       GrowWithinConstrainedSpace_AnimationInterrupted) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       ShrinkWithinConstrainedSpace_AnimationProceeds) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       ShrinkWithinConstrainedSpace_ExpandedSpaceHasNoEffect) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       ShrinkWithinConstrainedSpace_SnapToFinalLayout) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       ShrinkWithinConstrainedSpace_SnapToConstrainedLayout) {}

TEST_F(AnimatingLayoutManagerInFlexLayoutTest,
       ShrinkWithinConstrainedSpace_NoRestartOnLargerPreferredSize) {}

// Test without animation.
class AnimatingLayoutManagerInFlexLayoutNoAnimationTest
    : public AnimatingLayoutManagerInFlexLayoutTest {};

// Regression test for crbug.com/1311708
// This test will fail without the fix made to
// AnimatingLayoutManager::GetPreferredSize().
TEST_F(AnimatingLayoutManagerInFlexLayoutNoAnimationTest, ShrinkAndGrow) {}

// Realtime Tests --------------------------------------------------------------

// Test fixture for testing animations in realtime. Provides a parent view with
// an ImmediateLayoutManager so that when animation frames are triggered, the
// host view is laid out immediately. Animation durations are kept short to
// prevent tests from taking too long.
class AnimatingLayoutManagerRealtimeTest
    : public AnimatingLayoutManagerRootViewTest {};

TEST_F(AnimatingLayoutManagerRealtimeTest, TestAnimateSlide) {}

TEST_F(AnimatingLayoutManagerRealtimeTest, TestAnimateStretch) {}

TEST_F(AnimatingLayoutManagerRealtimeTest, TestConstrainedSpaceStopsAnimation) {}

TEST_F(AnimatingLayoutManagerRealtimeTest, TestConstrainedSpaceDoesNotRestart) {}

TEST_F(AnimatingLayoutManagerRealtimeTest,
       TestConstrainedSpaceRestartedAnimationSucceeds) {}

// TODO(dfried): figure out why these tests absolutely do not animate properly
// on Mac. Whatever magic makes the compositor animation runner go doesn't seem
// to want to work on Mac in non-browsertests :(
#if !BUILDFLAG(IS_MAC)

// Test fixture for testing sequences of the following four actions:
// * animating layout manager configured on host view
// * host view added to parent view
// * parent view added to widget
// * child view added to host view
//
// The result will either be an animation or no animation, but both will have
// the same final layout. We will not test all possible sequences, but a
// representative sample based on what sequences of actions we are (a) likely to
// see and (b) hit most possible code paths.
class AnimatingLayoutManagerSequenceTest : public ViewsTestBase {};

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddChild_AddToParent_Configure_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddChild_Configure_AddToParent_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       Configure_AddChild_AddToParent_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       Configure_AddToParent_AddChild_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddToParent_Configure_AddChild_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddToParent_AddChild_Configure_AddToWidget) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddToWidget_AddToParent_Configure_AddChild) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddToParent_AddToWidget_Configure_AddChild) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       Configure_AddToParent_AddToWidget_AddChild) {}

TEST_F(AnimatingLayoutManagerSequenceTest,
       AddToWidget_AddToParent_AddChild_Configure) {}

#endif  // !BUILDFLAG(IS_MAC)

}  // namespace views