// Copyright 2015 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/position.h"

#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"

namespace blink {

class PositionTest : public EditingTestBase {};

TEST_F(PositionTest, AtStartOfTreeWithAfterChildren) {
  ASSERT_TRUE(GetDocument().firstChild()) << "We should have <html>.";
  EXPECT_TRUE(Position(GetDocument(), 0).AtStartOfTree());
  EXPECT_FALSE(Position::LastPositionInNode(GetDocument()).AtStartOfTree());
}

TEST_F(PositionTest, IsEquivalent) {
  SetBodyContent("<a id=sample>0<b>1</b>2</a>");

  Element* sample = GetDocument().getElementById(AtomicString("sample"));

  EXPECT_TRUE(Position(sample, 0).IsEquivalent(Position(sample, 0)));

  EXPECT_TRUE(
      Position(sample, 0).IsEquivalent(Position::FirstPositionInNode(*sample)));
  EXPECT_TRUE(Position(sample, 0).IsEquivalent(
      Position::BeforeNode(*sample->firstChild())));
  EXPECT_TRUE(Position(sample, 1).IsEquivalent(
      Position::AfterNode(*sample->firstChild())));
  EXPECT_TRUE(Position(sample, 1).IsEquivalent(
      Position::BeforeNode(*sample->firstChild()->nextSibling())));
  EXPECT_TRUE(Position(sample, 2).IsEquivalent(
      Position::BeforeNode(*sample->lastChild())));
  EXPECT_TRUE(Position(sample, 3).IsEquivalent(
      Position::AfterNode(*sample->lastChild())));
  EXPECT_TRUE(
      Position(sample, 3).IsEquivalent(Position::LastPositionInNode(*sample)));

  EXPECT_FALSE(Position(sample, 0).IsEquivalent(Position(sample, 1)));
  EXPECT_FALSE(
      Position(sample, 0).IsEquivalent(Position::LastPositionInNode(*sample)));
}

TEST_F(PositionTest, NodeAsRangeLastNodeNull) {
  EXPECT_EQ(nullptr, Position().NodeAsRangeLastNode());
  EXPECT_EQ(nullptr, PositionInFlatTree().NodeAsRangeLastNode());
}

TEST_F(PositionTest, editingPositionOfWithEditingIgnoresContent) {
  const char* body_content =
      "<textarea id=textarea></textarea><a id=child1>1</a><b id=child2>2</b>";
  SetBodyContent(body_content);
  Node* textarea = GetDocument().getElementById(AtomicString("textarea"));

  EXPECT_EQ(Position::BeforeNode(*textarea),
            Position::EditingPositionOf(textarea, 0));
  EXPECT_EQ(Position::AfterNode(*textarea),
            Position::EditingPositionOf(textarea, 1));
  EXPECT_EQ(Position::AfterNode(*textarea),
            Position::EditingPositionOf(textarea, 2));

  // Change DOM tree to
  // <textarea id=textarea><a id=child1>1</a><b id=child2>2</b></textarea>
  Node* child1 = GetDocument().getElementById(AtomicString("child1"));
  Node* child2 = GetDocument().getElementById(AtomicString("child2"));
  textarea->appendChild(child1);
  textarea->appendChild(child2);

  EXPECT_EQ(Position::BeforeNode(*textarea),
            Position::EditingPositionOf(textarea, 0));
  EXPECT_EQ(Position::AfterNode(*textarea),
            Position::EditingPositionOf(textarea, 1));
  EXPECT_EQ(Position::AfterNode(*textarea),
            Position::EditingPositionOf(textarea, 2));
  EXPECT_EQ(Position::AfterNode(*textarea),
            Position::EditingPositionOf(textarea, 3));
}

// http://crbug.com/1248760
TEST_F(PositionTest, LastPositionInOrAfterNodeNotInFlatTree) {
  SetBodyContent("<option><select>A</select></option>");
  const Element& document_element = *GetDocument().documentElement();
  const Element& select = *QuerySelector("select");

  EXPECT_EQ(Position::LastPositionInNode(document_element),
            Position::LastPositionInOrAfterNode(document_element));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(document_element),
            PositionInFlatTree::LastPositionInOrAfterNode(document_element));

  EXPECT_EQ(Position::AfterNode(select),
            Position::LastPositionInOrAfterNode(select));
  EXPECT_NE(PositionInFlatTree::LastPositionInNode(select),
            PositionInFlatTree::LastPositionInOrAfterNode(select));
}

TEST_F(PositionTest, NodeAsRangeLastNode) {
  const char* body_content =
      "<p id='p1'>11</p><p id='p2'></p><p id='p3'>33</p>";
  SetBodyContent(body_content);
  Node* p1 = GetDocument().getElementById(AtomicString("p1"));
  Node* p2 = GetDocument().getElementById(AtomicString("p2"));
  Node* p3 = GetDocument().getElementById(AtomicString("p3"));
  Node* body = EditingStrategy::Parent(*p1);
  Node* t1 = EditingStrategy::FirstChild(*p1);
  Node* t3 = EditingStrategy::FirstChild(*p3);

  EXPECT_EQ(body, Position::InParentBeforeNode(*p1).NodeAsRangeLastNode());
  EXPECT_EQ(t1, Position::InParentBeforeNode(*p2).NodeAsRangeLastNode());
  EXPECT_EQ(p2, Position::InParentBeforeNode(*p3).NodeAsRangeLastNode());
  EXPECT_EQ(t1, Position::InParentAfterNode(*p1).NodeAsRangeLastNode());
  EXPECT_EQ(p2, Position::InParentAfterNode(*p2).NodeAsRangeLastNode());
  EXPECT_EQ(t3, Position::InParentAfterNode(*p3).NodeAsRangeLastNode());
  EXPECT_EQ(t3, Position::AfterNode(*p3).NodeAsRangeLastNode());

  EXPECT_EQ(body,
            PositionInFlatTree::InParentBeforeNode(*p1).NodeAsRangeLastNode());
  EXPECT_EQ(t1,
            PositionInFlatTree::InParentBeforeNode(*p2).NodeAsRangeLastNode());
  EXPECT_EQ(p2,
            PositionInFlatTree::InParentBeforeNode(*p3).NodeAsRangeLastNode());
  EXPECT_EQ(t1,
            PositionInFlatTree::InParentAfterNode(*p1).NodeAsRangeLastNode());
  EXPECT_EQ(p2,
            PositionInFlatTree::InParentAfterNode(*p2).NodeAsRangeLastNode());
  EXPECT_EQ(t3,
            PositionInFlatTree::InParentAfterNode(*p3).NodeAsRangeLastNode());
  EXPECT_EQ(t3, PositionInFlatTree::AfterNode(*p3).NodeAsRangeLastNode());
}

// TODO(crbug.com/1157146): This test breaks without Shadow DOM v0.
TEST_F(PositionTest, DISABLED_NodeAsRangeLastNodeShadow) {
  const char* body_content =
      "<p id='host'>00<b slot='#one' id='one'>11</b><b slot='#two' "
      "id='two'>22</b>33</p>";
  const char* shadow_content =
      "<a id='a'><slot name='#two'></slot><slot name='#one'></slot></a>";
  SetBodyContent(body_content);
  ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");

  Node* host = GetDocument().getElementById(AtomicString("host"));
  Node* n1 = GetDocument().getElementById(AtomicString("one"));
  Node* n2 = GetDocument().getElementById(AtomicString("two"));
  Node* t0 = EditingStrategy::FirstChild(*host);
  Node* t1 = EditingStrategy::FirstChild(*n1);
  Node* t2 = EditingStrategy::FirstChild(*n2);
  Node* t3 = EditingStrategy::LastChild(*host);
  Node* a = shadow_root->getElementById(AtomicString("a"));

  EXPECT_EQ(t0, Position::InParentBeforeNode(*n1).NodeAsRangeLastNode());
  EXPECT_EQ(t1, Position::InParentBeforeNode(*n2).NodeAsRangeLastNode());
  EXPECT_EQ(t1, Position::InParentAfterNode(*n1).NodeAsRangeLastNode());
  EXPECT_EQ(t2, Position::InParentAfterNode(*n2).NodeAsRangeLastNode());
  EXPECT_EQ(t3, Position::AfterNode(*host).NodeAsRangeLastNode());

  // TODO(crbug.com/1157146): This returns <slot name='#one'> instead of t2:
  EXPECT_EQ(t2,
            PositionInFlatTree::InParentBeforeNode(*n1).NodeAsRangeLastNode());
  // TODO(crbug.com/1157146): This returns <slot name='#two'> instead of a:
  EXPECT_EQ(a,
            PositionInFlatTree::InParentBeforeNode(*n2).NodeAsRangeLastNode());
  EXPECT_EQ(t1,
            PositionInFlatTree::InParentAfterNode(*n1).NodeAsRangeLastNode());
  EXPECT_EQ(t2,
            PositionInFlatTree::InParentAfterNode(*n2).NodeAsRangeLastNode());
  EXPECT_EQ(t1, PositionInFlatTree::AfterNode(*host).NodeAsRangeLastNode());
}

TEST_F(PositionTest, OperatorBool) {
  SetBodyContent("foo");
  EXPECT_FALSE(static_cast<bool>(Position()));
  EXPECT_TRUE(static_cast<bool>(Position(GetDocument().body(), 0)));
}

TEST_F(PositionTest, ToPositionInFlatTreeWithActiveInsertionPoint) {
  const char* body_content =
      "<p id='host'>00<b slot='#one' id='one'>11</b>22</p>";
  const char* shadow_content =
      "<a id='a'><slot name=#one id='content'></slot><slot></slot></a>";
  SetBodyContent(body_content);
  ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  Element* anchor = shadow_root->getElementById(AtomicString("a"));

  EXPECT_EQ(PositionInFlatTree(anchor, 0),
            ToPositionInFlatTree(Position(anchor, 0)));
  EXPECT_EQ(PositionInFlatTree(anchor, 1),
            ToPositionInFlatTree(Position(anchor, 1)));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*anchor),
            ToPositionInFlatTree(Position(anchor, 2)));
}

TEST_F(PositionTest, ToPositionInFlatTreeWithInactiveInsertionPoint) {
  const char* body_content = "<p id='p'><slot></slot></p>";
  SetBodyContent(body_content);
  Element* anchor = GetDocument().getElementById(AtomicString("p"));

  EXPECT_EQ(PositionInFlatTree(anchor, 0),
            ToPositionInFlatTree(Position(anchor, 0)));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*anchor),
            ToPositionInFlatTree(Position(anchor, 1)));
}

// This test comes from "editing/style/block-style-progress-crash.html".
TEST_F(PositionTest, ToPositionInFlatTreeWithNotDistributed) {
  SetBodyContent("<progress id=sample>foo</progress>");
  Element* sample = GetDocument().getElementById(AtomicString("sample"));

  EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*sample),
            ToPositionInFlatTree(Position(sample, 0)));
}

TEST_F(PositionTest, ToPositionInFlatTreeWithShadowRoot) {
  const char* body_content =
      "<p id='host'>00<b slot='#one' id='one'>11</b>22</p>";
  const char* shadow_content = "<a><slot name=#one></slot></a>";
  SetBodyContent(body_content);
  ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  Element* host = GetDocument().getElementById(AtomicString("host"));

  EXPECT_EQ(PositionInFlatTree(host, 0),
            ToPositionInFlatTree(Position(shadow_root, 0)));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*host),
            ToPositionInFlatTree(Position(shadow_root, 1)));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*host),
            ToPositionInFlatTree(Position::LastPositionInNode(*shadow_root)));
  EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*host),
            ToPositionInFlatTree(Position::FirstPositionInNode(*shadow_root)));
}

TEST_F(PositionTest,
       ToPositionInFlatTreeWithShadowRootContainingSingleContent) {
  const char* body_content =
      "<p id='host'>00<b slot='#one' id='one'>11</b>22</p>";
  const char* shadow_content = "<slot name=#one></slot>";
  SetBodyContent(body_content);
  ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  Element* host = GetDocument().getElementById(AtomicString("host"));

  EXPECT_EQ(PositionInFlatTree(host, 0),
            ToPositionInFlatTree(Position(shadow_root, 0)));
  EXPECT_EQ(PositionInFlatTree::LastPositionInNode(*host),
            ToPositionInFlatTree(Position(shadow_root, 1)));
}

TEST_F(PositionTest, ToPositionInFlatTreeWithEmptyShadowRoot) {
  const char* body_content = "<p id='host'>00<b id='one'>11</b>22</p>";
  const char* shadow_content = "";
  SetBodyContent(body_content);
  ShadowRoot* shadow_root = SetShadowContent(shadow_content, "host");
  Element* host = GetDocument().getElementById(AtomicString("host"));

  EXPECT_EQ(PositionInFlatTree::FirstPositionInNode(*host),
            ToPositionInFlatTree(Position(shadow_root, 0)));
}

TEST_F(PositionTest, NullPositionNotConnected) {
  EXPECT_FALSE(Position().IsConnected());
  EXPECT_FALSE(PositionInFlatTree().IsConnected());
}

TEST_F(PositionTest, IsConnectedBasic) {
  Position position = SetCaretTextToBody("<div>f|oo</div>");
  EXPECT_TRUE(position.IsConnected());
  EXPECT_TRUE(ToPositionInFlatTree(position).IsConnected());

  position.AnchorNode()->remove();
  EXPECT_FALSE(position.IsConnected());
  EXPECT_FALSE(ToPositionInFlatTree(position).IsConnected());
}

TEST_F(PositionTest, IsConnectedInFlatTree) {
  Position position = SetCaretTextToBody(
      "<div>f|oo<template data-mode=open>bar</template></div>");
  EXPECT_TRUE(position.IsConnected());
  EXPECT_FALSE(ToPositionInFlatTree(position).IsConnected());
}

TEST_F(PositionTest, FirstPositionInShadowHost) {
  SetBodyContent("<p id=host>foo</p>");
  SetShadowContent("bar", "host");
  Element* host = GetDocument().getElementById(AtomicString("host"));

  Position dom = Position::FirstPositionInNode(*host);
  PositionInFlatTree flat = PositionInFlatTree::FirstPositionInNode(*host);
  EXPECT_EQ(dom, ToPositionInDomTree(flat));
  EXPECT_EQ(flat, ToPositionInFlatTree(dom));
}

TEST_F(PositionTest, LastPositionInShadowHost) {
  SetBodyContent("<p id=host>foo</p>");
  SetShadowContent("bar", "host");
  Element* host = GetDocument().getElementById(AtomicString("host"));

  Position dom = Position::LastPositionInNode(*host);
  PositionInFlatTree flat = PositionInFlatTree::LastPositionInNode(*host);
  EXPECT_EQ(dom, ToPositionInDomTree(flat));
  EXPECT_EQ(flat, ToPositionInFlatTree(dom));
}

TEST_F(PositionTest, ComparePositionsAcrossShadowBoundary) {
  SetBodyContent("<p id=host>foo</p>");
  ShadowRoot* shadow_root = SetShadowContent("<div>bar</div>", "host");
  Element* body = GetDocument().body();
  Element* host = GetDocument().getElementById(AtomicString("host"));
  Node* child = shadow_root->firstChild();
  Node* grandchild = child->firstChild();
  std::array<Node*, 4> nodes = {body, host, child, grandchild};
  wtf_size_t size = nodes.size();
  for (wtf_size_t i = 0; i < size; ++i) {
    for (wtf_size_t j = 0; j < i; ++j) {
      EXPECT_LT(Position(nodes[j], 0), Position(nodes[i], 0));
      EXPECT_LT(PositionInFlatTree(nodes[j], 0),
                PositionInFlatTree(nodes[i], 0));
    }
    EXPECT_EQ(Position(nodes[i], 0), Position(nodes[i], 0));
    EXPECT_EQ(PositionInFlatTree(nodes[i], 0), PositionInFlatTree(nodes[i], 0));
    for (wtf_size_t j = i + 1; j < size; ++j) {
      EXPECT_GT(Position(nodes[j], 0), Position(nodes[i], 0));
      EXPECT_GT(PositionInFlatTree(nodes[j], 0),
                PositionInFlatTree(nodes[i], 0));
    }
  }
}

}  // namespace blink
