1 // Copyright 2017 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/editing/suggestion/text_suggestion_controller.h"
6
7 #include "build/build_config.h"
8 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
9 #include "third_party/blink/renderer/core/editing/frame_selection.h"
10 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
11 #include "third_party/blink/renderer/core/editing/markers/suggestion_marker_properties.h"
12 #include "third_party/blink/renderer/core/editing/selection_template.h"
13 #include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
14 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
15 #include "third_party/blink/renderer/core/editing/visible_selection.h"
16 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
17
18 using ui::mojom::ImeTextSpanThickness;
19 using ui::mojom::ImeTextSpanUnderlineStyle;
20
21 namespace blink {
22
23 class TextSuggestionControllerTest : public EditingTestBase {
24 public:
IsTextSuggestionHostAvailable()25 bool IsTextSuggestionHostAvailable() {
26 return bool(GetDocument()
27 .GetFrame()
28 ->GetTextSuggestionController()
29 .text_suggestion_host_.is_bound());
30 }
31
ShowSuggestionMenu(const HeapVector<std::pair<Member<const Text>,Member<DocumentMarker>>> & node_suggestion_marker_pairs,size_t max_number_of_suggestions)32 void ShowSuggestionMenu(
33 const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
34 node_suggestion_marker_pairs,
35 size_t max_number_of_suggestions) {
36 GetDocument().GetFrame()->GetTextSuggestionController().ShowSuggestionMenu(
37 node_suggestion_marker_pairs, max_number_of_suggestions);
38 }
39
ComputeRangeSurroundingCaret(const PositionInFlatTree & caret_position)40 EphemeralRangeInFlatTree ComputeRangeSurroundingCaret(
41 const PositionInFlatTree& caret_position) {
42 const Node* const position_node = caret_position.ComputeContainerNode();
43 const unsigned position_offset_in_node =
44 caret_position.ComputeOffsetInContainerNode();
45 // See ComputeRangeSurroundingCaret() in TextSuggestionController.
46 return EphemeralRangeInFlatTree(
47 PositionInFlatTree(position_node, position_offset_in_node - 1),
48 PositionInFlatTree(position_node, position_offset_in_node + 1));
49 }
50 };
51
TEST_F(TextSuggestionControllerTest,ApplySpellCheckSuggestion)52 TEST_F(TextSuggestionControllerTest, ApplySpellCheckSuggestion) {
53 SetBodyContent(
54 "<div contenteditable>"
55 "spllchck"
56 "</div>");
57 Element* div = GetDocument().QuerySelector("div");
58 Node* text = div->firstChild();
59
60 GetDocument().Markers().AddActiveSuggestionMarker(
61 EphemeralRange(Position(text, 0), Position(text, 8)), Color::kBlack,
62 ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
63 Color::kBlack, Color::kBlack);
64 // Select immediately before misspelling
65 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
66 SelectionInDOMTree::Builder()
67 .SetBaseAndExtent(Position(text, 0), Position(text, 0))
68 .Build());
69 GetDocument()
70 .GetFrame()
71 ->GetTextSuggestionController()
72 .ApplySpellCheckSuggestion("spellcheck");
73
74 EXPECT_EQ("spellcheck", text->textContent());
75
76 // Cursor should be at end of replaced text
77 const VisibleSelectionInFlatTree& selection =
78 GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
79 EXPECT_EQ(text, selection.Start().ComputeContainerNode());
80 EXPECT_EQ(10, selection.Start().ComputeOffsetInContainerNode());
81 EXPECT_EQ(text, selection.End().ComputeContainerNode());
82 EXPECT_EQ(10, selection.End().ComputeOffsetInContainerNode());
83 }
84
85 // Flaky on Android: http://crbug.com/1104700
86 #if defined(OS_ANDROID)
87 #define MAYBE_ApplyTextSuggestion DISABLED_ApplyTextSuggestion
88 #else
89 #define MAYBE_ApplyTextSuggestion ApplyTextSuggestion
90 #endif
TEST_F(TextSuggestionControllerTest,ApplyTextSuggestion)91 TEST_F(TextSuggestionControllerTest, ApplyTextSuggestion) {
92 SetBodyContent(
93 "<div contenteditable>"
94 "word1 word2 word3 word4"
95 "</div>");
96 Element* div = GetDocument().QuerySelector("div");
97 auto* text = To<Text>(div->firstChild());
98
99 // Add marker on "word1". This marker should *not* be cleared by the
100 // replace operation.
101 GetDocument().Markers().AddSuggestionMarker(
102 EphemeralRange(Position(text, 0), Position(text, 5)),
103 SuggestionMarkerProperties::Builder()
104 .SetSuggestions(Vector<String>({"marker1"}))
105 .Build());
106
107 // Add marker on "word1 word2 word3 word4". This marker should *not* be
108 // cleared by the replace operation.
109 GetDocument().Markers().AddSuggestionMarker(
110 EphemeralRange(Position(text, 0), Position(text, 23)),
111 SuggestionMarkerProperties::Builder()
112 .SetSuggestions(Vector<String>({"marker2"}))
113 .Build());
114
115 // Add marker on "word2 word3". This marker should *not* be cleared by the
116 // replace operation.
117 GetDocument().Markers().AddSuggestionMarker(
118 EphemeralRange(Position(text, 6), Position(text, 17)),
119 SuggestionMarkerProperties::Builder()
120 .SetSuggestions(Vector<String>({"marker3"}))
121 .Build());
122
123 // Add marker on "word4". This marker should *not* be cleared by the
124 // replace operation.
125 GetDocument().Markers().AddSuggestionMarker(
126 EphemeralRange(Position(text, 18), Position(text, 23)),
127 SuggestionMarkerProperties::Builder()
128 .SetSuggestions(Vector<String>({"marker4"}))
129 .Build());
130
131 // Add marker on "word1 word2". This marker should be cleared by the
132 // replace operation.
133 GetDocument().Markers().AddSuggestionMarker(
134 EphemeralRange(Position(text, 0), Position(text, 11)),
135 SuggestionMarkerProperties::Builder()
136 .SetSuggestions(Vector<String>({"marker5"}))
137 .Build());
138
139 // Add marker on "word3 word4". This marker should be cleared by the
140 // replace operation.
141 GetDocument().Markers().AddSuggestionMarker(
142 EphemeralRange(Position(text, 12), Position(text, 23)),
143 SuggestionMarkerProperties::Builder()
144 .SetSuggestions(Vector<String>({"marker6"}))
145 .Build());
146
147 // Select immediately before word2.
148 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
149 SelectionInDOMTree::Builder()
150 .SetBaseAndExtent(Position(text, 6), Position(text, 6))
151 .Build());
152
153 // Replace "word2 word3" with "marker3" (marker should have tag 3; tags start
154 // from 1, not 0).
155 GetDocument().GetFrame()->GetTextSuggestionController().ApplyTextSuggestion(
156 3, 0);
157
158 // This returns the markers sorted by start offset; we need them sorted by
159 // start *and* end offset, since we have multiple markers starting at 0.
160 DocumentMarkerVector markers = GetDocument().Markers().MarkersFor(*text);
161 std::sort(markers.begin(), markers.end(),
162 [](const DocumentMarker* marker1, const DocumentMarker* marker2) {
163 if (marker1->StartOffset() != marker2->StartOffset())
164 return marker1->StartOffset() < marker2->StartOffset();
165 return marker1->EndOffset() < marker2->EndOffset();
166 });
167
168 EXPECT_EQ(4u, markers.size());
169
170 // marker1
171 EXPECT_EQ(0u, markers[0]->StartOffset());
172 EXPECT_EQ(5u, markers[0]->EndOffset());
173
174 // marker2
175 EXPECT_EQ(0u, markers[1]->StartOffset());
176 EXPECT_EQ(19u, markers[1]->EndOffset());
177
178 // marker3
179 EXPECT_EQ(6u, markers[2]->StartOffset());
180 EXPECT_EQ(13u, markers[2]->EndOffset());
181
182 const auto* const suggestion_marker = To<SuggestionMarker>(markers[2].Get());
183 EXPECT_EQ(1u, suggestion_marker->Suggestions().size());
184 EXPECT_EQ(String("word2 word3"), suggestion_marker->Suggestions()[0]);
185
186 // marker4
187 EXPECT_EQ(14u, markers[3]->StartOffset());
188 EXPECT_EQ(19u, markers[3]->EndOffset());
189
190 // marker5 and marker6 should've been cleared
191
192 // Cursor should be at end of replaced text
193 const VisibleSelectionInFlatTree& selection =
194 GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
195 EXPECT_EQ(text, selection.Start().ComputeContainerNode());
196 EXPECT_EQ(13, selection.Start().ComputeOffsetInContainerNode());
197 EXPECT_EQ(text, selection.End().ComputeContainerNode());
198 EXPECT_EQ(13, selection.End().ComputeOffsetInContainerNode());
199 }
200
TEST_F(TextSuggestionControllerTest,ApplyingMisspellingTextSuggestionClearsMarker)201 TEST_F(TextSuggestionControllerTest,
202 ApplyingMisspellingTextSuggestionClearsMarker) {
203 SetBodyContent(
204 "<div contenteditable>"
205 "mispelled"
206 "</div>");
207 Element* div = GetDocument().QuerySelector("div");
208 auto* text = To<Text>(div->firstChild());
209
210 // Add marker on "mispelled". This marker should be cleared by the replace
211 // operation.
212 GetDocument().Markers().AddSuggestionMarker(
213 EphemeralRange(Position(text, 0), Position(text, 9)),
214 SuggestionMarkerProperties::Builder()
215 .SetType(SuggestionMarker::SuggestionType::kMisspelling)
216 .SetSuggestions(Vector<String>({"misspelled"}))
217 .Build());
218
219 // Check the tag for the marker that was just added (the current tag value is
220 // not reset between test cases).
221 int32_t marker_tag =
222 To<SuggestionMarker>(GetDocument().Markers().MarkersFor(*text)[0].Get())
223 ->Tag();
224
225 // Select immediately before "mispelled".
226 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
227 SelectionInDOMTree::Builder()
228 .SetBaseAndExtent(Position(text, 0), Position(text, 0))
229 .Build());
230
231 // Replace "mispelled" with "misspelled".
232 GetDocument().GetFrame()->GetTextSuggestionController().ApplyTextSuggestion(
233 marker_tag, 0);
234
235 EXPECT_EQ(0u, GetDocument().Markers().MarkersFor(*text).size());
236 EXPECT_EQ("misspelled", text->textContent());
237 }
238
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtEnd)239 TEST_F(TextSuggestionControllerTest, DeleteActiveSuggestionRange_DeleteAtEnd) {
240 SetBodyContent(
241 "<div contenteditable>"
242 "word1 word2"
243 "</div>");
244 Element* div = GetDocument().QuerySelector("div");
245 Node* text = div->firstChild();
246
247 // Mark "word2" as the active suggestion range
248 GetDocument().Markers().AddActiveSuggestionMarker(
249 EphemeralRange(Position(text, 6), Position(text, 11)),
250 Color::kTransparent, ImeTextSpanThickness::kThin,
251 ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
252 // Select immediately before word2
253 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
254 SelectionInDOMTree::Builder()
255 .SetBaseAndExtent(Position(text, 6), Position(text, 6))
256 .Build());
257 GetDocument()
258 .GetFrame()
259 ->GetTextSuggestionController()
260 .DeleteActiveSuggestionRange();
261
262 EXPECT_EQ("word1\xA0", text->textContent());
263 }
264
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteInMiddle)265 TEST_F(TextSuggestionControllerTest,
266 DeleteActiveSuggestionRange_DeleteInMiddle) {
267 SetBodyContent(
268 "<div contenteditable>"
269 "word1 word2 word3"
270 "</div>");
271 Element* div = GetDocument().QuerySelector("div");
272 Node* text = div->firstChild();
273
274 // Mark "word2" as the active suggestion range
275 GetDocument().Markers().AddActiveSuggestionMarker(
276 EphemeralRange(Position(text, 6), Position(text, 11)),
277 Color::kTransparent, ImeTextSpanThickness::kThin,
278 ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
279 // Select immediately before word2
280 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
281 SelectionInDOMTree::Builder()
282 .SetBaseAndExtent(Position(text, 6), Position(text, 6))
283 .Build());
284 GetDocument()
285 .GetFrame()
286 ->GetTextSuggestionController()
287 .DeleteActiveSuggestionRange();
288
289 // One of the extra spaces around "word2" should have been removed
290 EXPECT_EQ("word1\xA0word3", text->textContent());
291 }
292
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtBeginningWithSpaceAfter)293 TEST_F(TextSuggestionControllerTest,
294 DeleteActiveSuggestionRange_DeleteAtBeginningWithSpaceAfter) {
295 SetBodyContent(
296 "<div contenteditable>"
297 "word1 word2"
298 "</div>");
299 Element* div = GetDocument().QuerySelector("div");
300 Node* text = div->firstChild();
301
302 // Mark "word1" as the active suggestion range
303 GetDocument().Markers().AddActiveSuggestionMarker(
304 EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
305 ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
306 Color::kBlack, Color::kBlack);
307 // Select immediately before word1
308 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
309 SelectionInDOMTree::Builder()
310 .SetBaseAndExtent(Position(text, 0), Position(text, 0))
311 .Build());
312 GetDocument()
313 .GetFrame()
314 ->GetTextSuggestionController()
315 .DeleteActiveSuggestionRange();
316
317 // The space after "word1" should have been removed (to avoid leaving an
318 // empty space at the beginning of the composition)
319 EXPECT_EQ("word2", text->textContent());
320 }
321
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteEntireRange)322 TEST_F(TextSuggestionControllerTest,
323 DeleteActiveSuggestionRange_DeleteEntireRange) {
324 SetBodyContent(
325 "<div contenteditable>"
326 "word1"
327 "</div>");
328 Element* div = GetDocument().QuerySelector("div");
329 Node* text = div->firstChild();
330
331 // Mark "word1" as the active suggestion range
332 GetDocument().Markers().AddActiveSuggestionMarker(
333 EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
334 ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
335 Color::kBlack, Color::kBlack);
336 // Select immediately before word1
337 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
338 SelectionInDOMTree::Builder()
339 .SetBaseAndExtent(Position(text, 0), Position(text, 0))
340 .Build());
341 GetDocument()
342 .GetFrame()
343 ->GetTextSuggestionController()
344 .DeleteActiveSuggestionRange();
345
346 EXPECT_EQ("", text->textContent());
347 }
348
349 // The following two cases test situations that probably shouldn't occur in
350 // normal use (spell check/suggestoin markers not spanning a whole word), but
351 // are included anyway to verify that DeleteActiveSuggestionRange() is
352 // well-behaved in these cases
353
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteRangeWithTextBeforeAndSpaceAfter)354 TEST_F(TextSuggestionControllerTest,
355 DeleteActiveSuggestionRange_DeleteRangeWithTextBeforeAndSpaceAfter) {
356 SetBodyContent(
357 "<div contenteditable>"
358 "word1word2 word3"
359 "</div>");
360 Element* div = GetDocument().QuerySelector("div");
361 Node* text = div->firstChild();
362
363 // Mark "word2" as the active suggestion range
364 GetDocument().Markers().AddActiveSuggestionMarker(
365 EphemeralRange(Position(text, 5), Position(text, 10)),
366 Color::kTransparent, ImeTextSpanThickness::kThin,
367 ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
368 // Select immediately before word2
369 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
370 SelectionInDOMTree::Builder()
371 .SetBaseAndExtent(Position(text, 5), Position(text, 5))
372 .Build());
373 GetDocument()
374 .GetFrame()
375 ->GetTextSuggestionController()
376 .DeleteActiveSuggestionRange();
377
378 EXPECT_EQ("word1\xA0word3", text->textContent());
379 }
380
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteRangeWithSpaceBeforeAndTextAfter)381 TEST_F(TextSuggestionControllerTest,
382 DeleteActiveSuggestionRange_DeleteRangeWithSpaceBeforeAndTextAfter) {
383 SetBodyContent(
384 "<div contenteditable>"
385 "word1 word2word3"
386 "</div>");
387 Element* div = GetDocument().QuerySelector("div");
388 Node* text = div->firstChild();
389
390 // Mark "word2" as the active suggestion range
391 GetDocument().Markers().AddActiveSuggestionMarker(
392 EphemeralRange(Position(text, 6), Position(text, 11)),
393 Color::kTransparent, ImeTextSpanThickness::kThin,
394 ImeTextSpanUnderlineStyle::kSolid, Color::kBlack, Color::kBlack);
395 // Select immediately before word2
396 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
397 SelectionInDOMTree::Builder()
398 .SetBaseAndExtent(Position(text, 6), Position(text, 6))
399 .Build());
400 GetDocument()
401 .GetFrame()
402 ->GetTextSuggestionController()
403 .DeleteActiveSuggestionRange();
404
405 EXPECT_EQ("word1\xA0word3", text->textContent());
406 }
407
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_DeleteAtBeginningWithTextAfter)408 TEST_F(TextSuggestionControllerTest,
409 DeleteActiveSuggestionRange_DeleteAtBeginningWithTextAfter) {
410 SetBodyContent(
411 "<div contenteditable>"
412 "word1word2"
413 "</div>");
414 Element* div = GetDocument().QuerySelector("div");
415 Node* text = div->firstChild();
416
417 // Mark "word1" as the active suggestion range
418 GetDocument().Markers().AddActiveSuggestionMarker(
419 EphemeralRange(Position(text, 0), Position(text, 5)), Color::kTransparent,
420 ImeTextSpanThickness::kThin, ImeTextSpanUnderlineStyle::kSolid,
421 Color::kBlack, Color::kBlack);
422 // Select immediately before word1
423 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
424 SelectionInDOMTree::Builder()
425 .SetBaseAndExtent(Position(text, 0), Position(text, 0))
426 .Build());
427 GetDocument()
428 .GetFrame()
429 ->GetTextSuggestionController()
430 .DeleteActiveSuggestionRange();
431
432 EXPECT_EQ("word2", text->textContent());
433 }
434
TEST_F(TextSuggestionControllerTest,DeleteActiveSuggestionRange_OnNewWordAddedToDictionary)435 TEST_F(TextSuggestionControllerTest,
436 DeleteActiveSuggestionRange_OnNewWordAddedToDictionary) {
437 SetBodyContent(
438 "<div contenteditable>"
439 "embiggen"
440 "</div>");
441 Element* div = GetDocument().QuerySelector("div");
442 Node* text = div->firstChild();
443
444 // Mark "embiggen" as misspelled
445 GetDocument().Markers().AddSpellingMarker(
446 EphemeralRange(Position(text, 0), Position(text, 8)));
447 // Select inside before "embiggen"
448 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
449 SelectionInDOMTree::Builder()
450 .SetBaseAndExtent(Position(text, 1), Position(text, 1))
451 .Build());
452
453 // Add some other word to the dictionary
454 GetDocument()
455 .GetFrame()
456 ->GetTextSuggestionController()
457 .OnNewWordAddedToDictionary("cromulent");
458 // Verify the spelling marker is still present
459 EXPECT_NE(nullptr, GetDocument()
460 .GetFrame()
461 ->GetSpellChecker()
462 .GetSpellCheckMarkerUnderSelection()
463 .first);
464
465 // Add "embiggen" to the dictionary
466 GetDocument()
467 .GetFrame()
468 ->GetTextSuggestionController()
469 .OnNewWordAddedToDictionary("embiggen");
470 // Verify the spelling marker is gone
471 EXPECT_EQ(nullptr, GetDocument()
472 .GetFrame()
473 ->GetSpellChecker()
474 .GetSpellCheckMarkerUnderSelection()
475 .first);
476 }
477
TEST_F(TextSuggestionControllerTest,CallbackHappensAfterDocumentDestroyed)478 TEST_F(TextSuggestionControllerTest, CallbackHappensAfterDocumentDestroyed) {
479 LocalFrame& frame = *GetDocument().GetFrame();
480 frame.DomWindow()->FrameDestroyed();
481
482 // Shouldn't crash
483 frame.GetTextSuggestionController().SuggestionMenuTimeoutCallback(0);
484 }
485
TEST_F(TextSuggestionControllerTest,SuggestionMarkerWithEmptySuggestion)486 TEST_F(TextSuggestionControllerTest, SuggestionMarkerWithEmptySuggestion) {
487 SetBodyContent(
488 "<div contenteditable>"
489 "hello"
490 "</div>");
491 Element* div = GetDocument().QuerySelector("div");
492 auto* text = To<Text>(div->firstChild());
493
494 // Set suggestion marker with empty suggestion list.
495 GetDocument().Markers().AddSuggestionMarker(
496 EphemeralRange(Position(text, 0), Position(text, 5)),
497 SuggestionMarkerProperties::Builder()
498 .SetSuggestions(Vector<String>())
499 .Build());
500
501 // Set the caret inside the word.
502 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
503 SelectionInDOMTree::Builder()
504 .SetBaseAndExtent(Position(text, 3), Position(text, 3))
505 .Build());
506
507 // Handle potential suggestion tap on the caret position.
508 GetDocument()
509 .GetFrame()
510 ->GetTextSuggestionController()
511 .HandlePotentialSuggestionTap(PositionInFlatTree(text, 3));
512
513 // We don't trigger menu in this case so there shouldn't be any mojom
514 // connection available.
515 EXPECT_FALSE(IsTextSuggestionHostAvailable());
516
517 const VisibleSelectionInFlatTree& selection =
518 GetFrame().Selection().ComputeVisibleSelectionInFlatTree();
519 EXPECT_FALSE(selection.IsNone());
520
521 const EphemeralRangeInFlatTree& range_to_check =
522 ComputeRangeSurroundingCaret(selection.Start());
523
524 const HeapVector<std::pair<Member<const Text>, Member<DocumentMarker>>>&
525 node_suggestion_marker_pairs =
526 GetFrame().GetDocument()->Markers().MarkersIntersectingRange(
527 range_to_check, DocumentMarker::MarkerTypes::Suggestion());
528 EXPECT_FALSE(node_suggestion_marker_pairs.IsEmpty());
529
530 // Calling ShowSuggestionMenu() shouldn't crash. See crbug.com/901135.
531 // ShowSuggestionMenu() may still get called because of race condition.
532 ShowSuggestionMenu(node_suggestion_marker_pairs, 3);
533 }
534
TEST_F(TextSuggestionControllerTest,SuggestionMarkerWithSuggestion)535 TEST_F(TextSuggestionControllerTest, SuggestionMarkerWithSuggestion) {
536 SetBodyContent(
537 "<div contenteditable>"
538 "hello"
539 "</div>");
540 Element* div = GetDocument().QuerySelector("div");
541 auto* text = To<Text>(div->firstChild());
542
543 // Set suggestion marker with two suggestions.
544 GetDocument().Markers().AddSuggestionMarker(
545 EphemeralRange(Position(text, 0), Position(text, 5)),
546 SuggestionMarkerProperties::Builder()
547 .SetSuggestions(Vector<String>({"marker1", "marker2"}))
548 .Build());
549
550 // Set the caret inside the word.
551 GetDocument().GetFrame()->Selection().SetSelectionAndEndTyping(
552 SelectionInDOMTree::Builder()
553 .SetBaseAndExtent(Position(text, 3), Position(text, 3))
554 .Build());
555
556 // Handle potential suggestion tap on the caret position.
557 GetDocument()
558 .GetFrame()
559 ->GetTextSuggestionController()
560 .HandlePotentialSuggestionTap(PositionInFlatTree(text, 3));
561
562 EXPECT_TRUE(IsTextSuggestionHostAvailable());
563 }
564
565 } // namespace blink
566