910e62b5创建于 1月15日历史提交
// Copyright 2020 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/metadata/view_factory.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

using ViewFactoryTest = views::test::WidgetTest;

namespace internal {

class TestView : public views::View {
  METADATA_HEADER(TestView, views::View)

 public:
  TestView() = default;
  TestView(const TestView&) = delete;
  TestView& operator=(const TestView&) = delete;
  ~TestView() override = default;

  // Define a method with an arbitrary number of parameters (>1).
  // Most PODs and enums should work. Stick to parameters passed by-value
  // if possible.
  // TODO(kylixrd): Figure out support for ref and const-ref parameters
  //                  if ever needed
  void ArbitraryMethod(int int_param,
                       float float_param,
                       views::PropertyEffects property_effects) {
    int_param_ = int_param;
    float_param_ = float_param;
    property_effects_ = property_effects;
  }

  int get_int_param() const { return int_param_; }
  float get_float_param() const { return float_param_; }
  views::PropertyEffects get_property_effects() const {
    return property_effects_;
  }

 private:
  int int_param_ = 0;
  float float_param_ = 0.0;
  views::PropertyEffects property_effects_ = views::PropertyEffects::kNone;
};

BEGIN_VIEW_BUILDER(, TestView, views::View)
VIEW_BUILDER_METHOD(ArbitraryMethod, int, float, views::PropertyEffects)
END_VIEW_BUILDER

BEGIN_METADATA(TestView)
END_METADATA

}  // namespace internal

DEFINE_VIEW_BUILDER(, ::internal::TestView)

TEST_F(ViewFactoryTest, TestViewBuilder) {
  views::View* parent = nullptr;
  views::LabelButton* button = nullptr;
  views::LabelButton* scroll_button = nullptr;
  views::ScrollView* scroll_view = nullptr;
  views::View* view_with_layout_manager = nullptr;
  auto layout_manager = std::make_unique<views::FillLayout>();
  auto* layout_manager_ptr = layout_manager.get();
  auto view =
      views::Builder<views::View>()
          .CopyAddressTo(&parent)
          .SetEnabled(false)
          .SetVisible(true)
          .SetBackground(views::CreateSolidBackground(SK_ColorWHITE))
          .SetBorder(views::CreateEmptyBorder(0))
          .AddChildren(views::Builder<views::View>()
                           .SetEnabled(false)
                           .SetVisible(true)
                           .SetProperty(views::kMarginsKey, gfx::Insets(5)),
                       views::Builder<views::View>()
                           .SetGroup(5)
                           .SetID(1)
                           .SetFocusBehavior(views::View::FocusBehavior::NEVER),
                       views::Builder<views::LabelButton>()
                           .CopyAddressTo(&button)
                           .SetIsDefault(true)
                           .SetEnabled(true)
                           .SetText(u"Test"),
                       views::Builder<views::ScrollView>()
                           .CopyAddressTo(&scroll_view)
                           .SetContents(views::Builder<views::LabelButton>()
                                            .CopyAddressTo(&scroll_button)
                                            .SetText(u"ScrollTest"))
                           .SetHeader(views::Builder<views::View>().SetID(2)),
                       views::Builder<views::LabelButton>()
                           .CopyAddressTo(&view_with_layout_manager)
                           .SetLayoutManager(std::move(layout_manager)))
          .Build();
  ASSERT_TRUE(view.get());
  EXPECT_NE(parent, nullptr);
  EXPECT_NE(button, nullptr);
  EXPECT_TRUE(view->GetVisible());
  EXPECT_FALSE(view->GetEnabled());
  ASSERT_GT(view->children().size(), size_t{2});
  EXPECT_EQ(view->children()[1]->GetFocusBehavior(),
            views::View::FocusBehavior::NEVER);
  EXPECT_EQ(view->children()[2], button);
  EXPECT_EQ(button->GetText(), u"Test");
  EXPECT_NE(scroll_view, nullptr);
  EXPECT_NE(scroll_button, nullptr);
  EXPECT_EQ(scroll_button->GetText(), u"ScrollTest");
  EXPECT_EQ(scroll_button, scroll_view->contents());
  EXPECT_NE(view_with_layout_manager, nullptr);
  EXPECT_TRUE(views::IsViewClass<views::LabelButton>(view_with_layout_manager));
  EXPECT_EQ(view_with_layout_manager->GetLayoutManager(), layout_manager_ptr);
}

TEST_F(ViewFactoryTest, TestViewBuilderOwnerships) {
  views::View* parent = nullptr;
  views::LabelButton* button = nullptr;
  views::LabelButton* scroll_button = nullptr;
  views::ScrollView* scroll_view = nullptr;
  auto view_builder = views::Builder<views::View>();
  view_builder.CopyAddressTo(&parent)
      .SetEnabled(false)
      .SetVisible(true)
      .SetBackground(views::CreateSolidBackground(SK_ColorWHITE))
      .SetBorder(views::CreateEmptyBorder(0));
  view_builder.AddChild(views::Builder<views::View>()
                            .SetEnabled(false)
                            .SetVisible(true)
                            .SetProperty(views::kMarginsKey, gfx::Insets(5)));
  view_builder.AddChild(
      views::Builder<views::View>().SetGroup(5).SetID(1).SetFocusBehavior(
          views::View::FocusBehavior::NEVER));
  view_builder.AddChild(views::Builder<views::LabelButton>()
                            .CopyAddressTo(&button)
                            .SetIsDefault(true)
                            .SetEnabled(true)
                            .SetText(u"Test"));
  view_builder.AddChild(views::Builder<views::ScrollView>()
                            .CopyAddressTo(&scroll_view)
                            .SetContents(views::Builder<views::LabelButton>()
                                             .CopyAddressTo(&scroll_button)
                                             .SetText(u"ScrollTest"))
                            .SetHeader(views::Builder<views::View>().SetID(2)));
  auto view = std::move(view_builder).Build();

  ASSERT_TRUE(view.get());
  EXPECT_NE(parent, nullptr);
  EXPECT_NE(button, nullptr);
  EXPECT_TRUE(view->GetVisible());
  EXPECT_FALSE(view->GetEnabled());
  ASSERT_GT(view->children().size(), size_t{2});
  EXPECT_EQ(view->children()[1]->GetFocusBehavior(),
            views::View::FocusBehavior::NEVER);
  EXPECT_EQ(view->children()[2], button);
  EXPECT_EQ(button->GetText(), u"Test");
  EXPECT_NE(scroll_view, nullptr);
  EXPECT_NE(scroll_button, nullptr);
  EXPECT_EQ(scroll_button->GetText(), u"ScrollTest");
  EXPECT_EQ(scroll_button, scroll_view->contents());
}

TEST_F(ViewFactoryTest, TestViewBuilderArbitraryMethod) {
  auto view = views::Builder<internal::TestView>()
                  .SetEnabled(false)
                  .ArbitraryMethod(10, 5.5, views::PropertyEffects::kLayout)
                  .Build();

  EXPECT_FALSE(view->GetEnabled());
  EXPECT_EQ(view->get_int_param(), 10);
  EXPECT_EQ(view->get_float_param(), 5.5);
  EXPECT_EQ(view->get_property_effects(), views::PropertyEffects::kLayout);
}

TEST_F(ViewFactoryTest, TestViewBuilderCustomConfigure) {
  views::UniqueWidgetPtr widget =
      base::WrapUnique(CreateTopLevelPlatformWidget());
  auto* view = widget->GetContentsView()->AddChildView(
      views::Builder<internal::TestView>()
          .CustomConfigure(base::BindOnce([](internal::TestView* view) {
            view->SetEnabled(false);
            view->GetViewAccessibility().SetPosInSet(5);
            view->GetViewAccessibility().SetSetSize(10);
          }))
          .Build());
  EXPECT_FALSE(view->GetEnabled());
  ui::AXNodeData node_data;
  view->GetViewAccessibility().GetAccessibleNodeData(&node_data);
  EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet), 5);
  EXPECT_EQ(node_data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize), 10);
}

TEST_F(ViewFactoryTest, TestViewBuilderAddChildAtIndex) {
  views::View* parent = nullptr;
  views::LabelButton* ok_button = nullptr;
  views::LabelButton* cancel_button = nullptr;
  std::unique_ptr<views::View> view =
      views::Builder<views::View>()
          .CopyAddressTo(&parent)
          .AddChild(views::Builder<views::LabelButton>()
                        .CopyAddressTo(&cancel_button)
                        .SetIsDefault(false)
                        .SetEnabled(true)
                        .SetText(u"Cancel"))
          .AddChildAt(views::Builder<views::LabelButton>()
                          .CopyAddressTo(&ok_button)
                          .SetIsDefault(false)
                          .SetEnabled(true)
                          .SetText(u"OK"),
                      0)
          .Build();

  EXPECT_NE(parent, nullptr);
  EXPECT_NE(ok_button, nullptr);
  EXPECT_NE(cancel_button, nullptr);
  EXPECT_EQ(view.get(), parent);
  EXPECT_TRUE(view->GetVisible());
  // Make sure the OK button is inserted into the child list at index 0.
  EXPECT_EQ(ok_button, view->children()[0]);
  EXPECT_EQ(cancel_button, view->children()[1]);
}

TEST_F(ViewFactoryTest, TestOrderOfOperations) {
  using ViewCallback = base::OnceCallback<void(views::View*)>;

  views::View* view = nullptr;
  base::MockCallback<ViewCallback> custom_configure_callback;
  base::MockCallback<ViewCallback> after_build_callback;

  EXPECT_CALL(custom_configure_callback, Run).Times(0);
  EXPECT_CALL(after_build_callback, Run).Times(0);

  views::Builder<views::View> builder;
  builder.CopyAddressTo(&view)
      .SetID(1)
      .AddChild(views::Builder<views::View>())
      .CustomConfigure(custom_configure_callback.Get())
      .CustomConfigure(custom_configure_callback.Get())
      .AfterBuild(after_build_callback.Get())
      .AfterBuild(after_build_callback.Get());

  // Addresses should be copied *before* build but properties shouldn't be set,
  // children shouldn't be added, and callbacks shouldn't be run until *after*.
  ASSERT_NE(view, nullptr);
  EXPECT_EQ(view->GetID(), 0);
  EXPECT_EQ(view->children().size(), 0u);
  testing::Mock::VerifyAndClearExpectations(&custom_configure_callback);
  testing::Mock::VerifyAndClearExpectations(&after_build_callback);

  {
    testing::InSequence sequence;

    // Expect that two custom configure callbacks will be run *before* any
    // after build callbacks. The order of the custom configure callbacks is
    // not guaranteed by the builder.
    EXPECT_CALL(custom_configure_callback, Run(testing::Pointer(view)))
        .Times(2)
        .WillRepeatedly([](views::View* view) {
          // Properties should be set *before* but children shouldn't be added
          // until *after* custom callbacks are run.
          EXPECT_EQ(view->GetID(), 1);
          EXPECT_EQ(view->children().size(), 0u);
        });

    // Expect that two after build callbacks will be run *after* any custom
    // configure callbacks. The order of the after build callbacks is not
    // guaranteed by the builder.
    EXPECT_CALL(after_build_callback, Run(testing::Pointer(view)))
        .Times(2)
        .WillRepeatedly([](views::View* view) {
          // Children should be added *before* after build callbacks are run.
          EXPECT_EQ(view->children().size(), 1u);
        });
  }

  // Build the view and verify order of operations.
  std::ignore = std::move(builder).Build();
}