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

#include "components/ui_devtools/views/widget_element.h"

#include <memory>

#include "base/memory/raw_ptr.h"
#include "components/ui_devtools/protocol.h"
#include "components/ui_devtools/root_element.h"
#include "components/ui_devtools/ui_devtools_unittest_utils.h"
#include "components/ui_devtools/views/view_element.h"
#include "ui/views/test/views_test_base.h"

namespace ui_devtools {

using ::testing::_;

namespace {
const std::string kWidgetName = "A test widget";
}

class MockWidgetElementDelegate : public MockUIElementDelegate {
 public:
  MockWidgetElementDelegate() = default;
  MockWidgetElementDelegate(const MockWidgetElementDelegate&) = delete;
  MockWidgetElementDelegate& operator=(const MockWidgetElementDelegate&) =
      delete;
  ~MockWidgetElementDelegate() override = default;

  UIElement* root_element() { return &root_element_; }

 private:
  RootElement root_element_{this};
};

class WidgetElementTest : public views::ViewsTestBase {
 public:
  WidgetElementTest() = default;
  WidgetElementTest(const WidgetElementTest&) = delete;
  WidgetElementTest& operator=(const WidgetElementTest&) = delete;
  ~WidgetElementTest() override = default;

  void SetUp() override {
    views::ViewsTestBase::SetUp();

    widget_ = new views::Widget;
    views::Widget::InitParams params =
        CreateParams(views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
                     views::Widget::InitParams::TYPE_WINDOW);
    params.name = kWidgetName;
    widget_->Init(std::move(params));

    delegate_ =
        std::make_unique<testing::NiceMock<MockWidgetElementDelegate>>();
    element_ =
        std::make_unique<WidgetElement>(widget_, delegate_.get(), nullptr);
    // The widget element will delete the ViewElement in |OnWillRemoveView|
    // TODO(lgrey): I think probably WidgetElement should do this itself
    // rather than making the DOMAgent (or test)  put the tree together.
    element()->AddChild(
        new ViewElement(widget_->GetRootView(), delegate_.get(), element()));
  }

  void TearDown() override {
    if (widget())
      CloseWidget();
    views::ViewsTestBase::TearDown();
  }

 protected:
  views::Widget* widget() { return widget_; }
  void CloseWidget() {
    widget_->CloseNow();
    widget_ = nullptr;
  }
  WidgetElement* element() { return element_.get(); }
  MockWidgetElementDelegate* delegate() { return delegate_.get(); }
  void AddWidgetElementToTree() {
    element_->set_parent(delegate_->root_element());
    delegate_->root_element()->AddChild(element_.get());
  }

 private:
  raw_ptr<views::Widget, DanglingUntriaged> widget_ = nullptr;
  std::unique_ptr<WidgetElement> element_;
  std::unique_ptr<MockWidgetElementDelegate> delegate_;
};

TEST_F(WidgetElementTest, SettingsBoundsOnWidgetCallsDelegate) {
  // Once for the root view, and once for the widget.
  EXPECT_CALL(*delegate(), OnUIElementBoundsChanged(_))
      .Times(::testing::AtLeast(1));
  EXPECT_CALL(*delegate(), OnUIElementBoundsChanged(element()))
      .Times(::testing::AtLeast(1));
  widget()->SetBounds(gfx::Rect(10, 20, 300, 400));
}

TEST_F(WidgetElementTest, SettingsBoundsOnElementSetsOnWidget) {
  gfx::Rect old_bounds = widget()->GetRestoredBounds();
  gfx::Rect new_bounds = gfx::Rect(10, 20, 300, 400);
  DCHECK(old_bounds != new_bounds);

  element()->SetBounds(new_bounds);
  EXPECT_EQ(widget()->GetRestoredBounds(), new_bounds);
}

TEST_F(WidgetElementTest, SettingVisibleOnElementSetsOnWidget) {
  DCHECK(!widget()->IsVisible());

  element()->SetVisible(true);
  EXPECT_TRUE(widget()->IsVisible());

  element()->SetVisible(false);
  EXPECT_FALSE(widget()->IsVisible());
}

TEST_F(WidgetElementTest, GetBounds) {
  gfx::Rect actual_bounds;

  gfx::Rect new_bounds = gfx::Rect(10, 20, 300, 400);
  widget()->SetBounds(new_bounds);
  element()->GetBounds(&actual_bounds);
  EXPECT_EQ(actual_bounds, new_bounds);
}

TEST_F(WidgetElementTest, GetVisible) {
  DCHECK(!widget()->IsVisible());
  bool visible;

  element()->GetVisible(&visible);
  EXPECT_FALSE(visible);

  widget()->Show();
  element()->GetVisible(&visible);
  EXPECT_TRUE(visible);

  widget()->Hide();
  element()->GetVisible(&visible);
  EXPECT_FALSE(visible);
}

TEST_F(WidgetElementTest, GetAttributes) {
  std::vector<std::string> attrs = element()->GetAttributes();

  ASSERT_FALSE(widget()->IsActive());
  EXPECT_THAT(attrs,
              testing::ElementsAre("name", kWidgetName, "active", "false"));

  widget()->Activate();
  attrs = element()->GetAttributes();
  EXPECT_THAT(attrs,
              testing::ElementsAre("name", kWidgetName, "active", "true"));
}

TEST_F(WidgetElementTest, GetNodeWindowAndScreenBounds) {
  std::pair<gfx::NativeWindow, gfx::Rect> window_and_bounds =
      element()->GetNodeWindowAndScreenBounds();
  EXPECT_EQ(widget()->GetNativeWindow(), window_and_bounds.first);
  EXPECT_EQ(widget()->GetWindowBoundsInScreen(), window_and_bounds.second);
}

TEST_F(WidgetElementTest, GetNodeBoundsInScreen) {
  gfx::Rect test_bounds(10, 20, 300, 400);
  widget()->SetBounds(test_bounds);
  EXPECT_EQ(widget()->GetWindowBoundsInScreen(),
            element()->GetNodeBoundsInScreen());
}

TEST_F(WidgetElementTest, TrackNonParentedElementLifetime) {
  ASSERT_TRUE(widget());
  EXPECT_FALSE(element()->parent());
  // While closing the widget, the widget element and its children should be
  // removed as well.
  // Remove the root view element.
  EXPECT_CALL(*delegate(), OnUIElementRemoved(_));
  // Remove the widget element.
  EXPECT_CALL(*delegate(), OnUIElementRemoved(element()));
  CloseWidget();
}

TEST_F(WidgetElementTest, TrackParentedElementLifetime) {
  ASSERT_TRUE(widget());
  AddWidgetElementToTree();
  UIElement* root = delegate()->root_element();
  EXPECT_EQ(root, element()->parent());
  EXPECT_EQ(1u, root->children().size());

  // Remove the root view element.
  EXPECT_CALL(*delegate(), OnUIElementRemoved(_));
  // Remove the widget element.
  EXPECT_CALL(*delegate(), OnUIElementRemoved(element()));
  CloseWidget();
  // After closing the widget, the element tree should only has the root.
  EXPECT_EQ(0u, root->children().size());
}

}  // namespace ui_devtools
