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 = >[]; LoginRequest? request; @override Future login(LoginRequest input) { request = input; final completer = Completer(); 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 login(LoginRequest request) async => throw const AuthException(AuthError.accountNotVerified); @override Future 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.testTextInput.log, isNot( contains( isMethodCall('TextInput.finishAutofillContext', arguments: true), ), ), ); expect( tester .widget(find.byKey(const ValueKey('email'))) .controller! .text, 'ada@example.com', ); expect( tester .widget(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( tester.testTextInput.log, isNot( contains( isMethodCall('TextInput.finishAutofillContext', arguments: true), ), ), ); 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(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, ); expect( tester.testTextInput.log, isNot( contains( isMethodCall('TextInput.finishAutofillContext', arguments: true), ), ), ); 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.testTextInput.log, contains( isMethodCall('TextInput.finishAutofillContext', arguments: true), ), ); 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.testTextInput.log, isNot( contains( isMethodCall('TextInput.finishAutofillContext', arguments: true), ), ), ); expect(tester.takeException(), isNull); }); }