Merge branch 'master' into feature/trophies

# Conflicts:
#	lib/main.dart
#	lib/screens/dashboard.dart
This commit is contained in:
Roland Geider
2026-01-17 13:50:18 +01:00
18 changed files with 1047 additions and 124 deletions

View File

@@ -0,0 +1,121 @@
/*
* 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:mockito/annotations.dart';
import 'package:provider/provider.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/base_provider.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/widgets/core/settings/dashboard_visibility.dart';
import 'settings_dashboard_visibility_test.mocks.dart';
@GenerateMocks([
UserProvider,
WgerBaseProvider,
])
void main() {
late UserProvider userProvider;
late MockWgerBaseProvider mockBaseProvider;
setUp(() {
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
mockBaseProvider = MockWgerBaseProvider();
userProvider = UserProvider(mockBaseProvider);
});
Widget createWidget() {
return ChangeNotifierProvider<UserProvider>.value(
value: userProvider,
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: SettingsDashboardVisibility(),
),
),
);
}
testWidgets('renders list of dashboard widgets', (tester) async {
await tester.pumpWidget(createWidget());
await tester.pumpAndSettle();
// Verify all items are present
expect(find.byType(ListTile), findsNWidgets(DashboardWidget.values.length));
expect(find.text('Routines'), findsOneWidget);
});
testWidgets('toggle visibility updates provider', (tester) async {
await tester.pumpWidget(createWidget());
await tester.pumpAndSettle();
// Routines should be visible initally (default is true)
expect(userProvider.isDashboardWidgetVisible(DashboardWidget.routines), true);
// Find the visibility icon for Routines
final routineTile = find.byKey(const ValueKey(DashboardWidget.routines));
final iconBtn = find.descendant(of: routineTile, matching: find.byType(IconButton));
// Check icon is 'visibility'
expect(find.descendant(of: iconBtn, matching: find.byIcon(Icons.visibility)), findsOneWidget);
// Tap to toggle
await tester.tap(iconBtn);
await tester.pump(); // re-render
// Check provider state
expect(userProvider.isDashboardWidgetVisible(DashboardWidget.routines), false);
// Check icon is 'visibility_off'
expect(
find.descendant(of: iconBtn, matching: find.byIcon(Icons.visibility_off)),
findsOneWidget,
);
});
// Reordering test is a bit flaky without full drag setup, but we can try
testWidgets('dragging reorders items', (tester) async {
await tester.pumpWidget(createWidget());
await tester.pumpAndSettle();
// Initial order: routines, nutrition, weight...
expect(userProvider.dashboardOrder[0], DashboardWidget.routines);
expect(userProvider.dashboardOrder[1], DashboardWidget.nutrition);
// Find drag handle for Routines (index 0)
final handleFinder = find.byIcon(Icons.drag_handle);
final firstHandle = handleFinder.at(0);
// final secondHandle = handleFinder.at(1);
// Drag first item down
await tester.drag(firstHandle, const Offset(0, 100)); // Drag down enough to swap
await tester.pumpAndSettle();
// Verify order changed
// If swapped with second item (nutrition) and maybe third (weight) depending on height
// Based on running test: index 0 is nutrition, index 1 is weight.
expect(userProvider.dashboardOrder[0], DashboardWidget.nutrition);
expect(userProvider.dashboardOrder[1], DashboardWidget.weight);
expect(userProvider.dashboardOrder[2], DashboardWidget.routines);
});
}

View File

@@ -0,0 +1,349 @@
// Mocks generated by Mockito 5.4.6 from annotations
// in wger/test/core/settings_dashboard_visibility_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i9;
import 'dart:ui' as _i10;
import 'package:flutter/material.dart' as _i7;
import 'package:http/http.dart' as _i5;
import 'package:mockito/mockito.dart' as _i1;
import 'package:shared_preferences/shared_preferences.dart' as _i3;
import 'package:wger/models/user/profile.dart' as _i8;
import 'package:wger/providers/auth.dart' as _i4;
import 'package:wger/providers/base_provider.dart' as _i2;
import 'package:wger/providers/user.dart' as _i6;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: must_be_immutable
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
// ignore_for_file: invalid_use_of_internal_member
class _FakeWgerBaseProvider_0 extends _i1.SmartFake implements _i2.WgerBaseProvider {
_FakeWgerBaseProvider_0(Object parent, Invocation parentInvocation)
: super(parent, parentInvocation);
}
class _FakeSharedPreferencesAsync_1 extends _i1.SmartFake implements _i3.SharedPreferencesAsync {
_FakeSharedPreferencesAsync_1(Object parent, Invocation parentInvocation)
: super(parent, parentInvocation);
}
class _FakeAuthProvider_2 extends _i1.SmartFake implements _i4.AuthProvider {
_FakeAuthProvider_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
class _FakeClient_3 extends _i1.SmartFake implements _i5.Client {
_FakeClient_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
class _FakeUri_4 extends _i1.SmartFake implements Uri {
_FakeUri_4(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
class _FakeResponse_5 extends _i1.SmartFake implements _i5.Response {
_FakeResponse_5(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
/// A class which mocks [UserProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockUserProvider extends _i1.Mock implements _i6.UserProvider {
MockUserProvider() {
_i1.throwOnMissingStub(this);
}
@override
_i7.ThemeMode get themeMode =>
(super.noSuchMethod(
Invocation.getter(#themeMode),
returnValue: _i7.ThemeMode.system,
)
as _i7.ThemeMode);
@override
_i2.WgerBaseProvider get baseProvider =>
(super.noSuchMethod(
Invocation.getter(#baseProvider),
returnValue: _FakeWgerBaseProvider_0(
this,
Invocation.getter(#baseProvider),
),
)
as _i2.WgerBaseProvider);
@override
_i3.SharedPreferencesAsync get prefs =>
(super.noSuchMethod(
Invocation.getter(#prefs),
returnValue: _FakeSharedPreferencesAsync_1(
this,
Invocation.getter(#prefs),
),
)
as _i3.SharedPreferencesAsync);
@override
List<_i6.DashboardWidget> get dashboardOrder =>
(super.noSuchMethod(
Invocation.getter(#dashboardOrder),
returnValue: <_i6.DashboardWidget>[],
)
as List<_i6.DashboardWidget>);
@override
set themeMode(_i7.ThemeMode? value) => super.noSuchMethod(
Invocation.setter(#themeMode, value),
returnValueForMissingStub: null,
);
@override
set prefs(_i3.SharedPreferencesAsync? value) => super.noSuchMethod(
Invocation.setter(#prefs, value),
returnValueForMissingStub: null,
);
@override
set profile(_i8.Profile? value) => super.noSuchMethod(
Invocation.setter(#profile, value),
returnValueForMissingStub: null,
);
@override
bool get hasListeners =>
(super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool);
@override
void clear() => super.noSuchMethod(
Invocation.method(#clear, []),
returnValueForMissingStub: null,
);
@override
bool isDashboardWidgetVisible(_i6.DashboardWidget? key) =>
(super.noSuchMethod(
Invocation.method(#isDashboardWidgetVisible, [key]),
returnValue: false,
)
as bool);
@override
_i9.Future<void> setDashboardWidgetVisible(
_i6.DashboardWidget? key,
bool? visible,
) =>
(super.noSuchMethod(
Invocation.method(#setDashboardWidgetVisible, [key, visible]),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
)
as _i9.Future<void>);
@override
_i9.Future<void> setDashboardOrder(int? oldIndex, int? newIndex) =>
(super.noSuchMethod(
Invocation.method(#setDashboardOrder, [oldIndex, newIndex]),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
)
as _i9.Future<void>);
@override
void setThemeMode(_i7.ThemeMode? mode) => super.noSuchMethod(
Invocation.method(#setThemeMode, [mode]),
returnValueForMissingStub: null,
);
@override
_i9.Future<void> fetchAndSetProfile() =>
(super.noSuchMethod(
Invocation.method(#fetchAndSetProfile, []),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
)
as _i9.Future<void>);
@override
_i9.Future<void> saveProfile() =>
(super.noSuchMethod(
Invocation.method(#saveProfile, []),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
)
as _i9.Future<void>);
@override
_i9.Future<void> verifyEmail() =>
(super.noSuchMethod(
Invocation.method(#verifyEmail, []),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
)
as _i9.Future<void>);
@override
void addListener(_i10.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(#addListener, [listener]),
returnValueForMissingStub: null,
);
@override
void removeListener(_i10.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,
);
}
/// A class which mocks [WgerBaseProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
MockWgerBaseProvider() {
_i1.throwOnMissingStub(this);
}
@override
_i4.AuthProvider get auth =>
(super.noSuchMethod(
Invocation.getter(#auth),
returnValue: _FakeAuthProvider_2(this, Invocation.getter(#auth)),
)
as _i4.AuthProvider);
@override
_i5.Client get client =>
(super.noSuchMethod(
Invocation.getter(#client),
returnValue: _FakeClient_3(this, Invocation.getter(#client)),
)
as _i5.Client);
@override
set auth(_i4.AuthProvider? value) => super.noSuchMethod(
Invocation.setter(#auth, value),
returnValueForMissingStub: null,
);
@override
set client(_i5.Client? value) => super.noSuchMethod(
Invocation.setter(#client, value),
returnValueForMissingStub: null,
);
@override
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
(super.noSuchMethod(
Invocation.method(#getDefaultHeaders, [], {
#includeAuth: includeAuth,
}),
returnValue: <String, String>{},
)
as Map<String, String>);
@override
Uri makeUrl(
String? path, {
int? id,
String? objectMethod,
Map<String, dynamic>? query,
}) =>
(super.noSuchMethod(
Invocation.method(
#makeUrl,
[path],
{#id: id, #objectMethod: objectMethod, #query: query},
),
returnValue: _FakeUri_4(
this,
Invocation.method(
#makeUrl,
[path],
{#id: id, #objectMethod: objectMethod, #query: query},
),
),
)
as Uri);
@override
_i9.Future<dynamic> fetch(
Uri? uri, {
int? maxRetries = 3,
Duration? initialDelay = const Duration(milliseconds: 250),
}) =>
(super.noSuchMethod(
Invocation.method(
#fetch,
[uri],
{#maxRetries: maxRetries, #initialDelay: initialDelay},
),
returnValue: _i9.Future<dynamic>.value(),
)
as _i9.Future<dynamic>);
@override
_i9.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
(super.noSuchMethod(
Invocation.method(#fetchPaginated, [uri]),
returnValue: _i9.Future<List<dynamic>>.value(<dynamic>[]),
)
as _i9.Future<List<dynamic>>);
@override
_i9.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
(super.noSuchMethod(
Invocation.method(#post, [data, uri]),
returnValue: _i9.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i9.Future<Map<String, dynamic>>);
@override
_i9.Future<Map<String, dynamic>> patch(
Map<String, dynamic>? data,
Uri? uri,
) =>
(super.noSuchMethod(
Invocation.method(#patch, [data, uri]),
returnValue: _i9.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i9.Future<Map<String, dynamic>>);
@override
_i9.Future<_i5.Response> deleteRequest(String? url, int? id) =>
(super.noSuchMethod(
Invocation.method(#deleteRequest, [url, id]),
returnValue: _i9.Future<_i5.Response>.value(
_FakeResponse_5(
this,
Invocation.method(#deleteRequest, [url, id]),
),
),
)
as _i9.Future<_i5.Response>);
}

View File

@@ -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) 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.
*
* 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.
@@ -49,6 +49,9 @@ void main() {
setUp(() {
when(mockUserProvider.themeMode).thenReturn(ThemeMode.system);
when(
mockSharedPreferences.getString(UserProvider.PREFS_DASHBOARD_CONFIG),
).thenAnswer((_) async => null);
when(mockExerciseProvider.exercises).thenReturn(getTestExercises());
when(mockNutritionProvider.ingredients).thenReturn([ingredient1, ingredient2]);
});
@@ -100,18 +103,23 @@ void main() {
group('Theme settings', () {
test('Default theme is system', () async {
when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => null);
final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences);
final userProvider = UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences);
await Future.delayed(const Duration(milliseconds: 50)); // wait for async prefs load
expect(userProvider.themeMode, ThemeMode.system);
});
test('Loads light theme', () async {
when(mockSharedPreferences.getBool(PREFS_USER_DARK_THEME)).thenAnswer((_) async => false);
final userProvider = await UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences);
final userProvider = UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences);
await Future.delayed(const Duration(milliseconds: 50)); // wait for async prefs load
expect(userProvider.themeMode, ThemeMode.light);
});
test('Saves theme to prefs', () {
when(mockSharedPreferences.getBool(any)).thenAnswer((_) async => null);
when(
mockSharedPreferences.getString('dashboardWidgetVisibility'),
).thenAnswer((_) async => null);
final userProvider = UserProvider(MockWgerBaseProvider(), prefs: mockSharedPreferences);
userProvider.setThemeMode(ThemeMode.dark);
verify(mockSharedPreferences.setBool(PREFS_USER_DARK_THEME, true)).called(1);

View File

@@ -943,6 +943,14 @@ class MockUserProvider extends _i1.Mock implements _i21.UserProvider {
)
as _i14.SharedPreferencesAsync);
@override
List<_i21.DashboardWidget> get dashboardOrder =>
(super.noSuchMethod(
Invocation.getter(#dashboardOrder),
returnValue: <_i21.DashboardWidget>[],
)
as List<_i21.DashboardWidget>);
@override
set themeMode(_i22.ThemeMode? value) => super.noSuchMethod(
Invocation.setter(#themeMode, value),
@@ -971,6 +979,35 @@ class MockUserProvider extends _i1.Mock implements _i21.UserProvider {
returnValueForMissingStub: null,
);
@override
bool isDashboardWidgetVisible(_i21.DashboardWidget? key) =>
(super.noSuchMethod(
Invocation.method(#isDashboardWidgetVisible, [key]),
returnValue: false,
)
as bool);
@override
_i18.Future<void> setDashboardWidgetVisible(
_i21.DashboardWidget? key,
bool? visible,
) =>
(super.noSuchMethod(
Invocation.method(#setDashboardWidgetVisible, [key, visible]),
returnValue: _i18.Future<void>.value(),
returnValueForMissingStub: _i18.Future<void>.value(),
)
as _i18.Future<void>);
@override
_i18.Future<void> setDashboardOrder(int? oldIndex, int? newIndex) =>
(super.noSuchMethod(
Invocation.method(#setDashboardOrder, [oldIndex, newIndex]),
returnValue: _i18.Future<void>.value(),
returnValueForMissingStub: _i18.Future<void>.value(),
)
as _i18.Future<void>);
@override
void setThemeMode(_i22.ThemeMode? mode) => super.noSuchMethod(
Invocation.method(#setThemeMode, [mode]),

View File

@@ -404,6 +404,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
)
as String);
@override
String get dashboardWidgets =>
(super.noSuchMethod(
Invocation.getter(#dashboardWidgets),
returnValue: _i3.dummyValue<String>(
this,
Invocation.getter(#dashboardWidgets),
),
)
as String);
@override
String get labelDashboard =>
(super.noSuchMethod(

View File

@@ -389,6 +389,14 @@ class MockUserProvider extends _i1.Mock implements _i17.UserProvider {
)
as _i4.SharedPreferencesAsync);
@override
List<_i17.DashboardWidget> get dashboardOrder =>
(super.noSuchMethod(
Invocation.getter(#dashboardOrder),
returnValue: <_i17.DashboardWidget>[],
)
as List<_i17.DashboardWidget>);
@override
set themeMode(_i18.ThemeMode? value) => super.noSuchMethod(
Invocation.setter(#themeMode, value),
@@ -417,6 +425,35 @@ class MockUserProvider extends _i1.Mock implements _i17.UserProvider {
returnValueForMissingStub: null,
);
@override
bool isDashboardWidgetVisible(_i17.DashboardWidget? key) =>
(super.noSuchMethod(
Invocation.method(#isDashboardWidgetVisible, [key]),
returnValue: false,
)
as bool);
@override
_i15.Future<void> setDashboardWidgetVisible(
_i17.DashboardWidget? key,
bool? visible,
) =>
(super.noSuchMethod(
Invocation.method(#setDashboardWidgetVisible, [key, visible]),
returnValue: _i15.Future<void>.value(),
returnValueForMissingStub: _i15.Future<void>.value(),
)
as _i15.Future<void>);
@override
_i15.Future<void> setDashboardOrder(int? oldIndex, int? newIndex) =>
(super.noSuchMethod(
Invocation.method(#setDashboardOrder, [oldIndex, newIndex]),
returnValue: _i15.Future<void>.value(),
returnValueForMissingStub: _i15.Future<void>.value(),
)
as _i15.Future<void>);
@override
void setThemeMode(_i18.ThemeMode? mode) => super.noSuchMethod(
Invocation.method(#setThemeMode, [mode]),

View File

@@ -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) 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.
*
* 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.
@@ -21,6 +21,7 @@ import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.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/providers/base_provider.dart';
@@ -51,7 +52,6 @@ void main() {
);
setUp(() {
/// Replacement for SharedPreferences.setMockInitialValues()
SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty();
mockWgerBaseProvider = MockWgerBaseProvider();
userProvider = UserProvider(mockWgerBaseProvider);
@@ -103,4 +103,80 @@ void main() {
verify(userProvider.baseProvider.fetch(tEmailVerifyUri));
});
});
group('dashboard config', () {
test('initial config should be default (all visible, default order)', () {
expect(userProvider.dashboardOrder.length, 5);
expect(
userProvider.dashboardOrder,
orderedEquals([
DashboardWidget.routines,
DashboardWidget.nutrition,
DashboardWidget.weight,
DashboardWidget.measurements,
DashboardWidget.calendar,
]),
);
expect(userProvider.isDashboardWidgetVisible(DashboardWidget.routines), true);
});
test('toggling visibility should update state', () async {
// act
await userProvider.setDashboardWidgetVisible(DashboardWidget.routines, false);
// assert
expect(userProvider.isDashboardWidgetVisible(DashboardWidget.routines), false);
// re-enable
await userProvider.setDashboardWidgetVisible(DashboardWidget.routines, true);
expect(userProvider.isDashboardWidgetVisible(DashboardWidget.routines), true);
});
test('reordering should update order', () async {
// arrange
final initialFirst = userProvider.dashboardOrder[0];
final initialSecond = userProvider.dashboardOrder[1];
// act: move first to second position
// oldIndex: 0, newIndex: 2 (because insert is before index)
await userProvider.setDashboardOrder(0, 2);
// assert
expect(userProvider.dashboardOrder[0], initialSecond);
expect(userProvider.dashboardOrder[1], initialFirst);
});
test('should load config from prefs', () async {
// arrange
final prefs = SharedPreferencesAsync();
final customConfig = [
{'widget': 'nutrition', 'visible': true},
{'widget': 'routines', 'visible': false},
];
await prefs.setString(
UserProvider.PREFS_DASHBOARD_CONFIG,
jsonEncode(customConfig),
);
// act
final newProvider = UserProvider(mockWgerBaseProvider, prefs: prefs);
await Future.delayed(const Duration(milliseconds: 50)); // wait for async prefs load
// assert
// The loaded ones come first
expect(newProvider.dashboardOrder[0], DashboardWidget.nutrition);
expect(newProvider.dashboardOrder[1], DashboardWidget.routines);
// Check visibility
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.nutrition), true);
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.routines), false);
// Remaining items are added after
expect(newProvider.dashboardOrder.length, 5);
// Items not in the prefs are visible by default
expect(newProvider.isDashboardWidgetVisible(DashboardWidget.weight), true);
});
});
}

View File

@@ -231,6 +231,14 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
)
as _i4.SharedPreferencesAsync);
@override
List<_i13.DashboardWidget> get dashboardOrder =>
(super.noSuchMethod(
Invocation.getter(#dashboardOrder),
returnValue: <_i13.DashboardWidget>[],
)
as List<_i13.DashboardWidget>);
@override
set themeMode(_i14.ThemeMode? value) => super.noSuchMethod(
Invocation.setter(#themeMode, value),
@@ -259,6 +267,35 @@ class MockUserProvider extends _i1.Mock implements _i13.UserProvider {
returnValueForMissingStub: null,
);
@override
bool isDashboardWidgetVisible(_i13.DashboardWidget? key) =>
(super.noSuchMethod(
Invocation.method(#isDashboardWidgetVisible, [key]),
returnValue: false,
)
as bool);
@override
_i11.Future<void> setDashboardWidgetVisible(
_i13.DashboardWidget? key,
bool? visible,
) =>
(super.noSuchMethod(
Invocation.method(#setDashboardWidgetVisible, [key, visible]),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
)
as _i11.Future<void>);
@override
_i11.Future<void> setDashboardOrder(int? oldIndex, int? newIndex) =>
(super.noSuchMethod(
Invocation.method(#setDashboardOrder, [oldIndex, newIndex]),
returnValue: _i11.Future<void>.value(),
returnValueForMissingStub: _i11.Future<void>.value(),
)
as _i11.Future<void>);
@override
void setThemeMode(_i14.ThemeMode? mode) => super.noSuchMethod(
Invocation.method(#setThemeMode, [mode]),