Add trophies to configurable dashboard lists

This commit is contained in:
Roland Geider
2026-01-17 14:01:17 +01:00
parent 36bf6aa8fc
commit f5a591a3b7
8 changed files with 119 additions and 251 deletions

View File

@@ -29,10 +29,7 @@ import 'package:wger/widgets/core/settings/dashboard_visibility.dart';
import 'settings_dashboard_visibility_test.mocks.dart';
@GenerateMocks([
UserProvider,
WgerBaseProvider,
])
@GenerateMocks([WgerBaseProvider])
void main() {
late UserProvider userProvider;
late MockWgerBaseProvider mockBaseProvider;
@@ -98,24 +95,23 @@ void main() {
await tester.pumpWidget(createWidget());
await tester.pumpAndSettle();
// Initial order: routines, nutrition, weight...
expect(userProvider.dashboardOrder[0], DashboardWidget.routines);
expect(userProvider.dashboardOrder[1], DashboardWidget.nutrition);
// Initial order: trophies, routines, nutrition, weight...
expect(userProvider.dashboardOrder[0], DashboardWidget.trophies);
expect(userProvider.dashboardOrder[1], DashboardWidget.routines);
// Find drag handle for Routines (index 0)
// Find drag handle for Trophies (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);
// 100px drag seems to skip 2 items (trophies moves to index 2)
// [routines, nutrition, trophies, ...]
expect(userProvider.dashboardOrder[0], DashboardWidget.routines);
expect(userProvider.dashboardOrder[1], DashboardWidget.nutrition);
expect(userProvider.dashboardOrder[2], DashboardWidget.trophies);
});
}

View File

@@ -3,17 +3,12 @@
// 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 'dart:async' as _i5;
import 'package:flutter/material.dart' as _i7;
import 'package:http/http.dart' as _i5;
import 'package:http/http.dart' as _i3;
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;
import 'package:wger/providers/auth.dart' as _i2;
import 'package:wger/providers/base_provider.dart' as _i4;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -30,234 +25,67 @@ import 'package:wger/providers/user.dart' as _i6;
// 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 _FakeAuthProvider_0 extends _i1.SmartFake implements _i2.AuthProvider {
_FakeAuthProvider_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 _FakeClient_1 extends _i1.SmartFake implements _i3.Client {
_FakeClient_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 _FakeUri_2 extends _i1.SmartFake implements Uri {
_FakeUri_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,
);
class _FakeResponse_3 extends _i1.SmartFake implements _i3.Response {
_FakeResponse_3(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
/// A class which mocks [WgerBaseProvider].
///
/// See the documentation for Mockito's code generation for more information.
class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
class MockWgerBaseProvider extends _i1.Mock implements _i4.WgerBaseProvider {
MockWgerBaseProvider() {
_i1.throwOnMissingStub(this);
}
@override
_i4.AuthProvider get auth =>
_i2.AuthProvider get auth =>
(super.noSuchMethod(
Invocation.getter(#auth),
returnValue: _FakeAuthProvider_2(this, Invocation.getter(#auth)),
returnValue: _FakeAuthProvider_0(this, Invocation.getter(#auth)),
)
as _i4.AuthProvider);
as _i2.AuthProvider);
@override
_i5.Client get client =>
_i3.Client get client =>
(super.noSuchMethod(
Invocation.getter(#client),
returnValue: _FakeClient_3(this, Invocation.getter(#client)),
returnValue: _FakeClient_1(this, Invocation.getter(#client)),
)
as _i5.Client);
as _i3.Client);
@override
set auth(_i4.AuthProvider? value) => super.noSuchMethod(
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
Invocation.setter(#auth, value),
returnValueForMissingStub: null,
);
@override
set client(_i5.Client? value) => super.noSuchMethod(
set client(_i3.Client? value) => super.noSuchMethod(
Invocation.setter(#client, value),
returnValueForMissingStub: null,
);
@override
Map<String, String> getDefaultHeaders({bool? includeAuth = false}) =>
Map<String, String> getDefaultHeaders({
bool? includeAuth = false,
String? language,
}) =>
(super.noSuchMethod(
Invocation.method(#getDefaultHeaders, [], {
#includeAuth: includeAuth,
#language: language,
}),
returnValue: <String, String>{},
)
@@ -276,7 +104,7 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
[path],
{#id: id, #objectMethod: objectMethod, #query: query},
),
returnValue: _FakeUri_4(
returnValue: _FakeUri_2(
this,
Invocation.method(
#makeUrl,
@@ -288,62 +116,67 @@ class MockWgerBaseProvider extends _i1.Mock implements _i2.WgerBaseProvider {
as Uri);
@override
_i9.Future<dynamic> fetch(
_i5.Future<dynamic> fetch(
Uri? uri, {
int? maxRetries = 3,
Duration? initialDelay = const Duration(milliseconds: 250),
String? language,
}) =>
(super.noSuchMethod(
Invocation.method(
#fetch,
[uri],
{#maxRetries: maxRetries, #initialDelay: initialDelay},
{
#maxRetries: maxRetries,
#initialDelay: initialDelay,
#language: language,
},
),
returnValue: _i9.Future<dynamic>.value(),
returnValue: _i5.Future<dynamic>.value(),
)
as _i9.Future<dynamic>);
as _i5.Future<dynamic>);
@override
_i9.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri, {String? language}) =>
(super.noSuchMethod(
Invocation.method(#fetchPaginated, [uri]),
returnValue: _i9.Future<List<dynamic>>.value(<dynamic>[]),
Invocation.method(#fetchPaginated, [uri], {#language: language}),
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
)
as _i9.Future<List<dynamic>>);
as _i5.Future<List<dynamic>>);
@override
_i9.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
_i5.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(
returnValue: _i5.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i9.Future<Map<String, dynamic>>);
as _i5.Future<Map<String, dynamic>>);
@override
_i9.Future<Map<String, dynamic>> patch(
_i5.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(
returnValue: _i5.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i9.Future<Map<String, dynamic>>);
as _i5.Future<Map<String, dynamic>>);
@override
_i9.Future<_i5.Response> deleteRequest(String? url, int? id) =>
_i5.Future<_i3.Response> deleteRequest(String? url, int? id) =>
(super.noSuchMethod(
Invocation.method(#deleteRequest, [url, id]),
returnValue: _i9.Future<_i5.Response>.value(
_FakeResponse_5(
returnValue: _i5.Future<_i3.Response>.value(
_FakeResponse_3(
this,
Invocation.method(#deleteRequest, [url, id]),
),
),
)
as _i9.Future<_i5.Response>);
as _i5.Future<_i3.Response>);
}

View File

@@ -2207,6 +2207,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
)
as String);
@override
String get noTrophies =>
(super.noSuchMethod(
Invocation.getter(#noTrophies),
returnValue: _i3.dummyValue<String>(
this,
Invocation.getter(#noTrophies),
),
)
as String);
@override
String get noWeightEntries =>
(super.noSuchMethod(
@@ -3843,6 +3854,17 @@ class MockAppLocalizations extends _i1.Mock implements _i2.AppLocalizations {
)
as String);
@override
String topSet(String? value) =>
(super.noSuchMethod(
Invocation.method(#topSet, [value]),
returnValue: _i3.dummyValue<String>(
this,
Invocation.method(#topSet, [value]),
),
)
as String);
@override
String durationHoursMinutes(int? hours, int? minutes) =>
(super.noSuchMethod(

View File

@@ -106,11 +106,12 @@ void main() {
group('dashboard config', () {
test('initial config should be default (all visible, default order)', () {
expect(userProvider.dashboardOrder.length, 5);
expect(userProvider.dashboardOrder.length, 6);
expect(
userProvider.dashboardOrder,
orderedEquals([
DashboardWidget.trophies,
DashboardWidget.routines,
DashboardWidget.nutrition,
DashboardWidget.weight,
@@ -161,22 +162,28 @@ void main() {
// act
final newProvider = UserProvider(mockWgerBaseProvider, prefs: prefs);
await Future.delayed(const Duration(milliseconds: 50)); // wait for async prefs load
await Future.delayed(const Duration(milliseconds: 100)); // wait for async prefs load
// assert
// The loaded ones come first
expect(newProvider.dashboardOrder[0], DashboardWidget.nutrition);
expect(newProvider.dashboardOrder[1], DashboardWidget.routines);
// Loaded: [nutrition, routines]
// Missing: trophies (0), weight (3), measurements (4), calendar (5)
// 1. trophies (index 0) inserted at 0 -> [trophies, nutrition, routines]
// 2. weight (index 3) inserted at 3 -> [trophies, nutrition, routines, weight]
expect(newProvider.dashboardOrder[0], DashboardWidget.trophies);
expect(newProvider.dashboardOrder[1], DashboardWidget.nutrition);
expect(newProvider.dashboardOrder[2], DashboardWidget.routines);
expect(newProvider.dashboardOrder[3], DashboardWidget.weight);
// 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);
// Missing items should be visible by default
expect(
newProvider.isDashboardWidgetVisible(DashboardWidget.weight),
true,
);
});
});
}