Files
sxchnge/frontend/app/test/routing_test.dart
T
Stephan D f1cbf64282 Fix onboarding UX and add replaceable journey analytics
Make recipient changes explicit, prioritize verification retry, and label simulated outcomes prominently. Add shared typed analytics with bounded dummy capture, correlated service events and documented PostHog adapter contract. Validate privacy, retries, abandonment, keyboard recovery and accessibility. Related to #12.
2026-09-12 08:36:45 +02:00

158 lines
5.4 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: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 = <String, Completer<VerificationResult>>{};
@override
Future<VerificationResult> confirm({required String token}) =>
(results[token] = Completer<VerificationResult>()).future;
@override
Future<VerificationResult> send({
required String email,
required String locale,
}) async => const VerificationResult(isSimulated: true);
}
Future<GoRouter> 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('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<LocaleProvider>().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);
},
);
}