diff --git a/lib/helpers/consts.dart b/lib/helpers/consts.dart
index 7d538e02..e7136ca0 100644
--- a/lib/helpers/consts.dart
+++ b/lib/helpers/consts.dart
@@ -137,3 +137,7 @@ enum WeightUnitEnum { kg, lb }
const textInputTypeDecimal = TextInputType.numberWithOptions(decimal: true);
const String API_MAX_PAGE_SIZE = '999';
+
+/// Marker used for identifying interpolated values in a list, e.g. for measurements
+/// the milliseconds in the entry date are set to this value
+const INTERPOLATION_MARKER = 123;
diff --git a/lib/helpers/measurements.dart b/lib/helpers/measurements.dart
index 9f5d0050..f3cd5462 100644
--- a/lib/helpers/measurements.dart
+++ b/lib/helpers/measurements.dart
@@ -16,6 +16,7 @@
* along with this program. If not, see .
*/
+import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/date.dart';
import 'package:wger/widgets/measurements/charts.dart';
@@ -95,8 +96,15 @@ MeasurementChartEntry interpolateBetween(
// Create a special DateTime with milliseconds ending in 123 to mark it as interpolated
// which we leverage in the UI
- final markedDate =
- DateTime(date.year, date.month, date.day, date.hour, date.minute, date.second, 123);
+ final markedDate = DateTime(
+ date.year,
+ date.month,
+ date.day,
+ date.hour,
+ date.minute,
+ date.second,
+ INTERPOLATION_MARKER,
+ );
return MeasurementChartEntry(
before.value + (after.value - before.value) * (startDuration / totalDuration),
diff --git a/lib/widgets/measurements/charts.dart b/lib/widgets/measurements/charts.dart
index 4c09f139..076fa493 100644
--- a/lib/widgets/measurements/charts.dart
+++ b/lib/widgets/measurements/charts.dart
@@ -88,7 +88,7 @@ class _MeasurementChartWidgetFlState extends State {
DateFormat.Md(Localizations.localeOf(context).languageCode).format(date);
// Check if this is an interpolated point (milliseconds ending with 123)
- final bool isInterpolated = msSinceEpoch % 1000 == 123;
+ final bool isInterpolated = msSinceEpoch % 1000 == INTERPOLATION_MARKER;
final String interpolatedMarker = isInterpolated ? ' (interpolated)' : '';
return LineTooltipItem(
diff --git a/test/helpers/measurements_test.dart b/test/helpers/measurements_test.dart
new file mode 100644
index 00000000..ddf0886a
--- /dev/null
+++ b/test/helpers/measurements_test.dart
@@ -0,0 +1,90 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:wger/helpers/consts.dart';
+import 'package:wger/helpers/measurements.dart';
+import 'package:wger/widgets/measurements/charts.dart';
+
+void main() {
+ group('whereDateWithInterpolation', () {
+ // Helper to create entries
+ MeasurementChartEntry entry(num value, DateTime date) => MeasurementChartEntry(value, date);
+
+ // Test: No interpolation needed, exact start and end dates exist
+ test('returns entries within range when start and end exist', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 1)),
+ entry(20, DateTime(2023, 1, 2)),
+ entry(30, DateTime(2023, 1, 3)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 1, 1), DateTime(2023, 1, 3));
+
+ // Entries on start and end date should be included if they exist
+ expect(result.first.value, 10);
+ expect(result.last.value, 20);
+ });
+
+ // Test: Interpolates start if missing
+ test('interpolates start if missing', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 1)),
+ entry(30, DateTime(2023, 1, 3)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 1, 2), DateTime(2023, 1, 3));
+
+ // Only the interpolated value for 2nd Jan is included
+ expect(result.length, 1);
+ expect(result.first.value, closeTo(20, 0.0001));
+ expect(result.first.date.millisecond, INTERPOLATION_MARKER);
+ expect(result.first.date.day, 2);
+ });
+
+ // Test: Interpolates end if missing
+ test('interpolates end if missing', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 1)),
+ entry(30, DateTime(2023, 1, 3)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 1, 1), DateTime(2023, 1, 2));
+ // Should include the entry for 1st Jan and an interpolated value for 2nd Jan
+ expect(result.length, 2);
+ expect(result.first.value, 10);
+ expect(result.first.date.day, 1);
+ expect(result.last.value, closeTo(20, 0.0001));
+ expect(result.last.date.day, 2);
+ });
+
+ // Test: No interpolation if out of bounds
+ test('returns empty if no data in range', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 1)),
+ entry(20, DateTime(2023, 1, 2)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 2, 1), DateTime(2023, 2, 2));
+ expect(result, isEmpty);
+ });
+
+ // Test: Only start interpolation if data exists before and after
+ test('does not interpolate if no data before start', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 2)),
+ entry(20, DateTime(2023, 1, 3)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 1, 1), DateTime(2023, 1, 3));
+ // No interpolation possible for Jan 1, only entry for Jan 2 is included
+ expect(result.length, 1);
+ expect(result.first.date.day, 2);
+ });
+
+ // Test: Only end interpolation if data exists before and after
+ test('does not interpolate if no data after end', () {
+ final entries = [
+ entry(10, DateTime(2023, 1, 1)),
+ entry(20, DateTime(2023, 1, 2)),
+ ];
+ final result = entries.whereDateWithInterpolation(DateTime(2023, 1, 1), DateTime(2023, 1, 3));
+ // No interpolation possible for Jan 3, only entries for Jan 1 and Jan 2 are included
+ expect(result.length, 2);
+ expect(result.first.date.day, 1);
+ expect(result.last.date.day, 2);
+ });
+ });
+}