Enable credential autofill for signup and login (#56)
This commit is contained in:
1 parent
bd675357bb
commit
86b6aafe7d
6 files changed
+461
-117
No files matched your search
@@ -92,6 +92,14 @@ void main() {
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byKey(const ValueKey('verification-sent')), findsNothing);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<TextFormField>(find.byKey(const ValueKey('email')))
|
||||
@@ -153,6 +161,14 @@ void main() {
|
||||
'/verification-sent',
|
||||
);
|
||||
expect(backend.sends, 1);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
find.textContaining('a verification email is on its way'),
|
||||
findsOneWidget,
|
||||
@@ -198,6 +214,14 @@ void main() {
|
||||
find.text('Сервис недоступен. Попробуйте ещё раз.'),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(button);
|
||||
await tester.pump();
|
||||
expect(service.calls, hasLength(2));
|
||||
@@ -207,6 +231,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('preview-result')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('auth-error')), findsNothing);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
@@ -237,6 +267,14 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('confirmation')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('preview-result')), findsNothing);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
@@ -55,6 +55,12 @@ void main() {
|
||||
await submit(tester);
|
||||
expect(find.text('Enter your email address.'), findsOneWidget);
|
||||
expect(find.text('Enter your password.'), findsOneWidget);
|
||||
expect(
|
||||
tester.testTextInput.log.where(
|
||||
(call) => call.method == 'TextInput.finishAutofillContext',
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('email')),
|
||||
'test@example.com',
|
||||
@@ -79,6 +85,12 @@ void main() {
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('preview-result')), findsOneWidget);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
@@ -104,12 +116,26 @@ void main() {
|
||||
);
|
||||
await submit(tester);
|
||||
expect(find.text('Passwords do not match.'), findsOneWidget);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('confirmation')),
|
||||
'example-password',
|
||||
);
|
||||
await submit(tester);
|
||||
expect(find.byKey(const ValueKey('verification-sent')), findsOneWidget);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
);
|
||||
await tester.ensureVisible(
|
||||
find.byKey(const ValueKey('demo-confirmation')),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:sxchnge_app/main.dart';
|
||||
import 'package:sxchnge_app/providers/locale.dart';
|
||||
|
||||
Finder field(String key) => find.byKey(ValueKey(key));
|
||||
|
||||
EditableTextState editor(WidgetTester tester, String key) =>
|
||||
tester.state<EditableTextState>(
|
||||
find.descendant(of: field(key), matching: find.byType(EditableText)),
|
||||
);
|
||||
|
||||
void main() {
|
||||
for (final language in ['en', 'ru']) {
|
||||
testWidgets(
|
||||
'platform autofill fills the signup credential group ($language)',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(1440, 1200);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
await tester.pumpWidget(
|
||||
SxchngeApp(
|
||||
localeProvider: LocaleProvider(initialLocale: Locale(language)),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.text(language == 'ru' ? 'Регистрация' : 'Sign up'),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.showKeyboard(field('email'));
|
||||
|
||||
final configuration = tester.testTextInput.setClientArgs!;
|
||||
expect(
|
||||
configuration['inputType']['name'],
|
||||
'TextInputType.emailAddress',
|
||||
);
|
||||
final fields = (configuration['fields'] as List).cast<Map>();
|
||||
expect(fields, hasLength(4));
|
||||
expect(
|
||||
fields.map((field) => field['autofill']['hints'].first),
|
||||
containsAll(['name', 'username', 'newPassword', 'newPassword']),
|
||||
);
|
||||
|
||||
const values = {
|
||||
'name': 'Alex Example',
|
||||
'email': 'alex@example.com',
|
||||
'password': 'generated-example-password',
|
||||
'confirmation': 'generated-example-password',
|
||||
};
|
||||
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.textInput.name,
|
||||
SystemChannels.textInput.codec.encodeMethodCall(
|
||||
MethodCall('TextInputClient.updateEditingStateWithTag', [
|
||||
0,
|
||||
{
|
||||
for (final entry in values.entries)
|
||||
editor(tester, entry.key).autofillId: TextEditingValue(
|
||||
text: entry.value,
|
||||
).toJSON(),
|
||||
},
|
||||
]),
|
||||
),
|
||||
(_) {},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
for (final entry in values.entries) {
|
||||
expect(
|
||||
editor(tester, entry.key).currentTextEditingValue.text,
|
||||
entry.value,
|
||||
);
|
||||
}
|
||||
|
||||
// A visibility change must retain both the supplied value and its role.
|
||||
await tester.tap(
|
||||
find.byTooltip(
|
||||
language == 'ru' ? 'Показать пароль' : 'Show password',
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(editor(tester, 'password').widget.obscureText, isFalse);
|
||||
expect(editor(tester, 'password').widget.autofillHints, [
|
||||
'newPassword',
|
||||
]);
|
||||
expect(
|
||||
editor(tester, 'password').currentTextEditingValue.text,
|
||||
values['password'],
|
||||
);
|
||||
|
||||
await tester.tap(find.text(language == 'ru' ? 'Войти' : 'Log in'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.showKeyboard(field('email'));
|
||||
final loginFields =
|
||||
(tester.testTextInput.setClientArgs!['fields'] as List).cast<Map>();
|
||||
expect(loginFields, hasLength(2));
|
||||
expect(loginFields.map((field) => field['autofill']['hints'].first), [
|
||||
'username',
|
||||
'password',
|
||||
]);
|
||||
expect(
|
||||
editor(tester, 'password').currentTextEditingValue.text,
|
||||
isEmpty,
|
||||
);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: false),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('leaving an unfinished auth form cancels saving', (tester) async {
|
||||
await tester.pumpWidget(const SxchngeApp());
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(field('email'), 'alex@example.com');
|
||||
await tester.enterText(field('password'), 'unfinished-password');
|
||||
tester.testTextInput.log.clear();
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: false),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
tester.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -148,13 +148,38 @@ void main() {
|
||||
await signIn(t);
|
||||
expect(find.byKey(const ValueKey('two-factor-code')), findsOneWidget);
|
||||
expect(find.byKey(const ValueKey('preview-result')), findsNothing);
|
||||
expect(
|
||||
t.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
analytics.events.where((e) => e.kind == JourneyKind.login && e.terminal),
|
||||
isEmpty,
|
||||
);
|
||||
await t.enterText(find.byKey(const ValueKey('two-factor-code')), '000000');
|
||||
await tapFactor(t, 'two-factor-confirm');
|
||||
expect(find.byKey(const ValueKey('two-factor-error')), findsOneWidget);
|
||||
expect(
|
||||
t.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
await t.enterText(find.byKey(const ValueKey('two-factor-code')), '123456');
|
||||
await tapFactor(t, 'two-factor-confirm');
|
||||
expect(find.byKey(const ValueKey('account-eur')), findsOneWidget);
|
||||
expect(
|
||||
t.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
);
|
||||
final login = analytics.events
|
||||
.where((e) => e.kind == JourneyKind.login)
|
||||
.toList();
|
||||
@@ -172,6 +197,20 @@ void main() {
|
||||
await signIn(t);
|
||||
await tapFactor(t, 'two-factor-cancel');
|
||||
expect(find.byKey(const ValueKey('email')), findsOneWidget);
|
||||
expect(
|
||||
t.testTextInput.log,
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: false),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
t.testTextInput.log,
|
||||
isNot(
|
||||
contains(
|
||||
isMethodCall('TextInput.finishAutofillContext', arguments: true),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.byKey(const ValueKey('preview-result')), findsNothing);
|
||||
expect(
|
||||
t
|
||||
|
||||
Reference in new issue
Block a user