blob: c012d191ad10a33f7927319d81fa7ae233ae2c43 [file] [log] [blame]
// 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/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 = *GetDocument().QuerySelector(AtomicString("select"));
EXPECT_EQ(Position::LastPositionInNode(document_element),
Position::LastPositionInOrAfterNode(document_element));
EXPECT_EQ(PositionInFlatTree::LastPositionInNode(document_element),
PositionInFlatTree::LastPositionInOrAfterNode(document_element));
// Note: <select> isn't appeared in flat tree, because <option> doesn't
// take it as valid child node.
EXPECT_EQ(Position::AfterNode(select),
Position::LastPositionInOrAfterNode(select));
EXPECT_EQ(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};
unsigned size = nodes.size();
for (unsigned i = 0; i < size; ++i) {
for (unsigned 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 (unsigned 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