1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/page/scrolling/text_fragment_selector_generator.h"
6
7 #include <gtest/gtest.h>
8
9 #include "base/run_loop.h"
10 #include "base/test/bind.h"
11 #include "base/test/metrics/histogram_tester.h"
12 #include "components/shared_highlighting/core/common/shared_highlighting_metrics.h"
13 #include "components/ukm/test_ukm_recorder.h"
14 #include "mojo/public/cpp/bindings/receiver_set.h"
15 #include "services/metrics/public/cpp/ukm_builders.h"
16 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
17 #include "third_party/blink/public/mojom/link_to_text/link_to_text.mojom-blink.h"
18 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
19 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
20 #include "third_party/blink/renderer/core/frame/local_frame.h"
21 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
22 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
23
24 using LinkGenerationError = shared_highlighting::LinkGenerationError;
25
26 namespace blink {
27
28 namespace {
29 const char kSuccessUkmMetric[] = "Success";
30 const char kErrorUkmMetric[] = "Error";
31 } // namespace
32
33 class TextFragmentSelectorGeneratorTest : public SimTest {
34 public:
SetUp()35 void SetUp() override {
36 SimTest::SetUp();
37 WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
38 }
39
VerifySelector(Position selected_start,Position selected_end,String expected_selector)40 void VerifySelector(Position selected_start,
41 Position selected_end,
42 String expected_selector) {
43 String generated_selector = GenerateSelector(selected_start, selected_end);
44 EXPECT_EQ(expected_selector, generated_selector);
45
46 // Should not have logged errors in a success case.
47 histogram_tester_.ExpectTotalCount("SharedHighlights.LinkGenerated.Error",
48 0);
49
50 auto* recorder =
51 static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
52 auto entries = recorder->GetEntriesByName(
53 ukm::builders::SharedHighlights_LinkGenerated::kEntryName);
54 ASSERT_EQ(1u, entries.size());
55 const ukm::mojom::UkmEntry* entry = entries[0];
56 EXPECT_EQ(GetDocument().UkmSourceID(), entry->source_id);
57 recorder->ExpectEntryMetric(entry, kSuccessUkmMetric, true);
58 EXPECT_FALSE(recorder->GetEntryMetric(entry, kErrorUkmMetric));
59 }
60
VerifySelectorFails(Position selected_start,Position selected_end,LinkGenerationError error)61 void VerifySelectorFails(Position selected_start,
62 Position selected_end,
63 LinkGenerationError error) {
64 String generated_selector = GenerateSelector(selected_start, selected_end);
65 EXPECT_EQ("", generated_selector);
66
67 histogram_tester_.ExpectBucketCount("SharedHighlights.LinkGenerated.Error",
68 error, 1);
69
70 auto* recorder =
71 static_cast<ukm::TestUkmRecorder*>(GetDocument().UkmRecorder());
72 auto entries = recorder->GetEntriesByName(
73 ukm::builders::SharedHighlights_LinkGenerated::kEntryName);
74 ASSERT_EQ(1u, entries.size());
75 const ukm::mojom::UkmEntry* entry = entries[0];
76 EXPECT_EQ(GetDocument().UkmSourceID(), entry->source_id);
77 recorder->ExpectEntryMetric(entry, kSuccessUkmMetric, false);
78 recorder->ExpectEntryMetric(entry, kErrorUkmMetric,
79 static_cast<int64_t>(error));
80 }
81
GenerateSelector(Position selected_start,Position selected_end)82 String GenerateSelector(Position selected_start, Position selected_end) {
83 StubUkmRecorder();
84
85 GetDocument()
86 .GetFrame()
87 ->GetTextFragmentSelectorGenerator()
88 ->UpdateSelection(GetDocument().GetFrame(),
89 ToEphemeralRangeInFlatTree(
90 EphemeralRange(selected_start, selected_end)));
91
92 bool callback_called = false;
93 String selector;
94 auto lambda = [](bool& callback_called, String& selector,
95 const String& generated_selector) {
96 selector = generated_selector;
97 callback_called = true;
98 };
99 auto callback =
100 WTF::Bind(lambda, std::ref(callback_called), std::ref(selector));
101 GetDocument()
102 .GetFrame()
103 ->GetTextFragmentSelectorGenerator()
104 ->GenerateSelector(std::move(callback));
105 base::RunLoop().RunUntilIdle();
106
107 EXPECT_TRUE(callback_called);
108 return selector;
109 }
110
111 protected:
StubUkmRecorder()112 void StubUkmRecorder() {
113 // Needed to keep old recorders alive, as other instances might depend on
114 // one of them, causing tests to crash during teardown.
115 old_ukm_recorders_.push_back(std::move(GetDocument().ukm_recorder_));
116 GetDocument().ukm_recorder_ = std::make_unique<ukm::TestUkmRecorder>();
117 }
118
119 base::HistogramTester histogram_tester_;
120
121 // TODO(crbug.com/1153990): Find a better mocking solution and clean up this
122 // variable.
123 std::vector<std::unique_ptr<ukm::UkmRecorder>> old_ukm_recorders_;
124 };
125
126 // Basic exact selector case.
TEST_F(TextFragmentSelectorGeneratorTest,EmptySelection)127 TEST_F(TextFragmentSelectorGeneratorTest, EmptySelection) {
128 SimRequest request("https://example.com/test.html", "text/html");
129 LoadURL("https://example.com/test.html");
130 request.Complete(R"HTML(
131 <!DOCTYPE html>
132 <p id='first'>First paragraph</p>
133 )HTML");
134 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
135 const auto& selected_start = Position(first_paragraph, 5);
136 const auto& selected_end = Position(first_paragraph, 6);
137 ASSERT_EQ(" ", PlainText(EphemeralRange(selected_start, selected_end)));
138
139 VerifySelectorFails(selected_start, selected_end,
140 LinkGenerationError::kEmptySelection);
141 }
142
143 // Basic exact selector case.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector)144 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector) {
145 SimRequest request("https://example.com/test.html", "text/html");
146 LoadURL("https://example.com/test.html");
147 request.Complete(R"HTML(
148 <!DOCTYPE html>
149 <div>Test page</div>
150 <p id='first'>First paragraph text that is longer than 20 chars</p>
151 <p id='second'>Second paragraph text</p>
152 )HTML");
153 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
154 const auto& selected_start = Position(first_paragraph, 0);
155 const auto& selected_end = Position(first_paragraph, 28);
156 ASSERT_EQ("First paragraph text that is",
157 PlainText(EphemeralRange(selected_start, selected_end)));
158
159 VerifySelector(selected_start, selected_end,
160 "First%20paragraph%20text%20that%20is");
161 }
162
163 // Exact selector test where selection contains nested <i> node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextWithNestedTextNodes)164 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextWithNestedTextNodes) {
165 SimRequest request("https://example.com/test.html", "text/html");
166 LoadURL("https://example.com/test.html");
167 request.Complete(R"HTML(
168 <!DOCTYPE html>
169 <div>Test page</div>
170 <p id='first'>First paragraph text that is <i>longer than 20</i> chars</p>
171 <p id='second'>Second paragraph text</p>
172 )HTML");
173 Node* first_paragraph = GetDocument().getElementById("first");
174 const auto& selected_start = Position(first_paragraph->firstChild(), 0);
175 const auto& selected_end =
176 Position(first_paragraph->firstChild()->nextSibling()->firstChild(), 6);
177 ASSERT_EQ("First paragraph text that is longer",
178 PlainText(EphemeralRange(selected_start, selected_end)));
179
180 VerifySelector(selected_start, selected_end,
181 "First%20paragraph%20text%20that%20is%20longer");
182 }
183
184 // Exact selector test where selection contains multiple spaces.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextWithExtraSpace)185 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextWithExtraSpace) {
186 SimRequest request("https://example.com/test.html", "text/html");
187 LoadURL("https://example.com/test.html");
188 request.Complete(R"HTML(
189 <!DOCTYPE html>
190 <div>Test page</div>
191 <p id='first'>First paragraph text that is longer than 20 chars</p>
192 <p id='second'>Second paragraph
193 text</p>
194 )HTML");
195 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
196 const auto& selected_start = Position(second_paragraph, 0);
197 const auto& selected_end = Position(second_paragraph, 27);
198 ASSERT_EQ("Second paragraph text",
199 PlainText(EphemeralRange(selected_start, selected_end)));
200
201 VerifySelector(selected_start, selected_end, "Second%20paragraph%20text");
202 }
203
204 // Exact selector where selection is too short, in which case context is
205 // required.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_TooShortNeedsContext)206 TEST_F(TextFragmentSelectorGeneratorTest,
207 ExactTextSelector_TooShortNeedsContext) {
208 SimRequest request("https://example.com/test.html", "text/html");
209 LoadURL("https://example.com/test.html");
210 request.Complete(R"HTML(
211 <!DOCTYPE html>
212 <div>Test page</div>
213 <p id='first'>First paragraph prefix to unique snippet of text.</p>
214 <p id='second'>Second paragraph</p>
215 )HTML");
216 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
217 const auto& selected_start = Position(first_paragraph, 26);
218 const auto& selected_end = Position(first_paragraph, 40);
219 ASSERT_EQ("unique snippet",
220 PlainText(EphemeralRange(selected_start, selected_end)));
221
222 VerifySelector(selected_start, selected_end, "to-,unique%20snippet,-of");
223 }
224
225 // Exact selector with context test. Case when only one word for prefix and
226 // suffix is enough to disambiguate the selection.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_WithOneWordContext)227 TEST_F(TextFragmentSelectorGeneratorTest,
228 ExactTextSelector_WithOneWordContext) {
229 SimRequest request("https://example.com/test.html", "text/html");
230 LoadURL("https://example.com/test.html");
231 request.Complete(R"HTML(
232 <!DOCTYPE html>
233 <div>Test page</div>
234 <p id='first'>First paragraph text that is longer than 20 chars</p>
235 <p id='second'>Second paragraph text that is short</p>
236 )HTML");
237 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
238 const auto& selected_start = Position(first_paragraph, 6);
239 const auto& selected_end = Position(first_paragraph, 28);
240 ASSERT_EQ("paragraph text that is",
241 PlainText(EphemeralRange(selected_start, selected_end)));
242
243 VerifySelector(selected_start, selected_end,
244 "First-,paragraph%20text%20that%20is,-longer");
245 }
246
247 // Exact selector with context test. Case when multiple words for prefix and
248 // suffix is necessary to disambiguate the selection.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_MultipleWordContext)249 TEST_F(TextFragmentSelectorGeneratorTest,
250 ExactTextSelector_MultipleWordContext) {
251 SimRequest request("https://example.com/test.html", "text/html");
252 LoadURL("https://example.com/test.html");
253 request.Complete(R"HTML(
254 <!DOCTYPE html>
255 <div>Test page</div>
256 <p id='first'>First prefix to not unique snippet of text followed by suffix</p>
257 <p id='second'>Second prefix to not unique snippet of text followed by suffix</p>
258 )HTML");
259 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
260 const auto& selected_start = Position(first_paragraph, 16);
261 const auto& selected_end = Position(first_paragraph, 42);
262 ASSERT_EQ("not unique snippet of text",
263 PlainText(EphemeralRange(selected_start, selected_end)));
264
265 VerifySelector(selected_start, selected_end,
266 "First%20prefix%20to-,not%20unique%20snippet%20of%"
267 "20text,-followed%20by%20suffix");
268 }
269
270 // Exact selector with context test. Case when multiple words for prefix and
271 // suffix is necessary to disambiguate the selection and prefix and suffix
272 // contain extra space.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_MultipleWordContext_ExtraSpace)273 TEST_F(TextFragmentSelectorGeneratorTest,
274 ExactTextSelector_MultipleWordContext_ExtraSpace) {
275 SimRequest request("https://example.com/test.html", "text/html");
276 LoadURL("https://example.com/test.html");
277 request.Complete(R"HTML(
278 <!DOCTYPE html>
279 <div>Test page</div>
280 <p id='first'>First prefix to not unique snippet of text followed by suffix</p>
281 <p id='second'>Second prefix to not unique snippet of text followed by suffix</p>
282 )HTML");
283 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
284 const auto& selected_start = Position(first_paragraph, 21);
285 const auto& selected_end = Position(first_paragraph, 47);
286 ASSERT_EQ("not unique snippet of text",
287 PlainText(EphemeralRange(selected_start, selected_end)));
288
289 VerifySelector(selected_start, selected_end,
290 "First%20prefix%20to-,not%20unique%20snippet%20of%"
291 "20text,-followed%20by%20suffix");
292 }
293
294 // Exact selector with context test. Case when available prefix for all the
295 // occurrences of selected text is the same. In this case suffix should be
296 // extended until unique selector is found.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SamePrefix)297 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SamePrefix) {
298 SimRequest request("https://example.com/test.html", "text/html");
299 LoadURL("https://example.com/test.html");
300 request.Complete(R"HTML(
301 <!DOCTYPE html>
302 <div>Test page</div>
303 <p id='first'>Prefix to not unique snippet of text followed by different suffix</p>
304 <p id='second'>Prefix to not unique snippet of text followed by suffix</p>
305 )HTML");
306 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
307 const auto& selected_start = Position(first_paragraph, 10);
308 const auto& selected_end = Position(first_paragraph, 36);
309 ASSERT_EQ("not unique snippet of text",
310 PlainText(EphemeralRange(selected_start, selected_end)));
311
312 VerifySelector(selected_start, selected_end,
313 "Prefix%20to-,not%20unique%20snippet%20of%20text,-"
314 "followed%20by%20different");
315 }
316
317 // Exact selector with context test. Case when available suffix for all the
318 // occurrences of selected text is the same. In this case prefix should be
319 // extended until unique selector is found.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SameSuffix)320 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SameSuffix) {
321 SimRequest request("https://example.com/test.html", "text/html");
322 LoadURL("https://example.com/test.html");
323 request.Complete(R"HTML(
324 <!DOCTYPE html>
325 <div>Test page</div>
326 <p id='first'>First paragraph prefix to not unique snippet of text followed by suffix</p>
327 <p id='second'>Second paragraph prefix to not unique snippet of text followed by suffix</p>
328 )HTML");
329 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
330 const auto& selected_start = Position(first_paragraph, 26);
331 const auto& selected_end = Position(first_paragraph, 52);
332 ASSERT_EQ("not unique snippet of text",
333 PlainText(EphemeralRange(selected_start, selected_end)));
334
335 VerifySelector(selected_start, selected_end,
336 "First%20paragraph%20prefix%20to-,not%20unique%"
337 "20snippet%20of%20text,-followed%20by%20suffix");
338 }
339
340 // Exact selector with context test. Case when available prefix and suffix for
341 // all the occurrences of selected text are the same. In this case generation
342 // should be unsuccessful.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SamePrefixSuffix)343 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_SamePrefixSuffix) {
344 SimRequest request("https://example.com/test.html", "text/html");
345 LoadURL("https://example.com/test.html");
346 request.Complete(R"HTML(
347 <!DOCTYPE html>
348 <div>Test page</div>
349 <p id='first'>Same paragraph prefix to not unique snippet of text followed by suffix</p>
350 <p id='second'>Same paragraph prefix to not unique snippet of text followed by suffix</p>
351 )HTML");
352 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
353 const auto& selected_start = Position(first_paragraph, 25);
354 const auto& selected_end = Position(first_paragraph, 51);
355 ASSERT_EQ("not unique snippet of text",
356 PlainText(EphemeralRange(selected_start, selected_end)));
357
358 VerifySelectorFails(selected_start, selected_end,
359 LinkGenerationError::kContextExhausted);
360 }
361
362 // Exact selector with context test. Case when available prefix and suffix for
363 // all the occurrences of selected text are the same for the first 10 words. In
364 // this case generation should be unsuccessful.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_SimilarLongPreffixSuffix)365 TEST_F(TextFragmentSelectorGeneratorTest,
366 ExactTextSelector_SimilarLongPreffixSuffix) {
367 SimRequest request("https://example.com/test.html", "text/html");
368 LoadURL("https://example.com/test.html");
369 request.Complete(R"HTML(
370 <!DOCTYPE html>
371 <div>Test page</div>
372 <p id='first'>First paragraph prefix one two three four five six seven
373 eight nine ten to not unique snippet of text followed by suffix</p>
374 <p id='second'>Second paragraph prefix one two three four five six seven
375 eight nine ten to not unique snippet of text followed by suffix</p>
376 )HTML");
377 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
378 const auto& selected_start = Position(first_paragraph, 80);
379 const auto& selected_end = Position(first_paragraph, 106);
380 ASSERT_EQ("not unique snippet of text",
381 PlainText(EphemeralRange(selected_start, selected_end)));
382
383 VerifySelectorFails(selected_start, selected_end,
384 LinkGenerationError::kContextLimitReached);
385 }
386
387 // Exact selector with context test. Case when no prefix is available.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NoPrefix)388 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NoPrefix) {
389 SimRequest request("https://example.com/test.html", "text/html");
390 LoadURL("https://example.com/test.html");
391 request.Complete(R"HTML(
392 <!DOCTYPE html>
393 <p id='first'>Not unique snippet of text followed by first suffix</p>
394 <p id='second'>Not unique snippet of text followed by second suffix</p>
395 )HTML");
396 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
397 const auto& selected_start = Position(first_paragraph, 0);
398 const auto& selected_end = Position(first_paragraph, 26);
399 ASSERT_EQ("Not unique snippet of text",
400 PlainText(EphemeralRange(selected_start, selected_end)));
401
402 VerifySelector(selected_start, selected_end,
403 "Not%20unique%20snippet%20of%20text,-followed%20by%20first");
404 }
405
406 // Exact selector with context test. Case when no suffix is available.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NoSuffix)407 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NoSuffix) {
408 SimRequest request("https://example.com/test.html", "text/html");
409 LoadURL("https://example.com/test.html");
410 request.Complete(R"HTML(
411 <!DOCTYPE html>
412 <div>Test page</div>
413 <p id='first'>First prefix to not unique snippet of text</p>
414 <p id='second'>Second prefix to not unique snippet of text</p>
415 )HTML");
416 Node* first_paragraph = GetDocument().getElementById("second")->firstChild();
417 const auto& selected_start = Position(first_paragraph, 17);
418 const auto& selected_end = Position(first_paragraph, 43);
419 ASSERT_EQ("not unique snippet of text",
420 PlainText(EphemeralRange(selected_start, selected_end)));
421
422 VerifySelector(selected_start, selected_end,
423 "Second%20prefix%20to-,not%20unique%20snippet%20of%"
424 "20text");
425 }
426
427 // Exact selector with context test. Case when available prefix is the
428 // preceding block.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_PrevNodePrefix)429 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_PrevNodePrefix) {
430 SimRequest request("https://example.com/test.html", "text/html");
431 LoadURL("https://example.com/test.html");
432 request.Complete(R"HTML(
433 <!DOCTYPE html>
434 <div>Test page</div>
435 <p id='first'>First paragraph with not unique snippet</p>
436 <p id='second'>not unique snippet of text</p>
437 )HTML");
438 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
439 const auto& selected_start = Position(second_paragraph, 0);
440 const auto& selected_end = Position(second_paragraph, 18);
441 ASSERT_EQ("not unique snippet",
442 PlainText(EphemeralRange(selected_start, selected_end)));
443
444 VerifySelector(selected_start, selected_end,
445 "snippet-,not%20unique%20snippet,-of");
446 }
447
448 // Exact selector with context test. Case when available prefix is the
449 // preceding block, which is a text node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_PrevTextNodePrefix)450 TEST_F(TextFragmentSelectorGeneratorTest,
451 ExactTextSelector_PrevTextNodePrefix) {
452 SimRequest request("https://example.com/test.html", "text/html");
453 LoadURL("https://example.com/test.html");
454 request.Complete(R"HTML(
455 <!DOCTYPE html>
456 <div>Test page</div>
457 <p id='first'>First paragraph with not unique snippet</p>
458 text
459 <p id='second'>not unique snippet of text</p>
460 )HTML");
461 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
462 const auto& selected_start = Position(second_paragraph, 0);
463 const auto& selected_end = Position(second_paragraph, 18);
464 ASSERT_EQ("not unique snippet",
465 PlainText(EphemeralRange(selected_start, selected_end)));
466
467 VerifySelector(selected_start, selected_end,
468 "text-,not%20unique%20snippet,-of");
469 }
470
471 // Exact selector with context test. Case when available suffix is the next
472 // block.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NextNodeSuffix)473 TEST_F(TextFragmentSelectorGeneratorTest, ExactTextSelector_NextNodeSuffix) {
474 SimRequest request("https://example.com/test.html", "text/html");
475 LoadURL("https://example.com/test.html");
476 request.Complete(R"HTML(
477 <!DOCTYPE html>
478 <div>Test page</div>
479 <p id='first'>First paragraph with not unique snippet</p>
480 <p id='second'>not unique snippet of text</p>
481 )HTML");
482 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
483 const auto& selected_start = Position(first_paragraph, 21);
484 const auto& selected_end = Position(first_paragraph, 39);
485 ASSERT_EQ("not unique snippet",
486 PlainText(EphemeralRange(selected_start, selected_end)));
487
488 VerifySelector(selected_start, selected_end,
489 "with-,not%20unique%20snippet,-not");
490 }
491
492 // Exact selector with context test. Case when available suffix is the next
493 // block, which is a text node.
TEST_F(TextFragmentSelectorGeneratorTest,ExactTextSelector_NexttextNodeSuffix)494 TEST_F(TextFragmentSelectorGeneratorTest,
495 ExactTextSelector_NexttextNodeSuffix) {
496 SimRequest request("https://example.com/test.html", "text/html");
497 LoadURL("https://example.com/test.html");
498 request.Complete(R"HTML(
499 <!DOCTYPE html>
500 <div>Test page</div>
501 <p id='first'>First paragraph with not unique snippet</p>
502 text
503 <p id='second'>not unique snippet of text</p>
504 )HTML");
505 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
506 const auto& selected_start = Position(first_paragraph, 21);
507 const auto& selected_end = Position(first_paragraph, 39);
508 ASSERT_EQ("not unique snippet",
509 PlainText(EphemeralRange(selected_start, selected_end)));
510
511 VerifySelector(selected_start, selected_end,
512 "with-,not%20unique%20snippet,-text");
513 }
514
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector)515 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector) {
516 SimRequest request("https://example.com/test.html", "text/html");
517 LoadURL("https://example.com/test.html");
518 request.Complete(R"HTML(
519 <!DOCTYPE html>
520 <div>Test page</div>
521 <p id='first'>First paragraph text that is longer than 20 chars</p>
522 <p id='second'>Second paragraph text</p>
523 )HTML");
524 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
525 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
526 const auto& selected_start = Position(first_paragraph, 0);
527 const auto& selected_end = Position(second_paragraph, 6);
528 ASSERT_EQ("First paragraph text that is longer than 20 chars\n\nSecond",
529 PlainText(EphemeralRange(selected_start, selected_end)));
530
531 VerifySelector(selected_start, selected_end, "First,Second");
532 }
533
534 // It should be more than 300 characters selected from the same node so that
535 // ranges are used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode)536 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_SameNode) {
537 SimRequest request("https://example.com/test.html", "text/html");
538 LoadURL("https://example.com/test.html");
539 request.Complete(R"HTML(
540 <!DOCTYPE html>
541 <div>Test page</div>
542 <p id='first'>First paragraph text text text text text text text
543 text text text text text text text text text text text text text
544 text text text text text text text text text text text text text
545 text text text text text text text text text text text text text
546 text text text text text text text text text and last text</p>
547 )HTML");
548 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
549 const auto& selected_start = Position(first_paragraph, 0);
550 const auto& selected_end = Position(first_paragraph, 320);
551 ASSERT_EQ(
552 "First paragraph text text text text text text text \
553 text text text text text text text text text text text text text \
554 text text text text text text text text text text text text text \
555 text text text text text text text text text text text text text \
556 text text text text text text text text text and last text",
557 PlainText(EphemeralRange(selected_start, selected_end)));
558
559 VerifySelector(selected_start, selected_end, "First%20paragraph,last%20text");
560 }
561
562 // It should be more than 300 characters selected from the same node so that
563 // ranges are used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode_MultipleSelections)564 TEST_F(TextFragmentSelectorGeneratorTest,
565 RangeSelector_SameNode_MultipleSelections) {
566 SimRequest request("https://example.com/test.html", "text/html");
567 LoadURL("https://example.com/test.html");
568 request.Complete(R"HTML(
569 <!DOCTYPE html>
570 <div>Test page</div>
571 <p id='first'>First paragraph text text text text text text text
572 text text text text text text text text text text text text text
573 text text text text text text text text text text text text text
574 text text text text text text text text text text text text text
575 text text text text text text text text text text and last text</p>
576 )HTML");
577 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
578 const auto& selected_start = Position(first_paragraph, 0);
579 const auto& selected_end = Position(first_paragraph, 325);
580 ASSERT_EQ(
581 "First paragraph text text text text text text text \
582 text text text text text text text text text text text text text \
583 text text text text text text text text text text text text text \
584 text text text text text text text text text text text text text \
585 text text text text text text text text text text and last text",
586 PlainText(EphemeralRange(selected_start, selected_end)));
587 ASSERT_EQ(309u,
588 PlainText(EphemeralRange(selected_start, selected_end)).length());
589
590 VerifySelector(selected_start, selected_end, "First%20paragraph,last%20text");
591
592 const auto& second_selected_start = Position(first_paragraph, 6);
593 const auto& second_selected_end = Position(first_paragraph, 325);
594 ASSERT_EQ(
595 "paragraph text text text text text text text \
596 text text text text text text text text text text text text text \
597 text text text text text text text text text text text text text \
598 text text text text text text text text text text text text text \
599 text text text text text text text text text text and last text",
600 PlainText(EphemeralRange(second_selected_start, second_selected_end)));
601 ASSERT_EQ(303u, PlainText(EphemeralRange(second_selected_start,
602 second_selected_end))
603 .length());
604
605 VerifySelector(second_selected_start, second_selected_end,
606 "paragraph%20text,last%20text");
607 }
608
609 // When using all the selected text for the range is not enough for unique
610 // match, context should be added.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_RangeNotUnique)611 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_RangeNotUnique) {
612 SimRequest request("https://example.com/test.html", "text/html");
613 LoadURL("https://example.com/test.html");
614 request.Complete(R"HTML(
615 <!DOCTYPE html>
616 <div>Test page</div>
617 <p id='first'>First paragraph</p><p id='text1'>text</p>
618 <p id='second'>Second paragraph</p><p id='text2'>text</p>
619 )HTML");
620 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
621 Node* first_text = GetDocument().getElementById("text1")->firstChild();
622 const auto& selected_start = Position(first_paragraph, 6);
623 const auto& selected_end = Position(first_text, 4);
624 ASSERT_EQ("paragraph\n\ntext",
625 PlainText(EphemeralRange(selected_start, selected_end)));
626
627 VerifySelector(selected_start, selected_end, "First-,paragraph,text,-Second");
628 }
629
630 // When using all the selected text for the range is not enough for unique
631 // match, context should be added, but only prefxi and no suffix is available.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_RangeNotUnique_NoSuffix)632 TEST_F(TextFragmentSelectorGeneratorTest,
633 RangeSelector_RangeNotUnique_NoSuffix) {
634 SimRequest request("https://example.com/test.html", "text/html");
635 LoadURL("https://example.com/test.html");
636 request.Complete(R"HTML(
637 <!DOCTYPE html>
638 <div>Test page</div>
639 <p id='first'>First paragraph</p><p id='text1'>text</p>
640 <p id='second'>Second paragraph</p><p id='text2'>text</p>
641 )HTML");
642 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
643 Node* second_text = GetDocument().getElementById("text2")->firstChild();
644 const auto& selected_start = Position(second_paragraph, 7);
645 const auto& selected_end = Position(second_text, 4);
646 ASSERT_EQ("paragraph\n\ntext",
647 PlainText(EphemeralRange(selected_start, selected_end)));
648
649 VerifySelector(selected_start, selected_end, "Second-,paragraph,text");
650 }
651
652 // When no range end is available it should return empty selector.
653 // There is no range end available because there is no word break in the second
654 // half of the selection.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_NoRangeEnd)655 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_NoRangeEnd) {
656 SimRequest request("https://example.com/test.html", "text/html");
657 LoadURL("https://example.com/test.html");
658 request.Complete(R"HTML(
659 <!DOCTYPE html>
660 <div>Test page</div>
661 <p id='first'>First paragraph text text text text text text text
662 text text text text text text text text text text text text text
663 text text text text text text text text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_text_and_last_text</p>
664 )HTML");
665 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
666 const auto& selected_start = Position(first_paragraph, 0);
667 const auto& selected_end = Position(first_paragraph, 312);
668 ASSERT_EQ(
669 "First paragraph text text text text text text text \
670 text text text text text text text text text text text text text \
671 text text text text text text text text_text_text_text_text_text_\
672 text_text_text_text_text_text_text_text_text_text_text_text_text_\
673 text_text_text_text_text_text_text_text_text_and_last_text",
674 PlainText(EphemeralRange(selected_start, selected_end)));
675
676 VerifySelectorFails(selected_start, selected_end,
677 LinkGenerationError::kNoRange);
678 }
679
680 // Selection should be autocompleted to contain full words.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit)681 TEST_F(TextFragmentSelectorGeneratorTest, WordLimit) {
682 SimRequest request("https://example.com/test.html", "text/html");
683 LoadURL("https://example.com/test.html");
684 request.Complete(R"HTML(
685 <!DOCTYPE html>
686 <div>Test page</div>
687 <p id='first'>First paragraph text that is longer than 20 chars</p>
688 )HTML");
689 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
690 const auto& selected_start = Position(first_paragraph, 7);
691 const auto& selected_end = Position(first_paragraph, 33);
692 ASSERT_EQ("aragraph text that is long",
693 PlainText(EphemeralRange(selected_start, selected_end)));
694
695 VerifySelector(selected_start, selected_end,
696 "paragraph%20text%20that%20is%20longer");
697 }
698
699 // Selection should be autocompleted to contain full words. The autocompletion
700 // should work with extra spaces.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit_ExtraSpaces)701 TEST_F(TextFragmentSelectorGeneratorTest, WordLimit_ExtraSpaces) {
702 SimRequest request("https://example.com/test.html", "text/html");
703 LoadURL("https://example.com/test.html");
704 request.Complete(R"HTML(
705 <!DOCTYPE html>
706 <div>Test page</div>
707 <p id='first'>First
708 paragraph text
709 that is longer than 20 chars</p>
710 )HTML");
711 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
712 const auto& selected_start = Position(first_paragraph, 11);
713 const auto& selected_end = Position(first_paragraph, 41);
714 ASSERT_EQ("aragraph text that is long",
715 PlainText(EphemeralRange(selected_start, selected_end)));
716
717 VerifySelector(selected_start, selected_end,
718 "paragraph%20text%20that%20is%20longer");
719 }
720
721 // When selection starts at the end of a word, selection shouldn't be
722 // autocompleted to contain extra words.
TEST_F(TextFragmentSelectorGeneratorTest,WordLimit_SelectionStartsAndEndsAtWordLimit)723 TEST_F(TextFragmentSelectorGeneratorTest,
724 WordLimit_SelectionStartsAndEndsAtWordLimit) {
725 SimRequest request("https://example.com/test.html", "text/html");
726 LoadURL("https://example.com/test.html");
727 request.Complete(R"HTML(
728 <!DOCTYPE html>
729 <div>Test page</div>
730 <p id='first'>First paragraph text that is longer than 20 chars</p>
731 )HTML");
732 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
733 const auto& selected_start = Position(first_paragraph, 5);
734 const auto& selected_end = Position(first_paragraph, 37);
735 ASSERT_EQ(" paragraph text that is longer ",
736 PlainText(EphemeralRange(selected_start, selected_end)));
737
738 VerifySelector(selected_start, selected_end,
739 "paragraph%20text%20that%20is%20longer");
740 }
741
742 // Check the case when selections starts with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithImage)743 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithImage) {
744 SimRequest request("https://example.com/test.html", "text/html");
745 LoadURL("https://example.com/test.html");
746 request.Complete(R"HTML(
747 <!DOCTYPE html>
748 <div>Test page</div>
749 <img id="img">
750 <p id='first'>First paragraph text that is longer than 20 chars</p>
751 )HTML");
752 Node* img = GetDocument().getElementById("img");
753 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
754 const auto& start = Position(img, 0);
755 const auto& end = Position(first_paragraph, 5);
756 ASSERT_EQ("\nFirst", PlainText(EphemeralRange(start, end)));
757
758 VerifySelector(start, end, "page-,First,-paragraph");
759 }
760
761 // Check the case when selections starts with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithBlockWithImage)762 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithBlockWithImage) {
763 SimRequest request("https://example.com/test.html", "text/html");
764 LoadURL("https://example.com/test.html");
765 request.Complete(R"HTML(
766 <!DOCTYPE html>
767 <div>Test page</div>
768 <div id="img_div">
769 <img id="img">
770 </div>
771 <p id='first'>First paragraph text that is longer than 20 chars</p>
772 )HTML");
773 Node* img = GetDocument().getElementById("img_div");
774 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
775 const auto& start = Position(img, 0);
776 const auto& end = Position(first_paragraph, 5);
777 ASSERT_EQ("\nFirst", PlainText(EphemeralRange(start, end)));
778
779 VerifySelector(start, end, "page-,First,-paragraph");
780 }
781
782 // Check the case when selections starts with a node nested in "inline-block"
783 // node. crbug.com/1151474
TEST_F(TextFragmentSelectorGeneratorTest,StartsWithInlineBlockChild)784 TEST_F(TextFragmentSelectorGeneratorTest, StartsWithInlineBlockChild) {
785 SimRequest request("https://example.com/test.html", "text/html");
786 LoadURL("https://example.com/test.html");
787 request.Complete(R"HTML(
788 <!DOCTYPE html>
789 <style>
790 li {
791 display: inline-block;
792 }
793 </style>
794 <div>Test page</div>
795 <ul>
796 <li>
797 <a id="link1"/>
798 </li>
799 <li>
800 <a id="link2"/>
801 </li>
802 <li>
803 <a id="link3"/>
804 </li>
805 </ul>
806 <p id='first'>First paragraph text that is longer than 20 chars</p>
807 )HTML");
808
809 GetDocument().View()->UpdateAllLifecyclePhasesForTest();
810 Node* img = GetDocument().getElementById("link1");
811 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
812 const auto& start = Position(img, PositionAnchorType::kAfterChildren);
813 const auto& end = Position(first_paragraph, 5);
814 ASSERT_EQ(" \nFirst", PlainText(EphemeralRange(start, end)));
815
816 VerifySelector(start, end, "page-,First,-paragraph");
817 }
818
819 // Check the case when selections ends with an non text node.
TEST_F(TextFragmentSelectorGeneratorTest,EndswithImage)820 TEST_F(TextFragmentSelectorGeneratorTest, EndswithImage) {
821 SimRequest request("https://example.com/test.html", "text/html");
822 LoadURL("https://example.com/test.html");
823 request.Complete(R"HTML(
824 <!DOCTYPE html>
825 <div>Test page</div>
826 <p id='first'>First paragraph text that is longer than 20 chars</p>
827 <img id="img">
828 </img>
829 )HTML");
830 Node* img = GetDocument().getElementById("img");
831 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
832 const auto& start = Position(first_paragraph, 44);
833 const auto& end = Position(img, 0);
834 ASSERT_EQ("chars\n\n", PlainText(EphemeralRange(start, end)));
835
836 VerifySelector(start, end, "20-,chars");
837 }
838
839 // Check the case when selections starts at the end of the previous block.
TEST_F(TextFragmentSelectorGeneratorTest,StartIsEndofPrevBlock)840 TEST_F(TextFragmentSelectorGeneratorTest, StartIsEndofPrevBlock) {
841 SimRequest request("https://example.com/test.html", "text/html");
842 LoadURL("https://example.com/test.html");
843 request.Complete(R"HTML(
844 <!DOCTYPE html>
845 <p id='first'>First paragraph </p>
846 <p id='second'>Second paragraph</p>
847 )HTML");
848 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
849 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
850 const auto& start = Position(first_paragraph, 18);
851 const auto& end = Position(second_paragraph, 6);
852 ASSERT_EQ("\nSecond", PlainText(EphemeralRange(start, end)));
853
854 VerifySelector(start, end, "paragraph-,Second,-paragraph");
855 }
856
857 // Check the case when selections starts at the end of the previous block.
TEST_F(TextFragmentSelectorGeneratorTest,EndIsStartofNextBlock)858 TEST_F(TextFragmentSelectorGeneratorTest, EndIsStartofNextBlock) {
859 SimRequest request("https://example.com/test.html", "text/html");
860 LoadURL("https://example.com/test.html");
861 request.Complete(R"HTML(
862 <!DOCTYPE html>
863 <p id='first'>First paragraph</p>
864 <p id='second'> Second paragraph</p>
865 )HTML");
866 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
867 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
868 const auto& start = Position(first_paragraph, 0);
869 const auto& end = Position(second_paragraph, 2);
870 ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
871
872 VerifySelector(start, end, "First%20paragraph,-Second");
873 }
874
875 // Check the case when parent of selection start is a sibling of a node where
876 // selection ends.
877 // :root
878 // / \
879 // div p
880 // | |
881 // p "]Second"
882 // |
883 // "[First..."
884 // Where [] indicate selection. In this case, when the selection is adjusted, we
885 // want to ensure it correctly traverses the tree back to the previous text node
886 // and not to the <div>(sibling of second <p>).
887 //
888 // crbug.com/1154308 - checks the use of Previous instead of
889 // PreviousSkippingChildren in TextFragmentSelectorGenerator::AdjustSelection
TEST_F(TextFragmentSelectorGeneratorTest,PrevNodeIsSiblingsChild)890 TEST_F(TextFragmentSelectorGeneratorTest, PrevNodeIsSiblingsChild) {
891 SimRequest request("https://example.com/test.html", "text/html");
892 LoadURL("https://example.com/test.html");
893
894 // HTML is intentionally not formatted. Adding new lines and itendation
895 // creates empty text nodes which changes the dom tree.
896 request.Complete(R"HTML(
897 <!DOCTYPE html>
898 <div><p id='start'>First paragraph</p></div><p id='end'>Second paragraph</p>
899 )HTML");
900 GetDocument().UpdateStyleAndLayoutTree();
901 Node* first_paragraph = GetDocument().getElementById("start")->firstChild();
902 Node* second_paragraph = GetDocument().getElementById("end");
903 const auto& start = Position(first_paragraph, 0);
904 const auto& end = Position(second_paragraph, 0);
905 ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
906
907 VerifySelector(start, end, "First%20paragraph,-Second");
908 }
909
910 // Check the case when parent of selection start is a sibling of a node where
911 // selection ends.
912 // :root
913 // / | \
914 // div div p
915 // | | \
916 // p "test" "]Second"
917 // |
918 //"[First..."
919 //
920 // Where [] indicate selection. In this case, when the selection is adjusted, we
921 // want to ensure it correctly traverses the tree back to the previous text by
922 // correctly skipping the invisible div but not skipping the second <p>.
923 //
924 // crbug.com/1154308 - checks the use of Previous instead of
925 // PreviousSkippingChildren in FindBuffer::BackwardVisibleTextNode
TEST_F(TextFragmentSelectorGeneratorTest,PrevPrevNodeIsSiblingsChild)926 TEST_F(TextFragmentSelectorGeneratorTest, PrevPrevNodeIsSiblingsChild) {
927 SimRequest request("https://example.com/test.html", "text/html");
928 LoadURL("https://example.com/test.html");
929 // HTML is intentionally not formatted. Adding new lines and itendation
930 // creates empty text nodes which changes the dom tree.
931 request.Complete(R"HTML(
932 <!DOCTYPE html>
933 <div><p id='start'>First paragraph</p></div><div style='display:none'>test</div><p id='end'>Second paragraph</p>
934 )HTML");
935 GetDocument().UpdateStyleAndLayoutTree();
936 Node* first_paragraph = GetDocument().getElementById("start")->firstChild();
937 Node* second_paragraph = GetDocument().getElementById("end");
938 const auto& start = Position(first_paragraph, 0);
939 const auto& end = Position(second_paragraph, 0);
940 ASSERT_EQ("First paragraph\n\n", PlainText(EphemeralRange(start, end)));
941
942 VerifySelector(start, end, "First%20paragraph,-Second");
943 }
944
945 // Checks that for short selection that have nested block element range selector
946 // is used.
TEST_F(TextFragmentSelectorGeneratorTest,RangeSelector_SameNode_Interrupted)947 TEST_F(TextFragmentSelectorGeneratorTest, RangeSelector_SameNode_Interrupted) {
948 SimRequest request("https://example.com/test.html", "text/html");
949 LoadURL("https://example.com/test.html");
950 request.Complete(R"HTML(
951 <!DOCTYPE html>
952 <div id='first'>First <div>block text</div> paragraph text</div>
953 )HTML");
954 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
955 const auto& start = Position(first_paragraph, 0);
956 const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
957 ASSERT_EQ("First\nblock text\nparagraph",
958 PlainText(EphemeralRange(start, end)));
959
960 VerifySelector(start, end, "First,paragraph");
961 }
962
963 // Basic test case for |GetNextTextBlock|.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock)964 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock) {
965 SimRequest request("https://example.com/test.html", "text/html");
966 LoadURL("https://example.com/test.html");
967 request.Complete(R"HTML(
968 <!DOCTYPE html>
969 <p id='first'>First paragraph text</p>
970 )HTML");
971 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
972 const auto& start = Position(first_paragraph, 16);
973 const auto& end = Position(first_paragraph, 20);
974 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
975
976 EXPECT_EQ("First paragraph", GetDocument()
977 .GetFrame()
978 ->GetTextFragmentSelectorGenerator()
979 ->GetPreviousTextBlockForTesting(start));
980 }
981
982 // Check the case when available prefix contains collapsible space.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_ExtraSpace)983 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_ExtraSpace) {
984 SimRequest request("https://example.com/test.html", "text/html");
985 LoadURL("https://example.com/test.html");
986 request.Complete(R"HTML(
987 <!DOCTYPE html>
988 <p id='first'>First
989
990 paragraph text</p>
991 )HTML");
992 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
993 const auto& start = Position(first_paragraph, 26);
994 const auto& end = Position(first_paragraph, 30);
995 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
996
997 EXPECT_EQ("First paragraph", GetDocument()
998 .GetFrame()
999 ->GetTextFragmentSelectorGenerator()
1000 ->GetPreviousTextBlockForTesting(start));
1001 }
1002
1003 // Check the case when available prefix complete text content of the previous
1004 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevNode)1005 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_PrevNode) {
1006 SimRequest request("https://example.com/test.html", "text/html");
1007 LoadURL("https://example.com/test.html");
1008 request.Complete(R"HTML(
1009 <!DOCTYPE html>
1010 <p id='first'>First paragraph text</p>
1011 <p id='second'>Second paragraph text</p>
1012 )HTML");
1013 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1014 const auto& start = Position(second_paragraph, 0);
1015 const auto& end = Position(second_paragraph, 6);
1016 ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1017
1018 EXPECT_EQ("First paragraph text",
1019 GetDocument()
1020 .GetFrame()
1021 ->GetTextFragmentSelectorGenerator()
1022 ->GetPreviousTextBlockForTesting(start));
1023 }
1024
1025 // Check the case when there is a commented block between selection and the
1026 // available prefix.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevNode_WithComment)1027 TEST_F(TextFragmentSelectorGeneratorTest,
1028 GetPreviousTextBlock_PrevNode_WithComment) {
1029 SimRequest request("https://example.com/test.html", "text/html");
1030 LoadURL("https://example.com/test.html");
1031 request.Complete(R"HTML(
1032 <!DOCTYPE html>
1033 <p id='first'>First paragraph text</p>
1034 <!--
1035 multiline comment that should be ignored.
1036 //-->
1037 <p id='second'>Second paragraph text</p>
1038 )HTML");
1039 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1040 const auto& start = Position(second_paragraph, 0);
1041 const auto& end = Position(second_paragraph, 6);
1042 ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1043
1044 EXPECT_EQ("First paragraph text",
1045 GetDocument()
1046 .GetFrame()
1047 ->GetTextFragmentSelectorGenerator()
1048 ->GetPreviousTextBlockForTesting(start));
1049 }
1050
1051 // Check the case when available prefix is a text node outside of selection
1052 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_PrevTextNode)1053 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_PrevTextNode) {
1054 SimRequest request("https://example.com/test.html", "text/html");
1055 LoadURL("https://example.com/test.html");
1056 request.Complete(R"HTML(
1057 <!DOCTYPE html>
1058 text
1059 <p id='first'>First paragraph text</p>
1060 )HTML");
1061 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1062 const auto& start = Position(first_paragraph, 0);
1063 const auto& end = Position(first_paragraph, 5);
1064 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1065
1066 EXPECT_EQ("text", GetDocument()
1067 .GetFrame()
1068 ->GetTextFragmentSelectorGenerator()
1069 ->GetPreviousTextBlockForTesting(start));
1070 }
1071
1072 // Check the case when available prefix is a parent node text content outside of
1073 // selection block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_ParentNode)1074 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_ParentNode) {
1075 SimRequest request("https://example.com/test.html", "text/html");
1076 LoadURL("https://example.com/test.html");
1077 request.Complete(R"HTML(
1078 <!DOCTYPE html>
1079 <div>nested
1080 <p id='first'>First paragraph text</p></div>
1081 )HTML");
1082 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1083 const auto& start = Position(first_paragraph, 0);
1084 const auto& end = Position(first_paragraph, 5);
1085 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1086
1087 EXPECT_EQ("nested", GetDocument()
1088 .GetFrame()
1089 ->GetTextFragmentSelectorGenerator()
1090 ->GetPreviousTextBlockForTesting(start));
1091 }
1092
1093 // Check the case when available prefix contains non-block tag(e.g. <b>).
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedTextNode)1094 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_NestedTextNode) {
1095 SimRequest request("https://example.com/test.html", "text/html");
1096 LoadURL("https://example.com/test.html");
1097 request.Complete(R"HTML(
1098 <!DOCTYPE html>
1099 <p id='first'>First <b>bold text</b> paragraph text</p>
1100 )HTML");
1101 Node* first_paragraph = GetDocument().getElementById("first")->lastChild();
1102 const auto& start = Position(first_paragraph, 11);
1103 const auto& end = Position(first_paragraph, 15);
1104 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1105
1106 EXPECT_EQ("First bold text paragraph",
1107 GetDocument()
1108 .GetFrame()
1109 ->GetTextFragmentSelectorGenerator()
1110 ->GetPreviousTextBlockForTesting(start));
1111 }
1112
1113 // Check the case when available prefix is collected until nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedBlock)1114 TEST_F(TextFragmentSelectorGeneratorTest, GetPreviousTextBlock_NestedBlock) {
1115 SimRequest request("https://example.com/test.html", "text/html");
1116 LoadURL("https://example.com/test.html");
1117 request.Complete(R"HTML(
1118 <!DOCTYPE html>
1119 <div id='first'>First <div id='div'>div</div> paragraph text</div>
1120 )HTML");
1121 Node* first_paragraph = GetDocument().getElementById("div")->nextSibling();
1122 const auto& start = Position(first_paragraph, 11);
1123 const auto& end = Position(first_paragraph, 15);
1124 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1125
1126 EXPECT_EQ("paragraph", GetDocument()
1127 .GetFrame()
1128 ->GetTextFragmentSelectorGenerator()
1129 ->GetPreviousTextBlockForTesting(start));
1130 }
1131
1132 // Check the case when available prefix includes non-block element but stops at
1133 // nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedBlockInNestedText)1134 TEST_F(TextFragmentSelectorGeneratorTest,
1135 GetPreviousTextBlock_NestedBlockInNestedText) {
1136 SimRequest request("https://example.com/test.html", "text/html");
1137 LoadURL("https://example.com/test.html");
1138 request.Complete(R"HTML(
1139 <!DOCTYPE html>
1140 <div id='first'>First <b><div id='div'>div</div>bold</b> paragraph text</div>
1141 )HTML");
1142 Node* first_paragraph = GetDocument().getElementById("first")->lastChild();
1143 const auto& start = Position(first_paragraph, 11);
1144 const auto& end = Position(first_paragraph, 15);
1145 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1146
1147 EXPECT_EQ("bold paragraph", GetDocument()
1148 .GetFrame()
1149 ->GetTextFragmentSelectorGenerator()
1150 ->GetPreviousTextBlockForTesting(start));
1151 }
1152
1153 // Check the case when available prefix includes invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_NestedInvisibleBlock)1154 TEST_F(TextFragmentSelectorGeneratorTest,
1155 GetPreviousTextBlock_NestedInvisibleBlock) {
1156 SimRequest request("https://example.com/test.html", "text/html");
1157 LoadURL("https://example.com/test.html");
1158 request.Complete(R"HTML(
1159 <!DOCTYPE html>
1160 <div id='first'>First <div id='div' style='display:none'>invisible</div> paragraph text</div>
1161 )HTML");
1162 Node* first_paragraph = GetDocument().getElementById("div")->nextSibling();
1163 const auto& start = Position(first_paragraph, 0);
1164 const auto& end = Position(first_paragraph, 10);
1165 ASSERT_EQ("paragraph", PlainText(EphemeralRange(start, end)));
1166
1167 EXPECT_EQ("First", GetDocument()
1168 .GetFrame()
1169 ->GetTextFragmentSelectorGenerator()
1170 ->GetPreviousTextBlockForTesting(start));
1171 }
1172
1173 // Check the case when previous node is used for available prefix when selection
1174 // is not at index=0 but there is only space before it.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_SpacesBeforeSelection)1175 TEST_F(TextFragmentSelectorGeneratorTest,
1176 GetPreviousTextBlock_SpacesBeforeSelection) {
1177 SimRequest request("https://example.com/test.html", "text/html");
1178 LoadURL("https://example.com/test.html");
1179 request.Complete(R"HTML(
1180 <!DOCTYPE html>
1181 <p id='first'>First paragraph text</p>
1182 <p id='second'>
1183 Second paragraph text
1184 </p>
1185 )HTML");
1186 Node* second_paragraph = GetDocument().getElementById("second")->firstChild();
1187 const auto& start = Position(second_paragraph, 6);
1188 const auto& end = Position(second_paragraph, 13);
1189 ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1190
1191 EXPECT_EQ("First paragraph text",
1192 GetDocument()
1193 .GetFrame()
1194 ->GetTextFragmentSelectorGenerator()
1195 ->GetPreviousTextBlockForTesting(start));
1196 }
1197
1198 // Check the case when previous node is used for available prefix when selection
1199 // is not at index=0 but there is only invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetPreviousTextBlock_InvisibleBeforeSelection)1200 TEST_F(TextFragmentSelectorGeneratorTest,
1201 GetPreviousTextBlock_InvisibleBeforeSelection) {
1202 SimRequest request("https://example.com/test.html", "text/html");
1203 LoadURL("https://example.com/test.html");
1204 request.Complete(R"HTML(
1205 <!DOCTYPE html>
1206 <p id='first'>First paragraph text</p>
1207 <div id='second'>
1208 <p id='invisible' style='display:none'>
1209 invisible text
1210 </p>
1211 Second paragraph text
1212 </div>
1213 )HTML");
1214 Node* second_paragraph =
1215 GetDocument().getElementById("invisible")->nextSibling();
1216 const auto& start = Position(second_paragraph, 6);
1217 const auto& end = Position(second_paragraph, 13);
1218 ASSERT_EQ("Second", PlainText(EphemeralRange(start, end)));
1219
1220 EXPECT_EQ("First paragraph text",
1221 GetDocument()
1222 .GetFrame()
1223 ->GetTextFragmentSelectorGenerator()
1224 ->GetPreviousTextBlockForTesting(start));
1225 }
1226
1227 // Similar test for suffix.
1228
1229 // Basic test case for |GetNextTextBlock|.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock)1230 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock) {
1231 SimRequest request("https://example.com/test.html", "text/html");
1232 LoadURL("https://example.com/test.html");
1233 request.Complete(R"HTML(
1234 <!DOCTYPE html>
1235 <p id='first'>First paragraph text</p>
1236 )HTML");
1237 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1238 const auto& start = Position(first_paragraph, 0);
1239 const auto& end = Position(first_paragraph, 5);
1240 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1241
1242 EXPECT_EQ("paragraph text", GetDocument()
1243 .GetFrame()
1244 ->GetTextFragmentSelectorGenerator()
1245 ->GetNextTextBlockForTesting(end));
1246 }
1247
1248 // Check the case when available suffix contains collapsible space.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_ExtraSpace)1249 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_ExtraSpace) {
1250 SimRequest request("https://example.com/test.html", "text/html");
1251 LoadURL("https://example.com/test.html");
1252 request.Complete(R"HTML(
1253 <!DOCTYPE html>
1254 <p id='first'>First paragraph
1255
1256
1257 text</p>
1258 )HTML");
1259 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1260 const auto& start = Position(first_paragraph, 0);
1261 const auto& end = Position(first_paragraph, 5);
1262 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1263
1264 EXPECT_EQ("paragraph text", GetDocument()
1265 .GetFrame()
1266 ->GetTextFragmentSelectorGenerator()
1267 ->GetNextTextBlockForTesting(end));
1268 }
1269
1270 // Check the case when available suffix is complete text content of the next
1271 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextNode)1272 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NextNode) {
1273 SimRequest request("https://example.com/test.html", "text/html");
1274 LoadURL("https://example.com/test.html");
1275 request.Complete(R"HTML(
1276 <!DOCTYPE html>
1277 <p id='first'>First paragraph text</p>
1278 <p id='second'>Second paragraph text</p>
1279 )HTML");
1280 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1281 const auto& start = Position(first_paragraph, 0);
1282 const auto& end = Position(first_paragraph, 20);
1283 ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1284
1285 EXPECT_EQ("Second paragraph text", GetDocument()
1286 .GetFrame()
1287 ->GetTextFragmentSelectorGenerator()
1288 ->GetNextTextBlockForTesting(end));
1289 }
1290
1291 // Check the case when there is a commented block between selection and the
1292 // available suffix.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextNode_WithComment)1293 TEST_F(TextFragmentSelectorGeneratorTest,
1294 GetNextTextBlock_NextNode_WithComment) {
1295 SimRequest request("https://example.com/test.html", "text/html");
1296 LoadURL("https://example.com/test.html");
1297 request.Complete(R"HTML(
1298 <!DOCTYPE html>
1299 <p id='first'>First paragraph text</p>
1300 <!--
1301 multiline comment that should be ignored.
1302 //-->
1303 <p id='second'>Second paragraph text</p>
1304 )HTML");
1305 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1306 const auto& start = Position(first_paragraph, 0);
1307 const auto& end = Position(first_paragraph, 20);
1308 ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1309
1310 EXPECT_EQ("Second paragraph text", GetDocument()
1311 .GetFrame()
1312 ->GetTextFragmentSelectorGenerator()
1313 ->GetNextTextBlockForTesting(end));
1314 }
1315
1316 // Check the case when available suffix is a text node outside of selection
1317 // block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NextTextNode)1318 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NextTextNode) {
1319 SimRequest request("https://example.com/test.html", "text/html");
1320 LoadURL("https://example.com/test.html");
1321 request.Complete(R"HTML(
1322 <!DOCTYPE html>
1323 <p id='first'>First paragraph text</p>
1324 text
1325 )HTML");
1326 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1327 const auto& start = Position(first_paragraph, 0);
1328 const auto& end = Position(first_paragraph, 20);
1329 ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1330
1331 EXPECT_EQ("text", GetDocument()
1332 .GetFrame()
1333 ->GetTextFragmentSelectorGenerator()
1334 ->GetNextTextBlockForTesting(end));
1335 }
1336
1337 // Check the case when available suffix is a parent node text content outside of
1338 // selection block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_ParentNode)1339 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_ParentNode) {
1340 SimRequest request("https://example.com/test.html", "text/html");
1341 LoadURL("https://example.com/test.html");
1342 request.Complete(R"HTML(
1343 <!DOCTYPE html>
1344 <div><p id='first'>First paragraph text</p> nested</div>
1345 )HTML");
1346 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1347 const auto& start = Position(first_paragraph, 0);
1348 const auto& end = Position(first_paragraph, 20);
1349 ASSERT_EQ("First paragraph text", PlainText(EphemeralRange(start, end)));
1350
1351 EXPECT_EQ("nested", GetDocument()
1352 .GetFrame()
1353 ->GetTextFragmentSelectorGenerator()
1354 ->GetNextTextBlockForTesting(end));
1355 }
1356
1357 // Check the case when available suffix contains non-block tag(e.g. <b>).
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedTextNode)1358 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NestedTextNode) {
1359 SimRequest request("https://example.com/test.html", "text/html");
1360 LoadURL("https://example.com/test.html");
1361 request.Complete(R"HTML(
1362 <!DOCTYPE html>
1363 <p id='first'>First <b>bold text</b> paragraph text</p>
1364 )HTML");
1365 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1366 const auto& start = Position(first_paragraph, 0);
1367 const auto& end = Position(first_paragraph, 5);
1368 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1369
1370 EXPECT_EQ("bold text paragraph text", GetDocument()
1371 .GetFrame()
1372 ->GetTextFragmentSelectorGenerator()
1373 ->GetNextTextBlockForTesting(end));
1374 }
1375
1376 // Check the case when available suffix is collected until nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedBlock)1377 TEST_F(TextFragmentSelectorGeneratorTest, GetNextTextBlock_NestedBlock) {
1378 SimRequest request("https://example.com/test.html", "text/html");
1379 LoadURL("https://example.com/test.html");
1380 request.Complete(R"HTML(
1381 <!DOCTYPE html>
1382 <div id='first'>First paragraph <div id='div'>div</div> text</div>
1383 )HTML");
1384 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1385 const auto& start = Position(first_paragraph, 0);
1386 const auto& end = Position(first_paragraph, 5);
1387 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1388
1389 EXPECT_EQ("paragraph", GetDocument()
1390 .GetFrame()
1391 ->GetTextFragmentSelectorGenerator()
1392 ->GetNextTextBlockForTesting(end));
1393 }
1394
1395 // Check the case when available suffix includes non-block element but stops at
1396 // nested block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedBlockInNestedText)1397 TEST_F(TextFragmentSelectorGeneratorTest,
1398 GetNextTextBlock_NestedBlockInNestedText) {
1399 SimRequest request("https://example.com/test.html", "text/html");
1400 LoadURL("https://example.com/test.html");
1401 request.Complete(R"HTML(
1402 <!DOCTYPE html>
1403 <div id='first'>First <b>bold<div id='div'>div</div></b> paragraph text</div>
1404 )HTML");
1405 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1406 const auto& start = Position(first_paragraph, 0);
1407 const auto& end = Position(first_paragraph, 5);
1408 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1409
1410 EXPECT_EQ("bold", GetDocument()
1411 .GetFrame()
1412 ->GetTextFragmentSelectorGenerator()
1413 ->GetNextTextBlockForTesting(end));
1414 }
1415
1416 // Check the case when available suffix includes invisible block.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_NestedInvisibleBlock)1417 TEST_F(TextFragmentSelectorGeneratorTest,
1418 GetNextTextBlock_NestedInvisibleBlock) {
1419 SimRequest request("https://example.com/test.html", "text/html");
1420 LoadURL("https://example.com/test.html");
1421 request.Complete(R"HTML(
1422 <!DOCTYPE html>
1423 <div id='first'>First <div id='div' style='display:none'>invisible</div> paragraph text</div>
1424 )HTML");
1425 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1426 const auto& start = Position(first_paragraph, 0);
1427 const auto& end = Position(first_paragraph, 5);
1428 ASSERT_EQ("First", PlainText(EphemeralRange(start, end)));
1429
1430 EXPECT_EQ("paragraph text", GetDocument()
1431 .GetFrame()
1432 ->GetTextFragmentSelectorGenerator()
1433 ->GetNextTextBlockForTesting(end));
1434 }
1435
1436 // Check the case when next node is used for available suffix when selection is
1437 // not at last index but there is only space after it.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_SpacesAfterSelection)1438 TEST_F(TextFragmentSelectorGeneratorTest,
1439 GetNextTextBlock_SpacesAfterSelection) {
1440 SimRequest request("https://example.com/test.html", "text/html");
1441 LoadURL("https://example.com/test.html");
1442 request.Complete(R"HTML(
1443 <!DOCTYPE html>
1444 <p id='first'>
1445 First paragraph text
1446 </p>
1447 <p id='second'>
1448 Second paragraph text
1449 </p>
1450 )HTML");
1451 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1452 const auto& start = Position(first_paragraph, 23);
1453 const auto& end = Position(first_paragraph, 27);
1454 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1455
1456 EXPECT_EQ("Second paragraph text", GetDocument()
1457 .GetFrame()
1458 ->GetTextFragmentSelectorGenerator()
1459 ->GetNextTextBlockForTesting(end));
1460 }
1461
1462 // Check the case when next node is used for available suffix when selection is
1463 // not at last index but there is only invisible block after it.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_InvisibleAfterSelection)1464 TEST_F(TextFragmentSelectorGeneratorTest,
1465 GetNextTextBlock_InvisibleAfterSelection) {
1466 SimRequest request("https://example.com/test.html", "text/html");
1467 LoadURL("https://example.com/test.html");
1468 request.Complete(R"HTML(
1469 <!DOCTYPE html>
1470 <div id='first'>
1471 First paragraph text
1472 <div id='invisible' style='display:none'>
1473 invisible text
1474 </div>
1475 </div>
1476 <p id='second'>
1477 Second paragraph text
1478 </p>
1479 )HTML");
1480 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1481 const auto& start = Position(first_paragraph, 23);
1482 const auto& end = Position(first_paragraph, 27);
1483 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1484
1485 EXPECT_EQ("Second paragraph text", GetDocument()
1486 .GetFrame()
1487 ->GetTextFragmentSelectorGenerator()
1488 ->GetNextTextBlockForTesting(end));
1489 }
1490
1491 // Check the case when previous node is used for available prefix when selection
1492 // is not at last index but there is only invisible block. Invisible block
1493 // contains another block which also should be invisible.
TEST_F(TextFragmentSelectorGeneratorTest,GetNextTextBlock_InvisibleAfterSelection_WithNestedInvisible)1494 TEST_F(TextFragmentSelectorGeneratorTest,
1495 GetNextTextBlock_InvisibleAfterSelection_WithNestedInvisible) {
1496 SimRequest request("https://example.com/test.html", "text/html");
1497 LoadURL("https://example.com/test.html");
1498 request.Complete(R"HTML(
1499 <!DOCTYPE html>
1500 <div id='first'>
1501 First paragraph text
1502 <div id='invisible' style='display:none'>
1503 invisible text
1504 <div>
1505 nested invisible text
1506 </div
1507 </div>
1508 </div>
1509 <p id='second'>
1510 Second paragraph text
1511 <div id='invisible' style='display:none'>
1512 invisible text
1513 <div>
1514 nested invisible text
1515 </div
1516 </div>
1517 </p>
1518 test
1519 )HTML");
1520 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1521 const auto& start = Position(first_paragraph, 23);
1522 const auto& end = Position(first_paragraph, 27);
1523 ASSERT_EQ("text", PlainText(EphemeralRange(start, end)));
1524
1525 EXPECT_EQ("Second paragraph text", GetDocument()
1526 .GetFrame()
1527 ->GetTextFragmentSelectorGenerator()
1528 ->GetNextTextBlockForTesting(end));
1529 }
1530
1531 // Checks that selection in the same text node is considerered uninterrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_OneTextNode)1532 TEST_F(TextFragmentSelectorGeneratorTest,
1533 IsInSameUninterruptedBlock_OneTextNode) {
1534 SimRequest request("https://example.com/test.html", "text/html");
1535 LoadURL("https://example.com/test.html");
1536 request.Complete(R"HTML(
1537 <!DOCTYPE html>
1538 <div id='first'>First paragraph text</div>
1539 )HTML");
1540 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1541 const auto& start = Position(first_paragraph, 0);
1542 const auto& end = Position(first_paragraph, 15);
1543 ASSERT_EQ("First paragraph", PlainText(EphemeralRange(start, end)));
1544
1545 EXPECT_TRUE(GetDocument()
1546 .GetFrame()
1547 ->GetTextFragmentSelectorGenerator()
1548 ->IsInSameUninterruptedBlockForTesting(start, end));
1549 }
1550
1551 // Checks that selection in the same text node with nested non-block element is
1552 // considerered uninterrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_NonBlockInterruption)1553 TEST_F(TextFragmentSelectorGeneratorTest,
1554 IsInSameUninterruptedBlock_NonBlockInterruption) {
1555 SimRequest request("https://example.com/test.html", "text/html");
1556 LoadURL("https://example.com/test.html");
1557 request.Complete(R"HTML(
1558 <!DOCTYPE html>
1559 <div id='first'>First <i>styled text</i> paragraph text</div>
1560 )HTML");
1561 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1562 const auto& start = Position(first_paragraph, 0);
1563 const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
1564 ASSERT_EQ("First styled text paragraph",
1565 PlainText(EphemeralRange(start, end)));
1566
1567 EXPECT_TRUE(GetDocument()
1568 .GetFrame()
1569 ->GetTextFragmentSelectorGenerator()
1570 ->IsInSameUninterruptedBlockForTesting(start, end));
1571 }
1572
1573 // Checks that selection in the same text node with nested block element is
1574 // considerered interrupted.
TEST_F(TextFragmentSelectorGeneratorTest,IsInSameUninterruptedBlock_BlockInterruption)1575 TEST_F(TextFragmentSelectorGeneratorTest,
1576 IsInSameUninterruptedBlock_BlockInterruption) {
1577 SimRequest request("https://example.com/test.html", "text/html");
1578 LoadURL("https://example.com/test.html");
1579 request.Complete(R"HTML(
1580 <!DOCTYPE html>
1581 <div id='first'>First <div>block text</div> paragraph text</div>
1582 )HTML");
1583 Node* first_paragraph = GetDocument().getElementById("first")->firstChild();
1584 const auto& start = Position(first_paragraph, 0);
1585 const auto& end = Position(first_paragraph->nextSibling()->nextSibling(), 10);
1586 ASSERT_EQ("First\nblock text\nparagraph",
1587 PlainText(EphemeralRange(start, end)));
1588
1589 EXPECT_FALSE(GetDocument()
1590 .GetFrame()
1591 ->GetTextFragmentSelectorGenerator()
1592 ->IsInSameUninterruptedBlockForTesting(start, end));
1593 }
1594
1595 } // namespace blink
1596