107 lines
3.6 KiB
Dart
107 lines
3.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:glp1_companion/core/platform/notification_service.dart';
|
|
import 'package:glp1_companion/features/auth/presentation/controllers/dev_auth_bypass.dart';
|
|
import 'package:glp1_companion/features/auth/presentation/screens/login_screen.dart';
|
|
import 'package:glp1_companion/features/medication/domain/entities/dose_entry.dart';
|
|
import 'package:glp1_companion/features/medication/domain/repositories/medication_repository.dart';
|
|
import 'package:glp1_companion/features/medication/presentation/controllers/medication_controller.dart';
|
|
import 'package:glp1_companion/main.dart';
|
|
|
|
/// Empty history so the medication shell renders without touching drift.
|
|
class _EmptyMedicationRepository implements IMedicationRepository {
|
|
@override
|
|
Stream<List<DoseEntry>> watchDoseEntries({int limit = 500}) =>
|
|
Stream.value(const []);
|
|
|
|
@override
|
|
Future<List<DoseEntry>> getDoseEntries({int limit = 500}) async => const [];
|
|
|
|
@override
|
|
Future<DoseEntry> saveDoseEntry(DoseEntry entry) async => entry;
|
|
|
|
@override
|
|
Future<void> deleteDoseEntry(int id) async {}
|
|
|
|
@override
|
|
Future<DoseEntry?> getLatestAdministeredDose() async => null;
|
|
}
|
|
|
|
class _NoopNotificationService implements INotificationService {
|
|
@override
|
|
Future<void> initialize() async {}
|
|
|
|
@override
|
|
Future<bool> requestPermissions() async => false;
|
|
|
|
@override
|
|
Future<bool> scheduleDoseReminder({
|
|
required DateTime scheduledAt,
|
|
required String title,
|
|
required String body,
|
|
int id = kDoseReminderNotificationId,
|
|
String? payload,
|
|
}) async =>
|
|
false;
|
|
|
|
@override
|
|
Future<void> cancelReminder(int id) async {}
|
|
|
|
@override
|
|
Future<void> cancelAllReminders() async {}
|
|
}
|
|
|
|
Widget _buildApp() => ProviderScope(
|
|
overrides: [
|
|
medicationRepositoryProvider
|
|
.overrideWithValue(_EmptyMedicationRepository()),
|
|
notificationServiceProvider
|
|
.overrideWithValue(_NoopNotificationService()),
|
|
],
|
|
child: const GLP1CompanionApp(),
|
|
);
|
|
|
|
void main() {
|
|
testWidgets('unauthenticated sessions land on the login screen', (tester) async {
|
|
await tester.pumpWidget(_buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(LoginScreen), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('dev bypass button skips sign-in and opens the medication shell',
|
|
(tester) async {
|
|
// A tall phone-like viewport keeps the whole login column on-screen.
|
|
tester.view.physicalSize = const Size(1080, 2400);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.reset);
|
|
|
|
await tester.pumpWidget(_buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
// Debug builds expose the bypass affordance on the login screen.
|
|
final bypassButton = find.text('Continue Without Signing In');
|
|
expect(bypassButton, findsOneWidget);
|
|
|
|
await tester.tap(bypassButton);
|
|
await tester.pumpAndSettle();
|
|
|
|
// The guard admits the session and routes into the protected shell.
|
|
expect(find.byType(LoginScreen), findsNothing);
|
|
expect(find.text('Medication Tracker'), findsOneWidget);
|
|
// The dev banner marks the session as not signed in.
|
|
expect(find.text('Development mode - not signed in'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('bypass resets to disabled on every cold start', (tester) async {
|
|
await tester.pumpWidget(
|
|
const ProviderScope(child: SizedBox()),
|
|
);
|
|
|
|
final container = ProviderScope.containerOf(
|
|
tester.element(find.byType(SizedBox)),
|
|
);
|
|
expect(container.read(devAuthBypassProvider), isFalse);
|
|
});
|
|
} |