// 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 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'text_input_utils.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('AutofillClient', () { late FakeTextChannel fakeTextChannel; final scope = FakeAutofillScope(); setUp(() { fakeTextChannel = FakeTextChannel((MethodCall call) async {}); TextInput.setChannel(fakeTextChannel); scope.clients.clear(); }); tearDown(() { TextInputConnection.debugResetId(); TextInput.setChannel(SystemChannels.textInput); }); test('Does not throw if the hint list is empty', () async { Object? exception; try { const AutofillConfiguration( uniqueIdentifier: 'id', autofillHints: [], currentEditingValue: TextEditingValue.empty, ); } catch (e) { exception = e; } expect(exception, isNull); }); test( 'AutofillClients send the correct configuration to the platform and responds to updateEditingStateWithTag method correctly', () async { final client1 = FakeAutofillClient(const TextEditingValue(text: 'test1')); final client2 = FakeAutofillClient(const TextEditingValue(text: 'test2')); client1.textInputConfiguration = TextInputConfiguration( autofillConfiguration: AutofillConfiguration( uniqueIdentifier: client1.autofillId, autofillHints: const ['client1'], currentEditingValue: client1.currentTextEditingValue, ), ); client2.textInputConfiguration = TextInputConfiguration( autofillConfiguration: AutofillConfiguration( uniqueIdentifier: client2.autofillId, autofillHints: const ['client2'], currentEditingValue: client2.currentTextEditingValue, ), ); scope.register(client1); scope.register(client2); client1.currentAutofillScope = scope; client2.currentAutofillScope = scope; scope.attach(client1, client1.textInputConfiguration); final Map expectedConfiguration = client1.textInputConfiguration.toJson(); expectedConfiguration['fields'] = >[ client1.textInputConfiguration.toJson(), client2.textInputConfiguration.toJson(), ]; fakeTextChannel.validateOutgoingMethodCalls([ MethodCall('TextInput.setClient', [1, expectedConfiguration]), ]); const text2 = TextEditingValue(text: 'Text 2'); fakeTextChannel.incoming?.call( MethodCall('TextInputClient.updateEditingStateWithTag', [ 0, {client2.autofillId: text2.toJSON()}, ]), ); expect(client2.currentTextEditingValue, text2); }, ); }); 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 { FakeAutofillClient(this.currentTextEditingValue); @override String get autofillId => hashCode.toString(); @override late TextInputConfiguration textInputConfiguration; @override void updateEditingValue(TextEditingValue newEditingValue) { currentTextEditingValue = newEditingValue; latestMethodCall = 'updateEditingValue'; } @override AutofillScope? currentAutofillScope; String latestMethodCall = ''; @override TextEditingValue currentTextEditingValue; @override void performAction(TextInputAction action) { latestMethodCall = 'performAction'; } @override void performPrivateCommand(String action, Map data) { latestMethodCall = 'performPrivateCommand'; } @override void insertContent(KeyboardInsertedContent content) { latestMethodCall = 'commitContent'; } @override void updateFloatingCursor(RawFloatingCursorPoint point) { latestMethodCall = 'updateFloatingCursor'; } @override void connectionClosed() { latestMethodCall = 'connectionClosed'; } @override void showAutocorrectionPromptRect(int start, int end) { latestMethodCall = 'showAutocorrectionPromptRect'; } @override void didChangeInputControl(TextInputControl? oldControl, TextInputControl? newControl) { latestMethodCall = 'didChangeInputControl'; } @override void autofill(TextEditingValue newEditingValue) => updateEditingValue(newEditingValue); @override void showToolbar() { latestMethodCall = 'showToolbar'; } @override void insertTextPlaceholder(Size size) { latestMethodCall = 'insertTextPlaceholder'; } @override void removeTextPlaceholder() { latestMethodCall = 'removeTextPlaceholder'; } @override void performSelector(String selectorName) { latestMethodCall = 'performSelector'; } } class FakeAutofillScope with AutofillScopeMixin implements AutofillScope { final Map clients = {}; @override Iterable get autofillClients => clients.values; @override AutofillClient getAutofillClient(String autofillId) => clients[autofillId]!; void register(AutofillClient client) { clients.putIfAbsent(client.autofillId, () => client); } }