mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Assert valid composing TextRange (#60956)
This commit is contained in:
parent
c6fd42499a
commit
a6ce2d4bc7
|
@ -714,6 +714,17 @@ class TextEditingValue {
|
|||
);
|
||||
}
|
||||
|
||||
/// Whether the [composing] range is a valid range within [text].
|
||||
///
|
||||
/// Returns true if and only if the [composing] range is normalized, its start
|
||||
/// is greater than or equal to 0, and its end is less than or equal to
|
||||
/// [text]'s length.
|
||||
///
|
||||
/// If this property is false while the [composing] range's `isValid` is true,
|
||||
/// it usually indicates the current [composing] range is invalid because of a
|
||||
/// programming error.
|
||||
bool get isComposingRangeValid => composing.isValid && composing.isNormalized && composing.end <= text.length;
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'TextEditingValue')}(text: \u2524$text\u251C, selection: $selection, composing: $composing)';
|
||||
|
||||
|
|
|
@ -134,7 +134,13 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
|
|||
/// This constructor treats a null [value] argument as if it were
|
||||
/// [TextEditingValue.empty].
|
||||
TextEditingController.fromValue(TextEditingValue value)
|
||||
: super(value ?? TextEditingValue.empty);
|
||||
: assert(
|
||||
value == null || !value.composing.isValid || value.isComposingRangeValid,
|
||||
'New TextEditingValue $value has an invalid non-empty composing range '
|
||||
'${value.composing}. It is recommended to use a valid composing range, '
|
||||
'even for readonly text fields',
|
||||
),
|
||||
super(value ?? TextEditingValue.empty);
|
||||
|
||||
/// The current string the user is editing.
|
||||
String get text => value.text;
|
||||
|
@ -155,12 +161,27 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
|
|||
);
|
||||
}
|
||||
|
||||
@override
|
||||
set value(TextEditingValue newValue) {
|
||||
assert(
|
||||
!newValue.composing.isValid || newValue.isComposingRangeValid,
|
||||
'New TextEditingValue $newValue has an invalid non-empty composing range '
|
||||
'${newValue.composing}. It is recommended to use a valid composing range, '
|
||||
'even for readonly text fields',
|
||||
);
|
||||
super.value = newValue;
|
||||
}
|
||||
|
||||
/// Builds [TextSpan] from current editing value.
|
||||
///
|
||||
/// By default makes text in composing range appear as underlined.
|
||||
/// Descendants can override this method to customize appearance of text.
|
||||
/// By default makes text in composing range appear as underlined. Descendants
|
||||
/// can override this method to customize appearance of text.
|
||||
TextSpan buildTextSpan({TextStyle style , bool withComposing}) {
|
||||
if (!value.composing.isValid || !withComposing) {
|
||||
assert(!value.composing.isValid || !withComposing || value.isComposingRangeValid);
|
||||
// If the composing range is out of range for the current text, ignore it to
|
||||
// preserve the tree integrity, otherwise in release mode a RangeError will
|
||||
// be thrown and this EditableText will be built with a broken subtree.
|
||||
if (!value.isComposingRangeValid || !withComposing) {
|
||||
return TextSpan(style: style, text: text);
|
||||
}
|
||||
final TextStyle composingStyle = style.merge(
|
||||
|
|
|
@ -192,6 +192,33 @@ void main() {
|
|||
expect(client.latestMethodCall, 'showAutocorrectionPromptRect');
|
||||
});
|
||||
});
|
||||
|
||||
test('TextEditingValue.isComposingRangeValid', () async {
|
||||
// The composing range is empty.
|
||||
expect(const TextEditingValue(text: '').isComposingRangeValid, isFalse);
|
||||
|
||||
expect(
|
||||
const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 0)).isComposingRangeValid,
|
||||
isFalse,
|
||||
);
|
||||
|
||||
// The composing range is out of range for the text.
|
||||
expect(
|
||||
const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 5)).isComposingRangeValid,
|
||||
isFalse,
|
||||
);
|
||||
|
||||
// The composing range is out of range for the text.
|
||||
expect(
|
||||
const TextEditingValue(text: 'test', composing: TextRange(start: -1, end: 4)).isComposingRangeValid,
|
||||
isFalse,
|
||||
);
|
||||
|
||||
expect(
|
||||
const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 4)).isComposingRangeValid,
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class FakeTextInputClient implements TextInputClient {
|
||||
|
|
|
@ -4700,20 +4700,20 @@ void main() {
|
|||
|
||||
await tester.tap(find.byType(EditableText));
|
||||
await tester.showKeyboard(find.byType(EditableText));
|
||||
controller.text = '';
|
||||
controller.text = 'test';
|
||||
await tester.idle();
|
||||
|
||||
final EditableTextState state =
|
||||
tester.state<EditableTextState>(find.byType(EditableText));
|
||||
expect(tester.testTextInput.editingState['text'], equals(''));
|
||||
expect(tester.testTextInput.editingState['text'], equals('test'));
|
||||
expect(state.wantKeepAlive, true);
|
||||
|
||||
expect(formatter.formatCallCount, 0);
|
||||
state.updateEditingValue(const TextEditingValue(text: ''));
|
||||
state.updateEditingValue(const TextEditingValue(text: '', composing: TextRange(start: 1, end: 2)));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'test'));
|
||||
state.updateEditingValue(const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 2)));
|
||||
state.updateEditingValue(const TextEditingValue(text: '0')); // pass to formatter once to check the values.
|
||||
expect(formatter.lastOldValue.composing, const TextRange(start: 1, end: 2));
|
||||
expect(formatter.lastOldValue.text, '');
|
||||
expect(formatter.lastOldValue.text, 'test');
|
||||
});
|
||||
|
||||
testWidgets('Whitespace directionality formatter input Arabic', (WidgetTester tester) async {
|
||||
|
@ -5170,6 +5170,34 @@ void main() {
|
|||
expect(renderObject.textHeightBehavior, isNot(equals(inheritedTextHeightBehavior)));
|
||||
expect(renderObject.textHeightBehavior, equals(customTextHeightBehavior));
|
||||
});
|
||||
|
||||
test('Asserts if composing text is not valid', () async {
|
||||
void expectToAssert(TextEditingValue value, bool shouldAssert) {
|
||||
dynamic initException;
|
||||
dynamic updateException;
|
||||
controller = TextEditingController();
|
||||
try {
|
||||
controller = TextEditingController.fromValue(value);
|
||||
} catch (e) {
|
||||
initException = e;
|
||||
}
|
||||
|
||||
controller = TextEditingController();
|
||||
try {
|
||||
controller.value = value;
|
||||
} catch (e) {
|
||||
updateException = e;
|
||||
}
|
||||
|
||||
expect(initException?.toString(), shouldAssert ? contains('composing range'): isNull);
|
||||
expect(updateException?.toString(), shouldAssert ? contains('composing range'): isNull);
|
||||
}
|
||||
|
||||
expectToAssert(const TextEditingValue(text: ''), false);
|
||||
expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 0)), true);
|
||||
expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: 1, end: 9)), true);
|
||||
expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: -1, end: 9)), false);
|
||||
});
|
||||
}
|
||||
|
||||
class MockTextFormatter extends TextInputFormatter {
|
||||
|
|
Loading…
Reference in a new issue