From bc0cbc15d64fc58a1f8167ff892312cef337ed2f Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:43:56 -0700 Subject: [PATCH] Add spell check TextSpan creation logic that doesn't rely on composing region (#123481) Fixes https://github.com/flutter/flutter/issues/119534 by adding extra logic to allow the proper `TextSpan` trees to be build with spell check results that does not rely on the composing region. Also, refreshes the algorithm used to correct spell check results to improve cases where spell check results are out of date, but the composing region line does not help smooth over this fact by giving the framework extra time to update while a word is being composed (because a composing region line would cover up any flashing red underlines). ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] All existing and new tests are passing. [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat --- .../flutter/lib/src/widgets/spell_check.dart | 245 ++++++++++++------ .../test/widgets/spell_check_test.dart | 149 ++++++----- 2 files changed, 246 insertions(+), 148 deletions(-) diff --git a/packages/flutter/lib/src/widgets/spell_check.dart b/packages/flutter/lib/src/widgets/spell_check.dart index 32f77478ab3..a6fb8887e29 100644 --- a/packages/flutter/lib/src/widgets/spell_check.dart +++ b/packages/flutter/lib/src/widgets/spell_check.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show TargetPlatform, defaultTargetPlatform; import 'package:flutter/painting.dart'; import 'package:flutter/services.dart' show SpellCheckResults, SpellCheckService, SuggestionSpan, TextEditingValue; @@ -108,71 +109,63 @@ class SpellCheckConfiguration { List _correctSpellCheckResults( String newText, String resultsText, List results) { final List correctedSpellCheckResults = []; - int spanPointer = 0; int offset = 0; - int foundIndex; - int spanLength; - SuggestionSpan currentSpan; - SuggestionSpan adjustedSpan; - String currentSpanText; - String newSpanText = ''; - bool currentSpanValid = false; - RegExp regex; // Assumes that the order of spans has not been jumbled for optimization // purposes, and will only search since the previously found span. int searchStart = 0; while (spanPointer < results.length) { - // Try finding SuggestionSpan from old results (currentSpan) in new text. - currentSpan = results[spanPointer]; - currentSpanText = + final SuggestionSpan currentSpan = results[spanPointer]; + final String currentSpanText = resultsText.substring(currentSpan.range.start, currentSpan.range.end); + final int spanLength = currentSpan.range.end - currentSpan.range.start; - try { - // currentSpan was found and can be applied to new text. - newSpanText = newText.substring( - currentSpan.range.start + offset, currentSpan.range.end + offset); - currentSpanValid = true; - } catch (e) { - // currentSpan is invalid and needs to be searched for in newText. - currentSpanValid = false; - } + // Try finding SuggestionSpan from resultsText in new text. + final RegExp currentSpanTextRegexp = RegExp('\\b$currentSpanText\\b'); + final int foundIndex = newText.substring(searchStart).indexOf(currentSpanTextRegexp); - if (currentSpanValid && newSpanText == currentSpanText) { - // currentSpan was found at the same index in new text and old text - // (resultsText), so apply it to new text by adding it to the list of + // Check whether word was found exactly where expected or elsewhere in the newText. + final bool currentSpanFoundExactly = currentSpan.range.start == foundIndex + searchStart; + final bool currentSpanFoundExactlyWithOffset = currentSpan.range.start + offset == foundIndex + searchStart; + final bool currentSpanFoundElsewhere = foundIndex >= 0; + + if (currentSpanFoundExactly || currentSpanFoundExactlyWithOffset) { + // currentSpan was found at the same index in newText and resutsText + // or at the same index with the previously calculated adjustment by + // the offset value, so apply it to new text by adding it to the list of // corrected results. - searchStart = currentSpan.range.end + offset; - adjustedSpan = SuggestionSpan( - TextRange( - start: currentSpan.range.start + offset, end: searchStart), - currentSpan.suggestions + final SuggestionSpan adjustedSpan = SuggestionSpan( + TextRange( + start: currentSpan.range.start + offset, + end: currentSpan.range.end + offset, + ), + currentSpan.suggestions, ); + + // Start search for the next misspelled word at the end of currentSpan. + searchStart = currentSpan.range.end + 1 + offset; correctedSpellCheckResults.add(adjustedSpan); - } else { - // Search for currentSpan in new text and if found, apply it to new text - // by adding it to the list of corrected results. - regex = RegExp('\\b$currentSpanText\\b'); - foundIndex = newText.substring(searchStart).indexOf(regex); + } else if (currentSpanFoundElsewhere) { + // Word was pushed forward but not modified. + final int adjustedSpanStart = searchStart + foundIndex; + final int adjustedSpanEnd = adjustedSpanStart + spanLength; + final SuggestionSpan adjustedSpan = SuggestionSpan( + TextRange(start: adjustedSpanStart, end: adjustedSpanEnd), + currentSpan.suggestions, + ); - if (foundIndex >= 0) { - foundIndex += searchStart; - spanLength = currentSpan.range.end - currentSpan.range.start; - searchStart = foundIndex + spanLength; - adjustedSpan = SuggestionSpan( - TextRange(start: foundIndex, end: searchStart), - currentSpan.suggestions - ); - offset = foundIndex - currentSpan.range.start; - - correctedSpellCheckResults.add(adjustedSpan); - } + // Start search for the next misspelled word at the end of the + // adjusted currentSpan. + searchStart = adjustedSpanEnd + 1; + // Adjust offset to reflect the difference between where currentSpan + // was positioned in resultsText versus in newText. + offset = adjustedSpanStart - currentSpan.range.start; + correctedSpellCheckResults.add(adjustedSpan); } spanPointer++; } - return correctedSpellCheckResults; } @@ -201,31 +194,121 @@ TextSpan buildTextSpanWithSpellCheckSuggestions( value.text, spellCheckResultsText, spellCheckResultsSpans); } - return TextSpan( + // We will draw the TextSpan tree based on the composing region, if it is + // available. + // TODO(camsim99): The two separate stratgies for building TextSpan trees + // based on the availability of a composing region should be merged: + // https://github.com/flutter/flutter/issues/124142. + final bool shouldConsiderComposingRegion = defaultTargetPlatform == TargetPlatform.android; + if (shouldConsiderComposingRegion) { + return TextSpan( style: style, - children: _buildSubtreesWithMisspelledWordsIndicated( + children: _buildSubtreesWithComposingRegion( spellCheckResultsSpans, value, style, misspelledTextStyle, - composingWithinCurrentTextRange - ) + composingWithinCurrentTextRange, + ), ); + } + + return TextSpan( + style: style, + children: _buildSubtreesWithoutComposingRegion( + spellCheckResultsSpans, + value, + style, + misspelledTextStyle, + value.selection.baseOffset, + ), + ); } -/// Builds [TextSpan] subtree for text with misspelled words. -List _buildSubtreesWithMisspelledWordsIndicated( +/// Builds the [TextSpan] tree for spell check without considering the composing +/// region. Instead, uses the cursor to identify the word that's actively being +/// edited and shouldn't be spell checked. This is useful for platforms and IMEs +/// that don't use the composing region for the active word. +List _buildSubtreesWithoutComposingRegion( + List? spellCheckSuggestions, + TextEditingValue value, + TextStyle? style, + TextStyle misspelledStyle, + int cursorIndex, +) { + final List textSpanTreeChildren = []; + + int textPointer = 0; + int currentSpanPointer = 0; + int endIndex; + final String text = value.text; + final TextStyle misspelledJointStyle = + style?.merge(misspelledStyle) ?? misspelledStyle; + bool cursorInCurrentSpan = false; + + // Add text interwoven with any misspelled words to the tree. + if (spellCheckSuggestions != null) { + while (textPointer < text.length && + currentSpanPointer < spellCheckSuggestions.length) { + final SuggestionSpan currentSpan = spellCheckSuggestions[currentSpanPointer]; + + if (currentSpan.range.start > textPointer) { + endIndex = currentSpan.range.start < text.length + ? currentSpan.range.start + : text.length; + textSpanTreeChildren.add( + TextSpan( + style: style, + text: text.substring(textPointer, endIndex), + ) + ); + textPointer = endIndex; + } else { + endIndex = + currentSpan.range.end < text.length ? currentSpan.range.end : text.length; + cursorInCurrentSpan = currentSpan.range.start <= cursorIndex && currentSpan.range.end >= cursorIndex; + textSpanTreeChildren.add( + TextSpan( + style: cursorInCurrentSpan + ? style + : misspelledJointStyle, + text: text.substring(currentSpan.range.start, endIndex), + ) + ); + + textPointer = endIndex; + currentSpanPointer++; + } + } + } + + // Add any remaining text to the tree if applicable. + if (textPointer < text.length) { + textSpanTreeChildren.add( + TextSpan( + style: style, + text: text.substring(textPointer, text.length), + ) + ); + } + + return textSpanTreeChildren; +} + +/// Builds [TextSpan] subtree for text with misspelled words with logic based on +/// a valid composing region. +List _buildSubtreesWithComposingRegion( List? spellCheckSuggestions, TextEditingValue value, TextStyle? style, TextStyle misspelledStyle, bool composingWithinCurrentTextRange) { - final List tsTreeChildren = []; + final List textSpanTreeChildren = []; int textPointer = 0; - int currSpanPointer = 0; + int currentSpanPointer = 0; int endIndex; - SuggestionSpan currSpan; + SuggestionSpan currentSpan; final String text = value.text; final TextRange composingRegion = value.composing; final TextStyle composingTextStyle = @@ -234,17 +317,17 @@ List _buildSubtreesWithMisspelledWordsIndicated( final TextStyle misspelledJointStyle = style?.merge(misspelledStyle) ?? misspelledStyle; bool textPointerWithinComposingRegion = false; - bool currSpanIsComposingRegion = false; + bool currentSpanIsComposingRegion = false; // Add text interwoven with any misspelled words to the tree. if (spellCheckSuggestions != null) { while (textPointer < text.length && - currSpanPointer < spellCheckSuggestions.length) { - currSpan = spellCheckSuggestions[currSpanPointer]; + currentSpanPointer < spellCheckSuggestions.length) { + currentSpan = spellCheckSuggestions[currentSpanPointer]; - if (currSpan.range.start > textPointer) { - endIndex = currSpan.range.start < text.length - ? currSpan.range.start + if (currentSpan.range.start > textPointer) { + endIndex = currentSpan.range.start < text.length + ? currentSpan.range.start : text.length; textPointerWithinComposingRegion = composingRegion.start >= textPointer && @@ -252,19 +335,19 @@ List _buildSubtreesWithMisspelledWordsIndicated( !composingWithinCurrentTextRange; if (textPointerWithinComposingRegion) { - _addComposingRegionTextSpans(tsTreeChildren, text, textPointer, + _addComposingRegionTextSpans(textSpanTreeChildren, text, textPointer, composingRegion, style, composingTextStyle); - tsTreeChildren.add( + textSpanTreeChildren.add( TextSpan( style: style, - text: text.substring(composingRegion.end, endIndex) + text: text.substring(composingRegion.end, endIndex), ) ); } else { - tsTreeChildren.add( + textSpanTreeChildren.add( TextSpan( style: style, - text: text.substring(textPointer, endIndex) + text: text.substring(textPointer, endIndex), ) ); } @@ -272,21 +355,21 @@ List _buildSubtreesWithMisspelledWordsIndicated( textPointer = endIndex; } else { endIndex = - currSpan.range.end < text.length ? currSpan.range.end : text.length; - currSpanIsComposingRegion = textPointer >= composingRegion.start && + currentSpan.range.end < text.length ? currentSpan.range.end : text.length; + currentSpanIsComposingRegion = textPointer >= composingRegion.start && endIndex <= composingRegion.end && !composingWithinCurrentTextRange; - tsTreeChildren.add( + textSpanTreeChildren.add( TextSpan( - style: currSpanIsComposingRegion + style: currentSpanIsComposingRegion ? composingTextStyle : misspelledJointStyle, - text: text.substring(currSpan.range.start, endIndex) + text: text.substring(currentSpan.range.start, endIndex), ) ); textPointer = endIndex; - currSpanPointer++; + currentSpanPointer++; } } } @@ -295,27 +378,27 @@ List _buildSubtreesWithMisspelledWordsIndicated( if (textPointer < text.length) { if (textPointer < composingRegion.start && !composingWithinCurrentTextRange) { - _addComposingRegionTextSpans(tsTreeChildren, text, textPointer, + _addComposingRegionTextSpans(textSpanTreeChildren, text, textPointer, composingRegion, style, composingTextStyle); if (composingRegion.end != text.length) { - tsTreeChildren.add( + textSpanTreeChildren.add( TextSpan( style: style, - text: text.substring(composingRegion.end, text.length) + text: text.substring(composingRegion.end, text.length), ) ); } } else { - tsTreeChildren.add( + textSpanTreeChildren.add( TextSpan( - style: style, text: text.substring(textPointer, text.length) + style: style, text: text.substring(textPointer, text.length), ) ); } } - return tsTreeChildren; + return textSpanTreeChildren; } /// Helper method to create [TextSpan] tree children for specified range of @@ -330,13 +413,13 @@ void _addComposingRegionTextSpans( treeChildren.add( TextSpan( style: style, - text: text.substring(start, composingRegion.start) + text: text.substring(start, composingRegion.start), ) ); treeChildren.add( TextSpan( style: composingTextStyle, - text: text.substring(composingRegion.start, composingRegion.end) + text: text.substring(composingRegion.start, composingRegion.end), ) ); } diff --git a/packages/flutter/test/widgets/spell_check_test.dart b/packages/flutter/test/widgets/spell_check_test.dart index 4ac339b5765..f9b89dcde36 100644 --- a/packages/flutter/test/widgets/spell_check_test.dart +++ b/packages/flutter/test/widgets/spell_check_test.dart @@ -17,9 +17,9 @@ void main() { misspelledTextStyle = TextField.materialMisspelledTextStyle; }); - test( - 'buildTextSpanWithSpellCheckSuggestions ignores composing region when composing region out of range', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions ignores composing region when composing region out of range', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = true; @@ -43,12 +43,12 @@ void main() { spellCheckResults, ); - expect(textSpanTree, equals(expectedTextSpanTree)); - }); + expect(textSpanTree, equals(expectedTextSpanTree)); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions, isolated misspelled word with separate composing region example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions, isolated misspelled word with separate composing region example', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const TextEditingValue value = TextEditingValue( text: text, composing: TextRange(start: 14, end: 17)); @@ -75,11 +75,11 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android })); - test( - 'buildTextSpanWithSpellCheckSuggestions, composing region and misspelled words overlap example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions, composing region and misspelled words overlap example', + (WidgetTester tester) async { const String text = 'Right worng worng right'; const TextEditingValue value = TextEditingValue( text: text, composing: TextRange(start: 12, end: 17)); @@ -109,11 +109,11 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android })); - test( - 'buildTextSpanWithSpellCheckSuggestions, consecutive misspelled words example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions, consecutive misspelled words example', + (WidgetTester tester) async { const String text = 'Right worng worng right'; const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = true; @@ -142,15 +142,14 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text shorter than actual text example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text shorter than actual text example', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const String resultsText = 'Hello, wrold!'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 14, end: 17)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ @@ -161,8 +160,7 @@ void main() { final TextSpan expectedTextSpanTree = TextSpan(children: [ const TextSpan(text: 'Hello, '), TextSpan(style: misspelledTextStyle, text: 'wrold'), - const TextSpan(text: '! '), - TextSpan(style: composingStyle, text: 'Hey') + const TextSpan(text: '! Hey'), ]); final TextSpan textSpanTree = buildTextSpanWithSpellCheckSuggestions( @@ -174,15 +172,14 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text longer with more misspelled words than actual text example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text longer with more misspelled words than actual text example', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const String resultsText = 'Hello, wrold Hey feirnd!'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 14, end: 17)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ @@ -195,8 +192,7 @@ void main() { final TextSpan expectedTextSpanTree = TextSpan(children: [ const TextSpan(text: 'Hello, '), TextSpan(style: misspelledTextStyle, text: 'wrold'), - const TextSpan(text: '! '), - TextSpan(style: composingStyle, text: 'Hey') + const TextSpan(text: '! Hey'), ]); final TextSpan textSpanTree = buildTextSpanWithSpellCheckSuggestions( @@ -208,24 +204,22 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text mismatched example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results text mismatched example', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const String resultsText = 'Hello, wrild! Hey'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 14, end: 17)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ SuggestionSpan(TextRange(start: 7, end: 12), ['wild', 'world']), ]); - final TextSpan expectedTextSpanTree = TextSpan(children: [ - const TextSpan(text: 'Hello, wrold! '), - TextSpan(style: composingStyle, text: 'Hey') + const TextSpan expectedTextSpanTree = TextSpan(children: [ + TextSpan(text: 'Hello, wrold! Hey'), ]); final TextSpan textSpanTree = buildTextSpanWithSpellCheckSuggestions( @@ -237,15 +231,14 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted forward example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted forward example', + (WidgetTester tester) async { const String text = 'Hello, there wrold! Hey'; const String resultsText = 'Hello, wrold! Hey'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 20, end: 23)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ @@ -256,8 +249,7 @@ void main() { final TextSpan expectedTextSpanTree = TextSpan(children: [ const TextSpan(text: 'Hello, there '), TextSpan(style: misspelledTextStyle, text: 'wrold'), - const TextSpan(text: '! '), - TextSpan(style: composingStyle, text: 'Hey') + const TextSpan(text: '! Hey'), ]); final TextSpan textSpanTree = buildTextSpanWithSpellCheckSuggestions( @@ -269,15 +261,14 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted backwards example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted backwards example', + (WidgetTester tester) async { const String text = 'Hello, wrold! Hey'; const String resultsText = 'Hello, great wrold! Hey'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 14, end: 17)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ @@ -288,8 +279,7 @@ void main() { final TextSpan expectedTextSpanTree = TextSpan(children: [ const TextSpan(text: 'Hello, '), TextSpan(style: misspelledTextStyle, text: 'wrold'), - const TextSpan(text: '! '), - TextSpan(style: composingStyle, text: 'Hey') + const TextSpan(text: '! Hey'), ]); final TextSpan textSpanTree = buildTextSpanWithSpellCheckSuggestions( @@ -301,15 +291,14 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); - test( - 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted backwards and forwards example', - () { + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions corrects results when they lag, results shifted backwards and forwards example', + (WidgetTester tester) async { const String text = 'Hello, wrold! And Hye!'; const String resultsText = 'Hello, great wrold! Hye!'; - const TextEditingValue value = TextEditingValue( - text: text, composing: TextRange(start: 14, end: 17)); + const TextEditingValue value = TextEditingValue(text: text); const bool composingRegionOutOfRange = false; const SpellCheckResults spellCheckResults = SpellCheckResults(resultsText, [ @@ -321,9 +310,7 @@ void main() { final TextSpan expectedTextSpanTree = TextSpan(children: [ const TextSpan(text: 'Hello, '), TextSpan(style: misspelledTextStyle, text: 'wrold'), - const TextSpan(text: '! '), - TextSpan(style: composingStyle, text: 'And'), - const TextSpan(text: ' '), + const TextSpan(text: '! And '), TextSpan(style: misspelledTextStyle, text: 'Hye'), const TextSpan(text: '!') ]); @@ -337,5 +324,33 @@ void main() { ); expect(textSpanTree, equals(expectedTextSpanTree)); - }); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); + + testWidgets( + 'buildTextSpanWithSpellCheckSuggestions discards result when additions are made to misspelled word example', + (WidgetTester tester) async { + const String text = 'Hello, wroldd!'; + const String resultsText = 'Hello, wrold!'; + const TextEditingValue value = TextEditingValue(text: text); + const bool composingRegionOutOfRange = false; + const SpellCheckResults spellCheckResults = + SpellCheckResults(resultsText, [ + SuggestionSpan( + TextRange(start: 7, end: 12), ['world', 'word', 'old']), + ]); + + const TextSpan expectedTextSpanTree = TextSpan(children: [ + TextSpan(text: 'Hello, wroldd!'), + ]); + final TextSpan textSpanTree = + buildTextSpanWithSpellCheckSuggestions( + value, + composingRegionOutOfRange, + null, + misspelledTextStyle, + spellCheckResults, + ); + + expect(textSpanTree, equals(expectedTextSpanTree)); + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); }