// 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 "third_party/blink/renderer/core/editing/substring_util.h"

#include <CoreFoundation/CoreFoundation.h>

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_script_source.h"
#include "third_party/blink/public/web/web_view.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"

namespace blink {

class SubStringUtilTest : public testing::Test {
 public:
  SubStringUtilTest() : base_url_("http://www.test.com/") {}

  void TearDown() override {
    url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
  }

 protected:
  std::string RegisterMockedHttpUrlLoad(const std::string& file_name) {
    // TODO(crbug.com/751425): We should use the mock functionality
    // via |web_view_helper_|.
    return url_test_helpers::RegisterMockedURLLoadFromBase(
               WebString::FromUtf8(base_url_), test::CoreTestDataPath(),
               WebString::FromUtf8(file_name))
        .GetString()
        .Utf8();
  }

  test::TaskEnvironment task_environment_;

  std::string base_url_;
  frame_test_helpers::WebViewHelper web_view_helper_;
};

TEST_F(SubStringUtilTest, SubstringUtil) {
  RegisterMockedHttpUrlLoad("content_editable_populated.html");
  WebView* web_view = static_cast<WebView*>(web_view_helper_.InitializeAndLoad(
      base_url_ + "content_editable_populated.html"));

  web_view->GetSettings()->SetDefaultFontSize(12);
  web_view->MainFrameWidget()->Resize(gfx::Size(400, 400));
  WebLocalFrameImpl* frame =
      static_cast<WebLocalFrameImpl*>(web_view->MainFrame());

  gfx::Point baseline_point;
  base::apple::ScopedCFTypeRef<CFAttributedStringRef> result =
      SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 10, 3,
                                                baseline_point);
  ASSERT_TRUE(result);

  gfx::Point point(baseline_point);
  result.reset(SubstringUtil::AttributedWordAtPoint(frame->FrameWidgetImpl(),
                                                    point, baseline_point));
  ASSERT_TRUE(result);

  web_view->MainFrameWidget()->SetZoomLevel(3);

  result.reset(SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 5,
                                                         5, baseline_point));
  ASSERT_TRUE(result);

  point = baseline_point;
  result.reset(SubstringUtil::AttributedWordAtPoint(frame->FrameWidgetImpl(),
                                                    point, baseline_point));
  ASSERT_TRUE(result);
}

TEST_F(SubStringUtilTest, SubstringUtilBaselinePoint) {
  RegisterMockedHttpUrlLoad("content_editable_multiline.html");
  WebView* web_view = static_cast<WebView*>(web_view_helper_.InitializeAndLoad(
      base_url_ + "content_editable_multiline.html"));
  web_view->GetSettings()->SetDefaultFontSize(12);
  web_view->MainFrameWidget()->Resize(gfx::Size(400, 400));
  WebLocalFrameImpl* frame =
      static_cast<WebLocalFrameImpl*>(web_view->MainFrame());

  gfx::Point old_point;
  SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 3, 1, old_point);

  gfx::Point new_point;
  SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 3, 20,
                                            new_point);

  EXPECT_EQ(old_point.x(), new_point.x());
  EXPECT_EQ(old_point.y(), new_point.y());
}

TEST_F(SubStringUtilTest, SubstringUtilPinchZoom) {
  RegisterMockedHttpUrlLoad("content_editable_populated.html");
  WebView* web_view = static_cast<WebView*>(web_view_helper_.InitializeAndLoad(
      base_url_ + "content_editable_populated.html"));
  web_view->GetSettings()->SetDefaultFontSize(12);
  web_view->MainFrameWidget()->Resize(gfx::Size(400, 400));
  WebLocalFrameImpl* frame =
      static_cast<WebLocalFrameImpl*>(web_view->MainFrame());

  gfx::Point baseline_point;
  base::apple::ScopedCFTypeRef<CFAttributedStringRef> result =
      SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 10, 3,
                                                baseline_point);
  ASSERT_TRUE(result);

  web_view->SetPageScaleFactor(3);

  gfx::Point point_after_zoom;
  result.reset(SubstringUtil::AttributedSubstringInRange(frame->GetFrame(), 10,
                                                         3, point_after_zoom));
  ASSERT_TRUE(result);

  // We won't have moved by a full factor of 3 because of the translations, but
  // we should move by a factor of >2.
  EXPECT_LT(2 * baseline_point.x(), point_after_zoom.x());
  EXPECT_LT(2 * baseline_point.y(), point_after_zoom.y());
}

TEST_F(SubStringUtilTest, SubstringUtilIframe) {
  RegisterMockedHttpUrlLoad("single_iframe.html");
  RegisterMockedHttpUrlLoad("visible_iframe.html");
  WebView* web_view = static_cast<WebView*>(
      web_view_helper_.InitializeAndLoad(base_url_ + "single_iframe.html"));
  web_view->GetSettings()->SetDefaultFontSize(12);
  web_view->GetSettings()->SetJavaScriptEnabled(true);
  web_view->MainFrameWidget()->Resize(gfx::Size(400, 400));
  WebLocalFrameImpl* main_frame =
      static_cast<WebLocalFrameImpl*>(web_view->MainFrame());
  WebLocalFrameImpl* child_frame = WebLocalFrameImpl::FromFrame(
      To<LocalFrame>(main_frame->GetFrame()->Tree().FirstChild()));

  gfx::Point baseline_point;
  base::apple::ScopedCFTypeRef<CFAttributedStringRef> result =
      SubstringUtil::AttributedSubstringInRange(child_frame->GetFrame(), 11, 7,
                                                baseline_point);
  ASSERT_TRUE(result);

  gfx::Point point(baseline_point);
  result.reset(SubstringUtil::AttributedWordAtPoint(
      main_frame->FrameWidgetImpl(), point, baseline_point));
  ASSERT_TRUE(result);

  int y_before_change = baseline_point.y();

  // Now move the <iframe> down by 100px.
  main_frame->ExecuteScript(WebScriptSource(
      "document.querySelector('iframe').style.marginTop = '100px';"));

  point = gfx::Point(point.x(), point.y() + 100);
  result.reset(SubstringUtil::AttributedWordAtPoint(
      main_frame->FrameWidgetImpl(), point, baseline_point));
  ASSERT_TRUE(result);

  EXPECT_EQ(y_before_change, baseline_point.y() - 100);
}

}  // namespace blink
