301 lines
11 KiB
Dart
301 lines
11 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:convert' show jsonDecode;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'text_input_utils.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
group('DeltaTextInputClient', () {
|
|
late FakeTextChannel fakeTextChannel;
|
|
|
|
setUp(() {
|
|
fakeTextChannel = FakeTextChannel((MethodCall call) async {});
|
|
TextInput.setChannel(fakeTextChannel);
|
|
});
|
|
|
|
tearDown(() {
|
|
TextInputConnection.debugResetId();
|
|
TextInput.setChannel(SystemChannels.textInput);
|
|
});
|
|
|
|
test(
|
|
'DeltaTextInputClient send the correct configuration to the platform and responds to updateEditingValueWithDeltas method correctly',
|
|
() async {
|
|
// Assemble a TextInputConnection so we can verify its change in state.
|
|
final FakeDeltaTextInputClient client = FakeDeltaTextInputClient(TextEditingValue.empty);
|
|
const TextInputConfiguration configuration = TextInputConfiguration(enableDeltaModel: true);
|
|
TextInput.attach(client, configuration);
|
|
expect(client.configuration.enableDeltaModel, true);
|
|
|
|
expect(client.latestMethodCall, isEmpty);
|
|
|
|
const String jsonDelta = '{'
|
|
'"oldText": "",'
|
|
' "deltaText": "let there be text",'
|
|
' "deltaStart": 0,'
|
|
' "deltaEnd": 0,'
|
|
' "selectionBase": 17,'
|
|
' "selectionExtent": 17,'
|
|
' "selectionAffinity" : "TextAffinity.downstream" ,'
|
|
' "selectionIsDirectional": false,'
|
|
' "composingBase": -1,'
|
|
' "composingExtent": -1}';
|
|
|
|
// Send updateEditingValueWithDeltas message.
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'args': <dynamic>[
|
|
1,
|
|
jsonDecode('{"deltas": [$jsonDelta]}'),
|
|
],
|
|
'method': 'TextInputClient.updateEditingStateWithDeltas',
|
|
});
|
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/textinput',
|
|
messageBytes,
|
|
(ByteData? _) {},
|
|
);
|
|
|
|
expect(client.latestMethodCall, 'updateEditingValueWithDeltas');
|
|
},
|
|
);
|
|
|
|
test('Invalid TextRange fails loudly when being converted to JSON - NonTextUpdate', () async {
|
|
final List<FlutterErrorDetails> record = <FlutterErrorDetails>[];
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
record.add(details);
|
|
};
|
|
|
|
final FakeDeltaTextInputClient client = FakeDeltaTextInputClient(const TextEditingValue(text: '1'));
|
|
const TextInputConfiguration configuration = TextInputConfiguration(enableDeltaModel: true);
|
|
TextInput.attach(client, configuration);
|
|
|
|
const String jsonDelta = '{'
|
|
'"oldText": "1",'
|
|
' "deltaText": "",'
|
|
' "deltaStart": -1,'
|
|
' "deltaEnd": -1,'
|
|
' "selectionBase": 3,'
|
|
' "selectionExtent": 3,'
|
|
' "selectionAffinity" : "TextAffinity.downstream" ,'
|
|
' "selectionIsDirectional": false,'
|
|
' "composingBase": -1,'
|
|
' "composingExtent": -1}';
|
|
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'method': 'TextInputClient.updateEditingStateWithDeltas',
|
|
'args': <dynamic>[-1, jsonDecode('{"deltas": [$jsonDelta]}')],
|
|
});
|
|
|
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/textinput',
|
|
messageBytes,
|
|
(ByteData? _) {},
|
|
);
|
|
expect(record.length, 1);
|
|
// Verify the error message in parts because Web formats the message
|
|
// differently from others.
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bThe selection range: TextSelection.collapsed\(offset: 3, affinity: TextAffinity.downstream, isDirectional: false\)(?!\w)')));
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bis not within the bounds of text: 1 of length: 1\b')));
|
|
});
|
|
|
|
test('Invalid TextRange fails loudly when being converted to JSON - Faulty deltaStart and deltaEnd', () async {
|
|
final List<FlutterErrorDetails> record = <FlutterErrorDetails>[];
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
record.add(details);
|
|
};
|
|
|
|
final FakeDeltaTextInputClient client = FakeDeltaTextInputClient(TextEditingValue.empty);
|
|
const TextInputConfiguration configuration = TextInputConfiguration(enableDeltaModel: true);
|
|
TextInput.attach(client, configuration);
|
|
|
|
const String jsonDelta = '{'
|
|
'"oldText": "",'
|
|
' "deltaText": "hello",'
|
|
' "deltaStart": 0,'
|
|
' "deltaEnd": 1,'
|
|
' "selectionBase": 5,'
|
|
' "selectionExtent": 5,'
|
|
' "selectionAffinity" : "TextAffinity.downstream" ,'
|
|
' "selectionIsDirectional": false,'
|
|
' "composingBase": -1,'
|
|
' "composingExtent": -1}';
|
|
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'method': 'TextInputClient.updateEditingStateWithDeltas',
|
|
'args': <dynamic>[-1, jsonDecode('{"deltas": [$jsonDelta]}')],
|
|
});
|
|
|
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/textinput',
|
|
messageBytes,
|
|
(ByteData? _) {},
|
|
);
|
|
expect(record.length, 1);
|
|
// Verify the error message in parts because Web formats the message
|
|
// differently from others.
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bThe delta range: TextRange\(start: 0, end: 5\)(?!\w)')));
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bis not within the bounds of text: of length: 0\b')));
|
|
});
|
|
|
|
test('Invalid TextRange fails loudly when being converted to JSON - Faulty Selection', () async {
|
|
final List<FlutterErrorDetails> record = <FlutterErrorDetails>[];
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
record.add(details);
|
|
};
|
|
|
|
final FakeDeltaTextInputClient client = FakeDeltaTextInputClient(TextEditingValue.empty);
|
|
const TextInputConfiguration configuration = TextInputConfiguration(enableDeltaModel: true);
|
|
TextInput.attach(client, configuration);
|
|
|
|
const String jsonDelta = '{'
|
|
'"oldText": "",'
|
|
' "deltaText": "hello",'
|
|
' "deltaStart": 0,'
|
|
' "deltaEnd": 0,'
|
|
' "selectionBase": 6,'
|
|
' "selectionExtent": 6,'
|
|
' "selectionAffinity" : "TextAffinity.downstream" ,'
|
|
' "selectionIsDirectional": false,'
|
|
' "composingBase": -1,'
|
|
' "composingExtent": -1}';
|
|
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'method': 'TextInputClient.updateEditingStateWithDeltas',
|
|
'args': <dynamic>[-1, jsonDecode('{"deltas": [$jsonDelta]}')],
|
|
});
|
|
|
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/textinput',
|
|
messageBytes,
|
|
(ByteData? _) {},
|
|
);
|
|
expect(record.length, 1);
|
|
// Verify the error message in parts because Web formats the message
|
|
// differently from others.
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bThe selection range: TextSelection.collapsed\(offset: 6, affinity: TextAffinity.downstream, isDirectional: false\)(?!\w)')));
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bis not within the bounds of text: hello of length: 5\b')));
|
|
});
|
|
|
|
test('Invalid TextRange fails loudly when being converted to JSON - Faulty Composing Region', () async {
|
|
final List<FlutterErrorDetails> record = <FlutterErrorDetails>[];
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
record.add(details);
|
|
};
|
|
|
|
final FakeDeltaTextInputClient client = FakeDeltaTextInputClient(const TextEditingValue(text: 'worl'));
|
|
const TextInputConfiguration configuration = TextInputConfiguration(enableDeltaModel: true);
|
|
TextInput.attach(client, configuration);
|
|
|
|
const String jsonDelta = '{'
|
|
'"oldText": "worl",'
|
|
' "deltaText": "world",'
|
|
' "deltaStart": 0,'
|
|
' "deltaEnd": 4,'
|
|
' "selectionBase": 5,'
|
|
' "selectionExtent": 5,'
|
|
' "selectionAffinity" : "TextAffinity.downstream" ,'
|
|
' "selectionIsDirectional": false,'
|
|
' "composingBase": 0,'
|
|
' "composingExtent": 6}';
|
|
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'method': 'TextInputClient.updateEditingStateWithDeltas',
|
|
'args': <dynamic>[-1, jsonDecode('{"deltas": [$jsonDelta]}')],
|
|
});
|
|
|
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/textinput',
|
|
messageBytes,
|
|
(ByteData? _) {},
|
|
);
|
|
expect(record.length, 1);
|
|
// Verify the error message in parts because Web formats the message
|
|
// differently from others.
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bThe composing range: TextRange\(start: 0, end: 6\)(?!\w)')));
|
|
expect(record[0].exception.toString(), matches(RegExp(r'\bis not within the bounds of text: world of length: 5\b')));
|
|
});
|
|
});
|
|
}
|
|
|
|
class FakeDeltaTextInputClient implements DeltaTextInputClient {
|
|
FakeDeltaTextInputClient(this.currentTextEditingValue);
|
|
|
|
String latestMethodCall = '';
|
|
|
|
@override
|
|
TextEditingValue currentTextEditingValue;
|
|
|
|
@override
|
|
AutofillScope? get currentAutofillScope => null;
|
|
|
|
@override
|
|
void performAction(TextInputAction action) {
|
|
latestMethodCall = 'performAction';
|
|
}
|
|
|
|
@override
|
|
void performPrivateCommand(String action, Map<String, dynamic> data) {
|
|
latestMethodCall = 'performPrivateCommand';
|
|
}
|
|
|
|
@override
|
|
void updateEditingValue(TextEditingValue value) {
|
|
latestMethodCall = 'updateEditingValue';
|
|
}
|
|
|
|
@override
|
|
void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
|
|
latestMethodCall = 'updateEditingValueWithDeltas';
|
|
}
|
|
|
|
@override
|
|
void updateFloatingCursor(RawFloatingCursorPoint point) {
|
|
latestMethodCall = 'updateFloatingCursor';
|
|
}
|
|
|
|
@override
|
|
void connectionClosed() {
|
|
latestMethodCall = 'connectionClosed';
|
|
}
|
|
|
|
@override
|
|
void showAutocorrectionPromptRect(int start, int end) {
|
|
latestMethodCall = 'showAutocorrectionPromptRect';
|
|
}
|
|
|
|
@override
|
|
void insertTextPlaceholder(Size size) {
|
|
latestMethodCall = 'insertTextPlaceholder';
|
|
}
|
|
|
|
@override
|
|
void removeTextPlaceholder() {
|
|
latestMethodCall = 'removeTextPlaceholder';
|
|
}
|
|
|
|
@override
|
|
void showToolbar() {
|
|
latestMethodCall = 'showToolbar';
|
|
}
|
|
|
|
@override
|
|
void performSelector(String selectorName) {
|
|
latestMethodCall = 'performSelector';
|
|
}
|
|
|
|
TextInputConfiguration get configuration => const TextInputConfiguration(enableDeltaModel: true);
|
|
|
|
@override
|
|
void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) {
|
|
latestMethodCall = 'didChangeInputControl';
|
|
}
|
|
}
|