From 9b3957fe355b7123a768ebfa2fe5c327d5f0e7d6 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 16 Sep 2025 13:54:41 +0200 Subject: [PATCH] Use "clock" for mocking dates and datetimes This should make the tests a bit more robust, specially in CI since these tended to fail depending on how long and when the tests run. Also, rework a bit the way the start and end dates in sessions were being handled and initialised --- lib/helpers/json.dart | 4 +- lib/providers/gym_state.dart | 3 +- lib/widgets/routines/forms/session.dart | 24 +- .../routines/gym_mode/session_page.dart | 2 +- test/routine/gym_mode_screen_test.dart | 343 +++++++++--------- 5 files changed, 193 insertions(+), 183 deletions(-) diff --git a/lib/helpers/json.dart b/lib/helpers/json.dart index 1ac50ffb..a80fba71 100644 --- a/lib/helpers/json.dart +++ b/lib/helpers/json.dart @@ -67,8 +67,8 @@ String dateToUtcIso8601(DateTime dateTime) { * Needed e.g. when the wger api only sends a time but no date information. */ TimeOfDay stringToTime(String? time) { - final String out = time ?? '00:00'; - return TimeOfDay.fromDateTime(DateTime.parse('2020-01-01 $out')); + time ??= '00:00'; + return TimeOfDay.fromDateTime(DateTime.parse('2020-01-01 $time')); } TimeOfDay? stringToTimeNull(String? time) { diff --git a/lib/providers/gym_state.dart b/lib/providers/gym_state.dart index e18935ed..b208f806 100644 --- a/lib/providers/gym_state.dart +++ b/lib/providers/gym_state.dart @@ -1,3 +1,4 @@ +import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; @@ -26,7 +27,7 @@ class GymState { DateTime? validUntil, TimeOfDay? startTime}) { this.validUntil = validUntil ?? DateTime.now().add(DEFAULT_DURATION); - this.startTime = startTime ?? TimeOfDay.now(); + this.startTime = startTime ?? TimeOfDay.fromDateTime(clock.now()); } GymState copyWith({ diff --git a/lib/widgets/routines/forms/session.dart b/lib/widgets/routines/forms/session.dart index c48c8621..275408a9 100644 --- a/lib/widgets/routines/forms/session.dart +++ b/lib/widgets/routines/forms/session.dart @@ -44,8 +44,8 @@ class SessionForm extends StatefulWidget { dayId: dayId, impression: DEFAULT_IMPRESSION, date: clock.now(), - timeEnd: TimeOfDay.now(), - timeStart: TimeOfDay.now(), + timeEnd: TimeOfDay.fromDateTime(clock.now()), + timeStart: null, ); @override @@ -68,8 +68,10 @@ class _SessionFormState extends State { void initState() { super.initState(); - timeStartController.text = timeToString(widget._session.timeStart) ?? ''; - timeEndController.text = timeToString(widget._session.timeEnd) ?? ''; + timeStartController.text = + widget._session.timeStart == null ? '' : timeToString(widget._session.timeStart)!; + timeEndController.text = + widget._session.timeEnd == null ? '' : timeToString(widget._session.timeEnd)!; notesController.text = widget._session.notes; selectedImpression[widget._session.impression - 1] = true; @@ -129,6 +131,7 @@ class _SessionFormState extends State { }, ), Row( + spacing: 10, children: [ Flexible( child: TextFormField( @@ -163,16 +166,19 @@ class _SessionFormState extends State { if (timeStartController.text.isEmpty && timeEndController.text.isEmpty) { return null; } - final TimeOfDay startTime = stringToTime(timeStartController.text); - final TimeOfDay endTime = stringToTime(timeEndController.text); - if (startTime.isAfter(endTime)) { - return AppLocalizations.of(context).timeStartAhead; + + if (timeStartController.text.isNotEmpty && timeEndController.text.isNotEmpty) { + final TimeOfDay startTime = stringToTime(timeStartController.text); + final TimeOfDay endTime = stringToTime(timeEndController.text); + if (startTime.isAfter(endTime)) { + return AppLocalizations.of(context).timeStartAhead; + } } + return null; }, ), ), - const SizedBox(width: 10), Flexible( child: TextFormField( key: const ValueKey('time-end'), diff --git a/lib/widgets/routines/gym_mode/session_page.dart b/lib/widgets/routines/gym_mode/session_page.dart index 792dbdfa..d54240e7 100644 --- a/lib/widgets/routines/gym_mode/session_page.dart +++ b/lib/widgets/routines/gym_mode/session_page.dart @@ -45,8 +45,8 @@ class SessionPage extends StatelessWidget { routineId: _routine.id!, impression: DEFAULT_IMPRESSION, date: clock.now(), - timeEnd: TimeOfDay.now(), timeStart: start, + timeEnd: TimeOfDay.fromDateTime(clock.now()), ), ); diff --git a/test/routine/gym_mode_screen_test.dart b/test/routine/gym_mode_screen_test.dart index e13a4188..0615f4e9 100644 --- a/test/routine/gym_mode_screen_test.dart +++ b/test/routine/gym_mode_screen_test.dart @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod; import 'package:flutter_test/flutter_test.dart'; @@ -24,7 +25,6 @@ import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; -import 'package:wger/helpers/json.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; import 'package:wger/providers/base_provider.dart'; import 'package:wger/providers/exercises.dart'; @@ -101,192 +101,195 @@ void main() { exerciseIdToExclude: anyNamed('exerciseIdToExclude'), )).thenReturn([]); - await tester.pumpWidget(renderGymMode()); - //await tester.pumpWidget(createHomeScreen()); - await tester.tap(find.byType(TextButton)); - //print(find.byType(TextButton)); - await tester.pumpAndSettle(); - //await tester.ensureVisible(find.byKey(Key(key as String))); - // - // Start page - // - expect(find.byType(StartPage), findsOneWidget); - expect(find.text('Your workout today'), findsOneWidget); - expect(find.text('Bench press'), findsOneWidget); - expect(find.text('Side raises'), findsOneWidget); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.toc), findsOneWidget); - expect(find.byIcon(Icons.chevron_left), findsNothing); - expect(find.byIcon(Icons.chevron_right), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + await withClock(Clock.fixed(DateTime(2025, 3, 29, 14, 33)), () async { + await tester.pumpWidget(renderGymMode()); - // - // Bench press - exercise overview page - // - expect(find.text('Bench press'), findsOneWidget); - expect(find.byType(ExerciseOverview), findsOneWidget); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.toc), findsOneWidget); - expect(find.byIcon(Icons.chevron_left), findsOneWidget); - expect(find.byIcon(Icons.chevron_right), findsOneWidget); - await tester.drag(find.byType(ExerciseOverview), const Offset(-500.0, 0.0)); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TextButton)); - // - // Bench press - Log - // - expect(find.text('Bench press'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - expect(find.byType(Form), findsOneWidget); - // print(find.byType(Form)); - expect(find.byType(ListTile), findsNWidgets(3), reason: 'Two logs and the switch tile'); - expect(find.text('10 × 10 kg (1.5 RiR)'), findsOneWidget); - expect(find.text('12 × 10 kg (2 RiR)'), findsOneWidget); - expect(find.text('Make sure to warm up'), findsOneWidget, reason: 'Set comment'); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.toc), findsOneWidget); - expect(find.byIcon(Icons.chevron_left), findsOneWidget); - expect(find.byIcon(Icons.chevron_right), findsOneWidget); + await tester.pumpAndSettle(); + //await tester.ensureVisible(find.byKey(Key(key as String))); + // + // Start page + // + expect(find.byType(StartPage), findsOneWidget); + expect(find.text('Your workout today'), findsOneWidget); + expect(find.text('Bench press'), findsOneWidget); + expect(find.text('Side raises'), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.toc), findsOneWidget); + expect(find.byIcon(Icons.chevron_left), findsNothing); + expect(find.byIcon(Icons.chevron_right), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // Form shows only weight and reps - expect(find.byType(SwitchListTile), findsOneWidget); - expect(find.byType(TextFormField), findsNWidgets(2)); - expect(find.byType(RepetitionUnitInputWidget), findsNothing); - expect(find.byType(WeightUnitInputWidget), findsNothing); - expect(find.byType(RiRInputWidget), findsNothing); + // + // Bench press - exercise overview page + // + expect(find.text('Bench press'), findsOneWidget); + expect(find.byType(ExerciseOverview), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.toc), findsOneWidget); + expect(find.byIcon(Icons.chevron_left), findsOneWidget); + expect(find.byIcon(Icons.chevron_right), findsOneWidget); + await tester.drag(find.byType(ExerciseOverview), const Offset(-500.0, 0.0)); + await tester.pumpAndSettle(); - // Form shows unit and rir after tapping the toggle button - await tester.tap(find.byType(SwitchListTile)); - await tester.pump(); - expect(find.byType(RepetitionUnitInputWidget), findsOneWidget); - expect(find.byType(WeightUnitInputWidget), findsOneWidget); - expect(find.byType(RiRInputWidget), findsOneWidget); - await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0)); - await tester.pumpAndSettle(); + // + // Bench press - Log + // + expect(find.text('Bench press'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + expect(find.byType(Form), findsOneWidget); + // print(find.byType(Form)); + expect(find.byType(ListTile), findsNWidgets(3), reason: 'Two logs and the switch tile'); + expect(find.text('10 × 10 kg (1.5 RiR)'), findsOneWidget); + expect(find.text('12 × 10 kg (2 RiR)'), findsOneWidget); + expect(find.text('Make sure to warm up'), findsOneWidget, reason: 'Set comment'); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.toc), findsOneWidget); + expect(find.byIcon(Icons.chevron_left), findsOneWidget); + expect(find.byIcon(Icons.chevron_right), findsOneWidget); - // - // Bench press - pause - // - expect(find.text('Pause'), findsOneWidget); - expect(find.byType(TimerCountdownWidget), findsOneWidget); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.toc), findsOneWidget); - expect(find.byIcon(Icons.chevron_left), findsOneWidget); - expect(find.byIcon(Icons.chevron_right), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // Form shows only weight and reps + expect(find.byType(SwitchListTile), findsOneWidget); + expect(find.byType(TextFormField), findsNWidgets(2)); + expect(find.byType(RepetitionUnitInputWidget), findsNothing); + expect(find.byType(WeightUnitInputWidget), findsNothing); + expect(find.byType(RiRInputWidget), findsNothing); - // - // Bench press - log - // - expect(find.text('Bench press'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - expect(find.byType(Form), findsOneWidget); - await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0)); - await tester.pumpAndSettle(); + // Form shows unit and rir after tapping the toggle button + await tester.tap(find.byType(SwitchListTile)); + await tester.pump(); + expect(find.byType(RepetitionUnitInputWidget), findsOneWidget); + expect(find.byType(WeightUnitInputWidget), findsOneWidget); + expect(find.byType(RiRInputWidget), findsOneWidget); + await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0)); + await tester.pumpAndSettle(); - // - // Pause - // - expect(find.text('Pause'), findsOneWidget); - expect(find.byType(TimerCountdownWidget), findsOneWidget); - expect(find.byIcon(Icons.chevron_left), findsOneWidget); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.chevron_right), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Bench press - pause + // + expect(find.text('Pause'), findsOneWidget); + expect(find.byType(TimerCountdownWidget), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.toc), findsOneWidget); + expect(find.byIcon(Icons.chevron_left), findsOneWidget); + expect(find.byIcon(Icons.chevron_right), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Bench press - log - // - expect(find.text('Bench press'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - expect(find.byType(Form), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Bench press - log + // + expect(find.text('Bench press'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + expect(find.byType(Form), findsOneWidget); + await tester.drag(find.byType(LogPage), const Offset(-500.0, 0.0)); + await tester.pumpAndSettle(); - // - // Pause - // - expect(find.text('Pause'), findsOneWidget); - expect(find.byType(TimerCountdownWidget), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Pause + // + expect(find.text('Pause'), findsOneWidget); + expect(find.byType(TimerCountdownWidget), findsOneWidget); + expect(find.byIcon(Icons.chevron_left), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.chevron_right), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - overview - // - expect(find.text('Side raises'), findsOneWidget); - expect(find.byType(ExerciseOverview), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Bench press - log + // + expect(find.text('Bench press'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + expect(find.byType(Form), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - log - // - expect(find.text('Side raises'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Pause + // + expect(find.text('Pause'), findsOneWidget); + expect(find.byType(TimerCountdownWidget), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - timer - // - expect(find.byType(TimerWidget), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Side raises - overview + // + expect(find.text('Side raises'), findsOneWidget); + expect(find.byType(ExerciseOverview), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - log - // - expect(find.text('Side raises'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Side raises - log + // + expect(find.text('Side raises'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - timer - // - expect(find.byType(TimerWidget), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Side raises - timer + // + expect(find.byType(TimerWidget), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - log - // - expect(find.text('Side raises'), findsOneWidget); - expect(find.byType(LogPage), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Side raises - log + // + expect(find.text('Side raises'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Side raises - timer - // - expect(find.byType(TimerWidget), findsOneWidget); - await tester.tap(find.byIcon(Icons.chevron_right)); - await tester.pumpAndSettle(); + // + // Side raises - timer + // + expect(find.byType(TimerWidget), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // - // Session - // - expect(find.text('Workout session'), findsOneWidget); - expect(find.byType(SessionPage), findsOneWidget); - expect(find.byType(Form), findsOneWidget); - expect(find.byIcon(Icons.sentiment_very_dissatisfied), findsOneWidget); - expect(find.byIcon(Icons.sentiment_neutral), findsOneWidget); - expect(find.byIcon(Icons.sentiment_very_satisfied), findsOneWidget); - expect( - find.text(timeToString(TimeOfDay.now())!), - findsNWidgets(2), - reason: 'start and end time are the same', - ); - final toggleButtons = tester.widget(find.byType(ToggleButtons)); - expect(toggleButtons.isSelected[1], isTrue); - expect(find.byIcon(Icons.chevron_left), findsOneWidget); - expect(find.byIcon(Icons.close), findsOneWidget); - expect(find.byIcon(Icons.chevron_right), findsNothing); + // + // Side raises - log + // + expect(find.text('Side raises'), findsOneWidget); + expect(find.byType(LogPage), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); - // + // + // Side raises - timer + // + expect(find.byType(TimerWidget), findsOneWidget); + await tester.tap(find.byIcon(Icons.chevron_right)); + await tester.pumpAndSettle(); + + // + // Session + // + expect(find.text('Workout session'), findsOneWidget); + expect(find.byType(SessionPage), findsOneWidget); + expect(find.byType(Form), findsOneWidget); + expect(find.byIcon(Icons.sentiment_very_dissatisfied), findsOneWidget); + expect(find.byIcon(Icons.sentiment_neutral), findsOneWidget); + expect(find.byIcon(Icons.sentiment_very_satisfied), findsOneWidget); + expect( + find.text('14:33'), + findsNWidgets(2), + reason: 'start and end time are the same', + ); + final toggleButtons = tester.widget(find.byType(ToggleButtons)); + expect(toggleButtons.isSelected[1], isTrue); + expect(find.byIcon(Icons.chevron_left), findsOneWidget); + expect(find.byIcon(Icons.close), findsOneWidget); + expect(find.byIcon(Icons.chevron_right), findsNothing); + + // + }); }); }