blob: 7052e2578efe987dfc873540489cf79722fe4683 [file] [log] [blame]
// Copyright (c) 2019 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 <memory>
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/animation/animation_container.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/layout/animating_layout_manager.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/test/test_views.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
// This test suite simulates the kind of nested layouts we find in e.g. the
// toolbar without actually pulling in browser code. It's designed to test
// interactions between multiple levels of Flex and Animating layouts, in a
// situation that resembles how they are actually used.
//
// The test cases are designed to probe edge cases and interactions that are
// difficult to simulate in either the FlexLayout or AnimatingLayoutManager unit
// tests. They are not browser tests however, and uses TaskEnvironment and
// AnimationContainerTestApi to step animations rather than running them in
// realtime. This makes these tests as quick as unit tests, so they do not incur
// the costs associated with browser tests.
//
// This suite is part of views_unittests.
namespace views {
namespace {
constexpr base::TimeDelta kDefaultAnimationDuration =
base::TimeDelta::FromSeconds(1);
constexpr int kIconDimension = 20;
constexpr gfx::Size kIconSize(kIconDimension, kIconDimension);
constexpr int kLabelWidth = 70;
constexpr gfx::Size kLabelSize(kLabelWidth, kIconDimension);
constexpr int kBarMinimumWidth = 70;
constexpr int kBarPreferredWidth = 200;
constexpr gfx::Size kBarMinimumSize(kBarMinimumWidth, kIconDimension);
constexpr gfx::Size kBarPreferredSize(kBarPreferredWidth, kIconDimension);
constexpr gfx::Size kDefaultToolbarSize(400, kIconDimension);
// Base class for elements in the toolbar that animate; a stand-in for e.g.
// ToolbarIconContainer.
class SimulatedToolbarElement : public View {
public:
AnimatingLayoutManager* layout() {
return static_cast<AnimatingLayoutManager*>(GetLayoutManager());
}
const AnimatingLayoutManager* layout() const {
return static_cast<const AnimatingLayoutManager*>(GetLayoutManager());
}
protected:
SimulatedToolbarElement() {
auto* const animating_layout =
SetLayoutManager(std::make_unique<AnimatingLayoutManager>());
animating_layout
->SetBoundsAnimationMode(
AnimatingLayoutManager::BoundsAnimationMode::kAnimateMainAxis)
.SetOrientation(LayoutOrientation::kHorizontal)
.SetAnimationDuration(kDefaultAnimationDuration);
animating_layout->SetTargetLayoutManager(std::make_unique<FlexLayout>())
->SetOrientation(LayoutOrientation::kHorizontal);
}
void SetAnimationDuration(base::TimeDelta animation_duration) {
layout()->SetAnimationDuration(animation_duration);
}
FlexLayout* target_layout() {
return static_cast<FlexLayout*>(layout()->target_layout_manager());
}
};
// Simulates an avatar button on the Chrome toolbar, with a fixed-size icon and
// a label that can animate in and out.
class SimulatedAvatarButton : public SimulatedToolbarElement {
public:
SimulatedAvatarButton() {
AddChildView(std::make_unique<StaticSizedView>(kIconSize));
auto* const status =
AddChildView(std::make_unique<StaticSizedView>(kLabelSize));
status->SetVisible(false);
layout()->SetDefaultFadeMode(
AnimatingLayoutManager::FadeInOutMode::kScaleFromZero);
}
~SimulatedAvatarButton() override = default;
void FadeLabelIn() {
layout()->FadeIn(label());
showing_label_ = true;
}
void FadeLabelOut() {
layout()->FadeOut(label());
showing_label_ = false;
}
// Verifies that the label appears (or does not appear) directly to the right
// of the avatar icon, and fills available remaining space in this view. If
// the view is not animating, ensures that the label appears (or does not
// appear) at the exact size and position it should.
void EnsureLayout() const {
EXPECT_EQ(gfx::Rect(gfx::Point(), kIconSize), icon()->bounds());
if (layout()->is_animating()) {
if (label()->GetVisible()) {
EXPECT_EQ(0, label()->y());
EXPECT_EQ(kIconDimension, label()->height());
// TODO(dfried): eliminate potential for rounding error here. Currently
// it is possible for the left side of the label to round up a pixel
// when it is shrinking down due to how interpolation works, so we need
// to account for that in determining if the current state is valid.
EXPECT_GE(label()->x(), kIconDimension);
EXPECT_LE(label()->x(), kIconDimension + 1);
EXPECT_EQ(label()->width(), width() - label()->x());
}
} else if (showing_label_) {
EXPECT_TRUE(label()->GetVisible());
EXPECT_EQ(gfx::Rect(gfx::Point(kIconDimension, 0), kLabelSize),
label()->bounds());
} else {
EXPECT_FALSE(label()->GetVisible());
}
}
private:
// views::View:
const char* GetClassName() const override { return "SimulatedAvatarButton"; }
View* icon() { return children()[0]; }
const View* icon() const { return children()[0]; }
View* label() { return children()[1]; }
const View* label() const { return children()[1]; }
bool showing_label_ = false;
};
// Simulates extensions buttons in the new toolbar extensions view, with a fixed
// button on the right and buttons to the left that can animate in and out and
// be hidden if there is insufficient space.
class SimulatedExtensionsContainer : public SimulatedToolbarElement {
public:
SimulatedExtensionsContainer() {
auto* const main_button =
AddChildView(std::make_unique<StaticSizedView>(kIconSize));
main_button->SetProperty(kFlexBehaviorKey, FlexSpecification());
layout()->SetDefaultFadeMode(
AnimatingLayoutManager::FadeInOutMode::kSlideFromTrailingEdge);
target_layout()->SetDefault(
kFlexBehaviorKey,
FlexSpecification(LayoutOrientation::kHorizontal,
MinimumFlexSizeRule::kPreferredSnapToZero));
}
~SimulatedExtensionsContainer() override = default;
void AddIcons(std::vector<bool> visibility) {
int insertion_point = children().size() - 1;
for (bool visible : visibility)
AddIconAt(insertion_point++, visible);
}
void AddIconAt(int position, bool initially_visible) {
DCHECK_GE(position, 0);
DCHECK_LT(position, int{children().size()});
auto new_child = std::make_unique<StaticSizedView>(kIconSize);
new_child->SetVisible(initially_visible);
if (initially_visible)
visible_views_.insert(new_child.get());
AddChildViewAt(std::move(new_child), position);
}
void RemoveIconAt(int position) {
DCHECK_GE(position, 0);
DCHECK_LT(position, int{children().size()} - 1);
visible_views_.erase(RemoveChildViewT<View>(children()[position]).get());
}
void SetIconVisibility(int position, bool visible) {
DCHECK_GE(position, 0);
DCHECK_LT(position, int{children().size()} - 1);
auto* const button = children()[position];
if (visible) {
layout()->FadeIn(button);
visible_views_.insert(button);
} else {
layout()->FadeOut(button);
visible_views_.erase(button);
}
}
void MoveIcon(int from, int to) {
DCHECK_GE(from, 0);
DCHECK_GE(to, 0);
DCHECK_NE(from, to);
DCHECK_LT(from, int{children().size()} - 1);
DCHECK_LT(to, int{children().size()} - 1);
ReorderChildView(children()[from], to);
}
// Ensures that the extension icons appear with the size and placement and
// visibility expected, and that the final "extensions menu" button always
// appears and is flush with the right edge of the view:
//
// |[pinned][pinned][pinned][menu]| (unpinned extensions not visible)
//
// While animating, icons need only be the correct size and in the correct
// region of the view, and visible if they are going to be visible in the
// final layout (icons which are fading out may also be visible).
//
// If |expected_num_icons| is specified:
// - while animating, serves as a lower bound on the number of icons displayed
// - while not animating, must match the number of visible icons exactly
void EnsureLayout(base::Optional<int> expected_num_icons) const {
if (layout()->is_animating()) {
// For animating layouts, we ensure that icons are the correct size and
// appear between the left edge of the container and exactly overlapping
// the "extensions menu" icon (the final icon in the container).
const int available_width = width() - kIconDimension;
DCHECK_GE(available_width, 0);
int num_visible = 0;
for (int i = 0; i < int{children().size()} - 1; ++i) {
const View* const child = children()[i];
if (child->GetVisible()) {
++num_visible;
EXPECT_GE(child->x(), 0) << " icon " << i;
EXPECT_LE(child->x(), available_width) << " icon " << i;
EXPECT_EQ(kIconSize, child->size()) << " icon " << i;
}
}
if (expected_num_icons.has_value())
EXPECT_GE(num_visible, expected_num_icons.value());
} else {
// Calculate how many icons *should* be visible given the available space.
SizeBounds available_size = parent()->GetAvailableSize(this);
int num_visible = visible_views_.size();
if (available_size.width().is_bounded()) {
num_visible = std::min(
num_visible,
(available_size.width().value() - kIconDimension) / kIconDimension);
}
DCHECK_LT(num_visible, int{children().size()});
if (expected_num_icons.has_value())
EXPECT_EQ(expected_num_icons.value(), num_visible);
// Verify that the correct icons are visible and are in the correct place
// with the correct size.
int x = 0;
for (int i = 0; i < int{children().size()} - 1; ++i) {
const View* const child = children()[i];
if (base::Contains(visible_views_, child)) {
if (num_visible > 0) {
--num_visible;
EXPECT_TRUE(child->GetVisible()) << " icon " << i;
EXPECT_EQ(gfx::Rect(gfx::Point(x, 0), kIconSize), child->bounds())
<< " icon " << i;
x += kIconDimension;
} else {
// This is a pinned extension that overflowed the available space
// and therefore should be hidden.
EXPECT_FALSE(child->GetVisible())
<< " icon " << i
<< " should have been hidden; available size is "
<< available_size.ToString();
}
} else {
// This icon is explicitly hidden.
EXPECT_FALSE(child->GetVisible()) << " icon " << i;
}
}
}
EXPECT_TRUE(main_button()->GetVisible());
EXPECT_EQ(kIconSize, main_button()->size());
EXPECT_EQ(width(), main_button()->bounds().right());
}
private:
// views::View:
const char* GetClassName() const override {
return "SimulatedExtensionsContainer";
}
const View* main_button() const { return children()[children().size() - 1]; }
std::set<const View*> visible_views_;
};
// Simulates a toolbar with buttons on either side, a "location bar", and mock
// versions of the extensions container and avatar button.
class SimulatedToolbar : public View {
public:
SimulatedToolbar() {
AddChildView(std::make_unique<StaticSizedView>(kIconSize));
auto* const bar =
AddChildView(std::make_unique<StaticSizedView>(kBarPreferredSize));
bar->set_minimum_size(kBarMinimumSize);
extensions_ =
AddChildView(std::make_unique<SimulatedExtensionsContainer>());
avatar_ = AddChildView(std::make_unique<SimulatedAvatarButton>());
AddChildView(std::make_unique<StaticSizedView>(kIconSize));
SetLayoutManager(std::make_unique<FlexLayout>())
->SetOrientation(LayoutOrientation::kHorizontal);
avatar_->SetProperty(
kFlexBehaviorKey,
FlexSpecification(avatar_->layout()->GetDefaultFlexRule())
.WithOrder(1));
bar->SetProperty(kFlexBehaviorKey,
FlexSpecification(LayoutOrientation::kHorizontal,
MinimumFlexSizeRule::kScaleToMinimum,
MaximumFlexSizeRule::kUnbounded)
.WithOrder(2));
extensions_->SetProperty(
kFlexBehaviorKey,
FlexSpecification(extensions_->layout()->GetDefaultFlexRule())
.WithOrder(3));
}
SimulatedExtensionsContainer* extensions() { return extensions_; }
const SimulatedExtensionsContainer* extensions() const { return extensions_; }
SimulatedAvatarButton* avatar() { return avatar_; }
const SimulatedAvatarButton* avatar() const { return avatar_; }
View* location() { return children()[1]; }
const View* location() const { return children()[1]; }
// Ensures the layout of the toolbar which contains:
// - one dummy back/forward/home type button of fixed size
// - a location bar with flexible width
// - a simulated extensions container
// - a simulated avatar button
// - one dummy "wrench menu" type button of fixed size
//
// All child views must be laid out end-to-end adjacent to each other, in the
// right order, and at the correct size. Furthermore, EnsureLayout() is called
// on the child views that support it.
//
// The parameter |expected_num_extension_icons| is passed to
// SimulatedExtensionsContainer::EnsureLayout().
void EnsureLayout(base::Optional<int> expected_num_extension_icons) const {
EXPECT_EQ(kIconDimension, height());
EXPECT_EQ(gfx::Rect(gfx::Point(), kIconSize), children()[0]->bounds());
EXPECT_EQ(gfx::Point(kIconDimension, 0), location()->origin());
EXPECT_EQ(kIconDimension, location()->height());
EXPECT_GE(location()->width(), kBarMinimumWidth);
EXPECT_EQ(gfx::Point(location()->bounds().right(), 0),
extensions_->origin());
extensions_->EnsureLayout(expected_num_extension_icons);
EXPECT_EQ(gfx::Point(extensions_->bounds().right(), 0), avatar_->origin());
avatar_->EnsureLayout();
EXPECT_EQ(gfx::Rect(gfx::Point(avatar_->bounds().right(), 0), kIconSize),
children()[4]->bounds());
if (location()->width() == kBarMinimumWidth)
EXPECT_LE(width(), children()[4]->bounds().right());
else
EXPECT_EQ(width(), children()[4]->bounds().right());
}
private:
// views::View:
const char* GetClassName() const override { return "SimulatedToolbar"; }
SimulatedExtensionsContainer* extensions_;
SimulatedAvatarButton* avatar_;
};
} // anonymous namespace
// Test suite. Sets up the mock toolbar and ties animation of all child elements
// together so they can be controlled for testing. Use the utility methods here
// as much as possible rather than calling methods on the individual Views.
class CompositeLayoutTest : public testing::Test {
public:
void SetUp() override {
toolbar_ = std::make_unique<SimulatedToolbar>();
toolbar_->SetSize(kDefaultToolbarSize);
extensions_test_api_ = std::make_unique<gfx::AnimationContainerTestApi>(
extensions()->layout()->GetAnimationContainerForTesting());
avatar_test_api_ = std::make_unique<gfx::AnimationContainerTestApi>(
avatar()->layout()->GetAnimationContainerForTesting());
}
SimulatedAvatarButton* avatar() { return toolbar_->avatar(); }
const SimulatedAvatarButton* avatar() const { return toolbar_->avatar(); }
SimulatedExtensionsContainer* extensions() { return toolbar_->extensions(); }
const SimulatedExtensionsContainer* extensions() const {
return toolbar_->extensions();
}
SimulatedToolbar* toolbar() { return toolbar_.get(); }
const SimulatedToolbar* toolbar() const { return toolbar_.get(); }
void SetWidth(int width) {
toolbar()->SetSize(gfx::Size(width, kIconDimension));
}
void ChangeWidth(int delta) {
toolbar()->SetSize(
gfx::Size(std::max(0, toolbar()->width() + delta), kIconDimension));
}
void AdvanceAnimations(int ms) {
const auto delta = base::TimeDelta::FromMilliseconds(ms);
if (avatar()->layout()->is_animating())
avatar_test_api_->IncrementTime(delta);
if (extensions()->layout()->is_animating())
extensions_test_api_->IncrementTime(delta);
toolbar_->Layout();
}
void ResetAnimation() {
avatar()->layout()->ResetLayout();
extensions()->layout()->ResetLayout();
toolbar()->Layout();
}
bool IsAnimating() const {
return avatar()->layout()->is_animating() ||
extensions()->layout()->is_animating();
}
void FinishAnimations() {
// Advance the animation an unreasonable amount of times and fail if that
// doesn't actually cause the animation to complete. It's possible that one
// animation could lead to another so basing our limit only on the current
// animation durations is not necessarily reliable.
for (int i = 0; i < 100; ++i) {
if (!IsAnimating())
return;
// Advance by a small but significant step (1/10 of a second).
AdvanceAnimations(100);
}
GTEST_FAIL()
<< "Animations did not complete in a reasonable amount of time.";
}
// Ensures the toolbar layout and all child view layouts are as expected.
// If |expected_num_extension_icons| is specified, then exactly that many (or
// at minimum that many if animating) simulated extension icons must be
// visible.
void EnsureLayout(
base::Optional<int> expected_num_extension_icons = base::nullopt) {
toolbar_->EnsureLayout(expected_num_extension_icons);
}
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<gfx::AnimationContainerTestApi> extensions_test_api_;
std::unique_ptr<gfx::AnimationContainerTestApi> avatar_test_api_;
std::unique_ptr<SimulatedToolbar> toolbar_;
};
// ------------
// Basic tests.
TEST_F(CompositeLayoutTest, InitialLayout) {
EnsureLayout();
}
TEST_F(CompositeLayoutTest, SmallResize) {
toolbar()->SetSize(gfx::Size(300, kIconDimension));
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ShrinkLocationBar) {
toolbar()->SetSize(
gfx::Size(4 * kIconDimension + kBarMinimumWidth, kIconDimension));
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ShrinkLocationBarTooSmall) {
toolbar()->SetSize(
gfx::Size(4 * kIconDimension + kBarMinimumWidth - 20, kIconDimension));
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ProfileAnimates) {
avatar()->FadeLabelIn();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
avatar()->FadeLabelOut();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ProfileAnimationInterrupted) {
avatar()->FadeLabelIn();
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(500);
EnsureLayout();
avatar()->FadeLabelOut();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ProfileAnimationInterruptedImmediately) {
avatar()->FadeLabelIn();
avatar()->FadeLabelOut();
EXPECT_FALSE(IsAnimating());
EnsureLayout();
}
// ----------------------------------------------------------
// Tests which add/remove extension icons from the container.
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnAdd) {
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
extensions()->AddIconAt(2, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnAddMultiple) {
extensions()->AddIconAt(0, true);
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->AddIconAt(1, true);
extensions()->AddIconAt(3, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnAddMultipleStaggered) {
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(200);
EnsureLayout();
EXPECT_TRUE(IsAnimating());
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->AddIconAt(1, true);
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(200);
EnsureLayout();
EXPECT_TRUE(IsAnimating());
extensions()->AddIconAt(3, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionRemovedDuringAnimation) {
extensions()->AddIconAt(0, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->AddIconAt(0, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->AddIconAt(2, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->RemoveIconAt(1);
AdvanceAnimations(200);
EnsureLayout();
extensions()->AddIconAt(0, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->RemoveIconAt(2);
AdvanceAnimations(200);
EnsureLayout();
extensions()->RemoveIconAt(0);
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionRemovedImmediately) {
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->AddIconAt(1, true);
extensions()->RemoveIconAt(1);
EXPECT_FALSE(IsAnimating());
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionRemovedImmediatelyDuringAnimation) {
extensions()->AddIconAt(0, true);
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(500);
EnsureLayout();
extensions()->AddIconAt(1, true);
extensions()->RemoveIconAt(1);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
// -----------------------------------------------
// Tests which show/hide existing extension icons.
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnShow) {
extensions()->AddIcons({false, true, false});
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
extensions()->AddIconAt(2, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnShowMultiple) {
extensions()->AddIcons({true, false, true, false});
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(1, true);
extensions()->SetIconVisibility(3, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionsAnimateOnShowMultipleStaggered) {
extensions()->AddIcons({false, false, true, false});
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(200);
EnsureLayout();
EXPECT_TRUE(IsAnimating());
extensions()->SetIconVisibility(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(1, true);
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(200);
EnsureLayout();
EXPECT_TRUE(IsAnimating());
extensions()->SetIconVisibility(3, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionHiddenDuringAnimation) {
extensions()->AddIcons({false, false, true, false});
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(1, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(3, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(2, false);
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(0, true);
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(3, false);
AdvanceAnimations(200);
EnsureLayout();
extensions()->SetIconVisibility(0, false);
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionHiddenImmediately) {
extensions()->AddIcons({true, false});
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(1, true);
extensions()->SetIconVisibility(1, false);
EXPECT_FALSE(IsAnimating());
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionHiddenImmediatelyDuringAnimation) {
extensions()->AddIcons({true, false});
EXPECT_TRUE(IsAnimating());
AdvanceAnimations(500);
EnsureLayout();
extensions()->SetIconVisibility(1, true);
extensions()->SetIconVisibility(1, false);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
// ----------------------------------
// Tests where child views are moved.
TEST_F(CompositeLayoutTest, ExtensionOrderChanged) {
extensions()->AddIcons({true, true, true});
FinishAnimations();
EnsureLayout();
extensions()->MoveIcon(1, 2);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionHiddenAndPoppedOutImmediate) {
extensions()->AddIcons({true, true, true});
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(1, false);
extensions()->MoveIcon(1, 2);
extensions()->SetIconVisibility(2, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionHiddenAndPoppedOutDelayed) {
extensions()->AddIcons({true, true, true});
FinishAnimations();
EnsureLayout();
extensions()->SetIconVisibility(1, false);
extensions()->MoveIcon(1, 2);
AdvanceAnimations(500);
extensions()->SetIconVisibility(2, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionPinned) {
extensions()->AddIcons({true, true, false, false});
FinishAnimations();
EnsureLayout();
extensions()->MoveIcon(3, 0);
extensions()->SetIconVisibility(0, true);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, VisibleExtensionMoved) {
extensions()->AddIcons({true, true, false, false});
FinishAnimations();
EnsureLayout();
extensions()->MoveIcon(1, 0);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
}
TEST_F(CompositeLayoutTest, InvisibleExtensionMoved) {
extensions()->AddIcons({true, true, false, false});
FinishAnimations();
EnsureLayout();
extensions()->MoveIcon(2, 3);
EXPECT_FALSE(IsAnimating());
EnsureLayout();
}
// -----------------------------------------------
// Tests combining two different animating views.
TEST_F(CompositeLayoutTest, ExtensionsAndAvatarAnimateSimultaneously) {
// Expand both views.
extensions()->AddIcons({true, true});
avatar()->FadeLabelIn();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EXPECT_FALSE(IsAnimating());
EnsureLayout();
// Expand one, contract the other.
extensions()->AddIcons({true});
avatar()->FadeLabelOut();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout();
EXPECT_FALSE(IsAnimating());
// Then vice-versa.
extensions()->SetIconVisibility(1, false);
avatar()->FadeLabelIn();
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EXPECT_FALSE(IsAnimating());
EnsureLayout();
}
TEST_F(CompositeLayoutTest, ExtensionsAndAvatarAnimateStaggered) {
// Expand both views.
extensions()->AddIcons({true, true});
AdvanceAnimations(500);
EnsureLayout();
avatar()->FadeLabelIn();
FinishAnimations();
EnsureLayout();
// Expand one, contract the other.
extensions()->AddIcons({true});
AdvanceAnimations(500);
EnsureLayout();
avatar()->FadeLabelOut();
FinishAnimations();
EnsureLayout();
// Then vice-versa.
extensions()->SetIconVisibility(1, false);
AdvanceAnimations(500);
avatar()->FadeLabelIn();
FinishAnimations();
EnsureLayout();
}
// -----------------------
// Tests in limited space.
TEST_F(CompositeLayoutTest, ExtensionsNotShownWhenSpaceConstrained) {
// At the minimum size for the toolbar, no icons should be displayed.
toolbar()->SizeToPreferredSize();
extensions()->AddIcons({true, true});
EXPECT_FALSE(IsAnimating());
EnsureLayout(0);
// Increase the size gradually, exposing pinned icons.
// We don't really care if the individual icons animate out or not; as long as
// the correct number are displayed at each step.
ChangeWidth(kIconDimension / 2);
EXPECT_FALSE(IsAnimating());
EnsureLayout(0);
ChangeWidth(kIconDimension / 2);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout(1);
ChangeWidth(kIconDimension / 2);
EXPECT_FALSE(IsAnimating());
EnsureLayout(1);
ChangeWidth(kIconDimension / 2);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout(2);
}
TEST_F(CompositeLayoutTest, SomeExtensionsNotShownWhenSpaceConstrained) {
// Provide room for one of two icons.
SetWidth(toolbar()->GetPreferredSize().width() + kIconDimension);
extensions()->AddIcons({true, true});
FinishAnimations();
EnsureLayout(1);
// Increase the size gradually, exposing pinned icons.
// We don't really care if the individual icons animate out or not; as long as
// the correct number are displayed at each step.
ChangeWidth(kIconDimension / 2);
EXPECT_FALSE(IsAnimating());
EnsureLayout(1);
ChangeWidth(kIconDimension / 2);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout(2);
}
TEST_F(CompositeLayoutTest, ExtensionsShownSnapsWhenSpaceShrinks) {
// Provide room for both icons.
SetWidth(toolbar()->GetPreferredSize().width() + 2 * kIconDimension);
extensions()->AddIcons({true, true});
FinishAnimations();
EnsureLayout(2);
ChangeWidth(-kIconDimension);
EXPECT_FALSE(IsAnimating());
EnsureLayout(1);
ChangeWidth(-kIconDimension);
EXPECT_FALSE(IsAnimating());
EnsureLayout(0);
}
TEST_F(CompositeLayoutTest,
ExtensionsShowingAnimationRedirectsDueToSmallerAvailableSpace) {
// Provide room for both icons.
SetWidth(toolbar()->GetPreferredSize().width() + 2 * kIconDimension);
extensions()->AddIcons({true, true});
AdvanceAnimations(400);
// The icons are fading in, but not enough that the animation would be reset
// by changing the toolbar width by one icon width.
ChangeWidth(-kIconDimension);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout(1);
}
TEST_F(CompositeLayoutTest,
ExtensionsShowingAnimationCancelsDueToSmallerAvailableSpace) {
// Provide room for both icons.
SetWidth(toolbar()->GetPreferredSize().width() + 2 * kIconDimension);
extensions()->AddIcons({true, true});
AdvanceAnimations(800);
// The icons are fading in, far enough that the animation is reset by changing
// the toolbar width by one icon width.
ChangeWidth(-kIconDimension);
EXPECT_FALSE(IsAnimating());
EnsureLayout(1);
}
TEST_F(CompositeLayoutTest,
ExtensionsShowingAnimationRedirectsDueToLargerAvailableSpace) {
// Provide room for one of two icons.
SetWidth(toolbar()->GetPreferredSize().width() + kIconDimension);
extensions()->AddIcons({true, true});
AdvanceAnimations(400);
// Make room for the second icon; the animation should continue.
ChangeWidth(kIconDimension);
EXPECT_TRUE(IsAnimating());
FinishAnimations();
EnsureLayout(2);
}
TEST_F(CompositeLayoutTest, ExtensionsHiddenWhenAvatarExpands) {
extensions()->AddIcons({true, true, true, true, true});
ResetAnimation();
EnsureLayout(5);
toolbar()->SizeToPreferredSize();
EXPECT_FALSE(IsAnimating());
avatar()->FadeLabelIn();
// Halfway through, the label will have displaced 35 pixels, or two icons.
AdvanceAnimations(500);
EnsureLayout(3);
// At its largest, 70 pixels and four icons.
FinishAnimations();
EnsureLayout(1);
}
TEST_F(CompositeLayoutTest, ExtensionsShownWhenAvatarCollapses) {
extensions()->AddIcons({true});
avatar()->FadeLabelIn();
ResetAnimation();
toolbar()->SizeToPreferredSize();
// These should all be hidden.
extensions()->AddIcons({true, true, true, true});
EnsureLayout(1);
avatar()->FadeLabelOut();
// Halfway through, the label will cede back 35 pixels - enough to display an
// additional icon.
AdvanceAnimations(500);
EnsureLayout(2);
// Finish everything - this will include icons revealed at the very end. Since
// 70 pixels total are ceded back, three of the four newly-added icons can be
// shown.
FinishAnimations();
EnsureLayout(4);
}
TEST_F(CompositeLayoutTest, ExtensionsHideAndShowWhenAvatarAnimates) {
extensions()->AddIcons({true, true, true, true, true});
ResetAnimation();
EnsureLayout(5);
toolbar()->SizeToPreferredSize();
EXPECT_FALSE(IsAnimating());
avatar()->FadeLabelIn();
// Halfway through, the label will have displaced 35 pixels, or two icons.
AdvanceAnimations(500);
EnsureLayout(3);
// Interrupt most of the way through.
AdvanceAnimations(200);
EnsureLayout(2);
// Fade the label out and make sure all of the extensions reappeared.
avatar()->FadeLabelOut();
FinishAnimations();
EnsureLayout(5);
}
TEST_F(CompositeLayoutTest, ExtensionsShowAndHideWhenAvatarAnimates) {
extensions()->AddIcons({true});
avatar()->FadeLabelIn();
ResetAnimation();
toolbar()->SizeToPreferredSize();
// These should all be hidden.
extensions()->AddIcons({true, true, true, true});
ResetAnimation();
// Halfway through, the label will have ceded 35 pixels, or one icon.
avatar()->FadeLabelOut();
AdvanceAnimations(500);
EnsureLayout(2);
// Interrupt most of the way through.
AdvanceAnimations(200);
EnsureLayout(3);
// Fade the label back in and make sure all of the extensions re-hide.
avatar()->FadeLabelIn();
FinishAnimations();
EnsureLayout(1);
}
TEST_F(CompositeLayoutTest, MultipleAnimationAndLayoutChanges) {
extensions()->AddIcons({true});
avatar()->FadeLabelIn();
ResetAnimation();
toolbar()->SizeToPreferredSize();
// These should all be hidden.
extensions()->AddIcons({true, true, true});
ResetAnimation();
// Halfway through, the label will have ceded 35 pixels, or one icon.
avatar()->FadeLabelOut();
AdvanceAnimations(500);
EnsureLayout(2);
// Interrupt most of the way through to add a random icon.
AdvanceAnimations(100);
extensions()->AddIconAt(2, true);
AdvanceAnimations(100);
EnsureLayout(3);
extensions()->SetIconVisibility(1, false);
extensions()->SetIconVisibility(3, false);
FinishAnimations();
EnsureLayout(3);
// Fade the label back in and make sure all of the extensions re-hide.
avatar()->FadeLabelIn();
FinishAnimations();
EnsureLayout(1);
}
} // namespace views