import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:zenxercise/src/app.dart'; import 'package:zenxercise/src/exercises/exercise.dart'; import 'package:zenxercise/src/exercises/exercise_library.dart'; import 'package:zenxercise/src/modes/mode.dart'; import 'package:zenxercise/src/history/session_repository.dart'; import 'package:zenxercise/src/plans/plan_repository.dart'; import 'package:zenxercise/src/player/player_effects.dart'; import 'package:zenxercise/src/settings/settings_controller.dart'; import 'package:zenxercise/src/streak/reminder_scheduler.dart'; import 'test_database.dart'; Exercise exercise( String id, Mode mode, { String? name, List primary = const ['hamstrings'], List secondary = const [], String? equipment = 'body only', String level = 'beginner', List instructions = const ['Breathe.'], int imageCount = 0, }) => Exercise( id: id, name: name ?? id.replaceAll('_', ' '), mode: mode, category: mode == Mode.stretching ? 'stretching' : 'strength', level: level, equipment: equipment, primaryMuscles: primary, secondaryMuscles: secondary, instructions: instructions, imageCount: imageCount, ); /// Two stretches and a squat. The 90/90 Hamstring's images exist in the real assets. final sampleLibrary = ExerciseLibrary([ exercise( '90_90_Hamstring', Mode.stretching, name: '90/90 Hamstring', secondary: const ['calves'], instructions: const ['Lie on your back.', 'Straighten one leg.'], imageCount: 2, ), exercise('Calf_Stretch', Mode.stretching, primary: const ['calves'], equipment: 'other', level: 'intermediate'), exercise('Barbell_Squat', Mode.workout, primary: const ['quadriceps'], equipment: 'barbell'), ]); /// Counts chimes instead of playing them and leaves the screen alone. class FakePlayerEffects implements PlayerEffects { final chimes = []; bool screenOn = false; @override Future chime(Mode mode) async => chimes.add(mode); @override Future keepScreenOn(bool on) async => screenOn = on; } /// Records what would be scheduled instead of talking to Android. class FakeNotifier implements Notifier { final scheduled = <(DateTime, String)>[]; var permissionRequests = 0; @override Future init() async {} @override Future requestPermission() async { permissionRequests++; return true; } @override Future enabled() async => true; @override Future cancelAll() async => scheduled.clear(); @override Future schedule({required int id, required DateTime at, required String title, required String body}) async => scheduled.add((at, body)); } /// A Pixel-sized screen instead of the default 800×600, so the floating /// navigation bar does not cover what a test taps. void usePhoneScreen(WidgetTester tester) { tester.view.physicalSize = const Size(1080, 2400); tester.view.devicePixelRatio = 2.75; addTearDown(tester.view.reset); } /// The whole app over [library], with preferences starting from [prefs]. Future pumpApp( WidgetTester tester, { ExerciseLibrary? library, Map prefs = const {}, PlanRepository? plans, SessionRepository? sessions, PlayerEffects? effects, FakeNotifier? notifier, }) async { SharedPreferences.setMockInitialValues(prefs); final settings = SettingsController(SettingsService()); await settings.loadSettings(); final db = testDatabase(); addTearDown(db.close); final sessionRepository = sessions ?? SessionRepository(db); final fakeNotifier = notifier ?? FakeNotifier(); await tester.pumpWidget( MultiProvider( providers: [ ChangeNotifierProvider.value(value: settings), Provider.value(value: library ?? sampleLibrary), Provider.value(value: plans ?? PlanRepository(db)), Provider.value(value: sessionRepository), Provider.value(value: effects ?? FakePlayerEffects()), Provider.value(value: fakeNotifier), Provider.value(value: ReminderScheduler(fakeNotifier, settings, sessionRepository)), ], child: const ZenXerciseApp(), ), ); await tester.pumpAndSettle(); return settings; }