Merge branch 'main' into feat/crossing_upper_ramps

pull/40/head
RuiAlonso 4 years ago
commit 8643192fc3

@ -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

2
.gitignore vendored

@ -126,4 +126,6 @@ app.*.map.json
!.idea/dictionaries/ !.idea/dictionaries/
!.idea/runConfigurations/ !.idea/runConfigurations/
# Firebase related
.firebase .firebase
web/__/firebase/init.js

@ -8,16 +8,24 @@
// ignore_for_file: public_member_api_docs // ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/l10n/l10n.dart'; import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/landing/landing.dart'; import 'package:pinball/landing/landing.dart';
class App extends StatelessWidget { class App extends StatelessWidget {
const App({Key? key}) : super(key: key); const App({Key? key, required LeaderboardRepository leaderboardRepository})
: _leaderboardRepository = leaderboardRepository,
super(key: key);
final LeaderboardRepository _leaderboardRepository;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return RepositoryProvider.value(
value: _leaderboardRepository,
child: MaterialApp(
title: 'I/O Pinball', title: 'I/O Pinball',
theme: ThemeData( theme: ThemeData(
appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)), appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)),
@ -31,6 +39,7 @@ class App extends StatelessWidget {
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
home: const LandingPage(), home: const LandingPage(),
),
); );
} }
} }

@ -11,6 +11,7 @@ import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
class AppBlocObserver extends BlocObserver { class AppBlocObserver extends BlocObserver {
@ -27,7 +28,10 @@ class AppBlocObserver extends BlocObserver {
} }
} }
Future<void> bootstrap(FutureOr<Widget> Function() builder) async { Future<void> bootstrap(
Future<Widget> Function(FirebaseFirestore firestore) builder,
) async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) { FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack); log(details.exceptionAsString(), stackTrace: details.stack);
}; };
@ -35,7 +39,7 @@ Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
await runZonedGuarded( await runZonedGuarded(
() async { () async {
await BlocOverrides.runZoned( await BlocOverrides.runZoned(
() async => runApp(await builder()), () async => runApp(await builder(FirebaseFirestore.instance)),
blocObserver: AppBlocObserver(), blocObserver: AppBlocObserver(),
); );
}, },

@ -5,9 +5,13 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart'; import 'package:pinball/app/app.dart';
import 'package:pinball/bootstrap.dart'; import 'package:pinball/bootstrap.dart';
void main() { void main() {
bootstrap(() => const App()); bootstrap((firestore) async {
final leaderboardRepository = LeaderboardRepository(firestore);
return App(leaderboardRepository: leaderboardRepository);
});
} }

@ -5,9 +5,13 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart'; import 'package:pinball/app/app.dart';
import 'package:pinball/bootstrap.dart'; import 'package:pinball/bootstrap.dart';
void main() { void main() {
bootstrap(() => const App()); bootstrap((firestore) async {
final leaderboardRepository = LeaderboardRepository(firestore);
return App(leaderboardRepository: leaderboardRepository);
});
} }

@ -5,9 +5,13 @@
// license that can be found in the LICENSE file or at // license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/app/app.dart'; import 'package:pinball/app/app.dart';
import 'package:pinball/bootstrap.dart'; import 'package:pinball/bootstrap.dart';
void main() { void main() {
bootstrap(() => const App()); bootstrap((firestore) async {
final leaderboardRepository = LeaderboardRepository(firestore);
return App(leaderboardRepository: leaderboardRepository);
});
} }

@ -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,153 @@
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();
// TODO(allisonryan0002): see if we can find a more performant solution.
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,80 @@
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));
});
});
}

@ -78,6 +78,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
cloud_firestore:
dependency: "direct main"
description:
name: cloud_firestore
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.10"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.5.1"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.10"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -134,6 +155,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.2" version: "6.1.2"
firebase_core:
dependency: transitive
description:
name: firebase_core
url: "https://pub.dartlang.org"
source: hosted
version: "1.13.1"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.5"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.1"
flame: flame:
dependency: "direct main" dependency: "direct main"
description: description:
@ -184,6 +226,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
forge2d: forge2d:
dependency: transitive dependency: transitive
description: description:
@ -246,7 +293,21 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.4" version: "0.6.3"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.4.0"
leaderboard_repository:
dependency: "direct main"
description:
path: "packages/leaderboard_repository"
relative: true
source: path
version: "1.0.0+1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -338,6 +399,13 @@ packages:
relative: true relative: true
source: path source: path
version: "1.0.0+1" version: "1.0.0+1"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:

@ -8,6 +8,7 @@ environment:
dependencies: dependencies:
bloc: ^8.0.2 bloc: ^8.0.2
cloud_firestore: ^3.1.10
equatable: ^2.0.3 equatable: ^2.0.3
flame: ^1.1.0-releasecandidate.5 flame: ^1.1.0-releasecandidate.5
flame_bloc: ^1.2.0-releasecandidate.5 flame_bloc: ^1.2.0-releasecandidate.5
@ -20,6 +21,8 @@ dependencies:
geometry: geometry:
path: packages/geometry path: packages/geometry
intl: ^0.17.0 intl: ^0.17.0
leaderboard_repository:
path: packages/leaderboard_repository
pinball_theme: pinball_theme:
path: packages/pinball_theme path: packages/pinball_theme

@ -6,13 +6,25 @@
// https://opensource.org/licenses/MIT. // https://opensource.org/licenses/MIT.
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/app/app.dart'; import 'package:pinball/app/app.dart';
import 'package:pinball/landing/landing.dart'; import 'package:pinball/landing/landing.dart';
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
void main() { void main() {
group('App', () { group('App', () {
late LeaderboardRepository leaderboardRepository;
setUp(() {
leaderboardRepository = MockLeaderboardRepository();
});
testWidgets('renders LandingPage', (tester) async { testWidgets('renders LandingPage', (tester) async {
await tester.pumpWidget(const App()); await tester.pumpWidget(
App(leaderboardRepository: leaderboardRepository),
);
expect(find.byType(LandingPage), findsOneWidget); expect(find.byType(LandingPage), findsOneWidget);
}); });
}); });

File diff suppressed because one or more lines are too long

@ -0,0 +1,11 @@
if (typeof firebase === 'undefined') throw new Error('hosting/init-error: Firebase SDK not detected. You must include it before /__/firebase/init.js');
firebase.initializeApp({
"apiKey": "API_KEY",
"appId": "APP_ID",
"authDomain": "AUTH_DOMAIN",
"databaseURL": "",
"measurementId": "MEASUREMENT_ID",
"messagingSenderId": "MEASUREMENT_SENDER_ID",
"projectId": "PROJECT_ID",
"storageBucket": "STORAGE_BUCKET"
});

@ -32,6 +32,8 @@
<title>Pinball</title> <title>Pinball</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<script src="/__/firebase/8.9.1/firebase-app.js"></script>
<script src="/__/firebase/init.js"></script>
</head> </head>
<body> <body>

Loading…
Cancel
Save