From d45fb604a2d40efb0141bd7bbc2e7a528988f799 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 2 Dec 2025 23:00:43 +0100 Subject: [PATCH] Add test for the gym mode options --- lib/widgets/routines/gym_mode/start_page.dart | 12 +- .../gym_mode/gym_mode_options_test.dart | 171 ++++++++++++++++++ 2 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 test/widgets/routines/gym_mode/gym_mode_options_test.dart diff --git a/lib/widgets/routines/gym_mode/start_page.dart b/lib/widgets/routines/gym_mode/start_page.dart index 62f5e545..96383ef3 100644 --- a/lib/widgets/routines/gym_mode/start_page.dart +++ b/lib/widgets/routines/gym_mode/start_page.dart @@ -86,10 +86,11 @@ class _GymModeOptionsState extends ConsumerState { onChanged: (value) => gymNotifier.setShowTimerPages(value), ), ListTile( + key: const ValueKey('gym-mode-timer-type'), enabled: gymState.showTimerPages, title: Text(i18n.gymModeTimerType), trailing: DropdownButton( - key: const ValueKey('themeModeDropdown'), + key: const ValueKey('countdown-type-dropdown'), value: gymState.useCountdownBetweenSets, onChanged: gymState.showTimerPages ? (bool? newValue) { @@ -107,6 +108,7 @@ class _GymModeOptionsState extends ConsumerState { subtitle: Text(i18n.gymModeTimerTypeHelText), ), ListTile( + key: const ValueKey('gym-mode-default-countdown-time'), enabled: gymState.showTimerPages, title: TextFormField( controller: _countdownController, @@ -145,14 +147,6 @@ class _GymModeOptionsState extends ConsumerState { }, enabled: gymState.showTimerPages && gymState.useCountdownBetweenSets, ), - // trailing: IconButton( - // onPressed: gymState.showTimerPages && gymState.useCountdownBetweenSets - // ? () => gymNotifier.setDefaultCountdownDuration( - // DEFAULT_COUNTDOWN_DURATION, - // ) - // : null, - // icon: const Icon(Icons.refresh), - // ), ), SwitchListTile( key: const ValueKey('gym-mode-notify-countdown'), diff --git a/test/widgets/routines/gym_mode/gym_mode_options_test.dart b/test/widgets/routines/gym_mode/gym_mode_options_test.dart new file mode 100644 index 00000000..7a61cd13 --- /dev/null +++ b/test/widgets/routines/gym_mode/gym_mode_options_test.dart @@ -0,0 +1,171 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2020, 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:wger/l10n/generated/app_localizations.dart'; +import 'package:wger/providers/gym_state.dart'; +import 'package:wger/widgets/routines/gym_mode/start_page.dart'; + +import '../../../../test_data/routines.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late ProviderContainer container; + + setUp(() { + // Use in-memory shared preferences to avoid platform channels during tests + SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); + + container = ProviderContainer( + overrides: [ + gymStateProvider.overrideWith(() => GymStateNotifier()), + ], + ); + + final routine = getTestRoutine(); + final notifier = container.read(gymStateProvider.notifier); + notifier.state = GymModeState( + showExercisePages: true, + showTimerPages: true, + useCountdownBetweenSets: true, + defaultCountdownDuration: const Duration(seconds: DEFAULT_COUNTDOWN_DURATION), + alertOnCountdownEnd: false, + dayId: routine.days.first.id, + iteration: 1, + routine: routine, + ); + + notifier.calculatePages(); + }); + + tearDown(() { + container.dispose(); + }); + + Future pumpGymModeOptions(WidgetTester tester) async { + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: const MaterialApp( + locale: Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: GymModeOptions(), + ), + ), + ), + ); + await tester.pumpAndSettle(); + } + + testWidgets('Switches update the notifier state', (tester) async { + await pumpGymModeOptions(tester); + + // Open options (tap the ListTile to toggle _showOptions) + final optionsTile = find.byKey(const ValueKey('gym-mode-options-tile')); + expect(optionsTile, findsOneWidget); + await tester.tap(optionsTile); + await tester.pumpAndSettle(); + + // Toggle notify countdown first (it is only enabled while timer/countdown are active) + final notifySwitch = find.byKey(const ValueKey('gym-mode-notify-countdown')); + expect(notifySwitch, findsOneWidget); + await tester.tap(notifySwitch); + await tester.pumpAndSettle(); + + // Now toggle show exercises + final showExercisesSwitch = find.byKey(const ValueKey('gym-mode-option-show-exercises')); + expect(showExercisesSwitch, findsOneWidget); + await tester.tap(showExercisesSwitch); + await tester.pumpAndSettle(); + + // Toggle show timer (this will disable notify switch) + final showTimerSwitch = find.byKey(const ValueKey('gym-mode-option-show-timer')); + expect(showTimerSwitch, findsOneWidget); + await tester.tap(showTimerSwitch); + await tester.pumpAndSettle(); + + final notifier = container.read(gymStateProvider.notifier); + expect(notifier.state.showExercisePages, isFalse); + expect(notifier.state.showTimerPages, isFalse); + expect(notifier.state.alertOnCountdownEnd, isTrue); + }); + + testWidgets('Dropdown, text field and refresh button update notifier state', (tester) async { + await pumpGymModeOptions(tester); + + // Open options + final optionsTile = find.byKey(const ValueKey('gym-mode-options-tile')); + await tester.tap(optionsTile); + await tester.pumpAndSettle(); + + final notifier = container.read(gymStateProvider.notifier); + + // Change dropdown (countdown type) -> switch to stopwatch (false) + final dropdown = find.byKey(const ValueKey('countdown-type-dropdown')); + expect(dropdown, findsOneWidget); + + await tester.tap(dropdown); + await tester.pumpAndSettle(); + + // Select the visible menu entry by its text label (English: 'Stopwatch') to avoid hit-test issues + final stopwatchText = find.text('Stopwatch'); + expect(stopwatchText, findsWidgets); + await tester.tap(stopwatchText.first); + await tester.pumpAndSettle(); + + expect(notifier.state.useCountdownBetweenSets, isFalse); + + // switch back to countdown (true) + await tester.tap(dropdown); + await tester.pumpAndSettle(); + final countdownText = find.text('Countdown'); + expect(countdownText, findsWidgets); + await tester.tap(countdownText.first); + await tester.pumpAndSettle(); + + expect(notifier.state.useCountdownBetweenSets, isTrue); + + // Enter a new countdown duration in the TextFormField + final countdownField = find.byKey(const ValueKey('gym-mode-default-countdown-time')); + expect(countdownField, findsOneWidget); + + // The TextFormField is a descendant; find the editable TextField + final textField = find.descendant(of: countdownField, matching: find.byType(TextFormField)); + expect(textField, findsOneWidget); + + await tester.enterText(textField, '60'); + await tester.pumpAndSettle(); + + expect(notifier.state.defaultCountdownDuration.inSeconds, 60); + + // Tap refresh button (suffix icon). Find IconButton inside the input decoration + final refreshIcon = find.descendant(of: countdownField, matching: find.byIcon(Icons.refresh)); + expect(refreshIcon, findsOneWidget); + await tester.tap(refreshIcon); + await tester.pumpAndSettle(); + + expect(notifier.state.defaultCountdownDuration.inSeconds, DEFAULT_COUNTDOWN_DURATION); + }); +}