From 3ef8b62d539e61e591f700d925506965dacd732e Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 1 Feb 2025 15:29:14 +0100 Subject: [PATCH 1/8] Update exercise info API paths --- lib/models/exercises/exercise_api.dart | 2 +- lib/providers/add_exercise.dart | 2 +- lib/providers/exercises.dart | 2 +- test/exercises/exercise_provider_db_test.dart | 12 +++++----- .../exercise_provider_load_test.dart | 24 +++++++++---------- test/exercises/exercise_provider_test.dart | 14 +++++------ test/exercises/model_exercise_test.dart | 2 +- ...sponse.json => exerciseinfo_response.json} | 0 8 files changed, 29 insertions(+), 29 deletions(-) rename test/fixtures/exercises/{exercisebaseinfo_response.json => exerciseinfo_response.json} (100%) diff --git a/lib/models/exercises/exercise_api.dart b/lib/models/exercises/exercise_api.dart index f920e42d..f15d2785 100644 --- a/lib/models/exercises/exercise_api.dart +++ b/lib/models/exercises/exercise_api.dart @@ -11,7 +11,7 @@ import 'package:wger/models/exercises/video.dart'; part 'exercise_api.freezed.dart'; part 'exercise_api.g.dart'; -/// Model for an exercise as returned from the exercisebaseinfo endpoint +/// Model for an exercise as returned from the exerciseinfo endpoint /// /// Basically this is just used as a convenience to create "real" exercise /// objects and nothing more diff --git a/lib/providers/add_exercise.dart b/lib/providers/add_exercise.dart index 43da6282..2dee7912 100644 --- a/lib/providers/add_exercise.dart +++ b/lib/providers/add_exercise.dart @@ -37,7 +37,7 @@ class AddExerciseProvider with ChangeNotifier { List _primaryMuscles = []; List _secondaryMuscles = []; - static const _exerciseUrlPath = 'exercise-base'; + static const _exerciseUrlPath = 'exercise'; static const _imagesUrlPath = 'exerciseimage'; static const _exerciseTranslationUrlPath = 'exercise-translation'; static const _exerciseAliasPath = 'exercisealias'; diff --git a/lib/providers/exercises.dart b/lib/providers/exercises.dart index 1441c84b..1edc0612 100644 --- a/lib/providers/exercises.dart +++ b/lib/providers/exercises.dart @@ -48,7 +48,7 @@ class ExercisesProvider with ChangeNotifier { static const EXERCISE_CACHE_DAYS = 7; static const CACHE_VERSION = 4; - static const exerciseInfoUrlPath = 'exercisebaseinfo'; + static const exerciseInfoUrlPath = 'exerciseinfo'; static const exerciseSearchPath = 'exercise/search'; static const categoriesUrlPath = 'exercisecategory'; diff --git a/test/exercises/exercise_provider_db_test.dart b/test/exercises/exercise_provider_db_test.dart index 1c9435b3..11b384cc 100644 --- a/test/exercises/exercise_provider_db_test.dart +++ b/test/exercises/exercise_provider_db_test.dart @@ -22,7 +22,7 @@ void main() { late ExerciseDatabase database; const String categoryUrl = 'exercisecategory'; - const String exerciseBaseInfoUrl = 'exercisebaseinfo'; + const String exerciseInfoUrl = 'exerciseinfo'; const String muscleUrl = 'muscle'; const String equipmentUrl = 'equipment'; const String languageUrl = 'language'; @@ -36,13 +36,13 @@ void main() { final Uri tExerciseInfoUri = Uri( scheme: 'http', host: 'localhost', - path: 'api/v2/$exerciseBaseInfoUrl/', + path: 'api/v2/$exerciseInfoUrl/', ); final Uri tExerciseInfoDetailUri = Uri( scheme: 'http', host: 'localhost', - path: 'api/v2/$exerciseBaseInfoUrl/9/', + path: 'api/v2/$exerciseInfoUrl/9/', ); final Uri tMuscleEntriesUri = Uri( @@ -80,7 +80,7 @@ void main() { fixture('exercises/language_entries.json'), ); final Map tExerciseInfoMap = jsonDecode( - fixture('exercises/exercisebaseinfo_response.json'), + fixture('exercises/exerciseinfo_response.json'), ); setUp(() { @@ -118,12 +118,12 @@ void main() { ); // Mock base info response - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl)).thenReturn(tExerciseInfoUri); + when(mockBaseProvider.makeUrl(exerciseInfoUrl)).thenReturn(tExerciseInfoUri); when(mockBaseProvider.fetch(tExerciseInfoUri)).thenAnswer( (_) => Future.value(tExerciseInfoMap), ); - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 9)).thenReturn(tExerciseInfoDetailUri); + when(mockBaseProvider.makeUrl(exerciseInfoUrl, id: 9)).thenReturn(tExerciseInfoDetailUri); when(mockBaseProvider.fetch(tExerciseInfoDetailUri)).thenAnswer( (_) => Future.value(tExerciseInfoMap), ); diff --git a/test/exercises/exercise_provider_load_test.dart b/test/exercises/exercise_provider_load_test.dart index 9d9ad06a..d72ceaf6 100644 --- a/test/exercises/exercise_provider_load_test.dart +++ b/test/exercises/exercise_provider_load_test.dart @@ -18,22 +18,22 @@ void main() { late MockWgerBaseProvider mockBaseProvider; late ExercisesProvider provider; - const String exerciseBaseInfoUrl = 'exercisebaseinfo'; + const String exerciseInfoUrl = 'exerciseinfo'; - final Uri tExerciseBaseInfoUri = Uri( + final Uri tExerciseInfoUri = Uri( scheme: 'http', host: 'localhost', - path: 'api/v2/$exerciseBaseInfoUrl/9/', + path: 'api/v2/$exerciseInfoUrl/9/', ); - final Uri tExerciseBaseInfoUri2 = Uri( + final Uri tExerciseInfoUri2 = Uri( scheme: 'http', host: 'localhost', - path: 'api/v2/$exerciseBaseInfoUrl/1/', + path: 'api/v2/$exerciseInfoUrl/1/', ); final Map tExerciseInfoMap = jsonDecode( - fixture('exercises/exercisebaseinfo_response.json'), + fixture('exercises/exerciseinfo_response.json'), ); setUpAll(() async { @@ -54,12 +54,12 @@ void main() { provider.languages = [tLanguage1, tLanguage2, tLanguage3]; // Mock base info response - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 9)).thenReturn(tExerciseBaseInfoUri); - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 1)).thenReturn(tExerciseBaseInfoUri2); + when(mockBaseProvider.makeUrl(exerciseInfoUrl, id: 9)).thenReturn(tExerciseInfoUri); + when(mockBaseProvider.makeUrl(exerciseInfoUrl, id: 1)).thenReturn(tExerciseInfoUri2); - when(mockBaseProvider.fetch(tExerciseBaseInfoUri)) + when(mockBaseProvider.fetch(tExerciseInfoUri)) .thenAnswer((_) => Future.value(tExerciseInfoMap)); - when(mockBaseProvider.fetch(tExerciseBaseInfoUri2)) + when(mockBaseProvider.fetch(tExerciseInfoUri2)) .thenAnswer((_) => Future.value(tExerciseInfoMap)); }); @@ -69,7 +69,7 @@ void main() { final base = await provider.fetchAndSetExercise(1); // assert - verifyNever(provider.baseProvider.fetch(tExerciseBaseInfoUri2)); + verifyNever(provider.baseProvider.fetch(tExerciseInfoUri2)); expect(base?.id, 1); }); @@ -78,7 +78,7 @@ void main() { final base = await provider.fetchAndSetExercise(9); // assert - verify(provider.baseProvider.fetch(tExerciseBaseInfoUri)); + verify(provider.baseProvider.fetch(tExerciseInfoUri)); expect(base?.id, 9); }); diff --git a/test/exercises/exercise_provider_test.dart b/test/exercises/exercise_provider_test.dart index 5a24bf43..b970d115 100644 --- a/test/exercises/exercise_provider_test.dart +++ b/test/exercises/exercise_provider_test.dart @@ -23,7 +23,7 @@ void main() { late ExercisesProvider provider; const String categoryUrl = 'exercisecategory'; - const String exerciseBaseInfoUrl = 'exercisebaseinfo'; + const String exerciseInfoUrl = 'exerciseinfo'; const String muscleUrl = 'muscle'; const String equipmentUrl = 'equipment'; const String languageUrl = 'language'; @@ -38,7 +38,7 @@ void main() { final Uri tExerciseInfoUri = Uri( scheme: 'http', host: 'localhost', - path: 'api/v2/$exerciseBaseInfoUrl/1/', + path: 'api/v2/$exerciseInfoUrl/1/', ); final Uri tMuscleEntriesUri = Uri( @@ -80,8 +80,8 @@ void main() { final Map tLanguageMap = jsonDecode( fixture('exercises/language_entries.json'), ); - final Map tExerciseBaseInfoMap = jsonDecode( - fixture('exercises/exercisebaseinfo_response.json'), + final Map tExerciseInfoMap = jsonDecode( + fixture('exercises/exerciseinfo_response.json'), ); setUpAll(() { @@ -122,10 +122,10 @@ void main() { .thenAnswer((_) => Future.value(tLanguageMap['results'])); // Mock base info response - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 1)).thenReturn(tExerciseInfoUri); - when(mockBaseProvider.makeUrl(exerciseBaseInfoUrl, id: 2)).thenReturn(tExerciseInfoUri); + when(mockBaseProvider.makeUrl(exerciseInfoUrl, id: 1)).thenReturn(tExerciseInfoUri); + when(mockBaseProvider.makeUrl(exerciseInfoUrl, id: 2)).thenReturn(tExerciseInfoUri); when(mockBaseProvider.fetch(tExerciseInfoUri)) - .thenAnswer((_) => Future.value(tExerciseBaseInfoMap)); + .thenAnswer((_) => Future.value(tExerciseInfoMap)); }); group('findCategoryById()', () { diff --git a/test/exercises/model_exercise_test.dart b/test/exercises/model_exercise_test.dart index f5b9defc..176d0379 100644 --- a/test/exercises/model_exercise_test.dart +++ b/test/exercises/model_exercise_test.dart @@ -8,7 +8,7 @@ import '../fixtures/fixture_reader.dart'; void main() { final Map tExerciseInfoMap = jsonDecode( - fixture('exercises/exercisebaseinfo_response.json'), + fixture('exercises/exerciseinfo_response.json'), ); group('Model tests', () { diff --git a/test/fixtures/exercises/exercisebaseinfo_response.json b/test/fixtures/exercises/exerciseinfo_response.json similarity index 100% rename from test/fixtures/exercises/exercisebaseinfo_response.json rename to test/fixtures/exercises/exerciseinfo_response.json From 323b18320f18a41f983db182f315ee4bf6d99713 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 1 Feb 2025 15:34:00 +0100 Subject: [PATCH 2/8] Rename key to be more consistent --- lib/models/workouts/slot_entry.dart | 16 ++++++++-------- lib/models/workouts/slot_entry.g.dart | 4 ++-- lib/widgets/routines/forms/slot.dart | 8 ++++---- test/workout/slot_entry_model_test.dart | 6 +++--- test_data/routines.dart | 6 +++--- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/models/workouts/slot_entry.dart b/lib/models/workouts/slot_entry.dart index 92835734..609cf7d6 100644 --- a/lib/models/workouts/slot_entry.dart +++ b/lib/models/workouts/slot_entry.dart @@ -77,10 +77,10 @@ class SlotEntry { late num repetitionRounding; @JsonKey(required: false, name: 'reps_configs', includeToJson: false, defaultValue: []) - late List repsConfigs = []; + late List repetitionsConfigs = []; @JsonKey(required: false, name: 'max_reps_configs', includeToJson: false, defaultValue: []) - late List maxRepsConfigs = []; + late List maxRepetitionsConfigs = []; @JsonKey(required: true, name: 'weight_unit') late int weightUnitId; @@ -137,8 +137,8 @@ class SlotEntry { this.maxRirConfigs = const [], this.restTimeConfigs = const [], this.maxRestTimeConfigs = const [], - this.repsConfigs = const [], - this.maxRepsConfigs = const [], + this.repetitionsConfigs = const [], + this.maxRepetitionsConfigs = const [], RepetitionUnit? repetitionUnit, WeightUnit? weightUnit, Exercise? exercise, @@ -186,8 +186,8 @@ class SlotEntry { bool get hasProgressionRules { return weightConfigs.length > 1 || - repsConfigs.length > 1 || - maxRepsConfigs.length > 1 || + repetitionsConfigs.length > 1 || + maxRepetitionsConfigs.length > 1 || nrOfSetsConfigs.length > 1 || maxNrOfSetsConfigs.length > 1 || rirConfigs.length > 1 || @@ -209,9 +209,9 @@ class SlotEntry { case ConfigType.maxSets: return maxNrOfSetsConfigs; case ConfigType.reps: - return repsConfigs; + return repetitionsConfigs; case ConfigType.maxReps: - return maxRepsConfigs; + return maxRepetitionsConfigs; case ConfigType.rir: return rirConfigs; case ConfigType.maxRir: diff --git a/lib/models/workouts/slot_entry.g.dart b/lib/models/workouts/slot_entry.g.dart index 2d2a99b3..e576ac03 100644 --- a/lib/models/workouts/slot_entry.g.dart +++ b/lib/models/workouts/slot_entry.g.dart @@ -66,11 +66,11 @@ SlotEntry _$SlotEntryFromJson(Map json) { ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], - repsConfigs: (json['reps_configs'] as List?) + repetitionsConfigs: (json['reps_configs'] as List?) ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], - maxRepsConfigs: (json['max_reps_configs'] as List?) + maxRepetitionsConfigs: (json['max_reps_configs'] as List?) ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], diff --git a/lib/widgets/routines/forms/slot.dart b/lib/widgets/routines/forms/slot.dart index a8a1d9aa..0d3cafe5 100644 --- a/lib/widgets/routines/forms/slot.dart +++ b/lib/widgets/routines/forms/slot.dart @@ -102,11 +102,11 @@ class _SlotEntryFormState extends State { maxWeightController.text = widget.entry.maxWeightConfigs.first.value.toString(); } - if (widget.entry.repsConfigs.isNotEmpty) { - repsController.text = widget.entry.repsConfigs.first.value.round().toString(); + if (widget.entry.repetitionsConfigs.isNotEmpty) { + repsController.text = widget.entry.repetitionsConfigs.first.value.round().toString(); } - if (widget.entry.maxRepsConfigs.isNotEmpty) { - maxRepsController.text = widget.entry.maxRepsConfigs.first.value.round().toString(); + if (widget.entry.maxRepetitionsConfigs.isNotEmpty) { + maxRepsController.text = widget.entry.maxRepetitionsConfigs.first.value.round().toString(); } if (widget.entry.restTimeConfigs.isNotEmpty) { diff --git a/test/workout/slot_entry_model_test.dart b/test/workout/slot_entry_model_test.dart index 8d932bce..96fde3b0 100644 --- a/test/workout/slot_entry_model_test.dart +++ b/test/workout/slot_entry_model_test.dart @@ -37,9 +37,9 @@ void main() { expect(slotEntry.repetitionRounding, 1.25); expect(slotEntry.weightUnitId, 1); expect(slotEntry.weightRounding, 2.5); - expect(slotEntry.repsConfigs.length, 1); - expect(slotEntry.repsConfigs[0].id, 139); - expect(slotEntry.maxRepsConfigs.length, 1); + expect(slotEntry.repetitionsConfigs.length, 1); + expect(slotEntry.repetitionsConfigs[0].id, 139); + expect(slotEntry.maxRepetitionsConfigs.length, 1); expect(slotEntry.weightConfigs.length, 1); expect(slotEntry.maxWeightConfigs.length, 1); expect(slotEntry.nrOfSetsConfigs.length, 1); diff --git a/test_data/routines.dart b/test_data/routines.dart index 4f3cf48d..7d69f1f4 100644 --- a/test_data/routines.dart +++ b/test_data/routines.dart @@ -126,7 +126,7 @@ Routine getTestRoutine({List? exercises}) { nrOfSetsConfigs: [ BaseConfig.firstIteration(4, 1), ], - repsConfigs: [ + repetitionsConfigs: [ BaseConfig.firstIteration(3, 1), ], weightConfigs: [ @@ -170,7 +170,7 @@ Routine getTestRoutine({List? exercises}) { weightConfigs: [ BaseConfig.firstIteration(80, 1), ], - repsConfigs: [ + repetitionsConfigs: [ BaseConfig.firstIteration(5, 1), ], nrOfSetsConfigs: [ @@ -198,7 +198,7 @@ Routine getTestRoutine({List? exercises}) { nrOfSetsConfigs: [ BaseConfig.firstIteration(4, 1), ], - repsConfigs: [ + repetitionsConfigs: [ BaseConfig.firstIteration(12, 1), ], weightConfigs: [ From 2498183094afb1f7c66142e5aa7fe8ff85b02716 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sat, 1 Feb 2025 19:24:15 +0100 Subject: [PATCH 3/8] Rename further instances of "reps" to "repetitions" --- lib/helpers/misc.dart | 6 ++--- lib/models/workouts/set_config_data.dart | 14 +++++------ lib/models/workouts/set_config_data.g.dart | 27 +++++++++++----------- lib/models/workouts/slot_entry.dart | 12 +++++----- lib/models/workouts/slot_entry.g.dart | 4 ++-- lib/providers/routines.dart | 12 +++++----- lib/widgets/routines/forms/reps.dart | 8 +++---- lib/widgets/routines/forms/slot.dart | 25 ++++++++++---------- lib/widgets/routines/gym_mode.dart | 18 +++++++-------- test/fixtures/routines/slot_entry.json | 4 ++-- test_data/routines.dart | 26 ++++++++++----------- 11 files changed, 79 insertions(+), 77 deletions(-) diff --git a/lib/helpers/misc.dart b/lib/helpers/misc.dart index eb9bcf28..661c7105 100644 --- a/lib/helpers/misc.dart +++ b/lib/helpers/misc.dart @@ -24,7 +24,7 @@ import 'package:wger/models/workouts/weight_unit.dart'; /// Returns the text representation for a single setting, used in the gym mode String repText( - num? reps, + num? repetitions, RepetitionUnit repetitionUnitObj, num? weight, WeightUnit weightUnitObj, @@ -34,8 +34,8 @@ String repText( final List out = []; - if (reps != null) { - out.add(formatNum(reps).toString()); + if (repetitions != null) { + out.add(formatNum(repetitions).toString()); // The default repetition unit is 'reps', which we don't show unless there // is no weight defined so that we don't just output something like "8" but diff --git a/lib/models/workouts/set_config_data.dart b/lib/models/workouts/set_config_data.dart index 70c315fc..a12424e0 100644 --- a/lib/models/workouts/set_config_data.dart +++ b/lib/models/workouts/set_config_data.dart @@ -63,19 +63,19 @@ class SetConfigData { @JsonKey(required: true, name: 'weight_rounding', fromJson: stringToNumNull) late num? weightRounding; - @JsonKey(required: true, fromJson: stringToNumNull, name: 'reps') + @JsonKey(required: true, fromJson: stringToNumNull, name: 'repetitions') late num? repetitions; - @JsonKey(required: true, name: 'max_reps', fromJson: stringToNumNull) + @JsonKey(required: true, name: 'max_repetitions', fromJson: stringToNumNull) late num? maxRepetitions; - @JsonKey(required: true, name: 'reps_unit') + @JsonKey(required: true, name: 'repetitions_unit') late int? repetitionsUnitId; @JsonKey(includeToJson: false, includeFromJson: false) late RepetitionUnit repetitionsUnit; - @JsonKey(required: true, name: 'reps_rounding', fromJson: stringToNumNull) + @JsonKey(required: true, name: 'repetitions_rounding', fromJson: stringToNumNull) late num? repetitionsRounding; @JsonKey(required: true) @@ -119,7 +119,7 @@ class SetConfigData { this.textRepr = '', Exercise? exercise, WeightUnit? weightUnit, - RepetitionUnit? repsUnit, + RepetitionUnit? repetitionsUnit, }) { if (exercise != null) { this.exercise = exercise; @@ -127,8 +127,8 @@ class SetConfigData { if (weightUnit != null) { this.weightUnit = weightUnit; } - if (repsUnit != null) { - this.repetitionsUnit = repsUnit; + if (repetitionsUnit != null) { + this.repetitionsUnit = repetitionsUnit; } } diff --git a/lib/models/workouts/set_config_data.g.dart b/lib/models/workouts/set_config_data.g.dart index b0850730..dfc76fa5 100644 --- a/lib/models/workouts/set_config_data.g.dart +++ b/lib/models/workouts/set_config_data.g.dart @@ -20,10 +20,10 @@ SetConfigData _$SetConfigDataFromJson(Map json) { 'max_weight', 'weight_unit', 'weight_rounding', - 'reps', - 'max_reps', - 'reps_unit', - 'reps_rounding', + 'repetitions', + 'max_repetitions', + 'repetitions_unit', + 'repetitions_rounding', 'rir', 'max_rir', 'rpe', @@ -44,11 +44,12 @@ SetConfigData _$SetConfigDataFromJson(Map json) { weightRounding: json['weight_rounding'] == null ? 1.25 : stringToNumNull(json['weight_rounding'] as String?), - repetitions: stringToNumNull(json['reps'] as String?), - maxRepetitions: stringToNumNull(json['max_reps'] as String?), - repetitionsUnitId: (json['reps_unit'] as num?)?.toInt() ?? REP_UNIT_REPETITIONS_ID, - repetitionsRounding: - json['reps_rounding'] == null ? 1 : stringToNumNull(json['reps_rounding'] as String?), + repetitions: stringToNumNull(json['repetitions'] as String?), + maxRepetitions: stringToNumNull(json['max_repetitions'] as String?), + repetitionsUnitId: (json['repetitions_unit'] as num?)?.toInt() ?? REP_UNIT_REPETITIONS_ID, + repetitionsRounding: json['repetitions_rounding'] == null + ? 1 + : stringToNumNull(json['repetitions_rounding'] as String?), rir: json['rir'] as String?, maxRir: json['max_rir'] as String?, rpe: json['rpe'] as String?, @@ -70,10 +71,10 @@ Map _$SetConfigDataToJson(SetConfigData instance) => repetitionsConfigs = []; - @JsonKey(required: false, name: 'max_reps_configs', includeToJson: false, defaultValue: []) + @JsonKey(required: false, name: 'max_repetitions_configs', includeToJson: false, defaultValue: []) late List maxRepetitionsConfigs = []; @JsonKey(required: true, name: 'weight_unit') @@ -208,9 +208,9 @@ class SlotEntry { return nrOfSetsConfigs; case ConfigType.maxSets: return maxNrOfSetsConfigs; - case ConfigType.reps: + case ConfigType.repetitions: return repetitionsConfigs; - case ConfigType.maxReps: + case ConfigType.maxRepetitions: return maxRepetitionsConfigs; case ConfigType.rir: return rirConfigs; diff --git a/lib/models/workouts/slot_entry.g.dart b/lib/models/workouts/slot_entry.g.dart index e576ac03..5e06871f 100644 --- a/lib/models/workouts/slot_entry.g.dart +++ b/lib/models/workouts/slot_entry.g.dart @@ -66,11 +66,11 @@ SlotEntry _$SlotEntryFromJson(Map json) { ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], - repetitionsConfigs: (json['reps_configs'] as List?) + repetitionsConfigs: (json['repetitions_configs'] as List?) ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], - maxRepetitionsConfigs: (json['max_reps_configs'] as List?) + maxRepetitionsConfigs: (json['max_repetitions_configs'] as List?) ?.map((e) => BaseConfig.fromJson(e as Map)) .toList() ?? [], diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index c6740e03..b2307e96 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -58,8 +58,8 @@ class RoutinesProvider with ChangeNotifier { static const _routineConfigMaxSets = 'max-sets-config'; static const _routineConfigWeights = 'weight-config'; static const _routineConfigMaxWeights = 'max-weight-config'; - static const _routineConfigReps = 'reps-config'; - static const _routineConfigMaxReps = 'max-reps-config'; + static const _routineConfigRepetitions = 'repetitions-config'; + static const _routineConfigMaxRepetitions = 'max-repetitions-config'; static const _routineConfigRir = 'rir-config'; static const _routineConfigMaxRir = 'rest-config'; static const _routineConfigRestTime = 'rest-config'; @@ -584,10 +584,10 @@ class RoutinesProvider with ChangeNotifier { return _routineConfigWeights; case ConfigType.maxWeight: return _routineConfigMaxWeights; - case ConfigType.reps: - return _routineConfigReps; - case ConfigType.maxReps: - return _routineConfigMaxReps; + case ConfigType.repetitions: + return _routineConfigRepetitions; + case ConfigType.maxRepetitions: + return _routineConfigMaxRepetitions; case ConfigType.rir: return _routineConfigRir; case ConfigType.maxRir: diff --git a/lib/widgets/routines/forms/reps.dart b/lib/widgets/routines/forms/reps.dart index bfaab4c0..1c5d3768 100644 --- a/lib/widgets/routines/forms/reps.dart +++ b/lib/widgets/routines/forms/reps.dart @@ -20,12 +20,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:wger/models/workouts/slot_entry.dart'; -class RepsInputWidget extends StatelessWidget { - final _repsController = TextEditingController(); +class RepetitionsInputWidget extends StatelessWidget { + final _repetitionsController = TextEditingController(); final SlotEntry _setting; final bool _detailed; - RepsInputWidget(this._setting, this._detailed); + RepetitionsInputWidget(this._setting, this._detailed); @override Widget build(BuildContext context) { @@ -34,7 +34,7 @@ class RepsInputWidget extends StatelessWidget { labelText: _detailed ? AppLocalizations.of(context).repetitions : '', errorMaxLines: 2, ), - controller: _repsController, + controller: _repetitionsController, keyboardType: TextInputType.number, validator: (value) { try { diff --git a/lib/widgets/routines/forms/slot.dart b/lib/widgets/routines/forms/slot.dart index 0d3cafe5..c1dd6365 100644 --- a/lib/widgets/routines/forms/slot.dart +++ b/lib/widgets/routines/forms/slot.dart @@ -78,8 +78,8 @@ class _SlotEntryFormState extends State { final weightController = TextEditingController(); final maxWeightController = TextEditingController(); - final repsController = TextEditingController(); - final maxRepsController = TextEditingController(); + final repetitionsController = TextEditingController(); + final maxRepetitionsController = TextEditingController(); final restController = TextEditingController(); final maxRestController = TextEditingController(); final rirController = TextEditingController(); @@ -103,10 +103,11 @@ class _SlotEntryFormState extends State { } if (widget.entry.repetitionsConfigs.isNotEmpty) { - repsController.text = widget.entry.repetitionsConfigs.first.value.round().toString(); + repetitionsController.text = widget.entry.repetitionsConfigs.first.value.round().toString(); } if (widget.entry.maxRepetitionsConfigs.isNotEmpty) { - maxRepsController.text = widget.entry.maxRepetitionsConfigs.first.value.round().toString(); + maxRepetitionsController.text = + widget.entry.maxRepetitionsConfigs.first.value.round().toString(); } if (widget.entry.restTimeConfigs.isNotEmpty) { @@ -126,8 +127,8 @@ class _SlotEntryFormState extends State { weightController.dispose(); maxWeightController.dispose(); - repsController.dispose(); - maxRepsController.dispose(); + repetitionsController.dispose(); + maxRepetitionsController.dispose(); restController.dispose(); maxRestController.dispose(); @@ -252,7 +253,7 @@ class _SlotEntryFormState extends State { children: [ Flexible( child: TextFormField( - controller: repsController, + controller: repetitionsController, keyboardType: TextInputType.number, decoration: InputDecoration(labelText: i18n.repetitions), validator: (value) { @@ -266,7 +267,7 @@ class _SlotEntryFormState extends State { if (!widget.simpleMode) Flexible( child: TextFormField( - controller: maxRepsController, + controller: maxRepetitionsController, keyboardType: TextInputType.number, decoration: InputDecoration(labelText: i18n.max), validator: (value) { @@ -348,13 +349,13 @@ class _SlotEntryFormState extends State { provider.handleConfig( widget.entry, - repsController.text, - ConfigType.reps, + repetitionsController.text, + ConfigType.repetitions, ); provider.handleConfig( widget.entry, - maxRepsController.text, - ConfigType.maxReps, + maxRepetitionsController.text, + ConfigType.maxRepetitions, ); provider.handleConfig( diff --git a/lib/widgets/routines/gym_mode.dart b/lib/widgets/routines/gym_mode.dart index c468c119..fe264508 100644 --- a/lib/widgets/routines/gym_mode.dart +++ b/lib/widgets/routines/gym_mode.dart @@ -270,7 +270,7 @@ class LogPage extends StatefulWidget { class _LogPageState extends State { final _form = GlobalKey(); String rirValue = SlotEntry.DEFAULT_RIR; - final _repsController = TextEditingController(); + final _repetitionsController = TextEditingController(); final _weightController = TextEditingController(); var _detailed = false; bool _isSaving = false; @@ -284,7 +284,7 @@ class _LogPageState extends State { focusNode = FocusNode(); if (widget._configData.repetitions != null) { - _repsController.text = widget._configData.repetitions!.toString(); + _repetitionsController.text = widget._configData.repetitions!.toString(); } if (widget._configData.weight != null) { @@ -295,7 +295,7 @@ class _LogPageState extends State { @override void dispose() { focusNode.dispose(); - _repsController.dispose(); + _repetitionsController.dispose(); _weightController.dispose(); super.dispose(); } @@ -307,9 +307,9 @@ class _LogPageState extends State { icon: const Icon(Icons.remove, color: Colors.black), onPressed: () { try { - final int newValue = int.parse(_repsController.text) - 1; + final int newValue = int.parse(_repetitionsController.text) - 1; if (newValue > 0) { - _repsController.text = newValue.toString(); + _repetitionsController.text = newValue.toString(); } } on FormatException {} }, @@ -320,7 +320,7 @@ class _LogPageState extends State { labelText: AppLocalizations.of(context).repetitions, ), enabled: true, - controller: _repsController, + controller: _repetitionsController, keyboardType: TextInputType.number, focusNode: focusNode, onFieldSubmitted: (_) {}, @@ -342,8 +342,8 @@ class _LogPageState extends State { icon: const Icon(Icons.add, color: Colors.black), onPressed: () { try { - final int newValue = int.parse(_repsController.text) + 1; - _repsController.text = newValue.toString(); + final int newValue = int.parse(_repetitionsController.text) + 1; + _repetitionsController.text = newValue.toString(); } on FormatException {} }, ), @@ -544,7 +544,7 @@ class _LogPageState extends State { onTap: () { setState(() { // Text field - _repsController.text = log.repetitions.toString(); + _repetitionsController.text = log.repetitions.toString(); _weightController.text = log.weight.toString(); // Drop downs diff --git a/test/fixtures/routines/slot_entry.json b/test/fixtures/routines/slot_entry.json index dcce1053..75b9b193 100644 --- a/test/fixtures/routines/slot_entry.json +++ b/test/fixtures/routines/slot_entry.json @@ -9,7 +9,7 @@ "config": null, "repetition_unit": 1, "repetition_rounding": "1.25", - "reps_configs": [ + "repetitions_configs": [ { "id": 139, "slot_entry": 143, @@ -22,7 +22,7 @@ "requirements": null } ], - "max_reps_configs": [ + "max_repetitions_configs": [ { "id": 1, "slot_entry": 143, diff --git a/test_data/routines.dart b/test_data/routines.dart index 7d69f1f4..7c210958 100644 --- a/test_data/routines.dart +++ b/test_data/routines.dart @@ -245,7 +245,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 4, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -266,7 +266,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 4, repetitions: 12, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 10, weightUnit: testWeightUnit1, restTime: 60, @@ -295,7 +295,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 4, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -352,7 +352,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 5, repetitions: 8, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 105, weightUnit: testWeightUnit1, restTime: 120, @@ -383,7 +383,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -397,7 +397,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -411,7 +411,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -432,7 +432,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 12, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 10, weightUnit: testWeightUnit1, restTime: 60, @@ -446,7 +446,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 12, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 10, weightUnit: testWeightUnit1, restTime: 60, @@ -460,7 +460,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 12, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 10, weightUnit: testWeightUnit1, restTime: 60, @@ -489,7 +489,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -503,7 +503,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, @@ -517,7 +517,7 @@ Routine getTestRoutine({List? exercises}) { slotEntryId: 1, nrOfSets: 1, repetitions: 3, - repsUnit: testRepetitionUnit1, + repetitionsUnit: testRepetitionUnit1, weight: 100, weightUnit: testWeightUnit1, restTime: 120, From 888989a8f3e9ba646cc29f709195fc085b3b2938 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 2 Feb 2025 14:51:12 +0100 Subject: [PATCH 4/8] Update goldens --- .../goldens/routine_logs_screen_detail.png | Bin 179300 -> 170296 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/workout/goldens/routine_logs_screen_detail.png b/test/workout/goldens/routine_logs_screen_detail.png index 700227109e9f8e46c7763cb5ca268dfb4a0b2e1b..d378e0338273597a657f7a9b118847ebc8ab68c2 100644 GIT binary patch delta 63931 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�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 From 460b6e71258c2e2d011daf4e25eee10729d7eae7 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 2 Feb 2025 22:37:52 +0100 Subject: [PATCH 5/8] Add smoke test for fetchAndSetRoutineFull --- lib/models/workouts/base_config.dart | 5 - lib/models/workouts/base_config.g.dart | 3 - lib/providers/routines.dart | 23 +- .../routine_current_iteration_display.json | 145 ++ .../routine_current_iteration_gym.json | 277 ++++ .../routine_date_sequence_display.json | 696 +++++++++ .../routines/routine_date_sequence_gym.json | 1356 +++++++++++++++++ test/fixtures/routines/routine_logs.json | 284 ++++ test/fixtures/routines/routine_structure.json | 266 ++++ test/workout/day_form_test.mocks.dart | 12 + ...epetition_unit_form_widget_test.mocks.dart | 12 + .../routine_edit_screen_test.mocks.dart | 12 + test/workout/routine_edit_test.mocks.dart | 12 + test/workout/routine_form_test.mocks.dart | 12 + .../routine_logs_screen_test.mocks.dart | 12 + test/workout/routines_provider_test.dart | 79 +- .../workout/routines_provider_test.mocks.dart | 425 +++++- test/workout/slot_entry_form_test.mocks.dart | 12 + .../weight_unit_form_widget_test.mocks.dart | 12 + test_data/exercises.dart | 28 +- test_data/routines.dart | 1 - 21 files changed, 3636 insertions(+), 48 deletions(-) create mode 100644 test/fixtures/routines/routine_current_iteration_display.json create mode 100644 test/fixtures/routines/routine_current_iteration_gym.json create mode 100644 test/fixtures/routines/routine_date_sequence_display.json create mode 100644 test/fixtures/routines/routine_date_sequence_gym.json create mode 100644 test/fixtures/routines/routine_logs.json create mode 100644 test/fixtures/routines/routine_structure.json diff --git a/lib/models/workouts/base_config.dart b/lib/models/workouts/base_config.dart index 7807b443..3f78f740 100644 --- a/lib/models/workouts/base_config.dart +++ b/lib/models/workouts/base_config.dart @@ -44,9 +44,6 @@ class BaseConfig { @JsonKey(required: true) late String step; - @JsonKey(required: true, name: 'need_log_to_apply') - late bool needLogToApply; - @JsonKey(required: true, name: 'repeat') late bool repeat; @@ -61,7 +58,6 @@ class BaseConfig { required this.value, this.operation = 'r', this.step = 'abs', - this.needLogToApply = false, this.requirements = null, }); @@ -69,7 +65,6 @@ class BaseConfig { iteration = 1; operation = 'r'; step = 'abs'; - needLogToApply = false; requirements = null; repeat = false; } diff --git a/lib/models/workouts/base_config.g.dart b/lib/models/workouts/base_config.g.dart index a13e315d..564efef3 100644 --- a/lib/models/workouts/base_config.g.dart +++ b/lib/models/workouts/base_config.g.dart @@ -16,7 +16,6 @@ BaseConfig _$BaseConfigFromJson(Map json) { 'value', 'operation', 'step', - 'need_log_to_apply', 'repeat', 'requirements' ], @@ -29,7 +28,6 @@ BaseConfig _$BaseConfigFromJson(Map json) { value: stringOrIntToNum(json['value']), operation: json['operation'] as String? ?? 'r', step: json['step'] as String? ?? 'abs', - needLogToApply: json['need_log_to_apply'] as bool? ?? false, requirements: json['requirements'] ?? null, ); } @@ -40,7 +38,6 @@ Map _$BaseConfigToJson(BaseConfig instance) => _routines = []; List _weightUnits = []; @@ -79,7 +79,7 @@ class RoutinesProvider with ChangeNotifier { List? weightUnits, List? repetitionUnits, }) { - _exercises = exercises; + _exerciseProvider = exercises; _routines = entries; _weightUnits = weightUnits ?? []; _repetitionUnits = repetitionUnits ?? []; @@ -93,6 +93,10 @@ class RoutinesProvider with ChangeNotifier { return [..._weightUnits]; } + set weightUnits(List weightUnits) { + _weightUnits = weightUnits; + } + /// Clears all lists void clear() { _currentRoutine = null; @@ -112,6 +116,10 @@ class RoutinesProvider with ChangeNotifier { return [..._repetitionUnits]; } + set repetitionUnits(List repetitionUnits) { + _repetitionUnits = repetitionUnits; + } + RepetitionUnit findRepetitionUnitById(int id) => _repetitionUnits.firstWhere((element) => element.id == id); @@ -196,7 +204,7 @@ class RoutinesProvider with ChangeNotifier { for (final entry in entries) { for (final slot in entry.slots) { for (final setConfig in slot.setConfigs) { - setConfig.exercise = (await _exercises.fetchAndSetExercise(setConfig.exerciseId))!; + setConfig.exercise = (await _exerciseProvider.fetchAndSetExercise(setConfig.exerciseId))!; setConfig.repetitionsUnit = _repetitionUnits.firstWhere( (e) => e.id == setConfig.repetitionsUnitId, @@ -305,7 +313,8 @@ class RoutinesProvider with ChangeNotifier { for (final day in routine.days) { for (final slot in day.slots) { for (final slotEntry in slot.entries) { - slotEntry.exerciseObj = (await _exercises.fetchAndSetExercise(slotEntry.exerciseId))!; + slotEntry.exerciseObj = + (await _exerciseProvider.fetchAndSetExercise(slotEntry.exerciseId))!; slotEntry.repetitionUnitObj = _repetitionUnits.firstWhere( (e) => e.id == slotEntry.repetitionUnitId, ); @@ -327,7 +336,7 @@ class RoutinesProvider with ChangeNotifier { for (final log in session.logs) { log.weightUnit = _weightUnits.firstWhere((e) => e.id == log.weightUnitId); log.repetitionUnit = _repetitionUnits.firstWhere((e) => e.id == log.repetitionsUnitId); - log.exerciseBase = (await _exercises.fetchAndSetExercise(log.exerciseId))!; + log.exerciseBase = (await _exerciseProvider.fetchAndSetExercise(log.exerciseId))!; } } @@ -535,7 +544,7 @@ class RoutinesProvider with ChangeNotifier { baseProvider.makeUrl(_slotEntriesUrlPath), ); final newEntry = SlotEntry.fromJson(data); - newEntry.exerciseObj = (await _exercises.fetchAndSetExercise(newEntry.exerciseId))!; + newEntry.exerciseObj = (await _exerciseProvider.fetchAndSetExercise(newEntry.exerciseId))!; for (final routine in _routines) { for (final day in routine.days) { @@ -696,7 +705,7 @@ class RoutinesProvider with ChangeNotifier { newLog.weightUnit = _weightUnits.firstWhere((e) => e.id == log.weightUnitId); newLog.repetitionUnit = _repetitionUnits.firstWhere((e) => e.id == log.weightUnitId); - newLog.exerciseBase = (await _exercises.fetchAndSetExercise(log.exerciseId))!; + newLog.exerciseBase = (await _exerciseProvider.fetchAndSetExercise(log.exerciseId))!; final plan = findById(newLog.routineId); final session = plan.sessions.firstWhere((element) => element.session.id == newLog.sessionId); diff --git a/test/fixtures/routines/routine_current_iteration_display.json b/test/fixtures/routines/routine_current_iteration_display.json new file mode 100644 index 00000000..3c4c2340 --- /dev/null +++ b/test/fixtures/routines/routine_current_iteration_display.json @@ -0,0 +1,145 @@ +[ + { + "iteration": 4, + "date": "2025-01-27", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-28", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-29", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 23 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-30", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-31", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-01", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-02", + "label": null, + "day": null, + "slots": [] + } +] \ No newline at end of file diff --git a/test/fixtures/routines/routine_current_iteration_gym.json b/test/fixtures/routines/routine_current_iteration_gym.json new file mode 100644 index 00000000..12264e83 --- /dev/null +++ b/test/fixtures/routines/routine_current_iteration_gym.json @@ -0,0 +1,277 @@ +[ + { + "iteration": 4, + "date": "2025-01-27", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-28", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-29", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-30", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-31", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-01", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-02", + "label": null, + "day": null, + "slots": [] + } +] \ No newline at end of file diff --git a/test/fixtures/routines/routine_date_sequence_display.json b/test/fixtures/routines/routine_date_sequence_display.json new file mode 100644 index 00000000..faf59fa8 --- /dev/null +++ b/test/fixtures/routines/routine_date_sequence_display.json @@ -0,0 +1,696 @@ +[ + { + "iteration": 1, + "date": "2025-01-05", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 1, + "date": "2025-01-06", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-07", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "20.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 20-22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 1, + "date": "2025-01-08", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-09", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-10", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-11", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-12", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-13", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 2, + "date": "2025-01-14", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-15", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "21.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 21-22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 2, + "date": "2025-01-16", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-17", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-18", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-19", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-20", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 3, + "date": "2025-01-21", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-22", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "22.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 3, + "date": "2025-01-23", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-24", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-25", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-26", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-27", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-28", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-29", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 23 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-30", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-31", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-01", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-02", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 5, + "date": "2025-02-03", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 4, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 5, + "date": "2025-02-04", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 5, + "date": "2025-02-05", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 4, + "max_sets": null, + "weight": "24.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "4 Sätze, 10 × 24 kg 120s rest", + "comment": "" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/test/fixtures/routines/routine_date_sequence_gym.json b/test/fixtures/routines/routine_date_sequence_gym.json new file mode 100644 index 00000000..a5557aaa --- /dev/null +++ b/test/fixtures/routines/routine_date_sequence_gym.json @@ -0,0 +1,1356 @@ +[ + { + "iteration": 1, + "date": "2025-01-05", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 1, + "date": "2025-01-06", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-07", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "20.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 20-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "20.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 20-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "20.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 20-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "20.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 20-22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 1, + "date": "2025-01-08", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-09", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-10", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-11", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 1, + "date": "2025-01-12", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-13", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 2, + "date": "2025-01-14", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-15", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "21.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 21-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "21.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 21-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "21.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 21-22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "21.00", + "max_weight": "22.00", + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 21-22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 2, + "date": "2025-01-16", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-17", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-18", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 2, + "date": "2025-01-19", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-20", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 3, + "date": "2025-01-21", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-22", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "22.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "22.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "22.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 22 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "22.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 22 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 3, + "date": "2025-01-23", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-24", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-25", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 3, + "date": "2025-01-26", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-27", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-28", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-29", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "23.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 23 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 4, + "date": "2025-01-30", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-01-31", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-01", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 4, + "date": "2025-02-02", + "label": null, + "day": null, + "slots": [] + }, + { + "iteration": 5, + "date": "2025-02-03", + "label": null, + "day": { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 76 + ], + "sets": [ + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 2, + "exercise": 76, + "sets": 1, + "max_sets": null, + "weight": "80.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 80 kg 120s rest", + "comment": "" + } + ] + } + ] + }, + { + "iteration": 5, + "date": "2025-02-04", + "label": null, + "day": { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [] + }, + { + "iteration": 5, + "date": "2025-02-05", + "label": null, + "day": { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null + }, + "slots": [ + { + "comment": "", + "is_superset": false, + "exercises": [ + 92 + ], + "sets": [ + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "24.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 24 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "24.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 24 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "24.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 24 kg 120s rest", + "comment": "" + }, + { + "slot_entry_id": 3, + "exercise": 92, + "sets": 1, + "max_sets": null, + "weight": "24.00", + "max_weight": null, + "weight_unit": 1, + "weight_rounding": null, + "repetitions": "10.00", + "max_repetitions": null, + "repetitions_unit": 1, + "repetitions_rounding": null, + "rir": null, + "max_rir": null, + "rpe": null, + "rest": "120.00", + "max_rest": null, + "type": "normal", + "text_repr": "10 × 24 kg 120s rest", + "comment": "" + } + ] + } + ] + } +] \ No newline at end of file diff --git a/test/fixtures/routines/routine_logs.json b/test/fixtures/routines/routine_logs.json new file mode 100644 index 00000000..cca04829 --- /dev/null +++ b/test/fixtures/routines/routine_logs.json @@ -0,0 +1,284 @@ +[ + { + "session": { + "id": 5, + "routine": 2, + "day": null, + "date": "2025-01-06", + "notes": null, + "impression": "2", + "time_start": "20:28:49", + "time_end": "23:35:53" + }, + "logs": [ + { + "id": 4, + "date": "2025-01-06T00:00:00+01:00", + "session": 5, + "routine": 2, + "iteration": 1, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "11.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "78.00", + "weight_target": "80.00", + "rir": "4", + "rir_target": null, + "rest": 150, + "rest_target": 120 + }, + { + "id": 2, + "date": "2025-01-06T00:00:00+01:00", + "session": 5, + "routine": 2, + "iteration": 1, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "12.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "77.00", + "weight_target": "80.00", + "rir": "2", + "rir_target": null, + "rest": 159, + "rest_target": 120 + }, + { + "id": 1, + "date": "2025-01-06T00:00:00+01:00", + "session": 5, + "routine": 2, + "iteration": 1, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "12.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "78.00", + "weight_target": "80.00", + "rir": "2.5", + "rir_target": null, + "rest": 143, + "rest_target": 120 + }, + { + "id": 3, + "date": "2025-01-06T00:00:00+01:00", + "session": 5, + "routine": 2, + "iteration": 1, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "12.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "83.00", + "weight_target": "80.00", + "rir": "0.5", + "rir_target": null, + "rest": 160, + "rest_target": 120 + } + ] + }, + { + "session": { + "id": 6, + "routine": 2, + "day": null, + "date": "2025-01-08", + "notes": null, + "impression": "2", + "time_start": "12:55:54", + "time_end": "14:25:31" + }, + "logs": [ + { + "id": 7, + "date": "2025-01-08T00:00:00+01:00", + "session": 6, + "routine": 2, + "iteration": 1, + "slot_entry": 3, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "10.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "80.00", + "weight_target": "80.00", + "rir": "3", + "rir_target": null, + "rest": 131, + "rest_target": 120 + }, + { + "id": 8, + "date": "2025-01-08T00:00:00+01:00", + "session": 6, + "routine": 2, + "iteration": 1, + "slot_entry": 3, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "11.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "83.00", + "weight_target": "80.00", + "rir": "3", + "rir_target": null, + "rest": 128, + "rest_target": 120 + }, + { + "id": 5, + "date": "2025-01-08T00:00:00+01:00", + "session": 6, + "routine": 2, + "iteration": 1, + "slot_entry": 3, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "11.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "90.00", + "weight_target": "80.00", + "rir": "0.5", + "rir_target": null, + "rest": 121, + "rest_target": 120 + }, + { + "id": 6, + "date": "2025-01-08T00:00:00+01:00", + "session": 6, + "routine": 2, + "iteration": 1, + "slot_entry": 3, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "12.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "77.00", + "weight_target": "80.00", + "rir": "1.5", + "rir_target": null, + "rest": 129, + "rest_target": 120 + } + ] + }, + { + "session": { + "id": 7, + "routine": 2, + "day": null, + "date": "2025-01-13", + "notes": null, + "impression": "2", + "time_start": "18:48:45", + "time_end": "20:20:52" + }, + "logs": [ + { + "id": 11, + "date": "2025-01-13T00:00:00+01:00", + "session": 7, + "routine": 2, + "iteration": 2, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "9.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "84.00", + "weight_target": "80.00", + "rir": "0", + "rir_target": null, + "rest": 128, + "rest_target": 120 + }, + { + "id": 10, + "date": "2025-01-13T00:00:00+01:00", + "session": 7, + "routine": 2, + "iteration": 2, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "10.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "84.00", + "weight_target": "80.00", + "rir": "4", + "rir_target": null, + "rest": 149, + "rest_target": 120 + }, + { + "id": 12, + "date": "2025-01-13T00:00:00+01:00", + "session": 7, + "routine": 2, + "iteration": 2, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "10.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "89.00", + "weight_target": "80.00", + "rir": "1.5", + "rir_target": null, + "rest": 120, + "rest_target": 120 + }, + { + "id": 9, + "date": "2025-01-13T00:00:00+01:00", + "session": 7, + "routine": 2, + "iteration": 2, + "slot_entry": 2, + "next_log": null, + "exercise": 76, + "repetitions_unit": 1, + "repetitions": "12.00", + "repetitions_target": "10.00", + "weight_unit": 1, + "weight": "90.00", + "weight_target": "80.00", + "rir": "2.5", + "rir_target": null, + "rest": 119, + "rest_target": 120 + } + ] + } +] \ No newline at end of file diff --git a/test/fixtures/routines/routine_structure.json b/test/fixtures/routines/routine_structure.json new file mode 100644 index 00000000..aadf28cd --- /dev/null +++ b/test/fixtures/routines/routine_structure.json @@ -0,0 +1,266 @@ +{ + "id": 101, + "name": "Test 123", + "description": "", + "created": "2025-02-02T17:52:36.245680+01:00", + "start": "2025-01-06", + "end": "2025-03-28", + "fit_in_week": true, + "days": [ + { + "id": 8, + "routine": 2, + "order": 1, + "name": "Leg day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null, + "slots": [ + { + "id": 2, + "day": 8, + "order": 1, + "comment": "", + "entries": [ + { + "id": 2, + "slot": 2, + "exercise": 76, + "order": 1, + "comment": "", + "type": "normal", + "class_name": null, + "config": null, + "repetition_unit": 1, + "repetition_rounding": null, + "repetitions_configs": [ + { + "id": 2, + "slot_entry": 2, + "iteration": 1, + "value": "10.00", + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_repetitions_configs": [], + "weight_unit": 1, + "weight_rounding": null, + "weight_configs": [ + { + "id": 1, + "slot_entry": 2, + "iteration": 1, + "value": "80.00", + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_weight_configs": [], + "set_nr_configs": [ + { + "id": 2, + "slot_entry": 2, + "iteration": 1, + "value": 4, + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_set_nr_configs": [], + "rir_configs": [], + "max_rir_configs": [], + "rest_configs": [ + { + "id": 2, + "slot_entry": 2, + "iteration": 1, + "value": 120, + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_rest_configs": [] + } + ], + "config": null + } + ] + }, + { + "id": 9, + "routine": 2, + "order": 2, + "name": "", + "description": "", + "is_rest": true, + "need_logs_to_advance": false, + "type": "custom", + "config": null, + "slots": [] + }, + { + "id": 10, + "routine": 2, + "order": 3, + "name": "Arms day", + "description": "Leg day", + "is_rest": false, + "need_logs_to_advance": false, + "type": "custom", + "config": null, + "slots": [ + { + "id": 3, + "day": 10, + "order": 1, + "comment": "", + "entries": [ + { + "id": 3, + "slot": 3, + "exercise": 92, + "order": 1, + "comment": "", + "type": "normal", + "class_name": null, + "config": null, + "repetition_unit": 1, + "repetition_rounding": null, + "repetitions_configs": [ + { + "id": 3, + "slot_entry": 3, + "iteration": 1, + "value": "10.00", + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_repetitions_configs": [], + "weight_unit": 1, + "weight_rounding": null, + "weight_configs": [ + { + "id": 2, + "slot_entry": 3, + "iteration": 1, + "value": "20.00", + "operation": "r", + "step": "na", + "repeat": false, + "requirements": { + "rules": [] + } + }, + { + "id": 7, + "slot_entry": 3, + "iteration": 2, + "value": "1.00", + "operation": "+", + "step": "abs", + "repeat": true, + "requirements": { + "rules": [] + } + }, + { + "id": 8, + "slot_entry": 3, + "iteration": 8, + "value": "20.00", + "operation": "r", + "step": "na", + "repeat": false, + "requirements": { + "rules": [] + } + } + ], + "max_weight_configs": [ + { + "id": 1, + "slot_entry": 3, + "iteration": 1, + "value": "22.00", + "operation": "r", + "step": "na", + "repeat": false, + "requirements": { + "rules": [] + } + }, + { + "id": 2, + "slot_entry": 3, + "iteration": 2, + "value": "1.00", + "operation": "+", + "step": "abs", + "repeat": true, + "requirements": { + "rules": [] + } + }, + { + "id": 3, + "slot_entry": 3, + "iteration": 8, + "value": "22.00", + "operation": "r", + "step": "na", + "repeat": false, + "requirements": { + "rules": [] + } + } + ], + "set_nr_configs": [ + { + "id": 3, + "slot_entry": 3, + "iteration": 1, + "value": 4, + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_set_nr_configs": [], + "rir_configs": [], + "max_rir_configs": [], + "rest_configs": [ + { + "id": 3, + "slot_entry": 3, + "iteration": 1, + "value": 120, + "operation": "r", + "step": "abs", + "repeat": false, + "requirements": null + } + ], + "max_rest_configs": [] + } + ], + "config": null + } + ] + } + ] +} \ No newline at end of file diff --git a/test/workout/day_form_test.mocks.dart b/test/workout/day_form_test.mocks.dart index 1e941e3d..0a1cc72c 100644 --- a/test/workout/day_form_test.mocks.dart +++ b/test/workout/day_form_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/repetition_unit_form_widget_test.mocks.dart b/test/workout/repetition_unit_form_widget_test.mocks.dart index b7568d06..767865e4 100644 --- a/test/workout/repetition_unit_form_widget_test.mocks.dart +++ b/test/workout/repetition_unit_form_widget_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/routine_edit_screen_test.mocks.dart b/test/workout/routine_edit_screen_test.mocks.dart index dd67580c..1e9653af 100644 --- a/test/workout/routine_edit_screen_test.mocks.dart +++ b/test/workout/routine_edit_screen_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/routine_edit_test.mocks.dart b/test/workout/routine_edit_test.mocks.dart index dff0ee64..02bf8562 100644 --- a/test/workout/routine_edit_test.mocks.dart +++ b/test/workout/routine_edit_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/routine_form_test.mocks.dart b/test/workout/routine_form_test.mocks.dart index 96af3739..0d30517b 100644 --- a/test/workout/routine_form_test.mocks.dart +++ b/test/workout/routine_form_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/routine_logs_screen_test.mocks.dart b/test/workout/routine_logs_screen_test.mocks.dart index 51e60920..83c001fa 100644 --- a/test/workout/routine_logs_screen_test.mocks.dart +++ b/test/workout/routine_logs_screen_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/routines_provider_test.dart b/test/workout/routines_provider_test.dart index 8d473868..edd34f9c 100644 --- a/test/workout/routines_provider_test.dart +++ b/test/workout/routines_provider_test.dart @@ -33,10 +33,12 @@ import 'package:wger/providers/base_provider.dart'; import 'package:wger/providers/exercises.dart'; import 'package:wger/providers/routines.dart'; +import '../../test_data/exercises.dart'; +import '../../test_data/routines.dart'; import '../fixtures/fixture_reader.dart'; import 'routines_provider_test.mocks.dart'; -@GenerateMocks([WgerBaseProvider]) +@GenerateMocks([WgerBaseProvider, ExercisesProvider]) void main() { final mockBaseProvider = MockWgerBaseProvider(); @@ -59,7 +61,7 @@ void main() { 'description': 'Test workout abcd', 'start': '2021-12-20', 'end': '2022-06-06', - 'fit_in_week': false + 'fit_in_week': false, }), ); @@ -163,5 +165,78 @@ void main() { expect(true, DateTime.parse(prefsJson['date']).isBefore(DateTime.now())); expect(true, DateTime.parse(prefsJson['expiresIn']).isAfter(DateTime.now())); }); + + test('Smoke test fetchAndSetRoutineFull', () async { + //Arrange + final structureUri = Uri.https('localhost', 'api/v2/routine/101/structure/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'structure', id: 101)) + .thenReturn(structureUri); + when(mockBaseProvider.fetch(structureUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_structure.json')), + )); + + final dateSequenceDisplayUri = + Uri.https('localhost', 'api/v2/routine/101/date-sequence-display/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'date-sequence-display', id: 101)) + .thenReturn(dateSequenceDisplayUri); + when(mockBaseProvider.fetch(dateSequenceDisplayUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_date_sequence_display.json')), + )); + + final dateSequenceGymUri = Uri.https('localhost', 'api/v2/routine/101/date-sequence-gym/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'date-sequence-gym', id: 101)) + .thenReturn(dateSequenceGymUri); + when(mockBaseProvider.fetch(dateSequenceGymUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_date_sequence_gym.json')), + )); + + final currentIterationDisplayUri = + Uri.https('localhost', 'api/v2/routine/101/current-iteration-display/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'current-iteration-display', id: 101)) + .thenReturn(currentIterationDisplayUri); + when(mockBaseProvider.fetch(currentIterationDisplayUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_date_sequence_gym.json')), + )); + + final currentIterationGymUri = + Uri.https('localhost', 'api/v2/routine/101/current-iteration-gym/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'current-iteration-gym', id: 101)) + .thenReturn(currentIterationGymUri); + when(mockBaseProvider.fetch(currentIterationGymUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_current_iteration_gym.json')), + )); + + final logsUri = Uri.https('localhost', 'api/v2/routine/101/logs/'); + when(mockBaseProvider.makeUrl('routine', objectMethod: 'logs', id: 101)).thenReturn(logsUri); + when(mockBaseProvider.fetch(logsUri)).thenAnswer((_) async => Future.value( + jsonDecode(fixture('routines/routine_logs.json')), + )); + + final mockExercisesProvider = MockExercisesProvider(); + when(mockExercisesProvider.fetchAndSetExercise(76)).thenAnswer( + (_) async => Future.value(testBenchPress), + ); + when(mockExercisesProvider.fetchAndSetExercise(92)).thenAnswer( + (_) async => Future.value(testCrunches), + ); + + final provider = RoutinesProvider(mockBaseProvider, mockExercisesProvider, []); + provider.repetitionUnits = testRepetitionUnits; + provider.weightUnits = testWeightUnits; + + // Act + final result = await provider.fetchAndSetRoutineFull(101); + + // Assert + expect(result, isA()); + expect(result.id, 101); + expect(result.sessions.length, 3); + expect(result.days.length, 3); + expect(result.logs.length, 12); + expect(result.dayDataCurrentIteration.length, 32); + expect(result.dayDataCurrentIterationGym.length, 7); + expect(result.dayData.length, 32); + expect(result.dayDataGym.length, 32); + }); }); } diff --git a/test/workout/routines_provider_test.mocks.dart b/test/workout/routines_provider_test.mocks.dart index 9c26d49b..b94e0c8c 100644 --- a/test/workout/routines_provider_test.mocks.dart +++ b/test/workout/routines_provider_test.mocks.dart @@ -3,12 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; +import 'dart:async' as _i11; +import 'dart:ui' as _i13; import 'package:http/http.dart' as _i3; import 'package:mockito/mockito.dart' as _i1; +import 'package:wger/database/exercises/exercise_database.dart' as _i5; +import 'package:wger/models/exercises/category.dart' as _i7; +import 'package:wger/models/exercises/equipment.dart' as _i8; +import 'package:wger/models/exercises/exercise.dart' as _i6; +import 'package:wger/models/exercises/language.dart' as _i10; +import 'package:wger/models/exercises/muscle.dart' as _i9; import 'package:wger/providers/auth.dart' as _i2; import 'package:wger/providers/base_provider.dart' as _i4; +import 'package:wger/providers/exercises.dart' as _i12; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -40,6 +48,37 @@ class _FakeResponse_3 extends _i1.SmartFake implements _i3.Response { _FakeResponse_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); } +class _FakeWgerBaseProvider_4 extends _i1.SmartFake implements _i4.WgerBaseProvider { + _FakeWgerBaseProvider_4(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeExerciseDatabase_5 extends _i1.SmartFake implements _i5.ExerciseDatabase { + _FakeExerciseDatabase_5(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeExercise_6 extends _i1.SmartFake implements _i6.Exercise { + _FakeExercise_6(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); +} + +class _FakeExerciseCategory_7 extends _i1.SmartFake implements _i7.ExerciseCategory { + _FakeExerciseCategory_7(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + +class _FakeEquipment_8 extends _i1.SmartFake implements _i8.Equipment { + _FakeEquipment_8(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); +} + +class _FakeMuscle_9 extends _i1.SmartFake implements _i9.Muscle { + _FakeMuscle_9(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); +} + +class _FakeLanguage_10 extends _i1.SmartFake implements _i10.Language { + _FakeLanguage_10(Object parent, Invocation parentInvocation) : super(parent, parentInvocation); +} + /// A class which mocks [WgerBaseProvider]. /// /// See the documentation for Mockito's code generation for more information. @@ -104,46 +143,400 @@ class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider { ) as Uri); @override - _i5.Future fetch(Uri? uri) => (super.noSuchMethod( + _i11.Future fetch(Uri? uri) => (super.noSuchMethod( Invocation.method(#fetch, [uri]), - returnValue: _i5.Future.value(), - ) as _i5.Future); + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i5.Future> fetchPaginated(Uri? uri) => (super.noSuchMethod( + _i11.Future> fetchPaginated(Uri? uri) => (super.noSuchMethod( Invocation.method(#fetchPaginated, [uri]), - returnValue: _i5.Future>.value([]), - ) as _i5.Future>); + returnValue: _i11.Future>.value([]), + ) as _i11.Future>); @override - _i5.Future> post(Map? data, Uri? uri) => + _i11.Future> post( + Map? data, + Uri? uri, + ) => (super.noSuchMethod( Invocation.method(#post, [data, uri]), - returnValue: _i5.Future>.value( + returnValue: _i11.Future>.value( {}, ), - ) as _i5.Future>); + ) as _i11.Future>); @override - _i5.Future> patch( + _i11.Future> patch( Map? data, Uri? uri, ) => (super.noSuchMethod( Invocation.method(#patch, [data, uri]), - returnValue: _i5.Future>.value( + returnValue: _i11.Future>.value( {}, ), - ) as _i5.Future>); + ) as _i11.Future>); @override - _i5.Future<_i3.Response> deleteRequest(String? url, int? id) => (super.noSuchMethod( + _i11.Future<_i3.Response> deleteRequest(String? url, int? id) => (super.noSuchMethod( Invocation.method(#deleteRequest, [url, id]), - returnValue: _i5.Future<_i3.Response>.value( + returnValue: _i11.Future<_i3.Response>.value( _FakeResponse_3( this, Invocation.method(#deleteRequest, [url, id]), ), ), - ) as _i5.Future<_i3.Response>); + ) as _i11.Future<_i3.Response>); +} + +/// A class which mocks [ExercisesProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockExercisesProvider extends _i1.Mock implements _i12.ExercisesProvider { + MockExercisesProvider() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.WgerBaseProvider get baseProvider => (super.noSuchMethod( + Invocation.getter(#baseProvider), + returnValue: _FakeWgerBaseProvider_4( + this, + Invocation.getter(#baseProvider), + ), + ) as _i4.WgerBaseProvider); + + @override + _i5.ExerciseDatabase get database => (super.noSuchMethod( + Invocation.getter(#database), + returnValue: _FakeExerciseDatabase_5( + this, + Invocation.getter(#database), + ), + ) as _i5.ExerciseDatabase); + + @override + set database(_i5.ExerciseDatabase? _database) => super.noSuchMethod( + Invocation.setter(#database, _database), + returnValueForMissingStub: null, + ); + + @override + List<_i6.Exercise> get exercises => (super.noSuchMethod( + Invocation.getter(#exercises), + returnValue: <_i6.Exercise>[], + ) as List<_i6.Exercise>); + + @override + set exercises(List<_i6.Exercise>? _exercises) => super.noSuchMethod( + Invocation.setter(#exercises, _exercises), + returnValueForMissingStub: null, + ); + + @override + List<_i6.Exercise> get filteredExercises => (super.noSuchMethod( + Invocation.getter(#filteredExercises), + returnValue: <_i6.Exercise>[], + ) as List<_i6.Exercise>); + + @override + set filteredExercises(List<_i6.Exercise>? newFilteredExercises) => super.noSuchMethod( + Invocation.setter(#filteredExercises, newFilteredExercises), + returnValueForMissingStub: null, + ); + + @override + Map> get exerciseBasesByVariation => (super.noSuchMethod( + Invocation.getter(#exerciseBasesByVariation), + returnValue: >{}, + ) as Map>); + + @override + List<_i7.ExerciseCategory> get categories => (super.noSuchMethod( + Invocation.getter(#categories), + returnValue: <_i7.ExerciseCategory>[], + ) as List<_i7.ExerciseCategory>); + + @override + List<_i9.Muscle> get muscles => (super.noSuchMethod( + Invocation.getter(#muscles), + returnValue: <_i9.Muscle>[], + ) as List<_i9.Muscle>); + + @override + List<_i8.Equipment> get equipment => (super.noSuchMethod( + Invocation.getter(#equipment), + returnValue: <_i8.Equipment>[], + ) as List<_i8.Equipment>); + + @override + List<_i10.Language> get languages => (super.noSuchMethod( + Invocation.getter(#languages), + returnValue: <_i10.Language>[], + ) as List<_i10.Language>); + + @override + set languages(List<_i10.Language>? languages) => super.noSuchMethod( + Invocation.setter(#languages, languages), + returnValueForMissingStub: null, + ); + + @override + bool get hasListeners => + (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); + + @override + _i11.Future setFilters(_i12.Filters? newFilters) => (super.noSuchMethod( + Invocation.method(#setFilters, [newFilters]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + void initFilters() => super.noSuchMethod( + Invocation.method(#initFilters, []), + returnValueForMissingStub: null, + ); + + @override + _i11.Future findByFilters() => (super.noSuchMethod( + Invocation.method(#findByFilters, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + void clear() => super.noSuchMethod( + Invocation.method(#clear, []), + returnValueForMissingStub: null, + ); + + @override + _i6.Exercise findExerciseById(int? id) => (super.noSuchMethod( + Invocation.method(#findExerciseById, [id]), + returnValue: _FakeExercise_6( + this, + Invocation.method(#findExerciseById, [id]), + ), + ) as _i6.Exercise); + + @override + List<_i6.Exercise> findExercisesByVariationId( + int? id, { + int? exerciseBaseIdToExclude, + }) => + (super.noSuchMethod( + Invocation.method( + #findExercisesByVariationId, + [id], + {#exerciseBaseIdToExclude: exerciseBaseIdToExclude}, + ), + returnValue: <_i6.Exercise>[], + ) as List<_i6.Exercise>); + + @override + _i7.ExerciseCategory findCategoryById(int? id) => (super.noSuchMethod( + Invocation.method(#findCategoryById, [id]), + returnValue: _FakeExerciseCategory_7( + this, + Invocation.method(#findCategoryById, [id]), + ), + ) as _i7.ExerciseCategory); + + @override + _i8.Equipment findEquipmentById(int? id) => (super.noSuchMethod( + Invocation.method(#findEquipmentById, [id]), + returnValue: _FakeEquipment_8( + this, + Invocation.method(#findEquipmentById, [id]), + ), + ) as _i8.Equipment); + + @override + _i9.Muscle findMuscleById(int? id) => (super.noSuchMethod( + Invocation.method(#findMuscleById, [id]), + returnValue: _FakeMuscle_9( + this, + Invocation.method(#findMuscleById, [id]), + ), + ) as _i9.Muscle); + + @override + _i10.Language findLanguageById(int? id) => (super.noSuchMethod( + Invocation.method(#findLanguageById, [id]), + returnValue: _FakeLanguage_10( + this, + Invocation.method(#findLanguageById, [id]), + ), + ) as _i10.Language); + + @override + _i11.Future fetchAndSetCategoriesFromApi() => (super.noSuchMethod( + Invocation.method(#fetchAndSetCategoriesFromApi, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetMusclesFromApi() => (super.noSuchMethod( + Invocation.method(#fetchAndSetMusclesFromApi, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetEquipmentsFromApi() => (super.noSuchMethod( + Invocation.method(#fetchAndSetEquipmentsFromApi, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetLanguagesFromApi() => (super.noSuchMethod( + Invocation.method(#fetchAndSetLanguagesFromApi, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future<_i6.Exercise?> fetchAndSetExercise(int? exerciseId) => (super.noSuchMethod( + Invocation.method(#fetchAndSetExercise, [exerciseId]), + returnValue: _i11.Future<_i6.Exercise?>.value(), + ) as _i11.Future<_i6.Exercise?>); + + @override + _i11.Future<_i6.Exercise> handleUpdateExerciseFromApi( + _i5.ExerciseDatabase? database, + int? exerciseId, + ) => + (super.noSuchMethod( + Invocation.method(#handleUpdateExerciseFromApi, [ + database, + exerciseId, + ]), + returnValue: _i11.Future<_i6.Exercise>.value( + _FakeExercise_6( + this, + Invocation.method(#handleUpdateExerciseFromApi, [ + database, + exerciseId, + ]), + ), + ), + ) as _i11.Future<_i6.Exercise>); + + @override + _i11.Future initCacheTimesLocalPrefs({dynamic forceInit = false}) => (super.noSuchMethod( + Invocation.method(#initCacheTimesLocalPrefs, [], { + #forceInit: forceInit, + }), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future clearAllCachesAndPrefs() => (super.noSuchMethod( + Invocation.method(#clearAllCachesAndPrefs, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetInitialData() => (super.noSuchMethod( + Invocation.method(#fetchAndSetInitialData, []), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future setExercisesFromDatabase( + _i5.ExerciseDatabase? database, { + bool? forceDeleteCache = false, + }) => + (super.noSuchMethod( + Invocation.method( + #setExercisesFromDatabase, + [database], + {#forceDeleteCache: forceDeleteCache}, + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future updateExerciseCache(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + Invocation.method(#updateExerciseCache, [database]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetMuscles(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + Invocation.method(#fetchAndSetMuscles, [database]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetCategories(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + Invocation.method(#fetchAndSetCategories, [database]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetLanguages(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + Invocation.method(#fetchAndSetLanguages, [database]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future fetchAndSetEquipments(_i5.ExerciseDatabase? database) => (super.noSuchMethod( + Invocation.method(#fetchAndSetEquipments, [database]), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + + @override + _i11.Future> searchExercise( + String? name, { + String? languageCode = 'en', + bool? searchEnglish = false, + }) => + (super.noSuchMethod( + Invocation.method( + #searchExercise, + [name], + {#languageCode: languageCode, #searchEnglish: searchEnglish}, + ), + returnValue: _i11.Future>.value( + <_i6.Exercise>[], + ), + ) as _i11.Future>); + + @override + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( + Invocation.method(#addListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( + Invocation.method(#removeListener, [listener]), + returnValueForMissingStub: null, + ); + + @override + void dispose() => super.noSuchMethod( + Invocation.method(#dispose, []), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method(#notifyListeners, []), + returnValueForMissingStub: null, + ); } diff --git a/test/workout/slot_entry_form_test.mocks.dart b/test/workout/slot_entry_form_test.mocks.dart index c104eed1..85a9cd42 100644 --- a/test/workout/slot_entry_form_test.mocks.dart +++ b/test/workout/slot_entry_form_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test/workout/weight_unit_form_widget_test.mocks.dart b/test/workout/weight_unit_form_widget_test.mocks.dart index 386cdd96..dc95f310 100644 --- a/test/workout/weight_unit_form_widget_test.mocks.dart +++ b/test/workout/weight_unit_form_widget_test.mocks.dart @@ -107,6 +107,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i3.WeightUnit>[], ) as List<_i3.WeightUnit>); + @override + set weightUnits(List<_i3.WeightUnit>? weightUnits) => super.noSuchMethod( + Invocation.setter(#weightUnits, weightUnits), + returnValueForMissingStub: null, + ); + @override _i3.WeightUnit get defaultWeightUnit => (super.noSuchMethod( Invocation.getter(#defaultWeightUnit), @@ -122,6 +128,12 @@ class MockRoutinesProvider extends _i1.Mock implements _i12.RoutinesProvider { returnValue: <_i4.RepetitionUnit>[], ) as List<_i4.RepetitionUnit>); + @override + set repetitionUnits(List<_i4.RepetitionUnit>? repetitionUnits) => super.noSuchMethod( + Invocation.setter(#repetitionUnits, repetitionUnits), + returnValueForMissingStub: null, + ); + @override _i4.RepetitionUnit get defaultRepetitionUnit => (super.noSuchMethod( Invocation.getter(#defaultRepetitionUnit), diff --git a/test_data/exercises.dart b/test_data/exercises.dart index 63faa2d3..ab23e265 100644 --- a/test_data/exercises.dart +++ b/test_data/exercises.dart @@ -50,7 +50,7 @@ const tEquipment3 = Equipment(id: 3, name: 'Bench'); const tEquipment4 = Equipment(id: 10, name: 'Gym mat'); const testEquipment = [tEquipment1, tEquipment2, tEquipment3]; -final benchPress = Exercise( +final testBenchPress = Exercise( id: 1, uuid: '364f196c-881b-4839-8bfc-9e8f651521b6', created: DateTime(2021, 09, 01), @@ -62,7 +62,7 @@ final benchPress = Exercise( translations: [benchPressEn, benchPressDe], ); -final crunches = Exercise( +final testCrunches = Exercise( id: 2, uuid: '82415754-fc4c-49ea-8ca7-1516dd36d5a0', created: DateTime(2021, 08, 01), @@ -74,7 +74,7 @@ final crunches = Exercise( translations: [crunchesEn, crunchesDe, crunchesFr], ); -final deadLift = Exercise( +final testDeadLift = Exercise( id: 3, uuid: 'ca84e2c5-5608-4d6d-ba57-6d4b6b5e7acd', created: DateTime(2021, 08, 01), @@ -86,7 +86,7 @@ final deadLift = Exercise( translations: [deadLiftEn], ); -final curls = Exercise( +final testCurls = Exercise( id: 4, uuid: '361f024c-fdf8-4146-b7d7-0c1b67c58141', created: DateTime(2021, 08, 01), @@ -98,7 +98,7 @@ final curls = Exercise( translations: [curlsEn], ); -final squats = Exercise( +final testSquats = Exercise( id: 5, uuid: '361f024c-fdf8-4146-b7d7-0c1b67c58141', created: DateTime(2021, 08, 01), @@ -110,7 +110,7 @@ final squats = Exercise( translations: [squatsEn], ); -final sideRaises = Exercise( +final testSideRaises = Exercise( id: 6, uuid: '721ff972-c568-41e3-8cf5-cf1e5c5c801c', created: DateTime(2022, 11, 01), @@ -213,16 +213,16 @@ final sideRaisesEn = Translation( ); List getTestExercises() { - return [benchPress, crunches, deadLift, curls, squats, sideRaises]; + return [testBenchPress, testCrunches, testDeadLift, testCurls, testSquats, testSideRaises]; } List getScreenshotExercises() { - benchPress.translations = benchPressTranslations; - crunches.translations = crunchesTranslations; - deadLift.translations = deadLiftTranslations; - curls.translations = curlsTranslations; - squats.translations = squatsTranslations; - sideRaises.translations = raisesTranslations; + testBenchPress.translations = benchPressTranslations; + testCrunches.translations = crunchesTranslations; + testDeadLift.translations = deadLiftTranslations; + testCurls.translations = curlsTranslations; + testSquats.translations = squatsTranslations; + testSideRaises.translations = raisesTranslations; - return [benchPress, crunches, deadLift, curls, squats, sideRaises]; + return [testBenchPress, testCrunches, testDeadLift, testCurls, testSquats, testSideRaises]; } diff --git a/test_data/routines.dart b/test_data/routines.dart index 7c210958..89ec2f8f 100644 --- a/test_data/routines.dart +++ b/test_data/routines.dart @@ -138,7 +138,6 @@ Routine getTestRoutine({List? exercises}) { value: 5, operation: '+', step: 'abs', - needLogToApply: false, requirements: null, repeat: true, ), From 14a5d7285b51b4bae0442160e1c81dd0eb6a8bf1 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Sun, 16 Feb 2025 21:19:17 +0100 Subject: [PATCH 6/8] Extract the day data for the current routine instead of loading the same data from the server --- lib/models/workouts/routine.dart | 32 +- lib/providers/routines.dart | 29 +- .../routine_current_iteration_display.json | 145 ------- .../routine_current_iteration_gym.json | 277 ------------- .../routines/routine_date_sequence_gym.json | 2 +- test/workout/routine_edit_test.dart | 4 +- test/workout/routine_screen_test.dart | 7 +- test/workout/routines_provider_test.dart | 20 +- test_data/routines.dart | 364 ++++++++---------- 9 files changed, 200 insertions(+), 680 deletions(-) delete mode 100644 test/fixtures/routines/routine_current_iteration_display.json delete mode 100644 test/fixtures/routines/routine_current_iteration_gym.json diff --git a/lib/models/workouts/routine.dart b/lib/models/workouts/routine.dart index b5b03521..b43bc79b 100644 --- a/lib/models/workouts/routine.dart +++ b/lib/models/workouts/routine.dart @@ -18,6 +18,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:wger/helpers/json.dart'; +import 'package:wger/helpers/misc.dart'; import 'package:wger/models/exercises/exercise.dart'; import 'package:wger/models/workouts/day.dart'; import 'package:wger/models/workouts/day_data.dart'; @@ -69,12 +70,6 @@ class Routine { @JsonKey(includeFromJson: false, includeToJson: false) List dayDataGym = []; - @JsonKey(includeFromJson: false, includeToJson: false) - List dayDataCurrentIteration = []; - - @JsonKey(includeFromJson: false, includeToJson: false) - List dayDataCurrentIterationGym = []; - @JsonKey(required: false, includeToJson: false, defaultValue: []) List sessions = []; @@ -89,8 +84,6 @@ class Routine { this.days = const [], this.dayData = const [], this.dayDataGym = const [], - this.dayDataCurrentIteration = const [], - this.dayDataCurrentIterationGym = const [], this.sessions = const [], }) { this.created = created ?? DateTime.now(); @@ -121,6 +114,29 @@ class Routine { return out; } + int? getIteration({DateTime? date}) { + if (date == null) { + return null; + } + + for (final data in dayData) { + if (data.date.isSameDayAs(date)) { + return data.iteration; + } + } + return null; + } + + List get dayDataCurrentIteration { + final iteration = getIteration() ?? 1; + return dayData.where((data) => data.iteration == iteration).toList(); + } + + List get dayDataCurrentIterationGym { + final iteration = getIteration() ?? 1; + return dayDataGym.where((data) => data.iteration == iteration).toList(); + } + /// Filters the workout logs by exercise and sorts them by date /// /// Optionally, filters list so that only unique logs are returned. "Unique" diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index 28ef19c5..4dd5d044 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -46,7 +46,6 @@ class RoutinesProvider with ChangeNotifier { static const _routinesDateSequenceDisplaySubpath = 'date-sequence-display'; static const _routinesDateSequenceGymSubpath = 'date-sequence-gym'; static const _routinesCurrentIterationDisplaySubpath = 'current-iteration-display'; - static const _routinesCurrentIterationGymSubpath = 'current-iteration-gym'; static const _daysUrlPath = 'day'; static const _slotsUrlPath = 'slot'; static const _slotEntriesUrlPath = 'slot-entry'; @@ -257,20 +256,6 @@ class RoutinesProvider with ChangeNotifier { objectMethod: _routinesDateSequenceGymSubpath, ), ), - baseProvider.fetch( - baseProvider.makeUrl( - _routinesUrlPath, - id: routineId, - objectMethod: _routinesCurrentIterationDisplaySubpath, - ), - ), - baseProvider.fetch( - baseProvider.makeUrl( - _routinesUrlPath, - id: routineId, - objectMethod: _routinesCurrentIterationGymSubpath, - ), - ), baseProvider.fetch( baseProvider.makeUrl( _routinesUrlPath, @@ -284,9 +269,7 @@ class RoutinesProvider with ChangeNotifier { final dayData = results[1] as List; final dayDataGym = results[2] as List; - final currentIterationDayData = results[3] as List; - final currentIterationDayDataGym = results[4] as List; - final sessionData = results[5] as List; + final sessionData = results[3] as List; /* * Set exercise, repetition and weight unit objects @@ -299,14 +282,6 @@ class RoutinesProvider with ChangeNotifier { final dayDataEntriesGym = dayDataGym.map((entry) => DayData.fromJson(entry)).toList(); setExercisesAndUnits(dayDataEntriesGym); - final currentIteration = - currentIterationDayData.map((entry) => DayData.fromJson(entry)).toList(); - setExercisesAndUnits(currentIteration); - - final currentIterationGym = - currentIterationDayDataGym.map((entry) => DayData.fromJson(entry)).toList(); - setExercisesAndUnits(currentIterationGym); - final sessionDataEntries = sessionData.map((entry) => WorkoutSessionApi.fromJson(entry)).toList(); @@ -327,8 +302,6 @@ class RoutinesProvider with ChangeNotifier { routine.dayData = dayDataEntriesDisplay; routine.dayDataGym = dayDataEntriesGym; - routine.dayDataCurrentIteration = currentIteration; - routine.dayDataCurrentIterationGym = currentIterationGym; // Logs routine.sessions = List.from(sessionDataEntries); diff --git a/test/fixtures/routines/routine_current_iteration_display.json b/test/fixtures/routines/routine_current_iteration_display.json deleted file mode 100644 index 3c4c2340..00000000 --- a/test/fixtures/routines/routine_current_iteration_display.json +++ /dev/null @@ -1,145 +0,0 @@ -[ - { - "iteration": 4, - "date": "2025-01-27", - "label": null, - "day": { - "id": 8, - "routine": 2, - "order": 1, - "name": "Leg day", - "description": "Leg day", - "is_rest": false, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [ - { - "comment": "", - "is_superset": false, - "exercises": [ - 76 - ], - "sets": [ - { - "slot_entry_id": 2, - "exercise": 76, - "sets": 4, - "max_sets": null, - "weight": "80.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "4 Sätze, 10 × 80 kg 120s rest", - "comment": "" - } - ] - } - ] - }, - { - "iteration": 4, - "date": "2025-01-28", - "label": null, - "day": { - "id": 9, - "routine": 2, - "order": 2, - "name": "", - "description": "", - "is_rest": true, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-01-29", - "label": null, - "day": { - "id": 10, - "routine": 2, - "order": 3, - "name": "Arms day", - "description": "Leg day", - "is_rest": false, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [ - { - "comment": "", - "is_superset": false, - "exercises": [ - 92 - ], - "sets": [ - { - "slot_entry_id": 3, - "exercise": 92, - "sets": 4, - "max_sets": null, - "weight": "23.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "4 Sätze, 10 × 23 kg 120s rest", - "comment": "" - } - ] - } - ] - }, - { - "iteration": 4, - "date": "2025-01-30", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-01-31", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-02-01", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-02-02", - "label": null, - "day": null, - "slots": [] - } -] \ No newline at end of file diff --git a/test/fixtures/routines/routine_current_iteration_gym.json b/test/fixtures/routines/routine_current_iteration_gym.json deleted file mode 100644 index 12264e83..00000000 --- a/test/fixtures/routines/routine_current_iteration_gym.json +++ /dev/null @@ -1,277 +0,0 @@ -[ - { - "iteration": 4, - "date": "2025-01-27", - "label": null, - "day": { - "id": 8, - "routine": 2, - "order": 1, - "name": "Leg day", - "description": "Leg day", - "is_rest": false, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [ - { - "comment": "", - "is_superset": false, - "exercises": [ - 76 - ], - "sets": [ - { - "slot_entry_id": 2, - "exercise": 76, - "sets": 1, - "max_sets": null, - "weight": "80.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 80 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 2, - "exercise": 76, - "sets": 1, - "max_sets": null, - "weight": "80.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 80 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 2, - "exercise": 76, - "sets": 1, - "max_sets": null, - "weight": "80.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 80 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 2, - "exercise": 76, - "sets": 1, - "max_sets": null, - "weight": "80.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 80 kg 120s rest", - "comment": "" - } - ] - } - ] - }, - { - "iteration": 4, - "date": "2025-01-28", - "label": null, - "day": { - "id": 9, - "routine": 2, - "order": 2, - "name": "", - "description": "", - "is_rest": true, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-01-29", - "label": null, - "day": { - "id": 10, - "routine": 2, - "order": 3, - "name": "Arms day", - "description": "Leg day", - "is_rest": false, - "need_logs_to_advance": false, - "type": "custom", - "config": null - }, - "slots": [ - { - "comment": "", - "is_superset": false, - "exercises": [ - 92 - ], - "sets": [ - { - "slot_entry_id": 3, - "exercise": 92, - "sets": 1, - "max_sets": null, - "weight": "23.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 23 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 3, - "exercise": 92, - "sets": 1, - "max_sets": null, - "weight": "23.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 23 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 3, - "exercise": 92, - "sets": 1, - "max_sets": null, - "weight": "23.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 23 kg 120s rest", - "comment": "" - }, - { - "slot_entry_id": 3, - "exercise": 92, - "sets": 1, - "max_sets": null, - "weight": "23.00", - "max_weight": null, - "weight_unit": 1, - "weight_rounding": null, - "repetitions": "10.00", - "max_repetitions": null, - "repetitions_unit": 1, - "repetitions_rounding": null, - "rir": null, - "max_rir": null, - "rpe": null, - "rest": "120.00", - "max_rest": null, - "type": "normal", - "text_repr": "10 × 23 kg 120s rest", - "comment": "" - } - ] - } - ] - }, - { - "iteration": 4, - "date": "2025-01-30", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-01-31", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-02-01", - "label": null, - "day": null, - "slots": [] - }, - { - "iteration": 4, - "date": "2025-02-02", - "label": null, - "day": null, - "slots": [] - } -] \ No newline at end of file diff --git a/test/fixtures/routines/routine_date_sequence_gym.json b/test/fixtures/routines/routine_date_sequence_gym.json index a5557aaa..b1708632 100644 --- a/test/fixtures/routines/routine_date_sequence_gym.json +++ b/test/fixtures/routines/routine_date_sequence_gym.json @@ -8,7 +8,7 @@ "routine": 2, "order": 1, "name": "Leg day", - "description": "Leg day", + "description": "yes, we really do legs today", "is_rest": false, "need_logs_to_advance": false, "type": "custom", diff --git a/test/workout/routine_edit_test.dart b/test/workout/routine_edit_test.dart index 369fb79d..a18eb39f 100644 --- a/test/workout/routine_edit_test.dart +++ b/test/workout/routine_edit_test.dart @@ -66,8 +66,8 @@ void main() { expect(find.text('first day'), findsNWidgets(2)); expect(find.text('chest, shoulders'), findsNWidgets(2), reason: 'description'); - expect(find.text('second day'), findsNWidgets(3)); - expect(find.text('legs'), findsNWidgets(3), reason: 'description'); + expect(find.text('second day'), findsNWidgets(2)); + expect(find.text('legs'), findsNWidgets(2), reason: 'description'); // Edit the first day expect(find.byElementType(DayFormWidget), findsNothing); diff --git a/test/workout/routine_screen_test.dart b/test/workout/routine_screen_test.dart index 392bc491..9709ac61 100644 --- a/test/workout/routine_screen_test.dart +++ b/test/workout/routine_screen_test.dart @@ -85,10 +85,9 @@ void main() { expect(find.text('first day'), findsOneWidget); expect(find.text('chest, shoulders'), findsOneWidget); - // The second day is repeated - expect(find.text('second day'), findsNWidgets(2)); - expect(find.text('legs'), findsNWidgets(2)); + expect(find.text('second day'), findsOneWidget); + expect(find.text('legs'), findsOneWidget); - expect(find.byType(Card), findsNWidgets(3)); + expect(find.byType(Card), findsNWidgets(2)); }); } diff --git a/test/workout/routines_provider_test.dart b/test/workout/routines_provider_test.dart index edd34f9c..8f16666f 100644 --- a/test/workout/routines_provider_test.dart +++ b/test/workout/routines_provider_test.dart @@ -190,22 +190,6 @@ void main() { jsonDecode(fixture('routines/routine_date_sequence_gym.json')), )); - final currentIterationDisplayUri = - Uri.https('localhost', 'api/v2/routine/101/current-iteration-display/'); - when(mockBaseProvider.makeUrl('routine', objectMethod: 'current-iteration-display', id: 101)) - .thenReturn(currentIterationDisplayUri); - when(mockBaseProvider.fetch(currentIterationDisplayUri)).thenAnswer((_) async => Future.value( - jsonDecode(fixture('routines/routine_date_sequence_gym.json')), - )); - - final currentIterationGymUri = - Uri.https('localhost', 'api/v2/routine/101/current-iteration-gym/'); - when(mockBaseProvider.makeUrl('routine', objectMethod: 'current-iteration-gym', id: 101)) - .thenReturn(currentIterationGymUri); - when(mockBaseProvider.fetch(currentIterationGymUri)).thenAnswer((_) async => Future.value( - jsonDecode(fixture('routines/routine_current_iteration_gym.json')), - )); - final logsUri = Uri.https('localhost', 'api/v2/routine/101/logs/'); when(mockBaseProvider.makeUrl('routine', objectMethod: 'logs', id: 101)).thenReturn(logsUri); when(mockBaseProvider.fetch(logsUri)).thenAnswer((_) async => Future.value( @@ -233,8 +217,8 @@ void main() { expect(result.sessions.length, 3); expect(result.days.length, 3); expect(result.logs.length, 12); - expect(result.dayDataCurrentIteration.length, 32); - expect(result.dayDataCurrentIterationGym.length, 7); + expect(result.dayDataCurrentIteration.length, 8); + expect(result.dayDataCurrentIterationGym.length, 8); expect(result.dayData.length, 32); expect(result.dayDataGym.length, 32); }); diff --git a/test_data/routines.dart b/test_data/routines.dart index 89ec2f8f..9a49c810 100644 --- a/test_data/routines.dart +++ b/test_data/routines.dart @@ -226,6 +226,172 @@ Routine getTestRoutine({List? exercises}) { slots: [slotSquat], ); + final List dayDataGym = [ + DayData( + iteration: 1, + date: DateTime(2024, 11, 01), + label: '', + day: dayChestShoulders, + slots: [ + SlotData( + comment: 'Make sure to warm up', + isSuperset: false, + exerciseIds: [testExercises[0].id!], + setConfigs: [ + SetConfigData( + exerciseId: 1, + exercise: testExercises[0], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + SetConfigData( + exerciseId: testExercises[0].id!, + exercise: testExercises[0], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + SetConfigData( + exerciseId: testExercises[0].id!, + exercise: testExercises[0], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + ], + ), + SlotData( + comment: 'Side rises', + isSuperset: false, + exerciseIds: [testExercises[5].id!], + setConfigs: [ + SetConfigData( + exerciseId: testExercises[5].id!, + exercise: testExercises[5], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 12, + repetitionsUnit: testRepetitionUnit1, + weight: 10, + weightUnit: testWeightUnit1, + restTime: 60, + rir: '', + rpe: '', + textRepr: '12x10kg', + ), + SetConfigData( + exerciseId: testExercises[5].id!, + exercise: testExercises[5], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 12, + repetitionsUnit: testRepetitionUnit1, + weight: 10, + weightUnit: testWeightUnit1, + restTime: 60, + rir: '', + rpe: '', + textRepr: '12x10kg', + ), + SetConfigData( + exerciseId: testExercises[5].id!, + exercise: testExercises[5], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 12, + repetitionsUnit: testRepetitionUnit1, + weight: 10, + weightUnit: testWeightUnit1, + restTime: 60, + rir: '', + rpe: '', + textRepr: '12x10kg', + ), + ], + ), + ], + ), + DayData( + iteration: 1, + date: DateTime(2024, 11, 02), + label: '', + day: dayLegs, + slots: [ + SlotData( + comment: 'Squats', + isSuperset: false, + exerciseIds: [testExercises[4].id!], + setConfigs: [ + SetConfigData( + exerciseId: 8, + exercise: testExercises[4], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + SetConfigData( + exerciseId: testExercises[4].id!, + exercise: testExercises[4], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + SetConfigData( + exerciseId: testExercises[4].id!, + exercise: testExercises[4], + slotEntryId: 1, + nrOfSets: 1, + repetitions: 3, + repetitionsUnit: testRepetitionUnit1, + weight: 100, + weightUnit: testWeightUnit1, + restTime: 120, + rir: '1.5', + rpe: '8', + textRepr: '3x100kg', + ), + ], + ), + ], + ), + ]; + final List dayDataDisplay = [ DayData( iteration: 1, @@ -332,203 +498,7 @@ Routine getTestRoutine({List? exercises}) { days: [dayChestShoulders, dayLegs], sessions: [session1, session2], dayData: dayDataDisplay, - dayDataCurrentIteration: [ - ...dayDataDisplay, - DayData( - iteration: 2, - date: DateTime(2024, 11, 02), - label: '', - day: dayLegs, - slots: [ - SlotData( - comment: 'Squats', - isSuperset: false, - exerciseIds: [8], - setConfigs: [ - SetConfigData( - exerciseId: 8, - exercise: testExercises[4], - slotEntryId: 1, - nrOfSets: 5, - repetitions: 8, - repetitionsUnit: testRepetitionUnit1, - weight: 105, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1', - rpe: '9', - textRepr: '5 sets 8x105kg', - ), - ], - ), - ], - ), - ], - dayDataGym: [ - DayData( - iteration: 1, - date: DateTime(2024, 11, 01), - label: '', - day: dayChestShoulders, - slots: [ - SlotData( - comment: 'Make sure to warm up', - isSuperset: false, - exerciseIds: [testExercises[0].id!], - setConfigs: [ - SetConfigData( - exerciseId: 1, - exercise: testExercises[0], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - SetConfigData( - exerciseId: testExercises[0].id!, - exercise: testExercises[0], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - SetConfigData( - exerciseId: testExercises[0].id!, - exercise: testExercises[0], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - ], - ), - SlotData( - comment: 'Side rises', - isSuperset: false, - exerciseIds: [testExercises[5].id!], - setConfigs: [ - SetConfigData( - exerciseId: testExercises[5].id!, - exercise: testExercises[5], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 12, - repetitionsUnit: testRepetitionUnit1, - weight: 10, - weightUnit: testWeightUnit1, - restTime: 60, - rir: '', - rpe: '', - textRepr: '12x10kg', - ), - SetConfigData( - exerciseId: testExercises[5].id!, - exercise: testExercises[5], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 12, - repetitionsUnit: testRepetitionUnit1, - weight: 10, - weightUnit: testWeightUnit1, - restTime: 60, - rir: '', - rpe: '', - textRepr: '12x10kg', - ), - SetConfigData( - exerciseId: testExercises[5].id!, - exercise: testExercises[5], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 12, - repetitionsUnit: testRepetitionUnit1, - weight: 10, - weightUnit: testWeightUnit1, - restTime: 60, - rir: '', - rpe: '', - textRepr: '12x10kg', - ), - ], - ), - ], - ), - DayData( - iteration: 1, - date: DateTime(2024, 11, 02), - label: '', - day: dayLegs, - slots: [ - SlotData( - comment: 'Squats', - isSuperset: false, - exerciseIds: [testExercises[4].id!], - setConfigs: [ - SetConfigData( - exerciseId: 8, - exercise: testExercises[4], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - SetConfigData( - exerciseId: testExercises[4].id!, - exercise: testExercises[4], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - SetConfigData( - exerciseId: testExercises[4].id!, - exercise: testExercises[4], - slotEntryId: 1, - nrOfSets: 1, - repetitions: 3, - repetitionsUnit: testRepetitionUnit1, - weight: 100, - weightUnit: testWeightUnit1, - restTime: 120, - rir: '1.5', - rpe: '8', - textRepr: '3x100kg', - ), - ], - ), - ], - ), - ], + dayDataGym: dayDataGym, ); return routine; From 4f94a6a41ee5fbd374ba2a66c79bd10890a02c3d Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 21 Feb 2025 16:20:10 +0100 Subject: [PATCH 7/8] Make the repetition and weight units nullable as per the backend --- lib/helpers/misc.dart | 12 +++++++----- lib/models/workouts/log.dart | 16 ++++++++-------- lib/models/workouts/set_config_data.dart | 4 ++-- lib/models/workouts/slot_entry.dart | 8 ++++---- lib/models/workouts/slot_entry.g.dart | 4 ++-- lib/widgets/routines/forms/reps_unit.dart | 13 +++++++------ lib/widgets/routines/forms/weight_unit.dart | 13 +++++++------ .../repetition_unit_form_widget_test.dart | 4 ++-- test/workout/weight_unit_form_widget_test.dart | 4 ++-- 9 files changed, 41 insertions(+), 37 deletions(-) diff --git a/lib/helpers/misc.dart b/lib/helpers/misc.dart index 661c7105..4ed41077 100644 --- a/lib/helpers/misc.dart +++ b/lib/helpers/misc.dart @@ -25,9 +25,9 @@ import 'package:wger/models/workouts/weight_unit.dart'; /// Returns the text representation for a single setting, used in the gym mode String repText( num? repetitions, - RepetitionUnit repetitionUnitObj, + RepetitionUnit? repetitionUnitObj, num? weight, - WeightUnit weightUnitObj, + WeightUnit? weightUnitObj, String? rir, ) { // TODO(x): how to (easily?) translate strings like the units or 'RiR' @@ -42,15 +42,17 @@ String repText( // rather "8 repetitions". If there is weight we want to output "8 x 50kg", // since the repetitions are implied. If other units are used, we always // print them - if (repetitionUnitObj.id != REP_UNIT_REPETITIONS_ID || weight == 0 || weight == null) { - out.add(repetitionUnitObj.name); + if (repetitionUnitObj != null && repetitionUnitObj.id != REP_UNIT_REPETITIONS_ID || + weight == 0 || + weight == null) { + out.add(repetitionUnitObj!.name); } } if (weight != null && weight != 0) { out.add('×'); out.add(formatNum(weight).toString()); - out.add(weightUnitObj.name); + out.add(weightUnitObj!.name); } if (rir != null && rir != '') { diff --git a/lib/models/workouts/log.dart b/lib/models/workouts/log.dart index cd3ce774..74a1cc04 100644 --- a/lib/models/workouts/log.dart +++ b/lib/models/workouts/log.dart @@ -62,10 +62,10 @@ class Log { num? repetitionsTarget; @JsonKey(required: true, name: 'repetitions_unit') - late int repetitionsUnitId; + late int? repetitionsUnitId; @JsonKey(includeFromJson: false, includeToJson: false) - late RepetitionUnit repetitionsUnitObj; + late RepetitionUnit? repetitionsUnitObj; @JsonKey(required: true, fromJson: stringToNum, toJson: numToString) late num? weight; @@ -74,10 +74,10 @@ class Log { num? weightTarget; @JsonKey(required: true, name: 'weight_unit') - late int weightUnitId; + late int? weightUnitId; @JsonKey(includeFromJson: false, includeToJson: false) - late WeightUnit weightUnitObj; + late WeightUnit? weightUnitObj; @JsonKey(required: true, toJson: dateToYYYYMMDD) late DateTime date; @@ -111,14 +111,14 @@ class Log { exerciseId = base.id!; } - set weightUnit(WeightUnit weightUnit) { + set weightUnit(WeightUnit? weightUnit) { weightUnitObj = weightUnit; - weightUnitId = weightUnit.id; + weightUnitId = weightUnit?.id; } - set repetitionUnit(RepetitionUnit repetitionUnit) { + set repetitionUnit(RepetitionUnit? repetitionUnit) { repetitionsUnitObj = repetitionUnit; - repetitionsUnitId = repetitionUnit.id; + repetitionsUnitId = repetitionUnit?.id; } void setRir(String rir) { diff --git a/lib/models/workouts/set_config_data.dart b/lib/models/workouts/set_config_data.dart index a12424e0..82771913 100644 --- a/lib/models/workouts/set_config_data.dart +++ b/lib/models/workouts/set_config_data.dart @@ -58,7 +58,7 @@ class SetConfigData { late int? weightUnitId; @JsonKey(includeToJson: false, includeFromJson: false) - late WeightUnit weightUnit; + late WeightUnit? weightUnit; @JsonKey(required: true, name: 'weight_rounding', fromJson: stringToNumNull) late num? weightRounding; @@ -73,7 +73,7 @@ class SetConfigData { late int? repetitionsUnitId; @JsonKey(includeToJson: false, includeFromJson: false) - late RepetitionUnit repetitionsUnit; + late RepetitionUnit? repetitionsUnit; @JsonKey(required: true, name: 'repetitions_rounding', fromJson: stringToNumNull) late num? repetitionsRounding; diff --git a/lib/models/workouts/slot_entry.dart b/lib/models/workouts/slot_entry.dart index 5a42759d..c613991f 100644 --- a/lib/models/workouts/slot_entry.dart +++ b/lib/models/workouts/slot_entry.dart @@ -68,10 +68,10 @@ class SlotEntry { late int exerciseId; @JsonKey(required: true, name: 'repetition_unit') - late int repetitionUnitId; + late int? repetitionUnitId; @JsonKey(includeFromJson: false, includeToJson: false) - late RepetitionUnit repetitionUnitObj; + late RepetitionUnit? repetitionUnitObj; @JsonKey(required: true, name: 'repetition_rounding', fromJson: stringToNum) late num repetitionRounding; @@ -83,10 +83,10 @@ class SlotEntry { late List maxRepetitionsConfigs = []; @JsonKey(required: true, name: 'weight_unit') - late int weightUnitId; + late int? weightUnitId; @JsonKey(includeFromJson: false, includeToJson: false) - late WeightUnit weightUnitObj; + late WeightUnit? weightUnitObj; @JsonKey(required: true, name: 'weight_rounding', fromJson: stringToNum) late num weightRounding; diff --git a/lib/models/workouts/slot_entry.g.dart b/lib/models/workouts/slot_entry.g.dart index 5e06871f..a37de964 100644 --- a/lib/models/workouts/slot_entry.g.dart +++ b/lib/models/workouts/slot_entry.g.dart @@ -29,9 +29,9 @@ SlotEntry _$SlotEntryFromJson(Map json) { order: (json['order'] as num).toInt(), type: json['type'] as String, exerciseId: (json['exercise'] as num).toInt(), - repetitionUnitId: (json['repetition_unit'] as num).toInt(), + repetitionUnitId: (json['repetition_unit'] as num?)?.toInt(), repetitionRounding: stringToNum(json['repetition_rounding'] as String?), - weightUnitId: (json['weight_unit'] as num).toInt(), + weightUnitId: (json['weight_unit'] as num?)?.toInt(), weightRounding: stringToNum(json['weight_rounding'] as String?), comment: json['comment'] as String, weightConfigs: (json['weight_configs'] as List?) diff --git a/lib/widgets/routines/forms/reps_unit.dart b/lib/widgets/routines/forms/reps_unit.dart index 40d8561d..5e981565 100644 --- a/lib/widgets/routines/forms/reps_unit.dart +++ b/lib/widgets/routines/forms/reps_unit.dart @@ -26,12 +26,11 @@ import 'package:wger/providers/routines.dart'; /// /// Can be used with a Setting or a Log object class RepetitionUnitInputWidget extends StatefulWidget { - final int _initialValue; - late int selectedRepetitionUnit; - final ValueChanged onChanged; + late int? selectedRepetitionUnit; + final ValueChanged onChanged; - RepetitionUnitInputWidget(this._initialValue, {required this.onChanged}) { - selectedRepetitionUnit = _initialValue; + RepetitionUnitInputWidget(initialValue, {required this.onChanged}) { + selectedRepetitionUnit = initialValue; } @override @@ -43,7 +42,9 @@ class _RepetitionUnitInputWidgetState extends State { Widget build(BuildContext context) { final unitProvider = context.read(); - RepetitionUnit selectedWeightUnit = unitProvider.findRepetitionUnitById(widget._initialValue); + RepetitionUnit? selectedWeightUnit = widget.selectedRepetitionUnit != null + ? unitProvider.findRepetitionUnitById(widget.selectedRepetitionUnit!) + : null; return DropdownButtonFormField( value: selectedWeightUnit, diff --git a/lib/widgets/routines/forms/weight_unit.dart b/lib/widgets/routines/forms/weight_unit.dart index 2e27a14a..13273d8d 100644 --- a/lib/widgets/routines/forms/weight_unit.dart +++ b/lib/widgets/routines/forms/weight_unit.dart @@ -26,12 +26,11 @@ import 'package:wger/providers/routines.dart'; /// /// Can be used with a Setting or a Log object class WeightUnitInputWidget extends StatefulWidget { - final int _initialValue; - late int selectedWeightUnit; - final ValueChanged onChanged; + late int? selectedWeightUnit; + final ValueChanged onChanged; - WeightUnitInputWidget(this._initialValue, {required this.onChanged}) { - selectedWeightUnit = _initialValue; + WeightUnitInputWidget(initialValue, {required this.onChanged}) { + selectedWeightUnit = initialValue; } @override @@ -43,7 +42,9 @@ class _WeightUnitInputWidgetState extends State { Widget build(BuildContext context) { final unitProvider = context.read(); - WeightUnit selectedWeightUnit = unitProvider.findWeightUnitById(widget._initialValue); + WeightUnit? selectedWeightUnit = widget.selectedWeightUnit != null + ? unitProvider.findWeightUnitById(widget.selectedWeightUnit!) + : null; return DropdownButtonFormField( value: selectedWeightUnit, diff --git a/test/workout/repetition_unit_form_widget_test.dart b/test/workout/repetition_unit_form_widget_test.dart index b1984c3f..458cf743 100644 --- a/test/workout/repetition_unit_form_widget_test.dart +++ b/test/workout/repetition_unit_form_widget_test.dart @@ -37,7 +37,7 @@ void main() { const unit2 = RepetitionUnit(id: 2, name: 'another name'); const unit3 = RepetitionUnit(id: 3, name: 'this is repetition number 3'); - var result = -1; + int? result; final slotEntry = SlotEntry( slotId: 1, @@ -54,7 +54,7 @@ void main() { setUp(() { mockWorkoutPlans = MockRoutinesProvider(); - result = -1; + result = null; when(mockWorkoutPlans.repetitionUnits).thenAnswer((_) => [unit1, unit2, unit3]); when(mockWorkoutPlans.findRepetitionUnitById(1)).thenReturn(unit1); when(mockWorkoutPlans.findRepetitionUnitById(2)).thenReturn(unit2); diff --git a/test/workout/weight_unit_form_widget_test.dart b/test/workout/weight_unit_form_widget_test.dart index 082954bc..f93f42b4 100644 --- a/test/workout/weight_unit_form_widget_test.dart +++ b/test/workout/weight_unit_form_widget_test.dart @@ -33,7 +33,7 @@ import 'weight_unit_form_widget_test.mocks.dart'; @GenerateMocks([RoutinesProvider]) void main() { var mockWorkoutPlans = MockRoutinesProvider(); - var result = -1; + int? result; const unit1 = WeightUnit(id: 1, name: 'kg'); const unit2 = WeightUnit(id: 2, name: 'donkeys'); @@ -53,7 +53,7 @@ void main() { slotEntry.weightUnitObj = unit1; setUp(() { - result = -1; + result = null; mockWorkoutPlans = MockRoutinesProvider(); when(mockWorkoutPlans.weightUnits).thenAnswer((_) => [unit1, unit2, unit3]); when(mockWorkoutPlans.findWeightUnitById(1)).thenReturn(unit1); From f25e9867a2e5b4d3d169abc571ec40fb7245ad19 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Fri, 21 Feb 2025 22:05:29 +0100 Subject: [PATCH 8/8] Cleanup --- lib/providers/routines.dart | 1 - lib/screens/routine_edit_screen.dart | 4 ++-- lib/widgets/routines/forms/day.dart | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/providers/routines.dart b/lib/providers/routines.dart index 4dd5d044..8fb062b4 100644 --- a/lib/providers/routines.dart +++ b/lib/providers/routines.dart @@ -45,7 +45,6 @@ class RoutinesProvider with ChangeNotifier { static const _routinesLogsSubpath = 'logs'; static const _routinesDateSequenceDisplaySubpath = 'date-sequence-display'; static const _routinesDateSequenceGymSubpath = 'date-sequence-gym'; - static const _routinesCurrentIterationDisplaySubpath = 'current-iteration-display'; static const _daysUrlPath = 'day'; static const _slotsUrlPath = 'slot'; static const _slotEntriesUrlPath = 'slot-entry'; diff --git a/lib/screens/routine_edit_screen.dart b/lib/screens/routine_edit_screen.dart index b6c995e4..29f52622 100644 --- a/lib/screens/routine_edit_screen.dart +++ b/lib/screens/routine_edit_screen.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:wger/providers/routines.dart'; -import 'package:wger/widgets/routines/app_bar.dart'; +import 'package:wger/widgets/core/app_bar.dart'; import 'package:wger/widgets/routines/routine_edit.dart'; class RoutineEditScreen extends StatelessWidget { @@ -34,7 +34,7 @@ class RoutineEditScreen extends StatelessWidget { final routine = Provider.of(context).findById(routineId); return Scaffold( - appBar: RoutineDetailAppBar(routine), + appBar: EmptyAppBar(routine.name), body: RoutineEdit(routine), ); } diff --git a/lib/widgets/routines/forms/day.dart b/lib/widgets/routines/forms/day.dart index 07566696..46fac94d 100644 --- a/lib/widgets/routines/forms/day.dart +++ b/lib/widgets/routines/forms/day.dart @@ -132,7 +132,7 @@ class _ReorderableDaysListState extends State { ), onTap: () async { final day = Day.empty(); - day.name = i18n.newDay; + day.name = '${i18n.newDay} ${widget.days.length + 1}'; day.routineId = widget.routineId; final newDay = await provider.addDay(day);