243 lines
8.2 KiB
Dart
243 lines
8.2 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import 'package:sxchnge_analytics/sxchnge_analytics.dart';
|
|
|
|
import 'package:sxchnge_auth/sxchnge_auth.dart';
|
|
|
|
import 'package:sxchnge_app/main.dart';
|
|
import 'package:sxchnge_app/providers/locale.dart';
|
|
|
|
class ControlledLogin implements LoginService {
|
|
final calls = <Completer<AuthResult>>[];
|
|
LoginRequest? request;
|
|
@override
|
|
Future<AuthResult> login(LoginRequest input) {
|
|
request = input;
|
|
final completer = Completer<AuthResult>();
|
|
calls.add(completer);
|
|
return completer.future;
|
|
}
|
|
}
|
|
|
|
class UnverifiedBackend extends BackendAuthServices {
|
|
UnverifiedBackend()
|
|
: super(baseUri: Uri.parse('https://api.example/api/v1'), clientId: 'web');
|
|
int sends = 0;
|
|
@override
|
|
Future<AuthResult> login(LoginRequest request) async =>
|
|
throw const AuthException(AuthError.accountNotVerified);
|
|
@override
|
|
Future<VerificationResult> send({
|
|
required String email,
|
|
required String locale,
|
|
}) async {
|
|
sends++;
|
|
return const VerificationResult(isSimulated: false);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
for (final language in ['en', 'ru']) {
|
|
testWidgets(
|
|
'signup requiring assistance preserves input and records failure ($language)',
|
|
(tester) async {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
tester.platformDispatcher.textScaleFactorTestValue = 1.5;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
|
|
final analytics = DummyAnalyticsService();
|
|
final locale = LocaleProvider(initialLocale: Locale(language));
|
|
await tester.pumpWidget(
|
|
SxchngeApp(
|
|
localeProvider: locale,
|
|
analyticsService: analytics,
|
|
signupService: const DummySignupService(
|
|
delay: Duration.zero,
|
|
failure: AuthError.signupNeedsAssistance,
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final signupLabel = language == 'ru' ? 'Регистрация' : 'Sign up';
|
|
await tester.ensureVisible(find.text(signupLabel));
|
|
await tester.tap(find.text(signupLabel));
|
|
await tester.pumpAndSettle();
|
|
for (final entry in {
|
|
'name': 'Ada Lovelace',
|
|
'email': 'ada@example.com',
|
|
'password': 'password preserved',
|
|
'confirmation': 'password preserved',
|
|
}.entries) {
|
|
await tester.enterText(find.byKey(ValueKey(entry.key)), entry.value);
|
|
}
|
|
await tester.ensureVisible(find.byKey(const ValueKey('submit')));
|
|
await tester.tap(find.byKey(const ValueKey('submit')));
|
|
await tester.pumpAndSettle();
|
|
final feedback = find.byKey(const ValueKey('auth-error'));
|
|
await tester.ensureVisible(feedback);
|
|
expect(
|
|
find.text(
|
|
language == 'ru'
|
|
? 'Не удалось завершить создание аккаунта. Обратитесь в службу поддержки.'
|
|
: 'We could not finish setting up your account. Please contact support.',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byKey(const ValueKey('verification-sent')), findsNothing);
|
|
expect(
|
|
tester
|
|
.widget<TextFormField>(find.byKey(const ValueKey('email')))
|
|
.controller!
|
|
.text,
|
|
'ada@example.com',
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<TextFormField>(find.byKey(const ValueKey('password')))
|
|
.controller!
|
|
.text,
|
|
'password preserved',
|
|
);
|
|
expect(
|
|
analytics.events.where((event) => event.name == 'auth_signup_failed'),
|
|
hasLength(1),
|
|
);
|
|
expect(
|
|
analytics.events.where(
|
|
(event) => event.name == 'auth_signup_succeeded',
|
|
),
|
|
isEmpty,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
}
|
|
|
|
testWidgets('backend mode gates workspace and recovers unverified login', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(1440, 1200);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final backend = UnverifiedBackend();
|
|
await tester.pumpWidget(SxchngeApp(backend: backend));
|
|
await tester.pumpAndSettle();
|
|
final router = GoRouter.of(
|
|
tester.element(find.byKey(const ValueKey('email'))),
|
|
);
|
|
router.go('/accounts');
|
|
await tester.pumpAndSettle();
|
|
expect(router.routeInformationProvider.value.uri.path, '/');
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('email')),
|
|
'user@example.com',
|
|
);
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('password')),
|
|
'password-value',
|
|
);
|
|
await tester.ensureVisible(find.byKey(const ValueKey('submit')));
|
|
await tester.tap(find.byKey(const ValueKey('submit')));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
router.routeInformationProvider.value.uri.path,
|
|
'/verification-sent',
|
|
);
|
|
expect(backend.sends, 1);
|
|
expect(
|
|
find.textContaining('a verification email is on its way'),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'app shows loading, localized failure, and retries through injected service',
|
|
(tester) async {
|
|
tester.view.physicalSize = const Size(1440, 1200);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final service = ControlledLogin();
|
|
await tester.pumpWidget(
|
|
SxchngeApp(
|
|
loginService: service,
|
|
localeProvider: LocaleProvider(initialLocale: const Locale('ru')),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('email')),
|
|
'a@example.com',
|
|
);
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('password')),
|
|
'example-password',
|
|
);
|
|
final button = find.byKey(const ValueKey('submit'));
|
|
await tester.ensureVisible(button);
|
|
await tester.tap(button);
|
|
await tester.pump();
|
|
expect(tester.widget<FilledButton>(button).onPressed, isNull);
|
|
expect(find.text('Подождите…'), findsOneWidget);
|
|
expect(service.request!.locale, 'ru');
|
|
service.calls.single.completeError(
|
|
const AuthException(AuthError.unavailable),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.text('Сервис недоступен. Попробуйте ещё раз.'),
|
|
findsOneWidget,
|
|
);
|
|
await tester.tap(button);
|
|
await tester.pump();
|
|
expect(service.calls, hasLength(2));
|
|
service.calls.last.complete(
|
|
const AuthResult(userId: 'demo', isSimulated: true),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const ValueKey('preview-result')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('auth-error')), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets('changing mode while loading ignores the old login result', (
|
|
tester,
|
|
) async {
|
|
final service = ControlledLogin();
|
|
await tester.pumpWidget(SxchngeApp(loginService: service));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('email')),
|
|
'a@example.com',
|
|
);
|
|
await tester.enterText(
|
|
find.byKey(const ValueKey('password')),
|
|
'example-password',
|
|
);
|
|
await tester.ensureVisible(find.byKey(const ValueKey('submit')));
|
|
await tester.tap(find.byKey(const ValueKey('submit')));
|
|
await tester.pump();
|
|
await tester.ensureVisible(find.text('Sign up'));
|
|
await tester.tap(find.text('Sign up'));
|
|
await tester.pumpAndSettle();
|
|
service.calls.single.complete(
|
|
const AuthResult(userId: 'demo', isSimulated: true),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const ValueKey('confirmation')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('preview-result')), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|