import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:sxchnge_auth/sxchnge_auth.dart'; import 'package:sxchnge_app/main.dart'; import 'package:sxchnge_app/providers/locale.dart'; import 'package:sxchnge_app/routing/routes.dart'; import 'package:sxchnge_app/verification/page.dart'; class PendingConfirmation implements VerificationService { final results = >{}; @override Future confirm({required String token}) => (results[token] = Completer()).future; @override Future send({ required String email, required String locale, }) async => const VerificationResult(isSimulated: true); } Future openRoute( WidgetTester tester, String route, { VerificationService service = const DummyVerificationService( delay: Duration.zero, ), }) async { tester.platformDispatcher.defaultRouteNameTestValue = route; addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); await tester.pumpWidget(SxchngeApp(verificationService: service)); await tester.pump(); return GoRouter.of(tester.element(find.byType(Scaffold).last)); } void main() { testWidgets('queued signup receipt is transient and updates its recipient', ( tester, ) async { final router = await openRoute(tester, '/'); await tester.pumpAndSettle(); router.goNamed( AppRoutes.verificationSent, extra: const VerificationDelivery( email: 'first@example.com', isSimulated: false, ), ); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('verification-sent')), findsOneWidget); expect(find.text('first@example.com'), findsOneWidget); router.goNamed( AppRoutes.verificationSent, extra: const VerificationDelivery( email: 'second@example.com', isSimulated: false, ), ); await tester.pumpAndSettle(); expect(find.text('first@example.com'), findsNothing); expect(find.text('second@example.com'), findsOneWidget); final restored = router.routeInformationParser.restoreRouteInformation( router.routerDelegate.currentConfiguration, )!; expect(restored.state.toString(), isNot(contains('second@example.com'))); expect(restored.uri.toString(), '/verification-sent'); router.goNamed(AppRoutes.login); await tester.pumpAndSettle(); router.goNamed(AppRoutes.verificationSent); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('verification-idle')), findsOneWidget); expect(find.text('second@example.com'), findsNothing); }); for (final language in ['en', 'ru']) { testWidgets( 'verified link keeps success visible and prefills login ($language)', (tester) async { tester.platformDispatcher.defaultRouteNameTestValue = '/verify-email?token=demo-valid'; addTearDown(tester.platformDispatcher.clearDefaultRouteNameTestValue); await tester.pumpWidget( SxchngeApp( localeProvider: LocaleProvider(initialLocale: Locale(language)), verificationService: const DummyVerificationService( delay: Duration.zero, verifiedEmail: 'private-alias@example.com', ), ), ); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('verification-verified')), findsOneWidget, ); expect(find.byKey(const ValueKey('email')), findsNothing); final router = GoRouter.of( tester.element(find.byType(VerificationPage)), ); expect(router.routeInformationProvider.value.uri.path, '/verify-email'); await tester.ensureVisible( find.byKey(const ValueKey('verification-continue')), ); await tester.tap(find.byKey(const ValueKey('verification-continue'))); await tester.pumpAndSettle(); final email = tester.widget( find.byKey(const ValueKey('email')), ); expect(email.controller!.text, 'private-alias@example.com'); final password = tester.state( find.descendant( of: find.byKey(const ValueKey('password')), matching: find.byType(EditableText), ), ); expect(password.currentTextEditingValue.text, isEmpty); expect(password.widget.focusNode.hasFocus, isTrue); expect(router.routeInformationProvider.value.uri.toString(), '/'); final restored = router.routeInformationParser.restoreRouteInformation( router.routerDelegate.currentConfiguration, )!; expect( restored.state.toString(), isNot(contains('private-alias@example.com')), ); expect(restored.state.toString(), isNot(contains('demo-valid'))); await tester.enterText( find.byKey(const ValueKey('email')), 'changed@example.com', ); expect(email.controller!.text, 'changed@example.com'); expect(find.byKey(const ValueKey('confirmation')), findsNothing); }, ); } testWidgets('unknown deep link redirects to canonical login URL', ( tester, ) async { final router = await openRoute(tester, '/unknown?token=private'); await tester.pumpAndSettle(); expect(router.routeInformationProvider.value.uri.toString(), '/'); expect(find.byKey(const ValueKey('email')), findsOneWidget); }); testWidgets( 'changing confirmation token ignores the previous pending result', (tester) async { final service = PendingConfirmation(); final router = await openRoute( tester, '/verify-email?token=first', service: service, ); await tester.pump(); expect(service.results.containsKey('first'), isTrue); router.goNamed( AppRoutes.verifyEmail, queryParameters: {'token': 'second'}, ); await tester.pump(); await tester.pump(); service.results['second']!.completeError( const VerificationException(VerificationError.expired), ); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('verification-expired')), findsOneWidget, ); service.results['first']!.complete( const VerificationResult(isSimulated: true), ); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('verification-expired')), findsOneWidget, ); expect(find.byKey(const ValueKey('verification-verified')), findsNothing); }, ); testWidgets( 'platform back and forward route updates recheck confirmation tokens', (tester) async { final router = await openRoute(tester, '/verify-email?token=demo-valid'); await tester.pumpAndSettle(); await router.routeInformationProvider.didPushRouteInformation( RouteInformation(uri: Uri.parse('/verify-email?token=demo-expired')), ); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('verification-expired')), findsOneWidget, ); await router.routeInformationProvider.didPushRouteInformation( RouteInformation(uri: Uri.parse('/verify-email?token=demo-valid')), ); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('verification-verified')), findsOneWidget, ); expect( router.routeInformationProvider.value.uri.queryParameters['token'], 'demo-valid', ); }, ); testWidgets('locale rebuild preserves router, URL and confirmation state', ( tester, ) async { final router = await openRoute(tester, '/verify-email?token=demo-valid'); await tester.pumpAndSettle(); final context = tester.element(find.byType(VerificationPage)); await context.read().setLocale(const Locale('ru')); await tester.pumpAndSettle(); expect( GoRouter.of(tester.element(find.byType(VerificationPage))), same(router), ); expect(find.byKey(const ValueKey('verification-verified')), findsOneWidget); expect(find.text('Ваша почта\nподтверждена.'), findsOneWidget); }); testWidgets( 'signup email stays in memory and is excluded from serialized history', (tester) async { final router = await openRoute(tester, '/'); await tester.pumpAndSettle(); router.goNamed(AppRoutes.verificationSent, extra: 'alex@example.com'); await tester.pumpAndSettle(); expect(find.text('alex@example.com'), findsOneWidget); expect(find.byKey(const ValueKey('verification-sent')), findsOneWidget); final restored = router.routeInformationParser.restoreRouteInformation( router.routerDelegate.currentConfiguration, )!; expect(restored.state.toString(), isNot(contains('alex@example.com'))); expect(restored.uri.toString(), '/verification-sent'); router.goNamed(AppRoutes.login); await tester.pumpAndSettle(); router.goNamed(AppRoutes.verificationSent); await tester.pumpAndSettle(); expect(find.text('alex@example.com'), findsNothing); expect(find.byKey(const ValueKey('verification-idle')), findsOneWidget); }, ); }