Start adding support for trophies

This commit is contained in:
Roland Geider
2025-12-20 00:31:18 +01:00
parent 0d0a5d8e02
commit 5d39ae5088
13 changed files with 998 additions and 44 deletions

View File

@@ -0,0 +1,72 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 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.
*
* 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_test/flutter_test.dart';
import 'package:wger/models/trophies/trophy.dart';
void main() {
group('Trophy model', () {
final sampleJson = {
'id': 1,
'uuid': '550e8400-e29b-41d4-a716-446655440000',
'name': 'First Steps',
'description': 'Awarded for the first workout',
'image': 'https://example.org/trophy.png',
'trophy_type': 'count',
'is_hidden': false,
'is_progressive': true,
};
test('fromJson creates valid Trophy instance', () {
final trophy = Trophy.fromJson(sampleJson);
expect(trophy.id, 1);
expect(trophy.uuid, '550e8400-e29b-41d4-a716-446655440000');
expect(trophy.name, 'First Steps');
expect(trophy.description, 'Awarded for the first workout');
expect(trophy.image, 'https://example.org/trophy.png');
expect(trophy.type, TrophyType.count);
expect(trophy.isHidden, isFalse);
expect(trophy.isProgressive, isTrue);
});
test('toJson returns expected map', () {
final trophy = Trophy(
id: 2,
uuid: '00000000-0000-0000-0000-000000000000',
name: 'Progressor',
description: 'Progressive trophy',
image: 'https://example.org/prog.png',
type: TrophyType.time,
isHidden: true,
isProgressive: false,
);
final json = trophy.toJson();
expect(json['id'], 2);
expect(json['uuid'], '00000000-0000-0000-0000-000000000000');
expect(json['name'], 'Progressor');
expect(json['description'], 'Progressive trophy');
expect(json['image'], 'https://example.org/prog.png');
expect(json['trophy_type'], 'time');
expect(json['is_hidden'], true);
expect(json['is_progressive'], false);
});
});
}

View File

@@ -0,0 +1,97 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 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.
*
* 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_test/flutter_test.dart';
import 'package:wger/models/trophies/trophy.dart';
import 'package:wger/models/trophies/user_trophy_progression.dart';
void main() {
group('UserTrophyProgression model', () {
final trophyJson = {
'id': 1,
'uuid': '550e8400-e29b-41d4-a716-446655440000',
'name': 'First Steps',
'description': 'Awarded for the first workout',
'image': 'https://example.org/trophy.png',
'trophy_type': 'count',
'is_hidden': false,
'is_progressive': true,
};
final trophyProgressionJson = {
'trophy': trophyJson,
'is_earned': false,
'earned_at': '2020-01-02T15:04:05Z',
'progress': 42.5,
'current_value': '12.5',
'target_value': '100',
'progress_display': '12.5/100',
};
test('fromJson creates valid UserTrophyProgression instance', () {
final utp = UserTrophyProgression.fromJson(trophyProgressionJson);
expect(utp.trophy.id, 1);
expect(utp.trophy.uuid, '550e8400-e29b-41d4-a716-446655440000');
expect(utp.isEarned, isFalse);
final expectedEarnedAt = DateTime.parse('2020-01-02T15:04:05Z').toLocal();
expect(utp.earnedAt, expectedEarnedAt);
expect(utp.progress, 42.5);
expect(utp.currentValue, 12.5);
expect(utp.targetValue, 100);
expect(utp.progressDisplay, '12.5/100');
});
test('toJson returns expected map', () {
final trophy = Trophy(
id: 2,
uuid: '00000000-0000-0000-0000-000000000000',
name: 'Progressor',
description: 'Progressive trophy',
image: 'https://example.org/prog.png',
type: TrophyType.time,
isHidden: true,
isProgressive: false,
);
final earnedAt = DateTime.parse('2020-01-02T15:04:05Z').toLocal();
final utp = UserTrophyProgression(
trophy: trophy,
isEarned: true,
earnedAt: earnedAt,
progress: 75,
currentValue: 75,
targetValue: 100,
progressDisplay: '75/100',
);
final json = utp.toJson();
expect(json['trophy'], same(trophy));
expect(json['is_earned'], true);
expect(json['earned_at'], earnedAt.toIso8601String());
expect(json['progress'], 75);
expect(json['current_value'], 75);
expect(json['target_value'], 100);
expect(json['progress_display'], '75/100');
});
});
}

View File

@@ -0,0 +1,117 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 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.
*
* 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_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:wger/providers/base_provider.dart';
import 'package:wger/providers/trophies.dart';
import 'package:wger/providers/wger_base_riverpod.dart';
import 'trophies_provider_test.mocks.dart';
const trophyJson = {
'id': 1,
'uuid': '550e8400-e29b-41d4-a716-446655440000',
'name': 'First Steps',
'description': 'Awarded for the first workout',
'image': 'https://example.org/trophy.png',
'trophy_type': 'count',
'is_hidden': false,
'is_progressive': true,
};
@GenerateMocks([WgerBaseProvider])
void main() {
group('Trophies providers', () {
test('trophies provider returns list of Trophy models', () async {
// Arrange
final mockBase = MockWgerBaseProvider();
when(mockBase.fetchPaginated(any)).thenAnswer((_) async => [trophyJson]);
when(
mockBase.makeUrl(
any,
id: anyNamed('id'),
objectMethod: anyNamed('objectMethod'),
query: anyNamed('query'),
),
).thenReturn(Uri.parse('https://example.org/trophies'));
final container = ProviderContainer.test(
overrides: [
wgerBaseProvider.overrideWithValue(mockBase),
],
);
// Act
final result = await container.read(trophiesProvider.future);
// Assert
expect(result, isA<List>());
expect(result, hasLength(1));
final trophy = result.first;
expect(trophy.id, 1);
expect(trophy.name, 'First Steps');
expect(trophy.type.toString(), contains('count'));
});
test('trophyProgression provider returns list of UserTrophyProgression models', () async {
// Arrange
final progressionJson = {
'trophy': trophyJson,
'is_earned': true,
'earned_at': '2020-01-02T15:04:05Z',
'progress': 42.5,
'current_value': '12.5',
'target_value': '100',
'progress_display': '12.5/100',
};
final mockBase = MockWgerBaseProvider();
when(mockBase.fetchPaginated(any)).thenAnswer((_) async => [progressionJson]);
when(
mockBase.makeUrl(
any,
id: anyNamed('id'),
objectMethod: anyNamed('objectMethod'),
query: anyNamed('query'),
),
).thenReturn(Uri.parse('https://example.org/user_progressions'));
final container = ProviderContainer.test(
overrides: [
wgerBaseProvider.overrideWithValue(mockBase),
],
);
// Act
final result = await container.read(trophyProgressionProvider.future);
// Assert
expect(result, isA<List>());
expect(result, hasLength(1));
final p = result.first;
expect(p.isEarned, isTrue);
expect(p.progress, 42.5);
expect(p.currentValue, 12.5);
expect(p.progressDisplay, '12.5/100');
verify(mockBase.fetchPaginated(any)).called(1);
});
});
}

View File

@@ -0,0 +1,183 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 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.
*
* 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/>.
*/
// Mocks generated by Mockito 5.4.6 from annotations
// in wger/test/trophies/trophies_provider_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'package:http/http.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
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
// 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 _FakeAuthProvider_0 extends _i1.SmartFake implements _i2.AuthProvider {
_FakeAuthProvider_0(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 _FakeUri_2 extends _i1.SmartFake implements Uri {
_FakeUri_2(Object parent, Invocation parentInvocation) : super(parent, parentInvocation);
}
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 _i4.WgerBaseProvider {
MockWgerBaseProvider() {
_i1.throwOnMissingStub(this);
}
@override
_i2.AuthProvider get auth =>
(super.noSuchMethod(
Invocation.getter(#auth),
returnValue: _FakeAuthProvider_0(this, Invocation.getter(#auth)),
)
as _i2.AuthProvider);
@override
_i3.Client get client =>
(super.noSuchMethod(
Invocation.getter(#client),
returnValue: _FakeClient_1(this, Invocation.getter(#client)),
)
as _i3.Client);
@override
set auth(_i2.AuthProvider? value) => super.noSuchMethod(
Invocation.setter(#auth, value),
returnValueForMissingStub: null,
);
@override
set client(_i3.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_2(
this,
Invocation.method(
#makeUrl,
[path],
{#id: id, #objectMethod: objectMethod, #query: query},
),
),
)
as Uri);
@override
_i5.Future<dynamic> fetch(Uri? uri) =>
(super.noSuchMethod(
Invocation.method(#fetch, [uri]),
returnValue: _i5.Future<dynamic>.value(),
)
as _i5.Future<dynamic>);
@override
_i5.Future<List<dynamic>> fetchPaginated(Uri? uri) =>
(super.noSuchMethod(
Invocation.method(#fetchPaginated, [uri]),
returnValue: _i5.Future<List<dynamic>>.value(<dynamic>[]),
)
as _i5.Future<List<dynamic>>);
@override
_i5.Future<Map<String, dynamic>> post(Map<String, dynamic>? data, Uri? uri) =>
(super.noSuchMethod(
Invocation.method(#post, [data, uri]),
returnValue: _i5.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i5.Future<Map<String, dynamic>>);
@override
_i5.Future<Map<String, dynamic>> patch(
Map<String, dynamic>? data,
Uri? uri,
) =>
(super.noSuchMethod(
Invocation.method(#patch, [data, uri]),
returnValue: _i5.Future<Map<String, dynamic>>.value(
<String, dynamic>{},
),
)
as _i5.Future<Map<String, dynamic>>);
@override
_i5.Future<_i3.Response> deleteRequest(String? url, int? id) =>
(super.noSuchMethod(
Invocation.method(#deleteRequest, [url, id]),
returnValue: _i5.Future<_i3.Response>.value(
_FakeResponse_3(
this,
Invocation.method(#deleteRequest, [url, id]),
),
),
)
as _i5.Future<_i3.Response>);
}