mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-19 07:50:52 +01:00
Add tests
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* 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.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* 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.
|
||||
@@ -29,7 +29,7 @@ class RepetitionUnitInputWidget extends StatefulWidget {
|
||||
late int? selectedRepetitionUnit;
|
||||
final ValueChanged<int?> onChanged;
|
||||
|
||||
RepetitionUnitInputWidget(initialValue, {required this.onChanged}) {
|
||||
RepetitionUnitInputWidget(int? initialValue, {super.key, required this.onChanged}) {
|
||||
selectedRepetitionUnit = initialValue;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class _RepetitionUnitInputWidgetState extends State<RepetitionUnitInputWidget> {
|
||||
: null;
|
||||
|
||||
return DropdownButtonFormField(
|
||||
value: selectedWeightUnit,
|
||||
initialValue: selectedWeightUnit,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).repetitionUnit,
|
||||
),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* 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.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* 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.
|
||||
@@ -30,7 +30,7 @@ class RiRInputWidget extends StatefulWidget {
|
||||
|
||||
static const SLIDER_START = -0.5;
|
||||
|
||||
RiRInputWidget(this._initialValue, {required this.onChanged}) {
|
||||
RiRInputWidget(this._initialValue, {super.key, required this.onChanged}) {
|
||||
_logger.finer('Initializing with initial value: $_initialValue');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
* 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.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* 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.
|
||||
@@ -29,7 +29,7 @@ class WeightUnitInputWidget extends StatefulWidget {
|
||||
late int? selectedWeightUnit;
|
||||
final ValueChanged<int?> onChanged;
|
||||
|
||||
WeightUnitInputWidget(int? initialValue, {required this.onChanged}) {
|
||||
WeightUnitInputWidget(int? initialValue, {super.key, required this.onChanged}) {
|
||||
selectedWeightUnit = initialValue;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class _WeightUnitInputWidgetState extends State<WeightUnitInputWidget> {
|
||||
: null;
|
||||
|
||||
return DropdownButtonFormField(
|
||||
value: selectedWeightUnit,
|
||||
initialValue: selectedWeightUnit,
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).weightUnit),
|
||||
onChanged: (WeightUnit? newValue) {
|
||||
setState(() {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2025 wger Team
|
||||
* 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.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* 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.
|
||||
@@ -661,6 +661,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
children: [
|
||||
Flexible(
|
||||
child: LogsRepsWidget(
|
||||
key: const ValueKey('logs-reps-widget'),
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
@@ -673,6 +674,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: LogsWeightWidget(
|
||||
key: const ValueKey('logs-weight-widget'),
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
@@ -690,6 +692,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
children: [
|
||||
Flexible(
|
||||
child: LogsRepsWidget(
|
||||
key: const ValueKey('logs-reps-widget'),
|
||||
controller: _repetitionsController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
@@ -702,6 +705,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: RepetitionUnitInputWidget(
|
||||
key: const ValueKey('repetition-unit-input-widget'),
|
||||
_log.repetitionsUnitId,
|
||||
onChanged: (v) => {},
|
||||
),
|
||||
@@ -715,6 +719,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
children: [
|
||||
Flexible(
|
||||
child: LogsWeightWidget(
|
||||
key: const ValueKey('logs-weight-widget'),
|
||||
controller: _weightController,
|
||||
configData: widget.configData,
|
||||
focusNode: widget.focusNode,
|
||||
@@ -726,13 +731,18 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: WeightUnitInputWidget(_log.weightUnitId, onChanged: (v) => {}),
|
||||
child: WeightUnitInputWidget(
|
||||
_log.weightUnitId,
|
||||
onChanged: (v) => {},
|
||||
key: const ValueKey('weight-unit-input-widget'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
if (_detailed)
|
||||
RiRInputWidget(
|
||||
key: const ValueKey('rir-input-widget'),
|
||||
_log.rir,
|
||||
onChanged: (value) {
|
||||
if (value == '') {
|
||||
@@ -743,6 +753,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
},
|
||||
),
|
||||
SwitchListTile(
|
||||
key: const ValueKey('units-switch'),
|
||||
dense: true,
|
||||
title: Text(i18n.setUnitsAndRir),
|
||||
value: _detailed,
|
||||
@@ -753,6 +764,7 @@ class _LogFormWidgetState extends ConsumerState<LogFormWidget> {
|
||||
},
|
||||
),
|
||||
FilledButton(
|
||||
key: const ValueKey('save-log-button'),
|
||||
onPressed: _isSaving
|
||||
? null
|
||||
: () async {
|
||||
|
||||
@@ -20,12 +20,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
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/models/exercises/exercise.dart';
|
||||
import 'package:wger/models/workouts/day_data.dart';
|
||||
import 'package:wger/models/workouts/log.dart';
|
||||
import 'package:wger/models/workouts/set_config_data.dart';
|
||||
import 'package:wger/models/workouts/slot_data.dart';
|
||||
import 'package:wger/providers/exercises.dart';
|
||||
@@ -41,7 +43,7 @@ import 'log_page_test.mocks.dart';
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('LogPage smoke tests', () {
|
||||
group('LogPage tests', () {
|
||||
late List<Exercise> testExercises;
|
||||
late ProviderContainer container;
|
||||
|
||||
@@ -51,18 +53,29 @@ void main() {
|
||||
container = ProviderContainer.test();
|
||||
});
|
||||
|
||||
Future<void> pumpLogPage(WidgetTester tester) async {
|
||||
Future<void> pumpLogPage(WidgetTester tester, {RoutinesProvider? routinesProvider}) async {
|
||||
final providerValue = routinesProvider ?? MockRoutinesProvider();
|
||||
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: provider.ChangeNotifierProvider<RoutinesProvider>.value(
|
||||
value: MockRoutinesProvider(),
|
||||
value: providerValue,
|
||||
child: MaterialApp(
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: LogPage(PageController()),
|
||||
// Provide a PageView so the PageController used by LogPage is attached
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
final controller = PageController();
|
||||
return PageView(
|
||||
controller: controller,
|
||||
children: [LogPage(controller)],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -135,5 +148,91 @@ void main() {
|
||||
|
||||
expect(find.byType(LogPage), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('copy from past log updates form fields and shows SnackBar', (tester) async {
|
||||
// Arrange
|
||||
final notifier = container.read(gymStateProvider.notifier);
|
||||
final routine = testdata.getTestRoutine();
|
||||
notifier.state = notifier.state.copyWith(
|
||||
dayId: routine.days.first.id,
|
||||
routine: routine,
|
||||
iteration: 1,
|
||||
);
|
||||
notifier.calculatePages();
|
||||
|
||||
// Act
|
||||
// Log page is at index 2
|
||||
notifier.state = notifier.state.copyWith(currentPage: 2);
|
||||
expect(notifier.state.getSlotEntryPageByIndex()!.type, SlotPageType.log);
|
||||
await pumpLogPage(tester);
|
||||
|
||||
// Assert
|
||||
final pastLogTile = find.byKey(const ValueKey('past-log-1'));
|
||||
expect(pastLogTile, findsOneWidget);
|
||||
await tester.tap(pastLogTile);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editableFields = find.byType(EditableText);
|
||||
expect(editableFields, findsWidgets);
|
||||
|
||||
// Get controller texts
|
||||
final repControllerText = tester.widget<EditableText>(editableFields.at(0)).controller.text;
|
||||
final weightControllerText = tester
|
||||
.widget<EditableText>(editableFields.at(1))
|
||||
.controller
|
||||
.text;
|
||||
|
||||
expect(repControllerText, contains('10'));
|
||||
expect(weightControllerText, contains('10'));
|
||||
expect(find.byType(SnackBar), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('save button calls addLog on RoutinesProvider', (tester) async {
|
||||
// Arrange
|
||||
final notifier = container.read(gymStateProvider.notifier);
|
||||
final routine = testdata.getTestRoutine();
|
||||
notifier.state = notifier.state.copyWith(
|
||||
dayId: routine.days.first.id,
|
||||
routine: routine,
|
||||
iteration: 1,
|
||||
);
|
||||
notifier.calculatePages();
|
||||
notifier.state = notifier.state.copyWith(currentPage: 2);
|
||||
final mockRoutines = MockRoutinesProvider();
|
||||
|
||||
// Act
|
||||
await pumpLogPage(tester, routinesProvider: mockRoutines);
|
||||
|
||||
final editableFields = find.byType(EditableText);
|
||||
expect(editableFields, findsWidgets);
|
||||
|
||||
await tester.enterText(editableFields.at(0), '7');
|
||||
await tester.enterText(editableFields.at(1), '77');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
Log? capturedLog;
|
||||
when(mockRoutines.addLog(any)).thenAnswer((invocation) async {
|
||||
capturedLog = invocation.positionalArguments[0] as Log;
|
||||
capturedLog!.id = 42;
|
||||
return capturedLog!;
|
||||
});
|
||||
|
||||
final saveButton = find.byKey(const ValueKey('save-log-button'));
|
||||
expect(saveButton, findsOneWidget);
|
||||
|
||||
await tester.tap(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
verify(mockRoutines.addLog(any)).called(1);
|
||||
expect(capturedLog, isNotNull);
|
||||
expect(capturedLog!.repetitions, equals(7));
|
||||
expect(capturedLog!.weight, equals(77));
|
||||
|
||||
final currentSlotPage = notifier.state.getSlotEntryPageByIndex()!;
|
||||
expect(capturedLog!.slotEntryId, equals(currentSlotPage.setConfigData!.slotEntryId));
|
||||
expect(capturedLog!.routineId, equals(notifier.state.routine.id));
|
||||
expect(capturedLog!.iteration, equals(notifier.state.iteration));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user