258 lines
7.9 KiB
Dart
258 lines
7.9 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 '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: <String>[],
|
|
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 <String>['client1'],
|
|
currentEditingValue: client1.currentTextEditingValue,
|
|
),
|
|
);
|
|
|
|
client2.textInputConfiguration = TextInputConfiguration(
|
|
autofillConfiguration: AutofillConfiguration(
|
|
uniqueIdentifier: client2.autofillId,
|
|
autofillHints: const <String>['client2'],
|
|
currentEditingValue: client2.currentTextEditingValue,
|
|
),
|
|
);
|
|
|
|
scope.register(client1);
|
|
scope.register(client2);
|
|
client1.currentAutofillScope = scope;
|
|
client2.currentAutofillScope = scope;
|
|
|
|
scope.attach(client1, client1.textInputConfiguration);
|
|
|
|
final Map<String, dynamic> expectedConfiguration = client1.textInputConfiguration.toJson();
|
|
expectedConfiguration['fields'] = <Map<String, dynamic>>[
|
|
client1.textInputConfiguration.toJson(),
|
|
client2.textInputConfiguration.toJson(),
|
|
];
|
|
|
|
fakeTextChannel.validateOutgoingMethodCalls(<MethodCall>[
|
|
MethodCall('TextInput.setClient', <dynamic>[1, expectedConfiguration]),
|
|
]);
|
|
|
|
const text2 = TextEditingValue(text: 'Text 2');
|
|
fakeTextChannel.incoming?.call(
|
|
MethodCall('TextInputClient.updateEditingStateWithTag', <dynamic>[
|
|
0,
|
|
<String, dynamic>{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: <String>['client1'],
|
|
currentEditingValue: TextEditingValue.empty,
|
|
hintText: 'hint',
|
|
);
|
|
// ignore: prefer_const_constructors
|
|
fakeAutoFillConfiguration2 = AutofillConfiguration(
|
|
uniqueIdentifier: 'id1',
|
|
// ignore: prefer_const_literals_to_create_immutables
|
|
autofillHints: <String>['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<String, dynamic> 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<String, AutofillClient> clients = <String, AutofillClient>{};
|
|
|
|
@override
|
|
Iterable<AutofillClient> get autofillClients => clients.values;
|
|
|
|
@override
|
|
AutofillClient getAutofillClient(String autofillId) => clients[autofillId]!;
|
|
|
|
void register(AutofillClient client) {
|
|
clients.putIfAbsent(client.autofillId, () => client);
|
|
}
|
|
}
|