feat: Complete Phase 1 Authentication and setup Flutter project structure

- Completed authentication system with signup, login, password reset, and logout
- Enhanced error handling and accessibility across all auth flows
- Added comprehensive loading states and user feedback
- Implemented confirmation dialogs for destructive actions
- Setup complete Flutter project structure with proper configuration
- Added planning documentation for Phase 2 household creation
- All Phase 1 success criteria verified and complete
This commit is contained in:
Dani B
2026-01-28 22:58:09 -05:00
parent e20858f608
commit 2f9ea1a0e8
131 changed files with 5747 additions and 8 deletions

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:go_router/go_router.dart';
import 'package:sage/main.dart' as app;
import 'package:sage/core/router/app_router.dart';
/// Integration test for password reset flow
///
/// Tests the complete password reset journey:
/// 1. Navigate to password reset from login
/// 2. Request password reset
/// 3. Handle deep link with token
/// 4. Update password
/// 5. Navigate back to login
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Password Reset Flow', () {
testWidgets('complete password reset flow works correctly', (WidgetTester tester) async {
// Launch app
app.main();
await tester.pumpAndSettle();
// Start from login screen (if not already there)
// Navigate to password reset
final forgotPasswordButton = find.text('Forgot Password?');
expect(forgotPasswordButton, findsOneWidget);
await tester.tap(forgotPasswordButton);
await tester.pumpAndSettle();
// Should be on reset password page
expect(find.text('Reset Password'), findsOneWidget);
expect(find.text('Forgot Password?'), findsOneWidget);
// Test deep linking by simulating URL with token
// This would normally be triggered by email link
final context = tester.element(find.byType(Scaffold));
// Navigate to reset password confirm with token
GoRouter.of(context).go('/reset-password-confirm?token=test123&email=test@example.com');
await tester.pumpAndSettle();
// Should be on reset password confirm page
expect(find.text('Reset Password'), findsOneWidget);
// Navigate to update password with token
GoRouter.of(context).go('/update-password?token=test123&email=test@example.com');
await tester.pumpAndSettle();
// Should be on update password page
expect(find.text('Update Password'), findsOneWidget);
expect(find.text('Set New Password'), findsOneWidget);
});
testWidgets('password reset navigation from signup works', (WidgetTester tester) async {
// Launch app
app.main();
await tester.pumpAndSettle();
// Navigate to signup first
final signupButton = find.text('Sign Up');
if (signupButton.evaluate().isNotEmpty) {
await tester.tap(signupButton);
await tester.pumpAndSettle();
}
// Look for forgot password link on signup page
final signupForgotPassword = find.text('Forgot Password?');
expect(signupForgotPassword, findsOneWidget);
await tester.tap(signupForgotPassword);
await tester.pumpAndSettle();
// Should navigate to reset password page
expect(find.text('Reset Password'), findsOneWidget);
expect(find.text('Forgot Password?'), findsOneWidget);
});
testWidgets('deep linking handles malformed URLs gracefully', (WidgetTester tester) async {
// Launch app
app.main();
await tester.pumpAndSettle();
// Test malformed reset URL
final context = tester.element(find.byType(Scaffold));
GoRouter.of(context).go('/reset-password-invalid');
await tester.pumpAndSettle();
// Should show error page with option to request new reset
expect(find.text('Page not found'), findsOneWidget);
expect(find.text('Request New Reset Link'), findsOneWidget);
});
testWidgets('router extracts URL parameters correctly', (WidgetTester tester) async {
// Launch app
app.main();
await tester.pumpAndSettle();
// Navigate with URL parameters
final context = tester.element(find.byType(Scaffold));
GoRouter.of(context).go('/reset-password-confirm?token=abc123&email=user@example.com');
await tester.pumpAndSettle();
// Extract parameters using router helper
final params = AppRouter.extractQueryParameters(context);
expect(params['token'], 'abc123');
expect(params['email'], 'user@example.com');
// Test deep link handler
final resetData = AppRouter.handlePasswordResetDeepLink(context);
expect(resetData['token'], 'abc123');
expect(resetData['email'], 'user@example.com');
});
});
group('Navigation Guards', () {
testWidgets('password reset routes are accessible without auth', (WidgetTester tester) async {
// Launch app
app.main();
await tester.pumpAndSettle();
// Test all password reset routes are accessible
final context = tester.element(find.byType(Scaffold));
// Test /reset-password
GoRouter.of(context).go('/reset-password');
await tester.pumpAndSettle();
expect(find.text('Reset Password'), findsOneWidget);
// Test /reset-password-confirm
GoRouter.of(context).go('/reset-password-confirm');
await tester.pumpAndSettle();
expect(find.text('Reset Password'), findsOneWidget);
// Test /update-password
GoRouter.of(context).go('/update-password');
await tester.pumpAndSettle();
expect(find.text('Update Password'), findsOneWidget);
});
});
}

30
test/widget_test.dart Normal file
View File

@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sage/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}