blob: e165934f689ab748418967fd567e91c55c889395 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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/visible_units.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
namespace blink {
class VisibleUnitsParagraphTest : public EditingTestBase {
protected:
static PositionWithAffinity PositionWithAffinityInDOMTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return PositionWithAffinity(CanonicalPositionOf(Position(&anchor, offset)),
affinity);
}
static VisiblePosition CreateVisiblePositionInDOMTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return CreateVisiblePosition(Position(&anchor, offset), affinity);
}
static PositionInFlatTreeWithAffinity PositionWithAffinityInFlatTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return PositionInFlatTreeWithAffinity(
CanonicalPositionOf(PositionInFlatTree(&anchor, offset)), affinity);
}
static VisiblePositionInFlatTree CreateVisiblePositionInFlatTree(
Node& anchor,
int offset,
TextAffinity affinity = TextAffinity::kDownstream) {
return CreateVisiblePosition(PositionInFlatTree(&anchor, offset), affinity);
}
};
TEST_F(VisibleUnitsParagraphTest, endOfParagraphFirstLetter) {
SetBodyContent(
"<style>div::first-letter { color: red }</style><div "
"id=sample>1ab\nde</div>");
Node* sample = GetDocument().getElementById("sample");
Node* text = sample->firstChild();
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 0))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 1))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 2))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 3))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 4))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 5))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 6))
.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, endOfParagraphFirstLetterPre) {
SetBodyContent(
"<style>pre::first-letter { color: red }</style><pre "
"id=sample>1ab\nde</pre>");
Node* sample = GetDocument().getElementById("sample");
Node* text = sample->firstChild();
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 0))
.DeepEquivalent());
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 1))
.DeepEquivalent());
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 2))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 3))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 4))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 5))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 6))
.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, endOfParagraphShadow) {
const char* body_content =
"<span id=host><b slot='#one' id=one>1</b><b slot='#two' "
"id=two>22</b></span><b id=three>333</b>";
const char* shadow_content =
"<p><slot name=#two></slot></p><p><slot name=#one></slot></p>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Element* one = GetDocument().getElementById("one");
Element* two = GetDocument().getElementById("two");
Element* three = GetDocument().getElementById("three");
EXPECT_EQ(
Position(three->firstChild(), 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*one->firstChild(), 1))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(one->firstChild(), 1),
EndOfParagraph(CreateVisiblePositionInFlatTree(*one->firstChild(), 1))
.DeepEquivalent());
EXPECT_EQ(
Position(three->firstChild(), 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*two->firstChild(), 2))
.DeepEquivalent());
EXPECT_EQ(
PositionInFlatTree(two->firstChild(), 2),
EndOfParagraph(CreateVisiblePositionInFlatTree(*two->firstChild(), 2))
.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, endOfParagraphSimple) {
SetBodyContent("<div id=sample>1ab\nde</div>");
Node* sample = GetDocument().getElementById("sample");
Node* text = sample->firstChild();
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 0))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 1))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 2))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 3))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 4))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 5))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 6))
.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, endOfParagraphSimplePre) {
SetBodyContent("<pre id=sample>1ab\nde</pre>");
Node* sample = GetDocument().getElementById("sample");
Node* text = sample->firstChild();
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 0))
.DeepEquivalent());
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 1))
.DeepEquivalent());
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 2))
.DeepEquivalent());
EXPECT_EQ(Position(text, 3),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 3))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 4))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 5))
.DeepEquivalent());
EXPECT_EQ(Position(text, 6),
EndOfParagraph(CreateVisiblePositionInDOMTree(*text, 6))
.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, isEndOfParagraph) {
const char* body_content =
"<span id=host><b slot='#one' id=one>1</b><b slot='#two' "
"id=two>22</b></span><b id=three>333</b>";
const char* shadow_content =
"<p><slot name=#two></slot></p><p><slot name=#one></slot></p>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Node* one = GetDocument().getElementById("one")->firstChild();
Node* two = GetDocument().getElementById("two")->firstChild();
Node* three = GetDocument().getElementById("three")->firstChild();
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInDOMTree(*one, 0)));
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInFlatTree(*one, 0)));
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInDOMTree(*one, 1)));
EXPECT_TRUE(IsEndOfParagraph(CreateVisiblePositionInFlatTree(*one, 1)));
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInDOMTree(*two, 2)));
EXPECT_TRUE(IsEndOfParagraph(CreateVisiblePositionInFlatTree(*two, 2)));
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInDOMTree(*three, 0)));
EXPECT_FALSE(IsEndOfParagraph(CreateVisiblePositionInFlatTree(*three, 0)));
EXPECT_TRUE(IsEndOfParagraph(CreateVisiblePositionInDOMTree(*three, 3)));
EXPECT_TRUE(IsEndOfParagraph(CreateVisiblePositionInFlatTree(*three, 3)));
}
TEST_F(VisibleUnitsParagraphTest, isStartOfParagraph) {
const char* body_content =
"<b id=zero>0</b><span id=host><b slot='#one' id=one>1</b><b slot='#two' "
"id=two>22</b></span><b id=three>333</b>";
const char* shadow_content =
"<p><slot name=#two></slot></p><p><slot name=#one></slot></p>";
SetBodyContent(body_content);
SetShadowContent(shadow_content, "host");
Node* zero = GetDocument().getElementById("zero")->firstChild();
Node* one = GetDocument().getElementById("one")->firstChild();
Node* two = GetDocument().getElementById("two")->firstChild();
Node* three = GetDocument().getElementById("three")->firstChild();
EXPECT_TRUE(IsStartOfParagraph(CreateVisiblePositionInDOMTree(*zero, 0)));
EXPECT_TRUE(IsStartOfParagraph(CreateVisiblePositionInFlatTree(*zero, 0)));
EXPECT_FALSE(IsStartOfParagraph(CreateVisiblePositionInDOMTree(*one, 0)));
EXPECT_TRUE(IsStartOfParagraph(CreateVisiblePositionInFlatTree(*one, 0)));
EXPECT_FALSE(IsStartOfParagraph(CreateVisiblePositionInDOMTree(*one, 1)));
EXPECT_FALSE(IsStartOfParagraph(CreateVisiblePositionInFlatTree(*one, 1)));
EXPECT_FALSE(IsStartOfParagraph(CreateVisiblePositionInDOMTree(*two, 0)));
EXPECT_TRUE(IsStartOfParagraph(CreateVisiblePositionInFlatTree(*two, 0)));
EXPECT_FALSE(IsStartOfParagraph(CreateVisiblePositionInDOMTree(*three, 0)));
EXPECT_TRUE(IsStartOfParagraph(CreateVisiblePositionInFlatTree(*three, 0)));
}
TEST_F(VisibleUnitsParagraphTest,
endOfParagraphWithDifferentUpAndDownVisiblePositions) {
InsertStyleElement("span, div { display: inline-block; width: 50vw; }");
SetBodyContent("x<span></span><div></div>");
const Position& text_end =
Position::LastPositionInNode(*GetDocument().body()->firstChild());
const Position& before_div =
Position::BeforeNode(*GetDocument().QuerySelector("div"));
const VisiblePosition& upstream =
CreateVisiblePosition(before_div, TextAffinity::kUpstream);
const VisiblePosition& downstream =
CreateVisiblePosition(before_div, TextAffinity::kDownstream);
EXPECT_LT(upstream.DeepEquivalent(), downstream.DeepEquivalent());
EXPECT_EQ(text_end, upstream.DeepEquivalent());
EXPECT_EQ(before_div, downstream.DeepEquivalent());
// The end of paragraph of a position shouldn't precede it (bug 1179113).
const VisiblePosition& end_of_paragraph = EndOfParagraph(downstream);
EXPECT_LE(downstream.DeepEquivalent(), end_of_paragraph.DeepEquivalent());
// In in this case they are equal.
EXPECT_EQ(downstream.DeepEquivalent(), end_of_paragraph.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, endOfParagraphCannotBeBeforePosition) {
SetBodyContent(
"<span contenteditable>x<br contenteditable=false>"
"<br contenteditable=false></span>");
Element* span = GetDocument().QuerySelector("span");
const Position& p1 = Position(span, 2);
const Position& p2 = Position::LastPositionInNode(*span);
const Position& p3 = Position::AfterNode(*span);
const VisiblePosition& vp1 = CreateVisiblePosition(p1);
const VisiblePosition& vp2 = CreateVisiblePosition(p2);
const VisiblePosition& vp3 = CreateVisiblePosition(p3);
// The anchor should still be the span after the VisiblePosition
// normalization, or the test would become useless.
EXPECT_EQ(p1, vp1.DeepEquivalent());
EXPECT_EQ(p2, vp2.DeepEquivalent());
EXPECT_EQ(vp2.DeepEquivalent(), vp3.DeepEquivalent());
// No need to test vp3 since it's equal to vp2.
const VisiblePosition& end1 = EndOfParagraph(vp1);
const VisiblePosition& end2 = EndOfParagraph(vp2);
// EndOfParagraph() iterates nodes starting from the span, and "x"@1 would be
// a suitable candidate. But it's skipped because it precedes the positions.
EXPECT_LE(vp1.DeepEquivalent(), end1.DeepEquivalent());
EXPECT_LE(vp2.DeepEquivalent(), end2.DeepEquivalent());
// Test the actual values.
EXPECT_EQ(p1, end1.DeepEquivalent());
EXPECT_EQ(p2, end2.DeepEquivalent());
}
TEST_F(VisibleUnitsParagraphTest, startOfParagraphCannotBeAfterPosition) {
SetBodyContent(
"<span contenteditable><br contenteditable=false>"
"<br contenteditable=false>x</span>");
Element* span = GetDocument().QuerySelector("span");
const Position& p1 = Position(span, 1);
const Position& p2 = Position::FirstPositionInNode(*span);
const Position& p3 = Position::BeforeNode(*span);
const VisiblePosition& vp1 = CreateVisiblePosition(p1);
const VisiblePosition& vp2 = CreateVisiblePosition(p2);
const VisiblePosition& vp3 = CreateVisiblePosition(p3);
// The anchor should still be the span after the VisiblePosition
// normalization, or the test would become useless.
EXPECT_EQ(p1, vp1.DeepEquivalent());
EXPECT_EQ(p2, vp2.DeepEquivalent());
EXPECT_EQ(vp2.DeepEquivalent(), vp3.DeepEquivalent());
// No need to test vp3 since it's equal to vp2.
const VisiblePosition& start1 = StartOfParagraph(vp1);
const VisiblePosition& start2 = StartOfParagraph(vp2);
// StartOfParagraph() iterates nodes in post order starting from the span, and
// "x"@0 would be a suitable candidate. But it's skipped because it's after
// the positions.
EXPECT_LE(start1.DeepEquivalent(), vp1.DeepEquivalent());
EXPECT_LE(start2.DeepEquivalent(), vp2.DeepEquivalent());
// Test the actual values.
EXPECT_EQ(p1, start1.DeepEquivalent());
EXPECT_EQ(p2, start2.DeepEquivalent());
}
} // namespace blink