Merge branch 'feat/fixed-positioning' of github.com:VGVentures/pinball into feat/fixed-positioning

pull/70/head
Erick Zanardo 4 years ago
commit 9850f263ec

@ -3,7 +3,7 @@ import 'package:pinball/game/game.dart';
/// {@template board} /// {@template board}
/// The main flat surface of the [PinballGame], where the [Flipper]s, /// The main flat surface of the [PinballGame], where the [Flipper]s,
/// [RoundBumper]s, [SlingShot]s are arranged. /// [RoundBumper]s, [Kicker]s are arranged.
/// {entemplate} /// {entemplate}
class Board extends Component { class Board extends Component {
/// {@macro board} /// {@macro board}
@ -74,7 +74,7 @@ class _FlutterForest extends Component {
/// {@template bottom_group} /// {@template bottom_group}
/// Grouping of the board's bottom [Component]s. /// Grouping of the board's bottom [Component]s.
/// ///
/// The [_BottomGroup] consists of[Flipper]s, [Baseboard]s and [SlingShot]s. /// The [_BottomGroup] consists of[Flipper]s, [Baseboard]s and [Kicker]s.
/// {@endtemplate} /// {@endtemplate}
// TODO(alestiago): Consider renaming once entire Board is defined. // TODO(alestiago): Consider renaming once entire Board is defined.
class _BottomGroup extends Component { class _BottomGroup extends Component {
@ -92,7 +92,7 @@ class _BottomGroup extends Component {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
final spacing = this.spacing + Flipper.width / 2; final spacing = this.spacing + Flipper.size.x / 2;
final rightSide = _BottomGroupSide( final rightSide = _BottomGroupSide(
side: BoardSide.right, side: BoardSide.right,
position: position + Vector2(spacing, 0), position: position + Vector2(spacing, 0),
@ -133,17 +133,17 @@ class _BottomGroupSide extends Component {
final baseboard = Baseboard(side: _side) final baseboard = Baseboard(side: _side)
..initialPosition = _position + ..initialPosition = _position +
Vector2( Vector2(
(Flipper.width * direction) - direction, (Flipper.size.x * direction) - direction,
Flipper.height, Flipper.size.y,
); );
final slingShot = SlingShot( final kicker = Kicker(
side: _side, side: _side,
)..initialPosition = _position + )..initialPosition = _position +
Vector2( Vector2(
(Flipper.width) * direction, (Flipper.size.x) * direction,
Flipper.height + SlingShot.size.y, Flipper.size.y + Kicker.size.y,
); );
await addAll([flipper, baseboard, slingShot]); await addAll([flipper, baseboard, kicker]);
} }
} }

@ -3,7 +3,7 @@ import 'package:pinball/game/game.dart';
/// Indicates a side of the board. /// Indicates a side of the board.
/// ///
/// Usually used to position or mirror elements of a [PinballGame]; such as a /// Usually used to position or mirror elements of a [PinballGame]; such as a
/// [Flipper] or [SlingShot]. /// [Flipper] or [Kicker].
enum BoardSide { enum BoardSide {
/// The left side of the board. /// The left side of the board.
left, left,

@ -7,6 +7,7 @@ export 'flipper.dart';
export 'initial_position.dart'; export 'initial_position.dart';
export 'jetpack_ramp.dart'; export 'jetpack_ramp.dart';
export 'joint_anchor.dart'; export 'joint_anchor.dart';
export 'kicker.dart';
export 'launcher_ramp.dart'; export 'launcher_ramp.dart';
export 'layer.dart'; export 'layer.dart';
export 'pathway.dart'; export 'pathway.dart';
@ -14,6 +15,5 @@ export 'plunger.dart';
export 'ramp_opening.dart'; export 'ramp_opening.dart';
export 'round_bumper.dart'; export 'round_bumper.dart';
export 'score_points.dart'; export 'score_points.dart';
export 'sling_shot.dart';
export 'spaceship.dart'; export 'spaceship.dart';
export 'wall.dart'; export 'wall.dart';

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -58,11 +57,8 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
/// Sprite is preloaded by [PinballGameAssetsX]. /// Sprite is preloaded by [PinballGameAssetsX].
static const spritePath = 'components/flipper.png'; static const spritePath = 'components/flipper.png';
/// The width of the [Flipper]. /// The size of the [Flipper].
static const width = 12.0; static final size = Vector2(12, 2.8);
/// The height of the [Flipper].
static const height = 2.8;
/// The speed required to move the [Flipper] to its highest position. /// The speed required to move the [Flipper] to its highest position.
/// ///
@ -97,7 +93,7 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
final sprite = await gameRef.loadSprite(spritePath); final sprite = await gameRef.loadSprite(spritePath);
final spriteComponent = SpriteComponent( final spriteComponent = SpriteComponent(
sprite: sprite, sprite: sprite,
size: Vector2(width, height), size: size,
anchor: Anchor.center, anchor: Anchor.center,
); );
@ -134,21 +130,21 @@ class Flipper extends BodyComponent with KeyboardHandler, InitialPosition {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
final isLeft = side.isLeft; final isLeft = side.isLeft;
final bigCircleShape = CircleShape()..radius = height / 2; final bigCircleShape = CircleShape()..radius = 1.75;
bigCircleShape.position.setValues( bigCircleShape.position.setValues(
isLeft isLeft
? -(width / 2) + bigCircleShape.radius ? -(size.x / 2) + bigCircleShape.radius
: (width / 2) - bigCircleShape.radius, : (size.x / 2) - bigCircleShape.radius,
0, 0,
); );
final bigCircleFixtureDef = FixtureDef(bigCircleShape); final bigCircleFixtureDef = FixtureDef(bigCircleShape);
fixturesDef.add(bigCircleFixtureDef); fixturesDef.add(bigCircleFixtureDef);
final smallCircleShape = CircleShape()..radius = bigCircleShape.radius / 2; final smallCircleShape = CircleShape()..radius = 0.9;
smallCircleShape.position.setValues( smallCircleShape.position.setValues(
isLeft isLeft
? (width / 2) - smallCircleShape.radius ? (size.x / 2) - smallCircleShape.radius
: -(width / 2) + smallCircleShape.radius, : -(size.x / 2) + smallCircleShape.radius,
0, 0,
); );
final smallCircleFixtureDef = FixtureDef(smallCircleShape); final smallCircleFixtureDef = FixtureDef(smallCircleShape);
@ -227,8 +223,8 @@ class FlipperAnchor extends JointAnchor {
}) { }) {
initialPosition = Vector2( initialPosition = Vector2(
flipper.side.isLeft flipper.side.isLeft
? flipper.body.position.x - Flipper.width / 2 ? flipper.body.position.x - Flipper.size.x / 2
: flipper.body.position.x + Flipper.width / 2, : flipper.body.position.x + Flipper.size.x / 2,
flipper.body.position.y, flipper.body.position.y,
); );
} }

@ -6,15 +6,15 @@ import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' as geometry show centroid; import 'package:geometry/geometry.dart' as geometry show centroid;
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
/// {@template sling_shot} /// {@template kicker}
/// Triangular [BodyType.static] body that propels the [Ball] towards the /// Triangular [BodyType.static] body that propels the [Ball] towards the
/// opposite side. /// opposite side.
/// ///
/// [SlingShot]s are usually positioned above each [Flipper]. /// [Kicker]s are usually positioned above each [Flipper].
/// {@endtemplate sling_shot} /// {@endtemplate kicker}
class SlingShot extends BodyComponent with InitialPosition { class Kicker extends BodyComponent with InitialPosition {
/// {@macro sling_shot} /// {@macro kicker}
SlingShot({ Kicker({
required BoardSide side, required BoardSide side,
}) : _side = side { }) : _side = side {
// TODO(alestiago): Use sprite instead of color when provided. // TODO(alestiago): Use sprite instead of color when provided.
@ -23,14 +23,14 @@ class SlingShot extends BodyComponent with InitialPosition {
..style = PaintingStyle.fill; ..style = PaintingStyle.fill;
} }
/// Whether the [SlingShot] is on the left or right side of the board. /// Whether the [Kicker] is on the left or right side of the board.
/// ///
/// A [SlingShot] with [BoardSide.left] propels the [Ball] to the right, /// A [Kicker] with [BoardSide.left] propels the [Ball] to the right,
/// whereas a [SlingShot] with [BoardSide.right] propels the [Ball] to the /// whereas a [Kicker] with [BoardSide.right] propels the [Ball] to the
/// left. /// left.
final BoardSide _side; final BoardSide _side;
/// The size of the [SlingShot] body. /// The size of the [Kicker] body.
// TODO(alestiago): Use size from PositionedBodyComponent instead, // TODO(alestiago): Use size from PositionedBodyComponent instead,
// once a sprite is given. // once a sprite is given.
static final Vector2 size = Vector2(4, 10); static final Vector2 size = Vector2(4, 10);
@ -78,7 +78,7 @@ class SlingShot extends BodyComponent with InitialPosition {
final bottomLineFixtureDef = FixtureDef(bottomEdge)..friction = 0; final bottomLineFixtureDef = FixtureDef(bottomEdge)..friction = 0;
fixturesDefs.add(bottomLineFixtureDef); fixturesDefs.add(bottomLineFixtureDef);
final kickerEdge = EdgeShape() final bouncyEdge = EdgeShape()
..set( ..set(
upperCircle.position + upperCircle.position +
Vector2( Vector2(
@ -92,11 +92,11 @@ class SlingShot extends BodyComponent with InitialPosition {
), ),
); );
final kickerFixtureDef = FixtureDef(kickerEdge) final bouncyFixtureDef = FixtureDef(bouncyEdge)
// TODO(alestiago): Play with restitution value once game is bundled. // TODO(alestiago): Play with restitution value once game is bundled.
..restitution = 10.0 ..restitution = 10.0
..friction = 0; ..friction = 0;
fixturesDefs.add(kickerFixtureDef); fixturesDefs.add(bouncyFixtureDef);
// TODO(alestiago): Evaluate if there is value on centering the fixtures. // TODO(alestiago): Evaluate if there is value on centering the fixtures.
final centroid = geometry.centroid( final centroid = geometry.centroid(

@ -1,4 +1,4 @@
import 'package:flame/input.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';

@ -0,0 +1,64 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
part 'leaderboard_event.dart';
part 'leaderboard_state.dart';
/// {@template leaderboard_bloc}
/// Manages leaderboard events.
///
/// Uses a [LeaderboardRepository] to request and update players participations.
/// {@endtemplate}
class LeaderboardBloc extends Bloc<LeaderboardEvent, LeaderboardState> {
/// {@macro leaderboard_bloc}
LeaderboardBloc(this._leaderboardRepository)
: super(const LeaderboardState.initial()) {
on<Top10Fetched>(_onTop10Fetched);
on<LeaderboardEntryAdded>(_onLeaderboardEntryAdded);
}
final LeaderboardRepository _leaderboardRepository;
Future<void> _onTop10Fetched(
Top10Fetched event,
Emitter<LeaderboardState> emit,
) async {
emit(state.copyWith(status: LeaderboardStatus.loading));
try {
final top10Leaderboard =
await _leaderboardRepository.fetchTop10Leaderboard();
emit(
state.copyWith(
status: LeaderboardStatus.success,
leaderboard: top10Leaderboard,
),
);
} catch (error) {
emit(state.copyWith(status: LeaderboardStatus.error));
addError(error);
}
}
Future<void> _onLeaderboardEntryAdded(
LeaderboardEntryAdded event,
Emitter<LeaderboardState> emit,
) async {
emit(state.copyWith(status: LeaderboardStatus.loading));
try {
final ranking =
await _leaderboardRepository.addLeaderboardEntry(event.entry);
emit(
state.copyWith(
status: LeaderboardStatus.success,
ranking: ranking,
),
);
} catch (error) {
emit(state.copyWith(status: LeaderboardStatus.error));
addError(error);
}
}
}

@ -0,0 +1,36 @@
part of 'leaderboard_bloc.dart';
/// {@template leaderboard_event}
/// Represents the events available for [LeaderboardBloc].
/// {endtemplate}
abstract class LeaderboardEvent extends Equatable {
/// {@macro leaderboard_event}
const LeaderboardEvent();
}
/// {@template top_10_fetched}
/// Request the top 10 [LeaderboardEntry]s.
/// {endtemplate}
class Top10Fetched extends LeaderboardEvent {
/// {@macro top_10_fetched}
const Top10Fetched();
@override
List<Object?> get props => [];
}
/// {@template leaderboard_entry_added}
/// Writes a new [LeaderboardEntry].
///
/// Should be added when a player finishes a game.
/// {endtemplate}
class LeaderboardEntryAdded extends LeaderboardEvent {
/// {@macro leaderboard_entry_added}
const LeaderboardEntryAdded({required this.entry});
/// [LeaderboardEntry] to be written to the remote storage.
final LeaderboardEntry entry;
@override
List<Object?> get props => [entry];
}

@ -0,0 +1,59 @@
// ignore_for_file: public_member_api_docs
part of 'leaderboard_bloc.dart';
/// Defines the request status.
enum LeaderboardStatus {
/// Request is being loaded.
loading,
/// Request was processed successfully and received a valid response.
success,
/// Request was processed unsuccessfully and received an error.
error,
}
/// {@template leaderboard_state}
/// Represents the state of the leaderboard.
/// {@endtemplate}
class LeaderboardState extends Equatable {
/// {@macro leaderboard_state}
const LeaderboardState({
required this.status,
required this.ranking,
required this.leaderboard,
});
const LeaderboardState.initial()
: status = LeaderboardStatus.loading,
ranking = const LeaderboardRanking(
ranking: 0,
outOf: 0,
),
leaderboard = const [];
/// The current [LeaderboardStatus] of the state.
final LeaderboardStatus status;
/// Rank of the current player.
final LeaderboardRanking ranking;
/// List of top-ranked players.
final List<LeaderboardEntry> leaderboard;
@override
List<Object> get props => [status, ranking, leaderboard];
LeaderboardState copyWith({
LeaderboardStatus? status,
LeaderboardRanking? ranking,
List<LeaderboardEntry>? leaderboard,
}) {
return LeaderboardState(
status: status ?? this.status,
ranking: ranking ?? this.ranking,
leaderboard: leaderboard ?? this.leaderboard,
);
}
}

@ -0,0 +1 @@
export 'bloc/leaderboard_bloc.dart';

@ -182,21 +182,21 @@ packages:
name: flame name: flame
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0-releasecandidate.5" version: "1.1.0-releasecandidate.6"
flame_bloc: flame_bloc:
dependency: "direct main" dependency: "direct main"
description: description:
name: flame_bloc name: flame_bloc
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0-releasecandidate.5" version: "1.2.0-releasecandidate.6"
flame_forge2d: flame_forge2d:
dependency: "direct main" dependency: "direct main"
description: description:
name: flame_forge2d name: flame_forge2d
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.0-releasecandidate.5" version: "0.9.0-releasecandidate.6"
flame_test: flame_test:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -237,7 +237,7 @@ packages:
name: forge2d name: forge2d
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.2" version: "0.9.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:

@ -10,9 +10,9 @@ dependencies:
bloc: ^8.0.2 bloc: ^8.0.2
cloud_firestore: ^3.1.10 cloud_firestore: ^3.1.10
equatable: ^2.0.3 equatable: ^2.0.3
flame: ^1.1.0-releasecandidate.5 flame: ^1.1.0-releasecandidate.6
flame_bloc: ^1.2.0-releasecandidate.5 flame_bloc: ^1.2.0-releasecandidate.6
flame_forge2d: ^0.9.0-releasecandidate.5 flame_forge2d: ^0.9.0-releasecandidate.6
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: ^8.0.1 flutter_bloc: ^8.0.1

@ -65,14 +65,14 @@ void main() {
); );
flameTester.test( flameTester.test(
'has two SlingShots', 'has two Kickers',
(game) async { (game) async {
final board = Board(); final board = Board();
await game.ready(); await game.ready();
await game.ensureAdd(board); await game.ensureAdd(board);
final slingShots = board.findNestedChildren<SlingShot>(); final kickers = board.findNestedChildren<Kicker>();
expect(slingShots.length, equals(2)); expect(kickers.length, equals(2));
}, },
); );

@ -282,7 +282,7 @@ void main() {
final flipperAnchor = FlipperAnchor(flipper: flipper); final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor); await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(-Flipper.width / 2)); expect(flipperAnchor.body.position.x, equals(-Flipper.size.x / 2));
}, },
); );
@ -297,7 +297,7 @@ void main() {
final flipperAnchor = FlipperAnchor(flipper: flipper); final flipperAnchor = FlipperAnchor(flipper: flipper);
await game.ensureAdd(flipperAnchor); await game.ensureAdd(flipperAnchor);
expect(flipperAnchor.body.position.x, equals(Flipper.width / 2)); expect(flipperAnchor.body.position.x, equals(Flipper.size.x / 2));
}, },
); );
}); });

@ -6,43 +6,43 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
void main() { void main() {
group('SlingShot', () { group('Kicker', () {
// TODO(alestiago): Include golden tests for left and right. // TODO(alestiago): Include golden tests for left and right.
final flameTester = FlameTester(Forge2DGame.new); final flameTester = FlameTester(Forge2DGame.new);
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
final slingShot = SlingShot( final kicker = Kicker(
side: BoardSide.left, side: BoardSide.left,
); );
await game.ensureAdd(slingShot); await game.ensureAdd(kicker);
expect(game.contains(slingShot), isTrue); expect(game.contains(kicker), isTrue);
}, },
); );
flameTester.test( flameTester.test(
'body is static', 'body is static',
(game) async { (game) async {
final slingShot = SlingShot( final kicker = Kicker(
side: BoardSide.left, side: BoardSide.left,
); );
await game.ensureAdd(slingShot); await game.ensureAdd(kicker);
expect(slingShot.body.bodyType, equals(BodyType.static)); expect(kicker.body.bodyType, equals(BodyType.static));
}, },
); );
flameTester.test( flameTester.test(
'has restitution', 'has restitution',
(game) async { (game) async {
final slingShot = SlingShot( final kicker = Kicker(
side: BoardSide.left, side: BoardSide.left,
); );
await game.ensureAdd(slingShot); await game.ensureAdd(kicker);
final totalRestitution = slingShot.body.fixtures.fold<double>( final totalRestitution = kicker.body.fixtures.fold<double>(
0, 0,
(total, fixture) => total + fixture.restitution, (total, fixture) => total + fixture.restitution,
); );
@ -53,12 +53,12 @@ void main() {
flameTester.test( flameTester.test(
'has no friction', 'has no friction',
(game) async { (game) async {
final slingShot = SlingShot( final kicker = Kicker(
side: BoardSide.left, side: BoardSide.left,
); );
await game.ensureAdd(slingShot); await game.ensureAdd(kicker);
final totalFriction = slingShot.body.fixtures.fold<double>( final totalFriction = kicker.body.fixtures.fold<double>(
0, 0,
(total, fixture) => total + fixture.friction, (total, fixture) => total + fixture.friction,
); );

@ -0,0 +1,166 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
class MockLeaderboardRepository extends Mock implements LeaderboardRepository {}
void main() {
group('LeaderboardBloc', () {
late LeaderboardRepository leaderboardRepository;
setUp(() {
leaderboardRepository = MockLeaderboardRepository();
});
test('initial state has state loading no ranking and empty leaderboard',
() {
final bloc = LeaderboardBloc(leaderboardRepository);
expect(bloc.state.status, equals(LeaderboardStatus.loading));
expect(bloc.state.ranking.ranking, equals(0));
expect(bloc.state.ranking.outOf, equals(0));
expect(bloc.state.leaderboard.isEmpty, isTrue);
});
group('Top10Fetched', () {
const 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();
blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, success] statuses '
'when fetchTop10Leaderboard succeeds',
setUp: () {
when(() => leaderboardRepository.fetchTop10Leaderboard()).thenAnswer(
(_) async => top10Leaderboard,
);
},
build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(Top10Fetched()),
expect: () => [
LeaderboardState.initial(),
isA<LeaderboardState>()
..having(
(element) => element.status,
'status',
equals(LeaderboardStatus.success),
)
..having(
(element) => element.leaderboard.length,
'leaderboard',
equals(top10Leaderboard.length),
)
],
verify: (_) =>
verify(() => leaderboardRepository.fetchTop10Leaderboard())
.called(1),
);
blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, error] statuses '
'when fetchTop10Leaderboard fails',
setUp: () {
when(() => leaderboardRepository.fetchTop10Leaderboard()).thenThrow(
Exception(),
);
},
build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(Top10Fetched()),
expect: () => <LeaderboardState>[
LeaderboardState.initial(),
LeaderboardState.initial().copyWith(status: LeaderboardStatus.error),
],
verify: (_) =>
verify(() => leaderboardRepository.fetchTop10Leaderboard())
.called(1),
errors: () => [isA<Exception>()],
);
});
group('LeaderboardEntryAdded', () {
final leaderboardEntry = LeaderboardEntry(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
final ranking = LeaderboardRanking(ranking: 3, outOf: 4);
blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, success] statuses '
'when addLeaderboardEntry succeeds',
setUp: () {
when(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).thenAnswer(
(_) async => ranking,
);
},
build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardEntryAdded(entry: leaderboardEntry)),
expect: () => [
LeaderboardState.initial(),
isA<LeaderboardState>()
..having(
(element) => element.status,
'status',
equals(LeaderboardStatus.success),
)
..having(
(element) => element.ranking,
'ranking',
equals(ranking),
)
],
verify: (_) => verify(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).called(1),
);
blocTest<LeaderboardBloc, LeaderboardState>(
'emits [loading, error] statuses '
'when addLeaderboardEntry fails',
setUp: () {
when(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).thenThrow(
Exception(),
);
},
build: () => LeaderboardBloc(leaderboardRepository),
act: (bloc) => bloc.add(LeaderboardEntryAdded(entry: leaderboardEntry)),
expect: () => <LeaderboardState>[
LeaderboardState.initial(),
LeaderboardState.initial().copyWith(status: LeaderboardStatus.error),
],
verify: (_) => verify(
() => leaderboardRepository.addLeaderboardEntry(leaderboardEntry),
).called(1),
errors: () => [isA<Exception>()],
);
});
});
}

@ -0,0 +1,41 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
void main() {
group('GameEvent', () {
group('Top10Fetched', () {
test('can be instantiated', () {
expect(const Top10Fetched(), isNotNull);
});
test('supports value equality', () {
expect(
Top10Fetched(),
equals(const Top10Fetched()),
);
});
});
group('LeaderboardEntryAdded', () {
const leaderboardEntry = LeaderboardEntry(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
test('can be instantiated', () {
expect(const LeaderboardEntryAdded(entry: leaderboardEntry), isNotNull);
});
test('supports value equality', () {
expect(
LeaderboardEntryAdded(entry: leaderboardEntry),
equals(const LeaderboardEntryAdded(entry: leaderboardEntry)),
);
});
});
});
}

@ -0,0 +1,70 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter_test/flutter_test.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
void main() {
group('LeaderboardState', () {
test('supports value equality', () {
expect(
LeaderboardState.initial(),
equals(
LeaderboardState.initial(),
),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(
LeaderboardState.initial(),
isNotNull,
);
});
});
group('copyWith', () {
const leaderboardEntry = LeaderboardEntry(
playerInitials: 'ABC',
score: 1500,
character: CharacterType.dash,
);
test(
'copies correctly '
'when no argument specified',
() {
const leaderboardState = LeaderboardState.initial();
expect(
leaderboardState.copyWith(),
equals(leaderboardState),
);
},
);
test(
'copies correctly '
'when all arguments specified',
() {
const leaderboardState = LeaderboardState.initial();
final otherLeaderboardState = LeaderboardState(
status: LeaderboardStatus.success,
ranking: LeaderboardRanking(ranking: 0, outOf: 0),
leaderboard: const [leaderboardEntry],
);
expect(leaderboardState, isNot(equals(otherLeaderboardState)));
expect(
leaderboardState.copyWith(
status: otherLeaderboardState.status,
ranking: otherLeaderboardState.ranking,
leaderboard: otherLeaderboardState.leaderboard,
),
equals(otherLeaderboardState),
);
},
);
});
});
}
Loading…
Cancel
Save