// 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/layout_manager_base.h"

#include <algorithm>

#include "base/memory/raw_ptr.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/geometry/rect.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"

namespace views {

namespace {

constexpr int kChildViewPadding = 5;
constexpr gfx::Size kMinimumSize(40, 50);
constexpr gfx::Size kPreferredSize(100, 90);
constexpr gfx::Size kSquarishSize(10, 11);
constexpr gfx::Size kLongSize(20, 8);
constexpr gfx::Size kTallSize(4, 22);
constexpr gfx::Size kLargeSize(30, 28);

// Dummy class that minimally implements LayoutManagerBase for basic
// functionality testing.
class TestLayoutManagerBase : public LayoutManagerBase {
 public:
  std::vector<const View*> GetIncludedChildViews() const {
    std::vector<const View*> included;
    std::ranges::copy_if(host_view()->children(), std::back_inserter(included),
                         [=, this](const View* child) {
                           return IsChildIncludedInLayout(child);
                         });
    return included;
  }

  void OverrideProposedLayout(const ProposedLayout& forced_layout) {
    forced_layout_ = forced_layout;
    InvalidateHost(true);
  }

  // LayoutManagerBase:
  ProposedLayout CalculateProposedLayout(
      const SizeBounds& size_bounds) const override {
    if (forced_layout_) {
      return *forced_layout_;
    }

    ProposedLayout layout;
    layout.host_size.set_width(std::clamp<SizeBound>(size_bounds.width(),
                                                     kMinimumSize.width(),
                                                     kPreferredSize.width())
                                   .value());
    layout.host_size.set_height(std::clamp<SizeBound>(size_bounds.height(),
                                                      kMinimumSize.height(),
                                                      kPreferredSize.height())
                                    .value());
    return layout;
  }

  void LayoutImpl() override {
    ++layout_count_;
    LayoutManagerBase::LayoutImpl();
  }

  size_t layout_count() const { return layout_count_; }

 private:
  // If specified, will always return this layout.
  std::optional<ProposedLayout> forced_layout_;

  size_t layout_count_ = 0;
};

// This layout layout lays out included child views in the upper-left of the
// host view with kChildViewPadding around them. Views that will not fit are
// made invisible. Child views are expected to overlap as they all have the
// same top-left corner.
class MockLayoutManagerBase : public LayoutManagerBase {
 public:
  using LayoutManagerBase::AddOwnedLayout;
  using LayoutManagerBase::InvalidateHost;

  int num_invalidations() const { return num_invalidations_; }
  int num_layouts_generated() const { return num_layouts_generated_; }

  // LayoutManagerBase:
  ProposedLayout CalculateProposedLayout(
      const SizeBounds& size_bounds) const override {
    ProposedLayout layout;
    layout.host_size = {kChildViewPadding, kChildViewPadding};
    for (views::View* it : host_view()->children()) {
      if (!IsChildIncludedInLayout(it)) {
        continue;
      }
      const gfx::Size preferred_size = it->GetPreferredSize({});
      bool visible = false;
      gfx::Rect bounds;
      const int required_width = preferred_size.width() + 2 * kChildViewPadding;
      const int required_height =
          preferred_size.height() + 2 * kChildViewPadding;
      if ((required_width <= size_bounds.width()) &&
          (required_height <= size_bounds.height())) {
        visible = true;
        bounds = gfx::Rect(kChildViewPadding, kChildViewPadding,
                           preferred_size.width(), preferred_size.height());
        layout.host_size.set_width(std::max(
            layout.host_size.width(), bounds.right() + kChildViewPadding));
        layout.host_size.set_height(std::max(
            layout.host_size.height(), bounds.bottom() + kChildViewPadding));
      }
      layout.child_layouts.push_back({it, visible, bounds});
    }
    ++num_layouts_generated_;
    return layout;
  }

  void OnLayoutChanged() override {
    LayoutManagerBase::OnLayoutChanged();
    ++num_invalidations_;
  }

  using LayoutManagerBase::ApplyLayout;

 private:
  mutable int num_layouts_generated_ = 0;
  mutable int num_invalidations_ = 0;
};

void ExpectSameViews(const std::vector<const View*>& expected,
                     const std::vector<const View*>& actual) {
  EXPECT_EQ(expected.size(), actual.size());
  for (size_t i = 0; i < expected.size(); ++i) {
    EXPECT_EQ(expected[i], actual[i]);
  }
}

}  // namespace

TEST(LayoutManagerBaseTest, GetMinimumSize) {
  TestLayoutManagerBase layout;
  EXPECT_EQ(kMinimumSize, layout.GetMinimumSize(nullptr));
}

TEST(LayoutManagerBaseTest, GetPreferredSize) {
  TestLayoutManagerBase layout;
  EXPECT_EQ(kPreferredSize, layout.GetPreferredSize(nullptr));
}

TEST(LayoutManagerBaseTest, GetPreferredHeightForWidth) {
  constexpr int kWidth = 45;
  TestLayoutManagerBase layout;
  EXPECT_EQ(kPreferredSize.height(),
            layout.GetPreferredHeightForWidth(nullptr, kWidth));
}

TEST(LayoutManagerBaseTest, Installed) {
  auto layout_ptr = std::make_unique<TestLayoutManagerBase>();
  LayoutManagerBase* layout = layout_ptr.get();
  EXPECT_EQ(nullptr, layout->host_view());

  View view;
  view.SetLayoutManager(std::move(layout_ptr));
  EXPECT_EQ(&view, layout->host_view());
}

TEST(LayoutManagerBaseTest, SetChildIncludedInLayout) {
  View view;
  View* const child1 = view.AddChildView(std::make_unique<View>());
  View* const child2 = view.AddChildView(std::make_unique<View>());
  View* const child3 = view.AddChildView(std::make_unique<View>());

  auto layout_ptr = std::make_unique<TestLayoutManagerBase>();
  TestLayoutManagerBase* layout = layout_ptr.get();
  view.SetLayoutManager(std::move(layout_ptr));

  // All views should be present.
  ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());

  // Remove one.
  child2->SetProperty(kViewIgnoredByLayoutKey, true);
  ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());

  // Remove another.
  child1->SetProperty(kViewIgnoredByLayoutKey, true);
  ExpectSameViews({child3}, layout->GetIncludedChildViews());

  // Removing it again should have no effect.
  child1->SetProperty(kViewIgnoredByLayoutKey, true);
  ExpectSameViews({child3}, layout->GetIncludedChildViews());

  // Add one back.
  child1->SetProperty(kViewIgnoredByLayoutKey, false);
  ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());

  // Adding it back again should have no effect.
  child1->SetProperty(kViewIgnoredByLayoutKey, false);
  ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());

  // Add the other view back.
  child2->SetProperty(kViewIgnoredByLayoutKey, false);
  ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());
}

TEST(LayoutManagerBaseTest, InvalidateHost_NotInstalled) {
  MockLayoutManagerBase root_layout;
  MockLayoutManagerBase* const child1 =
      root_layout.AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
  MockLayoutManagerBase* const child2 =
      root_layout.AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
  MockLayoutManagerBase* const grandchild =
      child1->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());

  root_layout.InvalidateHost(false);
  EXPECT_EQ(0, root_layout.num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  child1->InvalidateHost(false);
  EXPECT_EQ(0, root_layout.num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  child2->InvalidateHost(false);
  EXPECT_EQ(0, root_layout.num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  grandchild->InvalidateHost(false);
  EXPECT_EQ(0, root_layout.num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  root_layout.InvalidateHost(true);
  EXPECT_EQ(1, root_layout.num_invalidations());
  EXPECT_EQ(1, child1->num_invalidations());
  EXPECT_EQ(1, child2->num_invalidations());
  EXPECT_EQ(1, grandchild->num_invalidations());

  child1->InvalidateHost(true);
  EXPECT_EQ(2, root_layout.num_invalidations());
  EXPECT_EQ(2, child1->num_invalidations());
  EXPECT_EQ(2, child2->num_invalidations());
  EXPECT_EQ(2, grandchild->num_invalidations());

  child2->InvalidateHost(true);
  EXPECT_EQ(3, root_layout.num_invalidations());
  EXPECT_EQ(3, child1->num_invalidations());
  EXPECT_EQ(3, child2->num_invalidations());
  EXPECT_EQ(3, grandchild->num_invalidations());

  grandchild->InvalidateHost(true);
  EXPECT_EQ(4, root_layout.num_invalidations());
  EXPECT_EQ(4, child1->num_invalidations());
  EXPECT_EQ(4, child2->num_invalidations());
  EXPECT_EQ(4, grandchild->num_invalidations());
}

TEST(LayoutManagerBaseTest, InvalidateHost_Installed) {
  View view;
  MockLayoutManagerBase* const root_layout =
      view.SetLayoutManager(std::make_unique<MockLayoutManagerBase>());
  MockLayoutManagerBase* const child1 =
      root_layout->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
  MockLayoutManagerBase* const child2 =
      root_layout->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());
  MockLayoutManagerBase* const grandchild =
      child1->AddOwnedLayout(std::make_unique<MockLayoutManagerBase>());

  root_layout->InvalidateHost(false);
  EXPECT_EQ(0, root_layout->num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  child1->InvalidateHost(false);
  EXPECT_EQ(0, root_layout->num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  child2->InvalidateHost(false);
  EXPECT_EQ(0, root_layout->num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  grandchild->InvalidateHost(false);
  EXPECT_EQ(0, root_layout->num_invalidations());
  EXPECT_EQ(0, child1->num_invalidations());
  EXPECT_EQ(0, child2->num_invalidations());
  EXPECT_EQ(0, grandchild->num_invalidations());

  root_layout->InvalidateHost(true);
  EXPECT_EQ(1, root_layout->num_invalidations());
  EXPECT_EQ(1, child1->num_invalidations());
  EXPECT_EQ(1, child2->num_invalidations());
  EXPECT_EQ(1, grandchild->num_invalidations());

  child1->InvalidateHost(true);
  EXPECT_EQ(2, root_layout->num_invalidations());
  EXPECT_EQ(2, child1->num_invalidations());
  EXPECT_EQ(2, child2->num_invalidations());
  EXPECT_EQ(2, grandchild->num_invalidations());

  child2->InvalidateHost(true);
  EXPECT_EQ(3, root_layout->num_invalidations());
  EXPECT_EQ(3, child1->num_invalidations());
  EXPECT_EQ(3, child2->num_invalidations());
  EXPECT_EQ(3, grandchild->num_invalidations());

  grandchild->InvalidateHost(true);
  EXPECT_EQ(4, root_layout->num_invalidations());
  EXPECT_EQ(4, child1->num_invalidations());
  EXPECT_EQ(4, child2->num_invalidations());
  EXPECT_EQ(4, grandchild->num_invalidations());
}

// Test LayoutManager functionality of LayoutManagerBase:

namespace {

// Base for tests that evaluate the LayoutManager functionality of
// LayoutManagerBase (rather than the LayoutManagerBase-specific behavior).
class LayoutManagerBaseManagerTest : public testing::Test {
 public:
  void SetUp() override {
    host_view_ = std::make_unique<View>();
    layout_manager_ =
        host_view_->SetLayoutManager(std::make_unique<MockLayoutManagerBase>());
  }

  View* AddChildView(gfx::Size preferred_size) {
    auto child = std::make_unique<StaticSizedView>(preferred_size);
    return host_view_->AddChildView(std::move(child));
  }

  // Directly calls the layout manager's `Layout()` method, as if from within
  // `View::Layout()` or an overridden version of the function.
  void DoLayoutManagerLayout() { layout_manager_->Layout(host_view_.get()); }

  View* host_view() { return host_view_.get(); }
  MockLayoutManagerBase* layout_manager() { return layout_manager_; }
  View* child(int index) { return host_view_->children().at(index); }

 private:
  std::unique_ptr<View> host_view_;
  raw_ptr<MockLayoutManagerBase> layout_manager_;
};

}  // namespace

TEST_F(LayoutManagerBaseManagerTest, ProposedLayout_GetLayoutFor) {
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());

  ProposedLayout layout;
  constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
  layout.child_layouts.push_back({child(0), true, kChild1Bounds});
  layout.child_layouts.push_back({child(1), false});

  const ProposedLayout& const_layout = layout;

  EXPECT_EQ(&layout.child_layouts[0], layout.GetLayoutFor(child(0)));
  EXPECT_EQ(&const_layout.child_layouts[0],
            const_layout.GetLayoutFor(child(0)));
  EXPECT_EQ(&layout.child_layouts[1], layout.GetLayoutFor(child(1)));
  EXPECT_EQ(&const_layout.child_layouts[1],
            const_layout.GetLayoutFor(child(1)));
  EXPECT_EQ(nullptr, layout.GetLayoutFor(child(2)));
  EXPECT_EQ(nullptr, const_layout.GetLayoutFor(child(2)));
}

TEST_F(LayoutManagerBaseManagerTest, ApplyLayout) {
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());

  // We don't want to set the size of the host view because it will trigger a
  // superfluous layout, so we'll just keep the old size and make sure it
  // doesn't change.
  const gfx::Size old_size = host_view()->size();

  ProposedLayout layout;
  // This should be ignored.
  layout.host_size = {123, 456};

  // Set the child visibility and bounds.
  constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
  constexpr gfx::Rect kChild3Bounds(20, 21, 12, 14);
  layout.child_layouts.push_back({child(0), true, kChild1Bounds});
  layout.child_layouts.push_back({child(1), false});
  layout.child_layouts.push_back({child(2), true, kChild3Bounds});

  layout_manager()->ApplyLayout(layout);

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(kChild1Bounds, child(0)->bounds());
  EXPECT_FALSE(child(1)->GetVisible());
  EXPECT_TRUE(child(2)->GetVisible());
  EXPECT_EQ(kChild3Bounds, child(2)->bounds());
  EXPECT_EQ(old_size, host_view()->size());
}

TEST_F(LayoutManagerBaseManagerTest, ApplyLayout_SkipsOmittedViews) {
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());
  AddChildView(gfx::Size());

  ProposedLayout layout;
  // Set the child visibility and bounds.
  constexpr gfx::Rect kChild1Bounds(3, 4, 10, 15);
  constexpr gfx::Rect kChild2Bounds(1, 2, 3, 4);
  layout.child_layouts.push_back({child(0), true, kChild1Bounds});
  layout.child_layouts.push_back({child(2), false});

  // We'll set the second child separately.
  child(1)->SetVisible(true);
  child(1)->SetBoundsRect(kChild2Bounds);

  layout_manager()->ApplyLayout(layout);

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(kChild1Bounds, child(0)->bounds());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(kChild2Bounds, child(1)->bounds());
  EXPECT_FALSE(child(2)->GetVisible());
}

TEST_F(LayoutManagerBaseManagerTest, Install) {
  EXPECT_EQ(host_view(), layout_manager()->host_view());
}

TEST_F(LayoutManagerBaseManagerTest, GetMinimumSize) {
  AddChildView(kSquarishSize);
  AddChildView(kLongSize);
  AddChildView(kTallSize);
  EXPECT_EQ(gfx::Size(kChildViewPadding, kChildViewPadding),
            host_view()->GetMinimumSize());
}

TEST_F(LayoutManagerBaseManagerTest, GetPreferredSize) {
  AddChildView(kSquarishSize);
  AddChildView(kLongSize);
  AddChildView(kTallSize);
  const gfx::Size expected(kLongSize.width() + 2 * kChildViewPadding,
                           kTallSize.height() + 2 * kChildViewPadding);
  EXPECT_EQ(expected, host_view()->GetPreferredSize({}));
}

TEST_F(LayoutManagerBaseManagerTest, GetPreferredHeightForWidth) {
  AddChildView(kSquarishSize);
  AddChildView(kLargeSize);
  const int expected = kSquarishSize.height() + 2 * kChildViewPadding;
  EXPECT_EQ(expected,
            layout_manager()->GetPreferredHeightForWidth(host_view(), 20));
  EXPECT_EQ(1, layout_manager()->num_layouts_generated());
  layout_manager()->GetPreferredHeightForWidth(host_view(), 20);
  EXPECT_EQ(1, layout_manager()->num_layouts_generated());
  layout_manager()->GetPreferredHeightForWidth(host_view(), 25);
  EXPECT_EQ(2, layout_manager()->num_layouts_generated());
}

TEST_F(LayoutManagerBaseManagerTest, InvalidateLayout) {
  // Some invalidation could have been triggered during setup.
  const int old_num_invalidations = layout_manager()->num_invalidations();

  host_view()->InvalidateLayout();
  EXPECT_EQ(old_num_invalidations + 1, layout_manager()->num_invalidations());
}

TEST_F(LayoutManagerBaseManagerTest, Layout) {
  constexpr gfx::Point kUpperLeft(kChildViewPadding, kChildViewPadding);
  AddChildView(kSquarishSize);
  AddChildView(kLongSize);
  AddChildView(kTallSize);

  // This should fit all of the child views and trigger layout.
  host_view()->SetSize({40, 40});
  EXPECT_EQ(1, layout_manager()->num_layouts_generated());
  EXPECT_EQ(gfx::Rect(kUpperLeft, child(0)->GetPreferredSize({})),
            child(0)->bounds());
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(gfx::Rect(kUpperLeft, child(1)->GetPreferredSize({})),
            child(1)->bounds());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(gfx::Rect(kUpperLeft, child(2)->GetPreferredSize({})),
            child(2)->bounds());
  EXPECT_TRUE(child(2)->GetVisible());

  // This should drop out some children.
  host_view()->SetSize({25, 25});
  EXPECT_EQ(2, layout_manager()->num_layouts_generated());
  EXPECT_EQ(gfx::Rect(kUpperLeft, child(0)->GetPreferredSize({})),
            child(0)->bounds());
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_FALSE(child(1)->GetVisible());
  EXPECT_FALSE(child(2)->GetVisible());
}

TEST_F(LayoutManagerBaseManagerTest, IgnoresChildWithViewIgnoredByLayoutKey) {
  AddChildView(kSquarishSize);
  AddChildView(kLongSize);
  AddChildView(kTallSize);

  child(1)->SetProperty(kViewIgnoredByLayoutKey, true);
  child(1)->SetSize(kLargeSize);

  // Makes enough room for all views, and triggers layout.
  host_view()->SetSize({50, 50});

  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_EQ(kLargeSize, child(1)->size());
  EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
}

TEST_F(LayoutManagerBaseManagerTest, ViewVisibilitySet) {
  AddChildView(kSquarishSize);
  AddChildView(kLongSize);
  AddChildView(kTallSize);

  child(1)->SetVisible(false);

  // Makes enough room for all views, and triggers layout.
  host_view()->SetSize({50, 50});

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_FALSE(child(1)->GetVisible());
  EXPECT_TRUE(child(2)->GetVisible());
  EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());

  // Turn the second child view back on and verify it's present in the layout
  // again.
  child(1)->SetVisible(true);
  test::RunScheduledLayout(host_view());

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
  EXPECT_TRUE(child(2)->GetVisible());
  EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());
}

TEST_F(LayoutManagerBaseManagerTest, ViewAdded) {
  AddChildView(kLongSize);
  AddChildView(kTallSize);

  // Makes enough room for all views, and triggers layout.
  host_view()->SetSize({50, 50});

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());

  // Add a new view and verify it is being laid out.
  View* new_view = AddChildView(kSquarishSize);
  test::RunScheduledLayout(host_view());

  EXPECT_TRUE(new_view->GetVisible());
  EXPECT_EQ(new_view->GetPreferredSize({}), new_view->size());
}

TEST_F(LayoutManagerBaseManagerTest, ViewAdded_NotVisible) {
  AddChildView(kLongSize);
  AddChildView(kTallSize);

  // Makes enough room for all views, and triggers layout.
  host_view()->SetSize({50, 50});

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());

  // Add a new view that is not visible and ensure that the layout manager
  // doesn't touch it during layout.
  View* new_view = new StaticSizedView(kSquarishSize);
  new_view->SetVisible(false);
  host_view()->AddChildViewRaw(new_view);
  test::RunScheduledLayout(host_view());

  EXPECT_FALSE(new_view->GetVisible());
}

TEST_F(LayoutManagerBaseManagerTest, ViewRemoved) {
  AddChildView(kSquarishSize);
  View* const child_view = AddChildView(kLongSize);
  AddChildView(kTallSize);

  // Makes enough room for all views, and triggers layout.
  host_view()->SetSize({50, 50});

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());
  EXPECT_TRUE(child(2)->GetVisible());
  EXPECT_EQ(child(2)->GetPreferredSize({}), child(2)->size());

  std::unique_ptr<View> owned_child_view =
      host_view()->RemoveChildViewT(child_view);
  owned_child_view->SetSize(kLargeSize);
  test::RunScheduledLayout(host_view());

  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(child(0)->GetPreferredSize({}), child(0)->size());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(child(1)->GetPreferredSize({}), child(1)->size());

  EXPECT_TRUE(owned_child_view->GetVisible());
  EXPECT_EQ(kLargeSize, owned_child_view->size());
}

class TestIgnoredView : public View {
  METADATA_HEADER(TestIgnoredView, View)
 public:
  TestIgnoredView() {
    SetProperty(kViewIgnoredByLayoutKey, true);
    SetBoundsRect(kExpectedBounds);
  }

  static constexpr gfx::Rect kExpectedBounds{1, 2, 10, 40};
};

BEGIN_METADATA(TestIgnoredView)
END_METADATA

// An ignored view's visibility and layout are managed outside of the layout
// manager, whether that's in the view itself or in a manual layout method in
// its parent view.
TEST_F(LayoutManagerBaseManagerTest, IgnoredView) {
  View* child_view = AddChildView(kSquarishSize);
  test::RunScheduledLayout(host_view());
  gfx::Rect host_view_bounds = host_view()->bounds();
  gfx::Rect child_view_bounds = child_view->bounds();

  TestIgnoredView* ignored_view =
      host_view()->AddChildView(std::make_unique<TestIgnoredView>());
  ignored_view->SetVisible(true);
  test::RunScheduledLayout(host_view());
  // The ignored view does not affect the layout of other views.
  EXPECT_EQ(host_view()->bounds(), host_view_bounds);
  EXPECT_EQ(child_view->bounds(), child_view_bounds);
  // The ignored view manages its own layout.
  EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);

  // The ignored view's visibility shouldn't be changed by the layout manager.
  ignored_view->SetVisible(false);
  test::RunScheduledLayout(host_view());
  EXPECT_EQ(ignored_view->GetVisible(), false);
  EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);
  ignored_view->SetVisible(true);
  test::RunScheduledLayout(host_view());
  EXPECT_EQ(ignored_view->GetVisible(), true);
  EXPECT_EQ(ignored_view->bounds(), ignored_view->kExpectedBounds);
}

TEST(LayoutManagerBase_ProposedLayoutTest, Equality) {
  std::array<View, 3> views;
  ProposedLayout a;
  ProposedLayout b;
  EXPECT_TRUE(a == b);
  a.host_size = {1, 2};
  EXPECT_FALSE(a == b);
  b.host_size = {1, 2};
  EXPECT_TRUE(a == b);
  a.child_layouts.push_back({&views[0], true, {1, 1, 2, 2}});
  EXPECT_FALSE(a == b);
  b.child_layouts.push_back(a.child_layouts[0]);
  EXPECT_TRUE(a == b);
  a.child_layouts[0].visible = false;
  EXPECT_FALSE(a == b);
  b.child_layouts[0].visible = false;
  EXPECT_TRUE(a == b);
  b.child_layouts[0].bounds = {0, 0, 3, 3};
  // Since |visible| == false, changing bounds doesn't change anything.
  EXPECT_TRUE(a == b);
  a.child_layouts[0].visible = true;
  b.child_layouts[0].visible = true;
  EXPECT_FALSE(a == b);
  a.child_layouts[0].visible = false;
  b.child_layouts[0].visible = false;
  a.child_layouts.push_back({&views[1], true, {1, 2, 3, 4}});
  b.child_layouts.push_back({&views[2], true, {1, 2, 3, 4}});
  EXPECT_FALSE(a == b);
  b.child_layouts[1].child_view = &views[1];
  EXPECT_TRUE(a == b);
}

class LayoutManagerBaseAvailableSizeTest : public testing::Test {
 public:
  void SetUp() override {
    view_ = std::make_unique<View>();
    layout_ =
        view_->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
  }

  View* view() { return view_.get(); }
  TestLayoutManagerBase* layout() { return layout_; }

 private:
  std::unique_ptr<View> view_;
  raw_ptr<TestLayoutManagerBase> layout_;
};

TEST_F(LayoutManagerBaseAvailableSizeTest, ReturnsCorrectValues) {
  const SizeBounds kChild1Bounds(3, 7);
  const SizeBounds kChild2Bounds(11, 13);
  View* const child1 = view()->AddChildView(std::make_unique<View>());
  View* const child2 = view()->AddChildView(std::make_unique<View>());
  View not_a_child;

  layout()->OverrideProposedLayout(
      {{10, 10},
       {{child1, true, {1, 1, 1, 1}, kChild1Bounds},
        {child2, true, {2, 2, 2, 2}, kChild2Bounds}}});
  view()->SizeToPreferredSize();

  EXPECT_EQ(kChild1Bounds, view()->GetAvailableSize(child1));
  EXPECT_EQ(kChild2Bounds, view()->GetAvailableSize(child2));
  EXPECT_EQ(SizeBounds(), view()->GetAvailableSize(&not_a_child));
}

TEST_F(LayoutManagerBaseAvailableSizeTest, AvailableSizesInNestedValuesAdd) {
  View* const child = view()->AddChildView(std::make_unique<View>());
  View* const grandchild = child->AddChildView(std::make_unique<View>());
  auto* const child_layout =
      child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());

  constexpr gfx::Size kViewSize(18, 17);
  constexpr SizeBounds kChildAvailableSize(16, 15);
  constexpr gfx::Size kChildSize(13, 12);
  constexpr SizeBounds kGrandchildAvailableSize(10, 9);
  constexpr gfx::Size kGrandchildSize(3, 2);
  layout()->OverrideProposedLayout(
      {kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
  child_layout->OverrideProposedLayout({kChildSize,
                                        {{grandchild,
                                          true,
                                          {{2, 2}, kGrandchildSize},
                                          kGrandchildAvailableSize}}});
  view()->SizeToPreferredSize();

  EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
  SizeBounds expected;
  expected.set_width(kGrandchildAvailableSize.width() +
                     kChildAvailableSize.width() - kChildSize.width());
  expected.set_height(kGrandchildAvailableSize.height() +
                      kChildAvailableSize.height() - kChildSize.height());
  EXPECT_EQ(expected, child->GetAvailableSize(grandchild));
}

TEST_F(LayoutManagerBaseAvailableSizeTest,
       PartiallySpecifiedAvailableSizesInNestedLayoutsAddPartially) {
  View* const child = view()->AddChildView(std::make_unique<View>());
  View* const grandchild = child->AddChildView(std::make_unique<View>());
  auto* const child_layout =
      child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());

  constexpr gfx::Size kViewSize(18, 17);
  constexpr SizeBounds kChildAvailableSize(16, SizeBound());
  constexpr gfx::Size kChildSize(13, 12);
  constexpr SizeBounds kGrandchildAvailableSize(10, 9);
  constexpr gfx::Size kGrandchildSize(3, 2);
  layout()->OverrideProposedLayout(
      {kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
  child_layout->OverrideProposedLayout({kChildSize,
                                        {{grandchild,
                                          true,
                                          {{2, 2}, kGrandchildSize},
                                          kGrandchildAvailableSize}}});
  view()->SizeToPreferredSize();

  EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
  SizeBounds expected;
  expected.set_width(kGrandchildAvailableSize.width() +
                     kChildAvailableSize.width() - kChildSize.width());
  expected.set_height(kGrandchildAvailableSize.height());
  EXPECT_EQ(expected, child->GetAvailableSize(grandchild));
}

TEST_F(LayoutManagerBaseAvailableSizeTest,
       MismatchedAvailableSizesInNestedLayoutsDoNotAdd) {
  View* const child = view()->AddChildView(std::make_unique<View>());
  View* const grandchild = child->AddChildView(std::make_unique<View>());
  auto* const child_layout =
      child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());

  constexpr gfx::Size kViewSize(18, 17);
  constexpr SizeBounds kChildAvailableSize(16, SizeBound());
  constexpr gfx::Size kChildSize(13, 12);
  constexpr SizeBounds kGrandchildAvailableSize(SizeBound(), 9);
  constexpr gfx::Size kGrandchildSize(3, 2);
  layout()->OverrideProposedLayout(
      {kViewSize, {{child, true, {{3, 3}, kChildSize}, kChildAvailableSize}}});
  child_layout->OverrideProposedLayout({kChildSize,
                                        {{grandchild,
                                          true,
                                          {{2, 2}, kGrandchildSize},
                                          kGrandchildAvailableSize}}});
  view()->SizeToPreferredSize();

  EXPECT_EQ(kChildAvailableSize, view()->GetAvailableSize(child));
  EXPECT_EQ(kGrandchildAvailableSize, child->GetAvailableSize(grandchild));
}

TEST_F(LayoutManagerBaseAvailableSizeTest,
       AvaialbleSizeChangeTriggersDescendantLayout) {
  View* const child = view()->AddChildView(std::make_unique<View>());
  TestLayoutManagerBase* const child_layout =
      child->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
  View* const grandchild = child->AddChildView(std::make_unique<View>());
  TestLayoutManagerBase* const grandchild_layout =
      grandchild->SetLayoutManager(std::make_unique<TestLayoutManagerBase>());
  View* const great_grandchild =
      grandchild->AddChildView(std::make_unique<View>());
  TestLayoutManagerBase* const great_grandchild_layout =
      great_grandchild->SetLayoutManager(
          std::make_unique<TestLayoutManagerBase>());

  // Create a default root layout with non-visible, zero-size child with no
  // available size.
  ProposedLayout root_layout;
  root_layout.child_layouts.emplace_back();
  root_layout.child_layouts[0].child_view = child;
  root_layout.child_layouts[0].available_size = SizeBounds(0, 0);

  // Set some default layouts for the rest of the hierarchy.
  layout()->OverrideProposedLayout(root_layout);
  child_layout->OverrideProposedLayout({{}, {{grandchild, false, {}, {0, 0}}}});
  grandchild_layout->OverrideProposedLayout(
      {{}, {{great_grandchild, false, {}, {0, 0}}}});

  test::RunScheduledLayout(view());

  const size_t num_grandchild_layouts = grandchild_layout->layout_count();
  const size_t num_great_grandchild_layouts =
      great_grandchild_layout->layout_count();

  // Set the same rootlayout again as a control. This should not have an effect
  // on the layout of the grand- and great-grandchild views.
  layout()->OverrideProposedLayout(root_layout);
  test::RunScheduledLayout(view());

  EXPECT_EQ(num_grandchild_layouts, grandchild_layout->layout_count());
  EXPECT_EQ(num_great_grandchild_layouts,
            great_grandchild_layout->layout_count());

  // Now set the child view to be visible with nonzero size and even larger
  // available size. Applying this layout should change the size available to
  // all views down the hierarchy, forcing a re-layout.
  root_layout.child_layouts[0].visible = true;
  root_layout.child_layouts[0].bounds = gfx::Rect(0, 0, 5, 5);
  root_layout.child_layouts[0].available_size = SizeBounds(10, 10);
  layout()->OverrideProposedLayout(root_layout);
  test::RunScheduledLayout(view());

  EXPECT_EQ(num_grandchild_layouts + 1, grandchild_layout->layout_count());
  EXPECT_EQ(num_great_grandchild_layouts + 1,
            great_grandchild_layout->layout_count());
}

using ManualLayoutUtilTest = LayoutManagerBaseManagerTest;

TEST_F(ManualLayoutUtilTest, SetCanBeVisibleForManualLayout) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  EXPECT_TRUE(child(0)->GetVisible());
  const gfx::Rect old_child1_bounds = child(0)->bounds();
  const gfx::Rect old_child2_bounds = child(1)->bounds();

  // Hide the view and remove from the layout.
  manual_layout_util.SetViewHidden(child(0), true);
  EXPECT_FALSE(child(0)->GetVisible());
  DoLayoutManagerLayout();
  EXPECT_FALSE(child(0)->GetVisible());
  // The second child should now have moved over into the space of the first.
  EXPECT_EQ(old_child1_bounds, child(1)->bounds());

  // These lines should have no effect as it is already set to hidden.
  manual_layout_util.SetViewHidden(child(0), true);
  DoLayoutManagerLayout();
  EXPECT_FALSE(child(0)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(1)->bounds());

  // Enable the view in the layout again. This won't immediately cause the view
  // to reappear; it may be made visible during the next layout pass.
  manual_layout_util.SetViewHidden(child(0), false);
  EXPECT_FALSE(child(0)->GetVisible());
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  // The first child should be in its original bounds.
  EXPECT_EQ(old_child1_bounds, child(0)->bounds());
  // The second child should move back to its original bounds.
  EXPECT_EQ(old_child2_bounds, child(1)->bounds());

  // Again, this should have no effect as the view is already enabled.
  manual_layout_util.SetViewHidden(child(0), false);
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(0)->bounds());
  EXPECT_EQ(old_child2_bounds, child(1)->bounds());

  EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
  EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
}

TEST_F(ManualLayoutUtilTest, TemporarilyExcludeOneViewFromLayout) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  EXPECT_TRUE(child(0)->GetVisible());
  const gfx::Rect old_child1_bounds = child(0)->bounds();
  const gfx::Rect old_child2_bounds = child(1)->bounds();

  {
    auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
    DoLayoutManagerLayout();
    // Views should have been slid over in the place of the excluded view.
    EXPECT_EQ(old_child1_bounds, child(1)->bounds());
    // Excluded view should still be visible and in its previous location.
    EXPECT_TRUE(child(0)->GetVisible());
    EXPECT_EQ(old_child1_bounds, child(0)->bounds());

    EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
  }

  // Exiting the block should cause the layout to be restored.
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(0)->bounds());
  EXPECT_EQ(old_child2_bounds, child(1)->bounds());
  EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
  EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}

TEST_F(ManualLayoutUtilTest, TemporarilyExcludeTwoViewsFromLayout) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  EXPECT_TRUE(child(0)->GetVisible());
  const gfx::Rect old_child1_bounds = child(0)->bounds();
  const gfx::Rect old_child2_bounds = child(1)->bounds();
  const gfx::Rect old_child3_bounds = child(2)->bounds();

  {
    auto exclusion1 = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
    auto exclusion2 = manual_layout_util.TemporarilyExcludeFromLayout(child(1));
    DoLayoutManagerLayout();
    // Views should have been slid over in the place of the excluded view.
    EXPECT_EQ(old_child1_bounds, child(2)->bounds());
    // Excluded view should still be visible and in its previous location.
    EXPECT_TRUE(child(0)->GetVisible());
    EXPECT_TRUE(child(1)->GetVisible());
    EXPECT_EQ(old_child1_bounds, child(0)->bounds());
    EXPECT_EQ(old_child2_bounds, child(1)->bounds());

    EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());
  }

  // Exiting the block should cause the layout to be restored.
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_TRUE(child(1)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(0)->bounds());
  EXPECT_EQ(old_child2_bounds, child(1)->bounds());
  EXPECT_EQ(old_child3_bounds, child(2)->bounds());
  EXPECT_EQ(old_invalidations + 4, layout_manager()->num_invalidations());
  EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
}

TEST_F(ManualLayoutUtilTest, ViewStaysExcludedIfAlreadyExcludedByProperty) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  EXPECT_TRUE(child(0)->GetVisible());
  const gfx::Rect old_child1_bounds = child(0)->bounds();

  child(0)->SetProperty(kViewIgnoredByLayoutKey, true);
  test::RunScheduledLayout(host_view());

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  {
    // This will have no effect as the view is already excluded.
    auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
    DoLayoutManagerLayout();
    // Views should have been slid over in the place of the excluded view.
    EXPECT_EQ(old_child1_bounds, child(1)->bounds());
    // Excluded view should still be visible and in its previous location.
    EXPECT_TRUE(child(0)->GetVisible());
    EXPECT_EQ(old_child1_bounds, child(0)->bounds());

    EXPECT_EQ(old_invalidations, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts, layout_manager()->num_layouts_generated());
  }

  // Exiting the block should have no effect.
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(0)->bounds());
  EXPECT_EQ(old_child1_bounds, child(1)->bounds());
  EXPECT_EQ(old_invalidations, layout_manager()->num_invalidations());
  EXPECT_EQ(old_layouts, layout_manager()->num_layouts_generated());
}

TEST_F(ManualLayoutUtilTest, ViewHiddenDuringTemporaryExclusion) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  EXPECT_TRUE(child(0)->GetVisible());
  const gfx::Rect old_child1_bounds = child(0)->bounds();

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  {
    // This will have no effect as the view is already excluded.
    auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
    DoLayoutManagerLayout();
    // Views should have been slid over in the place of the excluded view.
    EXPECT_EQ(old_child1_bounds, child(1)->bounds());
    // Excluded view should still be visible and in its previous location.
    EXPECT_TRUE(child(0)->GetVisible());
    EXPECT_EQ(old_child1_bounds, child(0)->bounds());

    EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());

    // This changes the view to hidden, which "stacks" with exclusion. Since it
    // is a different property, it will invalidate the layout.
    manual_layout_util.SetViewHidden(child(0), true);

    DoLayoutManagerLayout();
    EXPECT_FALSE(child(0)->GetVisible());
    EXPECT_EQ(old_child1_bounds, child(1)->bounds());
    EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
  }

  // Exiting the block will cause an invalidation, but since visibility
  // overrides inclusion, it will not change the layout.
  DoLayoutManagerLayout();
  EXPECT_FALSE(child(0)->GetVisible());
  EXPECT_EQ(old_child1_bounds, child(1)->bounds());
  EXPECT_EQ(old_invalidations + 3, layout_manager()->num_invalidations());
  EXPECT_EQ(old_layouts + 3, layout_manager()->num_layouts_generated());
}

TEST_F(ManualLayoutUtilTest, ViewUnhiddenDuringTemporaryExclusion) {
  ManualLayoutUtil manual_layout_util(layout_manager());

  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  AddChildView(kPreferredSize);
  child(0)->SetVisible(false);

  host_view()->SizeToPreferredSize();
  test::RunScheduledLayout(host_view());

  EXPECT_FALSE(child(0)->GetVisible());
  const gfx::Rect old_child2_bounds = child(1)->bounds();
  const gfx::Rect old_child3_bounds = child(2)->bounds();

  const int old_invalidations = layout_manager()->num_invalidations();
  const int old_layouts = layout_manager()->num_layouts_generated();

  {
    // This will have no effect as the view is already excluded, but will cause
    // another invalidation since exclusion and visibility are different
    // properties.
    auto exclusion = manual_layout_util.TemporarilyExcludeFromLayout(child(0));
    DoLayoutManagerLayout();
    // Views should have been slid over in the place of the hidden view.
    EXPECT_EQ(old_child2_bounds, child(1)->bounds());
    // Excluded and hidden view should not be visible.
    EXPECT_FALSE(child(0)->GetVisible());

    EXPECT_EQ(old_invalidations + 1, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 1, layout_manager()->num_layouts_generated());

    // This changes the view to not hidden. While this would not affect the
    // layout due to the exclusion, but since it's a different property, it
    // still triggers an invalidation.
    manual_layout_util.SetViewHidden(child(0), false);

    // Note that since the child view is still excluded from the layout, it
    // retains its existing state, including visibility.
    DoLayoutManagerLayout();
    EXPECT_FALSE(child(0)->GetVisible());
    EXPECT_EQ(old_child2_bounds, child(1)->bounds());
    EXPECT_EQ(old_invalidations + 2, layout_manager()->num_invalidations());
    EXPECT_EQ(old_layouts + 2, layout_manager()->num_layouts_generated());
  }

  // Exiting the block will re-add the view, which is now made visible by the
  // layout.
  DoLayoutManagerLayout();
  EXPECT_TRUE(child(0)->GetVisible());
  EXPECT_EQ(old_child2_bounds, child(0)->bounds());
  EXPECT_EQ(old_child3_bounds, child(1)->bounds());
  EXPECT_EQ(old_invalidations + 3, layout_manager()->num_invalidations());
  EXPECT_EQ(old_layouts + 3, layout_manager()->num_layouts_generated());
}

}  // namespace views
