Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ void main() {
when(mockRepo.updateLocalDrift(any)).thenAnswer((_) async {});
when(mockRepo.addLocalDrift(any)).thenAnswer((_) async {});

when(mockRepo.addLocalDriftGroupEntries(any)).thenAnswer((_) async {});

when(mockRepo.deleteLocalDriftCategory(any)).thenAnswer((_) async {});
when(mockRepo.updateLocalDriftCategory(any)).thenAnswer((_) async {});
when(mockRepo.addLocalDriftCategory(any)).thenAnswer((_) async {});
Expand Down Expand Up @@ -103,6 +105,23 @@ void main() {
});
});

group('addGroupEntries', () {
test('addGroupEntries calls repository', () async {
final notifier = container.read(measurementProvider.notifier);
final entries = [testNeasurementEntry9, testNeasurementEntry10];

await notifier.addGroupEntries(entries);
verify(mockRepo.addLocalDriftGroupEntries(entries)).called(1);
});

test('addGroupEntries forwards empty list to repository', () async {
final notifier = container.read(measurementProvider.notifier);

await notifier.addGroupEntries([]);
verify(mockRepo.addLocalDriftGroupEntries([])).called(1);
});
});

group('setCategoryOrder', () {
// Three top-level categories: Body fat ('1'), Biceps ('2') and the blood
// pressure group parent ('bp'), whose children must stay untouched.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ void main() {
final mockRepo = MockMeasurementRepository();
when(
mockRepo.watchAll(),
).thenAnswer((_) => Stream<List<MeasurementCategory>>.value(getMeasurementCategories()));
).thenAnswer(
(_) => Stream<List<MeasurementCategory>>.value([
...getMeasurementCategories(),
...getBloodPressureGroup(),
]),
);

return ProviderScope(
overrides: [
Expand All @@ -52,13 +57,19 @@ void main() {
}

testWidgets('Test the widgets on the measurement category screen', (WidgetTester tester) async {
tester.view.physicalSize = const Size(800, 2200);
tester.view.devicePixelRatio = 1.0;

await tester.pumpWidget(createMeasurementScreen());
await tester.pumpAndSettle();

expect(find.text('Measurements'), findsOneWidget);
expect(find.text('Body fat'), findsOneWidget);
expect(find.text('Biceps'), findsOneWidget);
expect(find.byType(Card), findsNWidgets(2));
expect(find.byType(MeasurementChartWidgetFl), findsNWidgets(2));
expect(find.text('Blood pressure'), findsOneWidget);
expect(find.text('Systolic'), findsOneWidget);
expect(find.text('Diastolic'), findsOneWidget);
expect(find.byType(ListTile), findsNWidgets(2)); // Systolic , Diastolic
expect(find.byType(MeasurementChartWidgetFl), findsNWidgets(2)); // Body fat, Biceps
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2026 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 <http://www.gnu.org/licenses/>.
*/

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:wger/features/measurements/models/measurement_category.dart';
import 'package:wger/features/measurements/widgets/charts.dart';
import 'package:wger/features/measurements/widgets/helpers.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

Widget _wrapChart(Widget chart) => MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: SizedBox(width: 400, height: 300, child: chart),
),
);

void main() {
final entries = [
MeasurementChartEntry(1000, DateTime(2026, 1, 1)),
MeasurementChartEntry(2000, DateTime(2026, 1, 2)),
];

group('buildChartForMetricType routing', () {
testWidgets('steps -> MeasurementBarChartWidgetFl', (tester) async {
final widget = buildChartForMetricType(MetricType.steps, entries, [], 'steps');
await tester.pumpWidget(_wrapChart(widget));
await tester.pumpAndSettle();

expect(find.byType(MeasurementBarChartWidgetFl), findsOneWidget);
expect(find.byType(MeasurementChartWidgetFl), findsNothing);
});

testWidgets('custom -> MeasurementChartWidgetFl', (tester) async {
final widget = buildChartForMetricType(MetricType.custom, entries, [], 'cm');
await tester.pumpWidget(_wrapChart(widget));
await tester.pumpAndSettle();

expect(find.byType(MeasurementChartWidgetFl), findsOneWidget);
expect(find.byType(MeasurementBarChartWidgetFl), findsNothing);
});

testWidgets('energy -> MeasurementBarChartWidgetFl', (tester) async {
final widget = buildChartForMetricType(MetricType.energy, entries, [], 'kcal');
await tester.pumpWidget(_wrapChart(widget));
await tester.pumpAndSettle();

expect(find.byType(MeasurementBarChartWidgetFl), findsOneWidget);
});
});
}
72 changes: 72 additions & 0 deletions test/features/measurements/widgets/categories_card_group_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2026 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 <http://www.gnu.org/licenses/>.
*/

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:wger/features/measurements/models/measurement_category.dart';
import 'package:wger/features/measurements/models/measurement_entry.dart';
import 'package:wger/features/measurements/widgets/categories_card.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

import '../../../../test_data/measurements.dart';

Widget _wrap(Widget child) => MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
routes: {
'/form': (ctx) => const Scaffold(body: Text('form screen')),
},
home: Scaffold(body: child),
);

MeasurementCategory _bpGroup({bool withEntries = false}) {
final sysEntries = withEntries ? [testNeasurementEntry9] : <MeasurementEntry>[];
final diaEntries = withEntries ? [testNeasurementEntry10] : <MeasurementEntry>[];
final sys = testMeasurementCategorySystolic.copyWith(entries: sysEntries);
final dia = testMeasurementCategoryDiastolic.copyWith(entries: diaEntries);

return testMeasurementCategoryBloodPressure.copyWith(children: [sys, dia]);
}

void main() {
group('CategoriesCard — group card', () {
testWidgets('shows one ListTile per child with latest reading', (tester) async {
await tester.pumpWidget(_wrap(CategoriesCard(_bpGroup(withEntries: true))));
await tester.pumpAndSettle();

expect(find.byType(ListTile), findsNWidgets(2));
expect(find.textContaining('120'), findsOneWidget);
expect(find.textContaining('80'), findsOneWidget);
});

testWidgets('shows dash when child has no entries', (tester) async {
await tester.pumpWidget(_wrap(CategoriesCard(_bpGroup(withEntries: false))));
await tester.pumpAndSettle();

// Text('—') should appear for both children
expect(find.text('—'), findsNWidgets(2));
});

testWidgets('add icon button is present on group card', (tester) async {
await tester.pumpWidget(_wrap(CategoriesCard(_bpGroup())));
await tester.pumpAndSettle();

expect(find.byIcon(Icons.add), findsOneWidget);
});
});
}
65 changes: 65 additions & 0 deletions test/features/measurements/widgets/categories_list_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:wger/features/measurements/providers/measurement_repository.dart';
import 'package:wger/features/measurements/widgets/categories.dart';
import 'package:wger/features/measurements/widgets/categories_card.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import '../../../../test_data/measurements.dart';
import '../providers/measurement_notifier_test.mocks.dart';

Widget _wrap(MockMeasurementRepository mockRepo) {
return ProviderScope(
overrides: [
measurementRepositoryProvider.overrideWithValue(mockRepo),
],
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(body: CategoriesList()),
),
);
}

void main() {
late MockMeasurementRepository mockRepo;

setUp(() {
mockRepo = MockMeasurementRepository();
});

group('CategoriesList', () {
testWidgets('two top-level categories render two CategoriesCard widgets', (tester) async {
when(mockRepo.watchAll()).thenAnswer((_) => Stream.value(getMeasurementCategories()));

await tester.pumpWidget(_wrap(mockRepo));
await tester.pumpAndSettle();

expect(find.byType(CategoriesCard), findsNWidgets(2));
});

testWidgets(' children of multi-value groups are not rendered as own list items', (
tester,
) async {
// Only 'bp' should produce a CategoriesCard; children stay inside it.
when(mockRepo.watchAll()).thenAnswer((_) => Stream.value(getBloodPressureGroup()));

await tester.pumpWidget(_wrap(mockRepo));
await tester.pumpAndSettle();

expect(find.byType(CategoriesCard), findsOneWidget);
expect(find.text('Systolic'), findsOneWidget);
expect(find.text('Diastolic'), findsOneWidget);
});

testWidgets('empty list renders no CategoriesCard', (tester) async {
when(mockRepo.watchAll()).thenAnswer((_) => Stream.value([]));

await tester.pumpWidget(_wrap(mockRepo));
await tester.pumpAndSettle();

expect(find.byType(CategoriesCard), findsNothing);
});
});
}
39 changes: 36 additions & 3 deletions test/features/measurements/widgets/charts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,51 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:wger/features/measurements/widgets/charts.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

Widget _wrap(Widget child) => MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: SizedBox(width: 400, height: 300, child: child),
),
);

void main() {
MeasurementChartEntry entry(num value, DateTime date) => MeasurementChartEntry(value, date);

group('aggregatePerDay', () {
test('returns an empty list for no entries', () {
group('MeasurementBarChartWidgetFl', () {
testWidgets('renders without error for empty entries', (tester) async {
await tester.pumpWidget(
_wrap(const MeasurementBarChartWidgetFl([], 'steps')),
);
await tester.pumpAndSettle();
expect(find.byType(MeasurementBarChartWidgetFl), findsOneWidget);
expect(tester.takeException(), isNull);
});

// T-24
testWidgets('BarChart is present for non-empty entries', (tester) async {
final entries = [
entry(1000, DateTime(2026, 1, 1)),
entry(2000, DateTime(2026, 1, 2)),
entry(1500, DateTime(2026, 1, 3)),
];
await tester.pumpWidget(_wrap(MeasurementBarChartWidgetFl(entries, 'steps')));
await tester.pumpAndSettle();

expect(find.byType(BarChart), findsOneWidget);
});

test('aggregatePerDay returns an empty list for no entries', () {
expect(aggregatePerDay([]), isEmpty);
});

test('sums entries sharing a calendar day into one point', () {
test('aggregatePerDay sums entries sharing a calendar day into one point', () {
final result = aggregatePerDay([
entry(1000, DateTime(2026, 1, 1, 8)),
entry(2500, DateTime(2026, 1, 1, 20)),
Expand Down
Loading