257 lines
9.2 KiB
Dart
257 lines
9.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: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('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<TextFormField>(
|
|
find.byKey(const ValueKey('email')),
|
|
);
|
|
expect(email.controller!.text, 'private-alias@example.com');
|
|
final password = tester.state<EditableTextState>(
|
|
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<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);
|
|
},
|
|
);
|
|
}
|