Create DeltaTextInputClient (#90205)

* Create DeltaTextInputClient

* Remove old tests as updateEditingValueWithDeltas is no longer implemented

* fix analyzer

* Update docs

* Make example more general

* Update docs

* Add assert to check that TextInputClient is a DeltaTextInputClient

* Update assert

* More docs

* update

* Clean up docs

* updates

* Update docs

* updates

* Fix test

* add test

* updates

* remove logs

* fix tests

* Address reviewer comments

* Add text_input_utils.dart

* Address reviewer comments
diff --git a/packages/flutter/lib/src/services/text_editing_delta.dart b/packages/flutter/lib/src/services/text_editing_delta.dart
index 5f6ab98..60e1ff1 100644
--- a/packages/flutter/lib/src/services/text_editing_delta.dart
+++ b/packages/flutter/lib/src/services/text_editing_delta.dart
@@ -35,10 +35,10 @@
 ///  * [TextEditingDeltaDeletion], a delta representing a deletion.
 ///  * [TextEditingDeltaReplacement], a delta representing a replacement.
 ///  * [TextEditingDeltaNonTextUpdate], a delta representing an update to the
-///  selection and/or composing region.
-///  * [TextInputConfiguration], to opt-in your [TextInputClient] to receive
-///  [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
-///  to true.
+///    selection and/or composing region.
+///  * [TextInputConfiguration], to opt-in your [DeltaTextInputClient] to receive
+///    [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
+///    to true.
 abstract class TextEditingDelta {
   /// Creates a delta for a given change to the editing state.
   ///
@@ -234,9 +234,9 @@
   /// {@template flutter.services.TextEditingDelta.optIn}
   /// See also:
   ///
-  ///  * [TextInputConfiguration], to opt-in your [TextInputClient] to receive
-  ///  [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
-  ///  to true.
+  ///  * [TextInputConfiguration], to opt-in your [DeltaTextInputClient] to receive
+  ///    [TextEditingDelta]'s you must set [TextInputConfiguration.enableDeltaModel]
+  ///    to true.
   /// {@endtemplate}
   const TextEditingDeltaInsertion({
     required String oldText,
diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart
index 4177892..8b2cbf0 100644
--- a/packages/flutter/lib/src/services/text_input.dart
+++ b/packages/flutter/lib/src/services/text_input.dart
@@ -647,19 +647,24 @@
   /// Whether to enable that the engine sends text input updates to the
   /// framework as [TextEditingDelta]'s or as one [TextEditingValue].
   ///
-  /// When this is enabled platform text input updates will
-  /// come through [TextInputClient.updateEditingValueWithDeltas].
-  ///
-  /// When this is disabled platform text input updates will come through
-  /// [TextInputClient.updateEditingValue].
-  ///
   /// Enabling this flag results in granular text updates being received from the
-  /// platforms text input control rather than a single new bulk editing state
-  /// given by [TextInputClient.updateEditingValue].
+  /// platform's text input control.
   ///
-  /// If the platform does not currently support the delta model then updates
-  /// for the editing state will continue to come through the
-  /// [TextInputClient.updateEditingValue] channel.
+  /// When this is enabled:
+  ///  * You must implement [DeltaTextInputClient] and not [TextInputClient] to
+  ///    receive granular updates from the platform's text input.
+  ///  * Platform text input updates will come through
+  ///    [DeltaTextInputClient.updateEditingValueWithDeltas].
+  ///  * If [TextInputClient] is implemented with this property enabled then
+  ///    you will experience unexpected behavior as [TextInputClient] does not implement
+  ///    a delta channel.
+  ///
+  /// When this is disabled:
+  ///  * If [DeltaTextInputClient] is implemented then updates for the
+  ///    editing state will continue to come through the
+  ///    [DeltaTextInputClient.updateEditingValue] channel.
+  ///  * If [TextInputClient] is implemented then updates for the editing
+  ///    state will come through [TextInputClient.updateEditingValue].
   ///
   /// Defaults to false. Cannot be null.
   final bool enableDeltaModel;
@@ -953,10 +958,15 @@
 
 /// An interface to receive information from [TextInput].
 ///
+/// If [TextInputConfiguration.enableDeltaModel] is set to true,
+/// [DeltaTextInputClient] must be implemented instead of this class.
+///
 /// See also:
 ///
 ///  * [TextInput.attach]
 ///  * [EditableText], a [TextInputClient] implementation.
+///  * [DeltaTextInputClient], a [TextInputClient] extension that receives
+///    granular information from the platform's text input.
 abstract class TextInputClient {
   /// Abstract const constructor. This constructor enables subclasses to provide
   /// const constructors so that they can be used in const expressions.
@@ -983,16 +993,6 @@
   /// formatting.
   void updateEditingValue(TextEditingValue value);
 
-  /// Requests that this client update its editing state by applying the deltas
-  /// received from the engine.
-  ///
-  /// The list of [TextEditingDelta]'s are treated as changes that will be applied
-  /// to the client's editing state. A change is any mutation to the raw text
-  /// value, or any updates to the selection and/or composing region.
-  ///
-  /// {@macro flutter.services.TextEditingDelta.optIn}
-  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas);
-
   /// Requests that this client perform the given action.
   void performAction(TextInputAction action);
 
@@ -1026,6 +1026,36 @@
   void connectionClosed();
 }
 
+/// An interface to receive granular information from [TextInput].
+///
+/// See also:
+///
+///  * [TextInput.attach]
+///  * [TextInputConfiguration], to opt-in to receive [TextEditingDelta]'s from
+///    the platforms [TextInput] you must set [TextInputConfiguration.enableDeltaModel]
+///    to true.
+abstract class DeltaTextInputClient extends TextInputClient {
+  /// Requests that this client update its editing state by applying the deltas
+  /// received from the engine.
+  ///
+  /// The list of [TextEditingDelta]'s are treated as changes that will be applied
+  /// to the client's editing state. A change is any mutation to the raw text
+  /// value, or any updates to the selection and/or composing region.
+  ///
+  /// Here is an example of what implementation of this method could look like:
+  /// {@tool snippet}
+  /// @override
+  /// void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
+  ///   TextEditingValue newValue = _previousValue;
+  ///   for (final TextEditingDelta delta in textEditingDeltas) {
+  ///     newValue = delta.apply(newValue);
+  ///   }
+  ///   _localValue = newValue;
+  /// }
+  /// {@end-tool}
+  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas);
+}
+
 /// An interface for interacting with a text input control.
 ///
 /// See also:
@@ -1485,6 +1515,7 @@
         _currentConnection!._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as Map<String, dynamic>));
         break;
       case 'TextInputClient.updateEditingStateWithDeltas':
+        assert(_currentConnection!._client is DeltaTextInputClient, 'You must be using a DeltaTextInputClient if TextInputConfiguration.enableDeltaModel is set to true');
         final List<TextEditingDelta> deltas = <TextEditingDelta>[];
 
         final Map<String, dynamic> encoded = args[1] as Map<String, dynamic>;
@@ -1494,7 +1525,7 @@
           deltas.add(delta);
         }
 
-        _currentConnection!._client.updateEditingValueWithDeltas(deltas);
+        (_currentConnection!._client as DeltaTextInputClient).updateEditingValueWithDeltas(deltas);
         break;
       case 'TextInputClient.performAction':
         _currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 51fa2fb..80aaefa 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -1796,15 +1796,6 @@
   TextEditingValue get currentTextEditingValue => _value;
 
   @override
-  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
-    TextEditingValue value = _value;
-    for (final TextEditingDelta delta in textEditingDeltas) {
-      value = delta.apply(value);
-    }
-    updateEditingValue(value);
-  }
-
-  @override
   void updateEditingValue(TextEditingValue value) {
     // This method handles text editing state updates from the platform text
     // input plugin. The [EditableText] may not have the focus or an open input
diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart
index 8609409..11fdcd9 100644
--- a/packages/flutter/test/services/autofill_test.dart
+++ b/packages/flutter/test/services/autofill_test.dart
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:convert' show utf8;
-
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 
+import 'text_input_utils.dart';
+
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
 
@@ -107,19 +107,6 @@
   }
 
   @override
-  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
-    TextEditingValue newEditingValue = currentTextEditingValue;
-
-    for (final TextEditingDelta delta in textEditingDeltas) {
-      newEditingValue = delta.apply(newEditingValue);
-    }
-
-    currentTextEditingValue = newEditingValue;
-
-    latestMethodCall = 'updateEditingValueWithDeltas';
-  }
-
-  @override
   AutofillScope? currentAutofillScope;
 
   String latestMethodCall = '';
@@ -169,62 +156,3 @@
     clients.putIfAbsent(client.autofillId, () => client);
   }
 }
-
-class FakeTextChannel implements MethodChannel {
-  FakeTextChannel(this.outgoing) : assert(outgoing != null);
-
-  Future<dynamic> Function(MethodCall) outgoing;
-  Future<void> Function(MethodCall)? incoming;
-
-  List<MethodCall> outgoingCalls = <MethodCall>[];
-
-  @override
-  BinaryMessenger get binaryMessenger => throw UnimplementedError();
-
-  @override
-  MethodCodec get codec => const JSONMethodCodec();
-
-  @override
-  Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
-
-  @override
-  Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
-
-  @override
-  Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
-    final MethodCall call = MethodCall(method, arguments);
-    outgoingCalls.add(call);
-    return await outgoing(call) as T;
-  }
-
-  @override
-  String get name => 'flutter/textinput';
-
-  @override
-  void setMethodCallHandler(Future<void> Function(MethodCall call)? handler) {
-    incoming = handler;
-  }
-
-  void validateOutgoingMethodCalls(List<MethodCall> calls) {
-    expect(outgoingCalls.length, calls.length);
-    bool hasError = false;
-    for (int i = 0; i < calls.length; i++) {
-      final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]);
-      final ByteData expectedData = codec.encodeMethodCall(calls[i]);
-      final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List());
-      final String expectedString = utf8.decode(expectedData.buffer.asUint8List());
-
-      if (outgoingString != expectedString) {
-        print(
-          'Index $i did not match:\n'
-          '  actual:   ${outgoingCalls[i]}\n'
-          '  expected: ${calls[i]}',
-        );
-        hasError = true;
-      }
-    }
-    if (hasError) {
-      fail('Calls did not match.');
-    }
-  }
-}
diff --git a/packages/flutter/test/services/delta_text_input_test.dart b/packages/flutter/test/services/delta_text_input_test.dart
new file mode 100644
index 0000000..e87a665
--- /dev/null
+++ b/packages/flutter/test/services/delta_text_input_test.dart
@@ -0,0 +1,118 @@
+// 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/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');
+      },
+    );
+  });
+}
+
+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';
+  }
+
+  TextInputConfiguration get configuration => const TextInputConfiguration(enableDeltaModel: true);
+}
diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart
index 9bd27e5..e6efaaf 100644
--- a/packages/flutter/test/services/text_input_test.dart
+++ b/packages/flutter/test/services/text_input_test.dart
@@ -3,12 +3,13 @@
 // found in the LICENSE file.
 
 
-import 'dart:convert' show utf8;
 import 'dart:convert' show jsonDecode;
 
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 
+import 'text_input_utils.dart';
+
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
 
@@ -452,11 +453,6 @@
   }
 
   @override
-  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
-    latestMethodCall = 'updateEditingValueWithDeltas';
-  }
-
-  @override
   void updateFloatingCursor(RawFloatingCursorPoint point) {
     latestMethodCall = 'updateFloatingCursor';
   }
@@ -473,60 +469,3 @@
 
   TextInputConfiguration get configuration => const TextInputConfiguration();
 }
-
-class FakeTextChannel implements MethodChannel {
-  FakeTextChannel(this.outgoing) : assert(outgoing != null);
-
-  Future<dynamic> Function(MethodCall) outgoing;
-  Future<void> Function(MethodCall)? incoming;
-
-  List<MethodCall> outgoingCalls = <MethodCall>[];
-
-  @override
-  BinaryMessenger get binaryMessenger => throw UnimplementedError();
-
-  @override
-  MethodCodec get codec => const JSONMethodCodec();
-
-  @override
-  Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
-
-  @override
-  Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
-
-  @override
-  Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
-    final MethodCall call = MethodCall(method, arguments);
-    outgoingCalls.add(call);
-    return await outgoing(call) as T;
-  }
-
-  @override
-  String get name => 'flutter/textinput';
-
-  @override
-  void setMethodCallHandler(Future<void> Function(MethodCall call)? handler) => incoming = handler;
-
-  void validateOutgoingMethodCalls(List<MethodCall> calls) {
-    expect(outgoingCalls.length, calls.length);
-    bool hasError = false;
-    for (int i = 0; i < calls.length; i++) {
-      final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]);
-      final ByteData expectedData = codec.encodeMethodCall(calls[i]);
-      final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List());
-      final String expectedString = utf8.decode(expectedData.buffer.asUint8List());
-
-      if (outgoingString != expectedString) {
-        print(
-          'Index $i did not match:\n'
-          '  actual:   $outgoingString\n'
-          '  expected: $expectedString',
-        );
-        hasError = true;
-      }
-    }
-    if (hasError) {
-      fail('Calls did not match.');
-    }
-  }
-}
diff --git a/packages/flutter/test/services/text_input_utils.dart b/packages/flutter/test/services/text_input_utils.dart
new file mode 100644
index 0000000..fd58e40
--- /dev/null
+++ b/packages/flutter/test/services/text_input_utils.dart
@@ -0,0 +1,65 @@
+// 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 utf8;
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+class FakeTextChannel implements MethodChannel {
+  FakeTextChannel(this.outgoing) : assert(outgoing != null);
+
+  Future<dynamic> Function(MethodCall) outgoing;
+  Future<void> Function(MethodCall)? incoming;
+
+  List<MethodCall> outgoingCalls = <MethodCall>[];
+
+  @override
+  BinaryMessenger get binaryMessenger => throw UnimplementedError();
+
+  @override
+  MethodCodec get codec => const JSONMethodCodec();
+
+  @override
+  Future<List<T>> invokeListMethod<T>(String method, [dynamic arguments]) => throw UnimplementedError();
+
+  @override
+  Future<Map<K, V>> invokeMapMethod<K, V>(String method, [dynamic arguments]) => throw UnimplementedError();
+
+  @override
+  Future<T> invokeMethod<T>(String method, [dynamic arguments]) async {
+    final MethodCall call = MethodCall(method, arguments);
+    outgoingCalls.add(call);
+    return await outgoing(call) as T;
+  }
+
+  @override
+  String get name => 'flutter/textinput';
+
+  @override
+  void setMethodCallHandler(Future<void> Function(MethodCall call)? handler) => incoming = handler;
+
+  void validateOutgoingMethodCalls(List<MethodCall> calls) {
+    expect(outgoingCalls.length, calls.length);
+    bool hasError = false;
+    for (int i = 0; i < calls.length; i++) {
+      final ByteData outgoingData = codec.encodeMethodCall(outgoingCalls[i]);
+      final ByteData expectedData = codec.encodeMethodCall(calls[i]);
+      final String outgoingString = utf8.decode(outgoingData.buffer.asUint8List());
+      final String expectedString = utf8.decode(expectedData.buffer.asUint8List());
+
+      if (outgoingString != expectedString) {
+        print(
+          'Index $i did not match:\n'
+          '  actual:   $outgoingString\n'
+          '  expected: $expectedString',
+        );
+        hasError = true;
+      }
+    }
+    if (hasError) {
+      fail('Calls did not match.');
+    }
+  }
+}
diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart
index 8bf1c86..060d4f2 100644
--- a/packages/flutter/test/widgets/editable_text_test.dart
+++ b/packages/flutter/test/widgets/editable_text_test.dart
@@ -2,7 +2,6 @@
 // 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/cupertino.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
@@ -6619,302 +6618,6 @@
     expect(focusNode.hasFocus, false);
   });
 
-  group('TextEditingDelta', () {
-    testWidgets('TextEditingDeltaInsertion verification', (WidgetTester tester) async {
-      final TextEditingController controller = TextEditingController();
-      await tester.pumpWidget(
-        MaterialApp(
-          home: MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    controller: controller,
-                    focusNode: focusNode,
-                    style: textStyle,
-                    cursorColor: Colors.red,
-                    backgroundCursorColor: Colors.red,
-                    keyboardType: TextInputType.multiline,
-                    onChanged: (String value) { },
-                  ),
-                ),
-              ),
-            ),
-          ),
-        ),
-      );
-
-      final EditableTextState state = tester.firstState(find.byType(EditableText));
-      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}';
-
-      final Map<String, dynamic> test = jsonDecode(jsonDelta) as Map<String, dynamic>;
-      final TextEditingDelta delta = TextEditingDelta.fromJSON(test);
-      expect(delta.runtimeType, TextEditingDeltaInsertion);
-
-      state.updateEditingValueWithDeltas(<TextEditingDelta>[delta]);
-      await tester.pump();
-      expect(controller.text, 'let there be text');
-      expect(controller.selection, delta.selection);
-      expect(state.currentTextEditingValue.composing, delta.composing);
-    });
-
-    testWidgets('TextEditingDeltaDeletion verification', (WidgetTester tester) async {
-      final TextEditingController controller = TextEditingController(text: 'let there be text');
-      await tester.pumpWidget(
-        MaterialApp(
-          home: MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    controller: controller,
-                    focusNode: focusNode,
-                    style: textStyle,
-                    cursorColor: Colors.red,
-                    backgroundCursorColor: Colors.red,
-                    keyboardType: TextInputType.multiline,
-                    onChanged: (String value) { },
-                  ),
-                ),
-              ),
-            ),
-          ),
-        ),
-      );
-
-      final EditableTextState state = tester.firstState(find.byType(EditableText));
-      const String jsonDelta = '{'
-          '"oldText": "let there be text",'
-          ' "deltaText": "",'
-          ' "deltaStart": 0,'
-          ' "deltaEnd": 17,'
-          ' "selectionBase": 0,'
-          ' "selectionExtent": 0,'
-          ' "selectionAffinity" : "TextAffinity.downstream" ,'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      final Map<String, dynamic> test = jsonDecode(jsonDelta) as Map<String, dynamic>;
-
-      final TextEditingDelta delta = TextEditingDelta.fromJSON(test);
-      expect(delta.runtimeType, TextEditingDeltaDeletion);
-
-      state.updateEditingValueWithDeltas(<TextEditingDelta>[delta]);
-      await tester.pump();
-      expect(controller.text, '');
-      expect(controller.selection, delta.selection);
-      expect(state.currentTextEditingValue.composing, delta.composing);
-    });
-
-    testWidgets('TextEditingDeltaReplacement verification', (WidgetTester tester) async {
-      final TextEditingController controller = TextEditingController(text: 'let there be text');
-      await tester.pumpWidget(
-        MaterialApp(
-          home: MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    controller: controller,
-                    focusNode: focusNode,
-                    style: textStyle,
-                    cursorColor: Colors.red,
-                    backgroundCursorColor: Colors.red,
-                    keyboardType: TextInputType.multiline,
-                    onChanged: (String value) { },
-                  ),
-                ),
-              ),
-            ),
-          ),
-        ),
-      );
-
-      final EditableTextState state = tester.firstState(find.byType(EditableText));
-      const String jsonDelta = '{'
-          '"oldText": "let there be text",'
-          ' "deltaText": "this is your replacement text",'
-          ' "deltaStart": 0,'
-          ' "deltaEnd": 17,'
-          ' "selectionBase": 0,'
-          ' "selectionExtent": 0,'
-          ' "selectionAffinity" : "TextAffinity.downstream",'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      final Map<String, dynamic> test = jsonDecode(jsonDelta) as Map<String, dynamic>;
-
-      final TextEditingDelta delta = TextEditingDelta.fromJSON(test);
-      expect(delta.runtimeType, TextEditingDeltaReplacement);
-
-      state.updateEditingValueWithDeltas(<TextEditingDelta>[delta]);
-      await tester.pump();
-      expect(controller.text, 'this is your replacement text');
-      expect(controller.selection, delta.selection);
-      expect(state.currentTextEditingValue.composing, delta.composing);
-    });
-
-    testWidgets('TextEditingDeltaNonTextUpdate verification', (WidgetTester tester) async {
-      final TextEditingController controller = TextEditingController(text: 'let there be text');
-      await tester.pumpWidget(
-        MaterialApp(
-          home: MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    controller: controller,
-                    focusNode: focusNode,
-                    style: textStyle,
-                    cursorColor: Colors.red,
-                    backgroundCursorColor: Colors.red,
-                    keyboardType: TextInputType.multiline,
-                    onChanged: (String value) { },
-                  ),
-                ),
-              ),
-            ),
-          ),
-        ),
-      );
-
-      final EditableTextState state = tester.firstState(find.byType(EditableText));
-      const String jsonDelta = '{'
-          '"oldText": "let there be text",'
-          ' "deltaText": "",'
-          ' "deltaStart": -1,'
-          ' "deltaEnd": -1,'
-          ' "selectionBase": 17,'
-          ' "selectionExtent": 17,'
-          ' "selectionAffinity" : "TextAffinity.downstream",'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      final Map<String, dynamic> test = jsonDecode(jsonDelta)  as Map<String, dynamic>;
-
-      final TextEditingDelta delta = TextEditingDelta.fromJSON(test);
-      expect(delta.runtimeType, TextEditingDeltaNonTextUpdate);
-
-      state.updateEditingValueWithDeltas(<TextEditingDelta>[delta]);
-      await tester.pump();
-      expect(controller.text, 'let there be text');
-      expect(controller.selection, delta.selection);
-      expect(state.currentTextEditingValue.composing, delta.composing);
-    });
-
-    testWidgets('TextEditingDelta verify batch deltas apply', (WidgetTester tester) async {
-      final TextEditingController controller = TextEditingController();
-      await tester.pumpWidget(
-        MaterialApp(
-          home: MediaQuery(
-            data: const MediaQueryData(devicePixelRatio: 1.0),
-            child: Directionality(
-              textDirection: TextDirection.ltr,
-              child: Center(
-                child: Material(
-                  child: EditableText(
-                    controller: controller,
-                    focusNode: focusNode,
-                    style: textStyle,
-                    cursorColor: Colors.red,
-                    backgroundCursorColor: Colors.red,
-                    keyboardType: TextInputType.multiline,
-                    onChanged: (String value) { },
-                  ),
-                ),
-              ),
-            ),
-          ),
-        ),
-      );
-
-      final EditableTextState state = tester.firstState(find.byType(EditableText));
-      const String jsonInsertionDelta = '{'
-          '"oldText": "",'
-          ' "deltaText": "let there be text",'
-          ' "deltaStart": 0,'
-          ' "deltaEnd": 0,'
-          ' "selectionBase": 17,'
-          ' "selectionExtent": 17,'
-          ' "selectionAffinity" : "TextAffinity.downstream" ,'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      const String jsonDeletionDelta = '{'
-          '"oldText": "let there be text",'
-          ' "deltaText": "",'
-          ' "deltaStart": 12,'
-          ' "deltaEnd": 17,'
-          ' "selectionBase": 12,'
-          ' "selectionExtent": 12,'
-          ' "selectionAffinity" : "TextAffinity.downstream" ,'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      const String jsonReplacementDelta = '{'
-          '"oldText": "let there be",'
-          ' "deltaText": "b light",'
-          ' "deltaStart": 10,'
-          ' "deltaEnd": 12,'
-          ' "selectionBase": 17,'
-          ' "selectionExtent": 17,'
-          ' "selectionAffinity" : "TextAffinity.downstream" ,'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      const String jsonNonTextUpdateDelta = '{'
-          '"oldText": "let there b light",'
-          ' "deltaText": "",'
-          ' "deltaStart": -1,'
-          ' "deltaEnd": -1,'
-          ' "selectionBase": 17,'
-          ' "selectionExtent": 17,'
-          ' "selectionAffinity" : "TextAffinity.downstream",'
-          ' "selectionIsDirectional": false,'
-          ' "composingBase": -1,'
-          ' "composingExtent": -1}';
-
-      final TextEditingDelta insertionDelta = TextEditingDelta.fromJSON(jsonDecode(jsonInsertionDelta) as Map<String, dynamic>);
-      final TextEditingDelta deletionDelta = TextEditingDelta.fromJSON(jsonDecode(jsonDeletionDelta) as Map<String, dynamic>);
-      final TextEditingDelta replacementDelta = TextEditingDelta.fromJSON(jsonDecode(jsonReplacementDelta) as Map<String, dynamic>);
-      final TextEditingDelta nonTextUpdateDelta = TextEditingDelta.fromJSON(jsonDecode(jsonNonTextUpdateDelta) as Map<String, dynamic>);
-      expect(insertionDelta.runtimeType, TextEditingDeltaInsertion);
-      expect(deletionDelta.runtimeType, TextEditingDeltaDeletion);
-      expect(replacementDelta.runtimeType, TextEditingDeltaReplacement);
-      expect(nonTextUpdateDelta.runtimeType, TextEditingDeltaNonTextUpdate);
-
-      state.updateEditingValueWithDeltas(<TextEditingDelta>[insertionDelta, deletionDelta, replacementDelta, nonTextUpdateDelta]);
-      await tester.pump();
-      expect(controller.text, 'let there b light');
-      expect(controller.selection, nonTextUpdateDelta.selection);
-      expect(state.currentTextEditingValue.composing, nonTextUpdateDelta.composing);
-    });
-  });
-
   group('TextEditingController', () {
     testWidgets('TextEditingController.text set to empty string clears field', (WidgetTester tester) async {
       final TextEditingController controller = TextEditingController();