Deliver localized auth app with verification and go_router
Integrate themed login/signup, EN/RU localization, shared dummy providers, verification recovery screens and named URL routes. Store frontend guidelines, workflow skills, validation and delivery documentation. Related to #2, #3, #5 and #6.
This commit is contained in:
1 parent
27424ed886
commit
10a278c65a
44 files changed
+4128
-28
No files matched your search
@@ -0,0 +1,136 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:sxchnge_auth/sxchnge_auth.dart';
|
||||
|
||||
import 'package:sxchnge_app/main.dart';
|
||||
|
||||
class RecoveringService implements VerificationService {
|
||||
int sends = 0;
|
||||
int confirmations = 0;
|
||||
@override
|
||||
Future<VerificationResult> send({
|
||||
required String email,
|
||||
required String locale,
|
||||
}) async {
|
||||
if (++sends == 1) throw StateError('private error');
|
||||
return const VerificationResult(isSimulated: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<VerificationResult> confirm({required String token}) async {
|
||||
if (++confirmations == 1) throw StateError('private error');
|
||||
return const VerificationResult(isSimulated: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> openVerification(
|
||||
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.pumpAndSettle();
|
||||
}
|
||||
|
||||
void main() {
|
||||
for (final scenario in {
|
||||
'demo-valid': 'verified',
|
||||
'demo-expired': 'expired',
|
||||
'wrong': 'invalid',
|
||||
'': 'invalid',
|
||||
'demo-internal': 'error',
|
||||
}.entries) {
|
||||
testWidgets(
|
||||
'confirmation link resolves ${scenario.key} as ${scenario.value}',
|
||||
(tester) async {
|
||||
await openVerification(tester, '/verify-email?token=${scenario.key}');
|
||||
expect(
|
||||
find.byKey(ValueKey('verification-${scenario.value}')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.textContaining('DEMO'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
testWidgets('internal confirmation error can retry without entering email', (
|
||||
tester,
|
||||
) async {
|
||||
final service = RecoveringService();
|
||||
await openVerification(
|
||||
tester,
|
||||
'/verify-email?token=opaque',
|
||||
service: service,
|
||||
);
|
||||
expect(find.text('private error'), findsNothing);
|
||||
final retry = find.byKey(const ValueKey('verification-retry'));
|
||||
await tester.ensureVisible(retry);
|
||||
await tester.tap(retry);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('verification-verified')), findsOneWidget);
|
||||
expect(service.confirmations, 2);
|
||||
});
|
||||
testWidgets(
|
||||
'request validates email, reports send error and resends successfully',
|
||||
(tester) async {
|
||||
final service = RecoveringService();
|
||||
await openVerification(tester, '/verification-sent', service: service);
|
||||
final resend = find.byKey(const ValueKey('verification-resend'));
|
||||
await tester.ensureVisible(resend);
|
||||
await tester.tap(resend);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Enter your email address.'), findsOneWidget);
|
||||
expect(service.sends, 0);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('verification-email')),
|
||||
'alex@example.com',
|
||||
);
|
||||
await tester.ensureVisible(resend);
|
||||
await tester.tap(resend);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('verification-error')), findsOneWidget);
|
||||
await tester.ensureVisible(resend);
|
||||
await tester.tap(resend);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const ValueKey('verification-sent')), findsOneWidget);
|
||||
expect(service.sends, 2);
|
||||
},
|
||||
);
|
||||
for (final route in [
|
||||
'/verification-sent',
|
||||
'/verify-email?token=demo-valid',
|
||||
'/verify-email?token=demo-expired',
|
||||
'/verify-email?token=bad',
|
||||
'/verify-email?token=demo-internal',
|
||||
]) {
|
||||
testWidgets('$route fits narrow viewport at 200% in EN and RU', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(320, 740);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.platformDispatcher.textScaleFactorTestValue = 2;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
|
||||
await openVerification(tester, route);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.tap(find.byKey(const ValueKey('locale-selector')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.ancestor(
|
||||
of: find.text('Русский'),
|
||||
matching: find.byType(CheckedPopupMenuItem<Locale>),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('RU'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user