From ce6817b0a1a71b08851dc70d6383e88955872c5b Mon Sep 17 00:00:00 2001 From: Paulik8 Date: Wed, 5 Feb 2025 22:46:56 +0100 Subject: [PATCH] Added equals and hashCode for TextInputConfiguration and AutofillConfiguration (#162238) - Added equals and hashCode for `TextInputConfiguration` and `AutofillConfiguration` (fixed https://github.com/flutter/flutter/issues/139033) - Fixed copyWith method in `TextInputConfiguration` (fixed https://github.com/flutter/flutter/issues/162236) - Added some tests *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## 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. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#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/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Bruno Leroux --- .../flutter/lib/src/services/autofill.dart | 39 ++++ .../flutter/lib/src/services/text_input.dart | 77 ++++++ .../flutter/test/services/autofill_test.dart | 68 ++++++ .../test/services/text_input_test.dart | 219 ++++++++++++++++++ 4 files changed, 403 insertions(+) diff --git a/packages/flutter/lib/src/services/autofill.dart b/packages/flutter/lib/src/services/autofill.dart index 80678d6574..cc8abfe55c 100644 --- a/packages/flutter/lib/src/services/autofill.dart +++ b/packages/flutter/lib/src/services/autofill.dart @@ -737,6 +737,45 @@ class AutofillConfiguration { } : null; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is AutofillConfiguration && + other.enabled == enabled && + other.uniqueIdentifier == uniqueIdentifier && + listEquals(other.autofillHints, autofillHints) && + other.currentEditingValue == currentEditingValue && + other.hintText == hintText; + } + + @override + int get hashCode { + return Object.hash( + enabled, + uniqueIdentifier, + Object.hashAll(autofillHints), + currentEditingValue, + hintText, + ); + } + + @override + String toString() { + final List description = [ + 'enabled: $enabled', + 'uniqueIdentifier: $uniqueIdentifier', + 'autofillHints: $autofillHints', + 'currentEditingValue: $currentEditingValue', + if (hintText != null) 'hintText: $hintText', + ]; + return 'AutofillConfiguration(${description.join(', ')})'; + } } /// An object that represents an autofillable input field in the autofill workflow. diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 01da51f85e..6d3702cab4 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -44,6 +44,7 @@ export 'package:vector_math/vector_math_64.dart' show Matrix4; export 'autofill.dart' show AutofillConfiguration, AutofillScope; export 'text_editing.dart' show TextSelection; + // TODO(a14n): the following export leads to Segmentation fault, see https://github.com/flutter/flutter/issues/106332 // export 'text_editing_delta.dart' show TextEditingDelta; @@ -713,6 +714,7 @@ class TextInputConfiguration { smartQuotesType: smartQuotesType ?? this.smartQuotesType, enableSuggestions: enableSuggestions ?? this.enableSuggestions, enableInteractiveSelection: enableInteractiveSelection ?? this.enableInteractiveSelection, + actionLabel: actionLabel ?? this.actionLabel, inputAction: inputAction ?? this.inputAction, textCapitalization: textCapitalization ?? this.textCapitalization, keyboardAppearance: keyboardAppearance ?? this.keyboardAppearance, @@ -772,6 +774,81 @@ class TextInputConfiguration { 'enableDeltaModel': enableDeltaModel, }; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is TextInputConfiguration && + other.viewId == viewId && + other.inputType == inputType && + other.readOnly == readOnly && + other.obscureText == obscureText && + other.autocorrect == autocorrect && + other.smartDashesType == smartDashesType && + other.smartQuotesType == smartQuotesType && + other.enableSuggestions == enableSuggestions && + other.enableInteractiveSelection == enableInteractiveSelection && + other.actionLabel == actionLabel && + other.inputAction == inputAction && + other.keyboardAppearance == keyboardAppearance && + other.textCapitalization == textCapitalization && + other.autofillConfiguration == autofillConfiguration && + other.enableIMEPersonalizedLearning == enableIMEPersonalizedLearning && + listEquals(other.allowedMimeTypes, allowedMimeTypes) && + other.enableDeltaModel == enableDeltaModel; + } + + @override + int get hashCode { + return Object.hash( + viewId, + inputType, + readOnly, + obscureText, + autocorrect, + smartDashesType, + smartQuotesType, + enableSuggestions, + enableInteractiveSelection, + actionLabel, + inputAction, + keyboardAppearance, + textCapitalization, + autofillConfiguration, + enableIMEPersonalizedLearning, + Object.hashAll(allowedMimeTypes), + enableDeltaModel, + ); + } + + @override + String toString() { + final List description = [ + if (viewId != null) 'viewId: $viewId', + 'inputType: $inputType', + 'readOnly: $readOnly', + 'obscureText: $obscureText', + 'autocorrect: $autocorrect', + 'smartDashesType: $smartDashesType', + 'smartQuotesType: $smartQuotesType', + 'enableSuggestions: $enableSuggestions', + 'enableInteractiveSelection: $enableInteractiveSelection', + if (actionLabel != null) 'actionLabel: $actionLabel', + 'inputAction: $inputAction', + 'keyboardAppearance: $keyboardAppearance', + 'textCapitalization: $textCapitalization', + 'autofillConfiguration: $autofillConfiguration', + 'enableIMEPersonalizedLearning: $enableIMEPersonalizedLearning', + 'allowedMimeTypes: $allowedMimeTypes', + 'enableDeltaModel: $enableDeltaModel', + ]; + return 'TextInputConfiguration(${description.join(', ')})'; + } } TextAffinity? _toTextAffinity(String? affinity) { diff --git a/packages/flutter/test/services/autofill_test.dart b/packages/flutter/test/services/autofill_test.dart index f8d360f82c..8adf53f89e 100644 --- a/packages/flutter/test/services/autofill_test.dart +++ b/packages/flutter/test/services/autofill_test.dart @@ -95,6 +95,74 @@ void main() { }, ); }); + + group('AutoFillConfiguration', () { + late AutofillConfiguration fakeAutoFillConfiguration; + late AutofillConfiguration fakeAutoFillConfiguration2; + + setUp(() { + // If you create two objects with `const` with the same values, the second object will be equal to the first one by reference. + // This means that even without overriding the `equals` method, the test will pass. + // ignore: prefer_const_constructors + fakeAutoFillConfiguration = AutofillConfiguration( + uniqueIdentifier: 'id1', + // ignore: prefer_const_literals_to_create_immutables + autofillHints: ['client1'], + currentEditingValue: TextEditingValue.empty, + hintText: 'hint', + ); + // ignore: prefer_const_constructors + fakeAutoFillConfiguration2 = AutofillConfiguration( + uniqueIdentifier: 'id1', + // ignore: prefer_const_literals_to_create_immutables + autofillHints: ['client1'], + currentEditingValue: TextEditingValue.empty, + hintText: 'hint', + ); + }); + + test('equality operator works correctly', () { + expect(fakeAutoFillConfiguration, equals(fakeAutoFillConfiguration2)); + expect(fakeAutoFillConfiguration.enabled, equals(fakeAutoFillConfiguration2.enabled)); + expect( + fakeAutoFillConfiguration.uniqueIdentifier, + equals(fakeAutoFillConfiguration2.uniqueIdentifier), + ); + expect( + fakeAutoFillConfiguration.autofillHints, + equals(fakeAutoFillConfiguration2.autofillHints), + ); + expect( + fakeAutoFillConfiguration.currentEditingValue, + equals(fakeAutoFillConfiguration2.currentEditingValue), + ); + expect(fakeAutoFillConfiguration.hintText, equals(fakeAutoFillConfiguration2.hintText)); + }); + + test('hashCode works correctly', () { + expect(fakeAutoFillConfiguration.hashCode, equals(fakeAutoFillConfiguration2.hashCode)); + expect( + fakeAutoFillConfiguration.enabled.hashCode, + equals(fakeAutoFillConfiguration2.enabled.hashCode), + ); + expect( + fakeAutoFillConfiguration.uniqueIdentifier.hashCode, + equals(fakeAutoFillConfiguration2.uniqueIdentifier.hashCode), + ); + expect( + Object.hashAll(fakeAutoFillConfiguration.autofillHints), + equals(Object.hashAll(fakeAutoFillConfiguration2.autofillHints)), + ); + expect( + fakeAutoFillConfiguration.currentEditingValue.hashCode, + equals(fakeAutoFillConfiguration2.currentEditingValue.hashCode), + ); + expect( + fakeAutoFillConfiguration.hintText.hashCode, + equals(fakeAutoFillConfiguration2.hintText.hashCode), + ); + }); + }); } class FakeAutofillClient implements TextInputClient, AutofillClient { diff --git a/packages/flutter/test/services/text_input_test.dart b/packages/flutter/test/services/text_input_test.dart index b4cdc24ce8..f7377ee3f0 100644 --- a/packages/flutter/test/services/text_input_test.dart +++ b/packages/flutter/test/services/text_input_test.dart @@ -321,10 +321,229 @@ void main() { }); group('TextInputConfiguration', () { + late TextInputConfiguration fakeTextInputConfiguration; + late TextInputConfiguration fakeTextInputConfiguration2; + + setUp(() { + // If you create two objects with `const` with the same values, the second object will be equal to the first one by reference. + // This means that even without overriding the `equals` method, the test will pass. + // ignore: prefer_const_constructors + fakeTextInputConfiguration = TextInputConfiguration( + viewId: 1, + actionLabel: 'label1', + smartDashesType: SmartDashesType.enabled, + smartQuotesType: SmartQuotesType.enabled, + // ignore: prefer_const_literals_to_create_immutables + allowedMimeTypes: ['text/plain', 'application/pdf'], + ); + fakeTextInputConfiguration2 = fakeTextInputConfiguration.copyWith(); + }); + tearDown(() { TextInputConnection.debugResetId(); }); + test('equality operator works correctly', () { + expect(fakeTextInputConfiguration, equals(fakeTextInputConfiguration2)); + expect(fakeTextInputConfiguration.viewId, equals(fakeTextInputConfiguration2.viewId)); + expect(fakeTextInputConfiguration.inputType, equals(fakeTextInputConfiguration2.inputType)); + expect( + fakeTextInputConfiguration.inputAction, + equals(fakeTextInputConfiguration2.inputAction), + ); + expect( + fakeTextInputConfiguration.autocorrect, + equals(fakeTextInputConfiguration2.autocorrect), + ); + expect( + fakeTextInputConfiguration.enableSuggestions, + equals(fakeTextInputConfiguration2.enableSuggestions), + ); + expect( + fakeTextInputConfiguration.obscureText, + equals(fakeTextInputConfiguration2.obscureText), + ); + expect(fakeTextInputConfiguration.readOnly, equals(fakeTextInputConfiguration2.readOnly)); + expect( + fakeTextInputConfiguration.smartDashesType, + equals(fakeTextInputConfiguration2.smartDashesType), + ); + expect( + fakeTextInputConfiguration.smartQuotesType, + equals(fakeTextInputConfiguration2.smartQuotesType), + ); + expect( + fakeTextInputConfiguration.enableInteractiveSelection, + equals(fakeTextInputConfiguration2.enableInteractiveSelection), + ); + expect( + fakeTextInputConfiguration.actionLabel, + equals(fakeTextInputConfiguration2.actionLabel), + ); + expect( + fakeTextInputConfiguration.keyboardAppearance, + equals(fakeTextInputConfiguration2.keyboardAppearance), + ); + expect( + fakeTextInputConfiguration.textCapitalization, + equals(fakeTextInputConfiguration2.textCapitalization), + ); + expect( + fakeTextInputConfiguration.autofillConfiguration, + equals(fakeTextInputConfiguration2.autofillConfiguration), + ); + expect( + fakeTextInputConfiguration.enableIMEPersonalizedLearning, + equals(fakeTextInputConfiguration2.enableIMEPersonalizedLearning), + ); + expect( + fakeTextInputConfiguration.allowedMimeTypes, + equals(fakeTextInputConfiguration2.allowedMimeTypes), + ); + expect( + fakeTextInputConfiguration.enableDeltaModel, + equals(fakeTextInputConfiguration2.enableDeltaModel), + ); + }); + + test('copyWith method works correctly', () { + fakeTextInputConfiguration2 = fakeTextInputConfiguration.copyWith(); + + expect(fakeTextInputConfiguration, equals(fakeTextInputConfiguration2)); + expect(fakeTextInputConfiguration.viewId, equals(fakeTextInputConfiguration2.viewId)); + expect(fakeTextInputConfiguration.inputType, equals(fakeTextInputConfiguration2.inputType)); + expect( + fakeTextInputConfiguration.inputAction, + equals(fakeTextInputConfiguration2.inputAction), + ); + expect( + fakeTextInputConfiguration.autocorrect, + equals(fakeTextInputConfiguration2.autocorrect), + ); + expect( + fakeTextInputConfiguration.enableSuggestions, + equals(fakeTextInputConfiguration2.enableSuggestions), + ); + expect( + fakeTextInputConfiguration.obscureText, + equals(fakeTextInputConfiguration2.obscureText), + ); + expect(fakeTextInputConfiguration.readOnly, equals(fakeTextInputConfiguration2.readOnly)); + expect( + fakeTextInputConfiguration.smartDashesType, + equals(fakeTextInputConfiguration2.smartDashesType), + ); + expect( + fakeTextInputConfiguration.smartQuotesType, + equals(fakeTextInputConfiguration2.smartQuotesType), + ); + expect( + fakeTextInputConfiguration.enableInteractiveSelection, + equals(fakeTextInputConfiguration2.enableInteractiveSelection), + ); + expect( + fakeTextInputConfiguration.actionLabel, + equals(fakeTextInputConfiguration2.actionLabel), + ); + expect( + fakeTextInputConfiguration.keyboardAppearance, + equals(fakeTextInputConfiguration2.keyboardAppearance), + ); + expect( + fakeTextInputConfiguration.textCapitalization, + equals(fakeTextInputConfiguration2.textCapitalization), + ); + expect( + fakeTextInputConfiguration.autofillConfiguration, + equals(fakeTextInputConfiguration2.autofillConfiguration), + ); + expect( + fakeTextInputConfiguration.enableIMEPersonalizedLearning, + equals(fakeTextInputConfiguration2.enableIMEPersonalizedLearning), + ); + expect( + fakeTextInputConfiguration.allowedMimeTypes, + equals(fakeTextInputConfiguration2.allowedMimeTypes), + ); + expect( + fakeTextInputConfiguration.enableDeltaModel, + equals(fakeTextInputConfiguration2.enableDeltaModel), + ); + }); + + test('hashCode works correctly', () { + expect(fakeTextInputConfiguration.hashCode, equals(fakeTextInputConfiguration2.hashCode)); + + expect( + fakeTextInputConfiguration.viewId.hashCode, + equals(fakeTextInputConfiguration2.viewId.hashCode), + ); + expect( + fakeTextInputConfiguration.inputType.hashCode, + equals(fakeTextInputConfiguration2.inputType.hashCode), + ); + expect( + fakeTextInputConfiguration.inputAction.hashCode, + equals(fakeTextInputConfiguration2.inputAction.hashCode), + ); + expect( + fakeTextInputConfiguration.autocorrect.hashCode, + equals(fakeTextInputConfiguration2.autocorrect.hashCode), + ); + expect( + fakeTextInputConfiguration.enableSuggestions.hashCode, + equals(fakeTextInputConfiguration2.enableSuggestions.hashCode), + ); + expect( + fakeTextInputConfiguration.obscureText.hashCode, + equals(fakeTextInputConfiguration2.obscureText.hashCode), + ); + expect( + fakeTextInputConfiguration.readOnly.hashCode, + equals(fakeTextInputConfiguration2.readOnly.hashCode), + ); + expect( + fakeTextInputConfiguration.smartDashesType.hashCode, + equals(fakeTextInputConfiguration2.smartDashesType.hashCode), + ); + expect( + fakeTextInputConfiguration.smartQuotesType.hashCode, + equals(fakeTextInputConfiguration2.smartQuotesType.hashCode), + ); + expect( + fakeTextInputConfiguration.enableInteractiveSelection.hashCode, + equals(fakeTextInputConfiguration2.enableInteractiveSelection.hashCode), + ); + expect( + fakeTextInputConfiguration.actionLabel.hashCode, + equals(fakeTextInputConfiguration2.actionLabel.hashCode), + ); + expect( + fakeTextInputConfiguration.keyboardAppearance.hashCode, + equals(fakeTextInputConfiguration2.keyboardAppearance.hashCode), + ); + expect( + fakeTextInputConfiguration.textCapitalization.hashCode, + equals(fakeTextInputConfiguration2.textCapitalization.hashCode), + ); + expect( + fakeTextInputConfiguration.autofillConfiguration.hashCode, + equals(fakeTextInputConfiguration2.autofillConfiguration.hashCode), + ); + expect( + fakeTextInputConfiguration.enableIMEPersonalizedLearning.hashCode, + equals(fakeTextInputConfiguration2.enableIMEPersonalizedLearning.hashCode), + ); + expect( + Object.hashAll(fakeTextInputConfiguration.allowedMimeTypes), + equals(Object.hashAll(fakeTextInputConfiguration2.allowedMimeTypes)), + ); + expect( + fakeTextInputConfiguration.enableDeltaModel.hashCode, + equals(fakeTextInputConfiguration2.enableDeltaModel.hashCode), + ); + }); + test('sets expected defaults', () { const TextInputConfiguration configuration = TextInputConfiguration(); expect(configuration.inputType, TextInputType.text);