mirror of https://github.com/flutter/pinball.git
feat: add leaderboard repository (#54)
* feat: add leaderboard repository * refactor: move leaderboard ranking to models * refactor: username to playerInitials * docs: typo fix * docs: adjust repo wording * fix: failing testpull/56/head
parent
aba49660c2
commit
c0175b0d30
@ -0,0 +1,18 @@
|
||||
name: leaderboard_repository
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "packages/leaderboard_repository/**"
|
||||
- ".github/workflows/leaderboard_repository.yaml"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- "packages/leaderboard_repository/**"
|
||||
- ".github/workflows/leaderboard_repository.yaml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
|
||||
with:
|
||||
working_directory: packages/leaderboard_repository
|
@ -0,0 +1,39 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# VSCode related
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
@ -0,0 +1,11 @@
|
||||
# leaderboard_repository
|
||||
|
||||
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
|
||||
[![License: MIT][license_badge]][license_link]
|
||||
|
||||
Repository to access leaderboard data in Firebase Cloud Firestore.
|
||||
|
||||
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
[license_link]: https://opensource.org/licenses/MIT
|
||||
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
|
||||
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
|
@ -0,0 +1 @@
|
||||
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
@ -0,0 +1,4 @@
|
||||
library leaderboard_repository;
|
||||
|
||||
export 'src/leaderboard_repository.dart';
|
||||
export 'src/models/models.dart';
|
@ -0,0 +1,152 @@
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
|
||||
/// {@template leaderboard_exception}
|
||||
/// Base exception for leaderboard repository failures.
|
||||
/// {@endtemplate}
|
||||
abstract class LeaderboardException implements Exception {
|
||||
/// {@macro leaderboard_exception}
|
||||
const LeaderboardException(this.error, this.stackTrace);
|
||||
|
||||
/// The error that was caught.
|
||||
final Object error;
|
||||
|
||||
/// The Stacktrace associated with the [error].
|
||||
final StackTrace stackTrace;
|
||||
}
|
||||
|
||||
/// {@template leaderboard_deserialization_exception}
|
||||
/// Exception thrown when leaderboard data cannot be deserialized in the
|
||||
/// expected way.
|
||||
/// {@endtemplate}
|
||||
class LeaderboardDeserializationException extends LeaderboardException {
|
||||
/// {@macro leaderboard_deserialization_exception}
|
||||
const LeaderboardDeserializationException(
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) : super(
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template fetch_top_10_leaderboard_exception}
|
||||
/// Exception thrown when failure occurs while fetching top 10 leaderboard.
|
||||
/// {@endtemplate}
|
||||
class FetchTop10LeaderboardException extends LeaderboardException {
|
||||
/// {@macro fetch_top_10_leaderboard_exception}
|
||||
const FetchTop10LeaderboardException(
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) : super(
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template add_leaderboard_entry_exception}
|
||||
/// Exception thrown when failure occurs while adding entry to leaderboard.
|
||||
/// {@endtemplate}
|
||||
class AddLeaderboardEntryException extends LeaderboardException {
|
||||
/// {@macro add_leaderboard_entry_exception}
|
||||
const AddLeaderboardEntryException(
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) : super(
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template fetch_player_ranking_exception}
|
||||
/// Exception thrown when failure occurs while fetching player ranking.
|
||||
/// {@endtemplate}
|
||||
class FetchPlayerRankingException extends LeaderboardException {
|
||||
/// {@macro fetch_player_ranking_exception}
|
||||
const FetchPlayerRankingException(
|
||||
Object error,
|
||||
StackTrace stackTrace,
|
||||
) : super(
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
/// {@template leaderboard_repository}
|
||||
/// Repository to access leaderboard data in Firebase Cloud Firestore.
|
||||
/// {@endtemplate}
|
||||
class LeaderboardRepository {
|
||||
/// {@macro leaderboard_repository}
|
||||
const LeaderboardRepository(
|
||||
FirebaseFirestore firebaseFirestore,
|
||||
) : _firebaseFirestore = firebaseFirestore;
|
||||
|
||||
final FirebaseFirestore _firebaseFirestore;
|
||||
|
||||
/// Acquires top 10 [LeaderboardEntry]s.
|
||||
Future<List<LeaderboardEntry>> fetchTop10Leaderboard() async {
|
||||
final leaderboardEntries = <LeaderboardEntry>[];
|
||||
late List<QueryDocumentSnapshot> documents;
|
||||
|
||||
try {
|
||||
final querySnapshot = await _firebaseFirestore
|
||||
.collection('leaderboard')
|
||||
.orderBy('score')
|
||||
.limit(10)
|
||||
.get();
|
||||
documents = querySnapshot.docs;
|
||||
} on Exception catch (error, stackTrace) {
|
||||
throw FetchTop10LeaderboardException(error, stackTrace);
|
||||
}
|
||||
|
||||
for (final document in documents) {
|
||||
final data = document.data() as Map<String, dynamic>?;
|
||||
if (data != null) {
|
||||
try {
|
||||
leaderboardEntries.add(LeaderboardEntry.fromJson(data));
|
||||
} catch (error, stackTrace) {
|
||||
throw LeaderboardDeserializationException(error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return leaderboardEntries;
|
||||
}
|
||||
|
||||
/// Adds player's score entry to the leaderboard and gets their
|
||||
/// [LeaderboardRanking].
|
||||
Future<LeaderboardRanking> addLeaderboardEntry(LeaderboardEntry entry) async {
|
||||
late DocumentReference entryReference;
|
||||
try {
|
||||
entryReference = await _firebaseFirestore
|
||||
.collection('leaderboard')
|
||||
.add(entry.toJson());
|
||||
} on Exception catch (error, stackTrace) {
|
||||
throw AddLeaderboardEntryException(error, stackTrace);
|
||||
}
|
||||
|
||||
try {
|
||||
final querySnapshot = await _firebaseFirestore
|
||||
.collection('leaderboard')
|
||||
.orderBy('score')
|
||||
.get();
|
||||
|
||||
final documents = querySnapshot.docs;
|
||||
final ranking = documents.indexWhere(
|
||||
(document) => document.id == entryReference.id,
|
||||
) +
|
||||
1;
|
||||
|
||||
if (ranking > 0) {
|
||||
return LeaderboardRanking(ranking: ranking, outOf: documents.length);
|
||||
} else {
|
||||
throw FetchPlayerRankingException(
|
||||
'Player score could not be found and ranking cannot be provided.',
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
} on Exception catch (error, stackTrace) {
|
||||
throw FetchPlayerRankingException(error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'leaderboard_entry.g.dart';
|
||||
|
||||
/// Google character type associated with a [LeaderboardEntry].
|
||||
enum CharacterType {
|
||||
/// Dash character.
|
||||
dash,
|
||||
|
||||
/// Sparky character.
|
||||
|
||||
sparky,
|
||||
|
||||
/// Android character.
|
||||
|
||||
android,
|
||||
|
||||
/// Dino character.
|
||||
|
||||
dino,
|
||||
}
|
||||
|
||||
/// {@template leaderboard_entry}
|
||||
/// A model representing a leaderboard entry containing the player's initials,
|
||||
/// score, and chosen character.
|
||||
///
|
||||
/// Stored in Firestore `leaderboard` collection.
|
||||
///
|
||||
/// Example:
|
||||
/// ```json
|
||||
/// {
|
||||
/// "playerInitials" : "ABC",
|
||||
/// "score" : 1500,
|
||||
/// "character" : "dash"
|
||||
/// }
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable()
|
||||
class LeaderboardEntry extends Equatable {
|
||||
/// {@macro leaderboard_entry}
|
||||
const LeaderboardEntry({
|
||||
required this.playerInitials,
|
||||
required this.score,
|
||||
required this.character,
|
||||
});
|
||||
|
||||
/// Factory which converts a [Map] into a [LeaderboardEntry].
|
||||
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) {
|
||||
return _$LeaderboardEntryFromJson(json);
|
||||
}
|
||||
|
||||
/// Converts the [LeaderboardEntry] to [Map].
|
||||
Map<String, dynamic> toJson() => _$LeaderboardEntryToJson(this);
|
||||
|
||||
/// Player's chosen initials for [LeaderboardEntry].
|
||||
///
|
||||
/// Example: 'ABC'.
|
||||
@JsonKey(name: 'playerInitials')
|
||||
final String playerInitials;
|
||||
|
||||
/// Score for [LeaderboardEntry].
|
||||
///
|
||||
/// Example: 1500.
|
||||
@JsonKey(name: 'score')
|
||||
final int score;
|
||||
|
||||
/// [CharacterType] for [LeaderboardEntry].
|
||||
///
|
||||
/// Example: [CharacterType.dash].
|
||||
@JsonKey(name: 'character')
|
||||
final CharacterType character;
|
||||
|
||||
/// An empty [LeaderboardEntry] object.
|
||||
static const empty = LeaderboardEntry(
|
||||
playerInitials: '',
|
||||
score: 0,
|
||||
character: CharacterType.dash,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [playerInitials, score, character];
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'leaderboard_entry.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
LeaderboardEntry _$LeaderboardEntryFromJson(Map<String, dynamic> json) =>
|
||||
LeaderboardEntry(
|
||||
playerInitials: json['playerInitials'] as String,
|
||||
score: json['score'] as int,
|
||||
character: $enumDecode(_$CharacterTypeEnumMap, json['character']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$LeaderboardEntryToJson(LeaderboardEntry instance) =>
|
||||
<String, dynamic>{
|
||||
'playerInitials': instance.playerInitials,
|
||||
'score': instance.score,
|
||||
'character': _$CharacterTypeEnumMap[instance.character],
|
||||
};
|
||||
|
||||
const _$CharacterTypeEnumMap = {
|
||||
CharacterType.dash: 'dash',
|
||||
CharacterType.sparky: 'sparky',
|
||||
CharacterType.android: 'android',
|
||||
CharacterType.dino: 'dino',
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
|
||||
/// {@template leaderboard_ranking}
|
||||
/// Contains [ranking] for a single [LeaderboardEntry] and the number of players
|
||||
/// the [ranking] is [outOf].
|
||||
/// {@endtemplate}
|
||||
class LeaderboardRanking extends Equatable {
|
||||
/// {@macro leaderboard_ranking}
|
||||
const LeaderboardRanking({required this.ranking, required this.outOf});
|
||||
|
||||
/// Place ranking by score for a [LeaderboardEntry].
|
||||
final int ranking;
|
||||
|
||||
/// Number of [LeaderboardEntry]s at the time of score entry.
|
||||
final int outOf;
|
||||
|
||||
@override
|
||||
List<Object> get props => [ranking, outOf];
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'leaderboard_entry.dart';
|
||||
export 'leaderboard_ranking.dart';
|
@ -0,0 +1,23 @@
|
||||
name: leaderboard_repository
|
||||
description: Repository to access leaderboard data in Firebase Cloud Firestore.
|
||||
version: 1.0.0+1
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: ">=2.16.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
cloud_firestore: ^3.1.10
|
||||
equatable: ^2.0.3
|
||||
flutter:
|
||||
sdk: flutter
|
||||
json_annotation: ^4.4.0
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.8
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
json_serializable: ^6.1.5
|
||||
mocktail: ^0.2.0
|
||||
test: ^1.19.2
|
||||
very_good_analysis: ^2.4.0
|
@ -0,0 +1,226 @@
|
||||
// ignore_for_file: prefer_const_constructors, subtype_of_sealed_class
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockFirebaseFirestore extends Mock implements FirebaseFirestore {}
|
||||
|
||||
class MockCollectionReference extends Mock
|
||||
implements CollectionReference<Map<String, dynamic>> {}
|
||||
|
||||
class MockQuery extends Mock implements Query<Map<String, dynamic>> {}
|
||||
|
||||
class MockQuerySnapshot extends Mock
|
||||
implements QuerySnapshot<Map<String, dynamic>> {}
|
||||
|
||||
class MockQueryDocumentSnapshot extends Mock
|
||||
implements QueryDocumentSnapshot<Map<String, dynamic>> {}
|
||||
|
||||
class MockDocumentReference extends Mock
|
||||
implements DocumentReference<Map<String, dynamic>> {}
|
||||
|
||||
void main() {
|
||||
group('LeaderboardRepository', () {
|
||||
late FirebaseFirestore firestore;
|
||||
|
||||
setUp(() {
|
||||
firestore = MockFirebaseFirestore();
|
||||
});
|
||||
|
||||
test('can be instantiated', () {
|
||||
expect(LeaderboardRepository(firestore), isNotNull);
|
||||
});
|
||||
|
||||
group('fetchTop10Leaderboard', () {
|
||||
late LeaderboardRepository leaderboardRepository;
|
||||
late CollectionReference<Map<String, dynamic>> collectionReference;
|
||||
late Query<Map<String, dynamic>> query;
|
||||
late QuerySnapshot<Map<String, dynamic>> querySnapshot;
|
||||
late List<QueryDocumentSnapshot<Map<String, dynamic>>>
|
||||
queryDocumentSnapshots;
|
||||
|
||||
final top10Scores = [
|
||||
2500,
|
||||
2200,
|
||||
2200,
|
||||
2000,
|
||||
1800,
|
||||
1400,
|
||||
1300,
|
||||
1000,
|
||||
600,
|
||||
300,
|
||||
100,
|
||||
];
|
||||
|
||||
final top10Leaderboard = top10Scores
|
||||
.map(
|
||||
(score) => LeaderboardEntry(
|
||||
playerInitials: 'user$score',
|
||||
score: score,
|
||||
character: CharacterType.dash,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
setUp(() {
|
||||
leaderboardRepository = LeaderboardRepository(firestore);
|
||||
collectionReference = MockCollectionReference();
|
||||
query = MockQuery();
|
||||
querySnapshot = MockQuerySnapshot();
|
||||
queryDocumentSnapshots = top10Scores.map((score) {
|
||||
final queryDocumentSnapshot = MockQueryDocumentSnapshot();
|
||||
when(queryDocumentSnapshot.data).thenReturn(<String, dynamic>{
|
||||
'character': 'dash',
|
||||
'playerInitials': 'user$score',
|
||||
'score': score
|
||||
});
|
||||
return queryDocumentSnapshot;
|
||||
}).toList();
|
||||
|
||||
when(() => firestore.collection('leaderboard'))
|
||||
.thenAnswer((_) => collectionReference);
|
||||
when(() => collectionReference.orderBy('score'))
|
||||
.thenAnswer((_) => query);
|
||||
when(() => query.limit(10)).thenAnswer((_) => query);
|
||||
when(query.get).thenAnswer((_) async => querySnapshot);
|
||||
when(() => querySnapshot.docs).thenReturn(queryDocumentSnapshots);
|
||||
});
|
||||
|
||||
test(
|
||||
'returns top 10 entries when '
|
||||
'retrieving information from firestore succeeds', () async {
|
||||
final top10LeaderboardResults =
|
||||
await leaderboardRepository.fetchTop10Leaderboard();
|
||||
|
||||
expect(top10LeaderboardResults, equals(top10Leaderboard));
|
||||
});
|
||||
|
||||
test(
|
||||
'throws FetchTop10LeaderboardException when Exception occurs '
|
||||
'when trying to retrieve information from firestore', () async {
|
||||
when(() => firestore.collection('leaderboard')).thenThrow(Exception());
|
||||
|
||||
expect(
|
||||
() => leaderboardRepository.fetchTop10Leaderboard(),
|
||||
throwsA(isA<FetchTop10LeaderboardException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'throws LeaderboardDeserializationException when Exception occurs '
|
||||
'during deserialization', () async {
|
||||
final top10LeaderboardDataMalformed = <String, dynamic>{
|
||||
'playerInitials': 'ABC',
|
||||
'score': 1500,
|
||||
};
|
||||
final queryDocumentSnapshot = MockQueryDocumentSnapshot();
|
||||
when(() => querySnapshot.docs).thenReturn([queryDocumentSnapshot]);
|
||||
when(queryDocumentSnapshot.data)
|
||||
.thenReturn(top10LeaderboardDataMalformed);
|
||||
|
||||
expect(
|
||||
() => leaderboardRepository.fetchTop10Leaderboard(),
|
||||
throwsA(isA<LeaderboardDeserializationException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('addLeaderboardEntry', () {
|
||||
late LeaderboardRepository leaderboardRepository;
|
||||
late CollectionReference<Map<String, dynamic>> collectionReference;
|
||||
late DocumentReference<Map<String, dynamic>> documentReference;
|
||||
late Query<Map<String, dynamic>> query;
|
||||
late QuerySnapshot<Map<String, dynamic>> querySnapshot;
|
||||
late List<QueryDocumentSnapshot<Map<String, dynamic>>>
|
||||
queryDocumentSnapshots;
|
||||
|
||||
const entryScore = 1500;
|
||||
final leaderboardScores = [
|
||||
2500,
|
||||
2200,
|
||||
entryScore,
|
||||
1000,
|
||||
];
|
||||
final leaderboardEntry = LeaderboardEntry(
|
||||
playerInitials: 'ABC',
|
||||
score: entryScore,
|
||||
character: CharacterType.dash,
|
||||
);
|
||||
const entryDocumentId = 'id$entryScore';
|
||||
final ranking = LeaderboardRanking(ranking: 3, outOf: 4);
|
||||
|
||||
setUp(() {
|
||||
leaderboardRepository = LeaderboardRepository(firestore);
|
||||
collectionReference = MockCollectionReference();
|
||||
documentReference = MockDocumentReference();
|
||||
query = MockQuery();
|
||||
querySnapshot = MockQuerySnapshot();
|
||||
queryDocumentSnapshots = leaderboardScores.map((score) {
|
||||
final queryDocumentSnapshot = MockQueryDocumentSnapshot();
|
||||
when(queryDocumentSnapshot.data).thenReturn(<String, dynamic>{
|
||||
'character': 'dash',
|
||||
'username': 'user$score',
|
||||
'score': score
|
||||
});
|
||||
when(() => queryDocumentSnapshot.id).thenReturn('id$score');
|
||||
return queryDocumentSnapshot;
|
||||
}).toList();
|
||||
|
||||
when(() => firestore.collection('leaderboard'))
|
||||
.thenAnswer((_) => collectionReference);
|
||||
when(() => collectionReference.add(any()))
|
||||
.thenAnswer((_) async => documentReference);
|
||||
when(() => collectionReference.orderBy('score'))
|
||||
.thenAnswer((_) => query);
|
||||
when(query.get).thenAnswer((_) async => querySnapshot);
|
||||
when(() => querySnapshot.docs).thenReturn(queryDocumentSnapshots);
|
||||
when(() => documentReference.id).thenReturn(entryDocumentId);
|
||||
});
|
||||
|
||||
test(
|
||||
'adds leaderboard entry and returns player ranking when '
|
||||
'firestore operations succeed', () async {
|
||||
final rankingResult =
|
||||
await leaderboardRepository.addLeaderboardEntry(leaderboardEntry);
|
||||
|
||||
expect(rankingResult, equals(ranking));
|
||||
});
|
||||
|
||||
test(
|
||||
'throws AddLeaderboardEntryException when Exception occurs '
|
||||
'when trying to add entry to firestore', () async {
|
||||
when(() => firestore.collection('leaderboard')).thenThrow(Exception());
|
||||
|
||||
expect(
|
||||
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
|
||||
throwsA(isA<AddLeaderboardEntryException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'throws FetchPlayerRankingException when Exception occurs '
|
||||
'when trying to retrieve information from firestore', () async {
|
||||
when(() => collectionReference.orderBy('score')).thenThrow(Exception());
|
||||
|
||||
expect(
|
||||
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
|
||||
throwsA(isA<FetchPlayerRankingException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'throws FetchPlayerRankingException when score cannot be found '
|
||||
'in firestore leaderboard data', () async {
|
||||
when(() => documentReference.id).thenReturn('nonexistentDocumentId');
|
||||
|
||||
expect(
|
||||
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
|
||||
throwsA(isA<FetchPlayerRankingException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('LeaderboardEntry', () {
|
||||
const data = <String, dynamic>{
|
||||
'playerInitials': 'ABC',
|
||||
'score': 1500,
|
||||
'character': 'dash',
|
||||
};
|
||||
|
||||
const leaderboardEntry = LeaderboardEntry(
|
||||
playerInitials: 'ABC',
|
||||
score: 1500,
|
||||
character: CharacterType.dash,
|
||||
);
|
||||
|
||||
test('can be instantiated', () {
|
||||
const leaderboardEntry = LeaderboardEntry.empty;
|
||||
|
||||
expect(leaderboardEntry, isNotNull);
|
||||
});
|
||||
|
||||
test('supports value equality.', () {
|
||||
const leaderboardEntry = LeaderboardEntry.empty;
|
||||
const leaderboardEntry2 = LeaderboardEntry.empty;
|
||||
|
||||
expect(leaderboardEntry, equals(leaderboardEntry2));
|
||||
});
|
||||
|
||||
test('can be converted to json', () {
|
||||
expect(leaderboardEntry.toJson(), equals(data));
|
||||
});
|
||||
|
||||
test('can be obtained from json', () {
|
||||
final leaderboardEntryFrom = LeaderboardEntry.fromJson(data);
|
||||
|
||||
expect(leaderboardEntry, equals(leaderboardEntryFrom));
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import 'package:leaderboard_repository/leaderboard_repository.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('LeaderboardRanking', () {
|
||||
test('can be instantiated', () {
|
||||
const leaderboardRanking = LeaderboardRanking(ranking: 1, outOf: 1);
|
||||
|
||||
expect(leaderboardRanking, isNotNull);
|
||||
});
|
||||
|
||||
test('supports value equality.', () {
|
||||
const leaderboardRanking = LeaderboardRanking(ranking: 1, outOf: 1);
|
||||
const leaderboardRanking2 = LeaderboardRanking(ranking: 1, outOf: 1);
|
||||
|
||||
expect(leaderboardRanking, equals(leaderboardRanking2));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue