refactor: moved ramp sensor cubit to spaceship ramp

pull/296/head
RuiAlonso 3 years ago
parent ae687c4913
commit 96cc57b233

@ -1,28 +0,0 @@
// ignore_for_file: public_member_api_docs
import 'package:bloc/bloc.dart';
import 'package:pinball_components/pinball_components.dart';
part 'ramp_sensor_state.dart';
class RampSensorCubit extends Cubit<RampSensorState> {
RampSensorCubit() : super(const RampSensorState.initial());
void onDoor(Ball ball) {
emit(
state.copyWith(
type: RampSensorType.door,
ball: ball,
),
);
}
void onInside(Ball ball) {
emit(
state.copyWith(
type: RampSensorType.inside,
ball: ball,
),
);
}
}

@ -1,35 +0,0 @@
// ignore_for_file: public_member_api_docs
part of 'ramp_sensor_cubit.dart';
/// Used to know when a [Ball] gets into the [SpaceshipRamp] against every ball
/// that crosses the opening.
enum RampSensorType {
/// Sensor at the entrance of the opening.
door,
/// Sensor inside the [SpaceshipRamp].
inside,
}
class RampSensorState {
const RampSensorState({
required this.type,
this.ball,
});
const RampSensorState.initial() : this(type: RampSensorType.door);
final RampSensorType type;
final Ball? ball;
RampSensorState copyWith({
RampSensorType? type,
Ball? ball,
}) {
return RampSensorState(
type: type ?? this.type,
ball: ball ?? this.ball,
);
}
}

@ -0,0 +1,43 @@
// ignore_for_file: public_member_api_docs
import 'dart:collection';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_components/pinball_components.dart';
part 'spaceship_ramp_state.dart';
class SpaceshipRampCubit extends Cubit<SpaceshipRampState> {
SpaceshipRampCubit() : super(SpaceshipRampState.initial());
void onDoor(Ball ball) {
if (!state.balls.contains(ball)) {
emit(
state.copyWith(
balls: state.balls..add(ball),
status: SpaceshipRampStatus.withoutBonus,
),
);
}
}
void onInside(Ball ball) {
if (state.balls.contains(ball)) {
final hits = state.hits + 1;
final bonus = (hits % 10 == 0)
? SpaceshipRampStatus.withBonus
: SpaceshipRampStatus.withoutBonus;
final shot = hits % 10 != 0;
emit(
state.copyWith(
hits: hits,
balls: state.balls..remove(ball),
shot: shot,
status: bonus,
),
);
}
}
}

@ -0,0 +1,57 @@
// ignore_for_file: public_member_api_docs
part of 'spaceship_ramp_cubit.dart';
/// Used to know when a [Ball] gets into the [SpaceshipRamp] against every ball
/// that crosses the opening.
enum RampSensorType {
/// Sensor at the entrance of the opening.
door,
/// Sensor inside the [SpaceshipRamp].
inside,
}
enum SpaceshipRampStatus {
withoutBonus,
withBonus,
}
class SpaceshipRampState extends Equatable {
const SpaceshipRampState({
required this.hits,
required this.balls,
required this.shot,
required this.status,
}) : assert(hits >= 0, "Hits can't be negative");
SpaceshipRampState.initial()
: this(
hits: 0,
balls: HashSet(),
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
final int hits;
final Set<Ball> balls;
final bool shot;
final SpaceshipRampStatus status;
SpaceshipRampState copyWith({
int? hits,
Set<Ball>? balls,
bool? shot,
SpaceshipRampStatus? status,
}) {
return SpaceshipRampState(
hits: hits ?? this.hits,
balls: balls ?? this.balls,
shot: shot ?? this.shot,
status: status ?? this.status,
);
}
@override
List<Object?> get props => [hits, balls, shot, status];
}

@ -8,7 +8,7 @@ import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/src/components/spaceship_ramp/behavior/behavior.dart'; import 'package:pinball_components/src/components/spaceship_ramp/behavior/behavior.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
export 'cubit/ramp_sensor_cubit.dart'; export 'cubit/spaceship_ramp_cubit.dart';
/// {@template spaceship_ramp} /// {@template spaceship_ramp}
/// Ramp leading into the [AndroidSpaceship]. /// Ramp leading into the [AndroidSpaceship].
@ -17,14 +17,23 @@ class SpaceshipRamp extends Component {
/// {@macro spaceship_ramp} /// {@macro spaceship_ramp}
SpaceshipRamp({ SpaceshipRamp({
Iterable<Component>? children, Iterable<Component>? children,
}) : super( }) : bloc = SpaceshipRampCubit(),
super(
children: [ children: [
// TODO(ruimiguel): refactor RampSensor and RampOpening to be in // TODO(ruimiguel): refactor RampSensor and RampOpening to be in
// only one sensor. // only one sensor.
RampSensor(type: RampSensorType.door) RampSensor(
..initialPosition = Vector2(1.7, -20.4), type: RampSensorType.door,
RampSensor(type: RampSensorType.inside) children: [
..initialPosition = Vector2(1.7, -22), RampContactBehavior(),
],
)..initialPosition = Vector2(1.7, -20.4),
RampSensor(
type: RampSensorType.inside,
children: [
RampContactBehavior(),
],
)..initialPosition = Vector2(1.7, -22),
_SpaceshipRampOpening( _SpaceshipRampOpening(
outsidePriority: ZIndexes.ballOnBoard, outsidePriority: ZIndexes.ballOnBoard,
rotation: math.pi, rotation: math.pi,
@ -53,7 +62,21 @@ class SpaceshipRamp extends Component {
/// ///
/// This can be used for testing [SpaceshipRamp]'s behaviors in isolation. /// This can be used for testing [SpaceshipRamp]'s behaviors in isolation.
@visibleForTesting @visibleForTesting
SpaceshipRamp.test(); SpaceshipRamp.test({
required this.bloc,
Iterable<Component>? children,
}) : super(children: children);
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final SpaceshipRampCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
/// Forwards the sprite to the next [SpaceshipRampArrowSpriteState]. /// Forwards the sprite to the next [SpaceshipRampArrowSpriteState].
/// ///
@ -398,13 +421,12 @@ class _SpaceshipRampOpening extends LayerSensor {
class RampSensor extends BodyComponent class RampSensor extends BodyComponent
with ParentIsA<SpaceshipRamp>, InitialPosition, Layered { with ParentIsA<SpaceshipRamp>, InitialPosition, Layered {
/// {@macro ramp_sensor} /// {@macro ramp_sensor}
RampSensor({required this.type}) RampSensor({
: bloc = RampSensorCubit(), required this.type,
super( Iterable<Component>? children,
children: [ }) : super(
RampContactBehavior(), children: children,
], renderBody: false,
renderBody: true,
) { ) {
layer = Layer.spaceshipEntranceRamp; layer = Layer.spaceshipEntranceRamp;
} }
@ -414,23 +436,11 @@ class RampSensor extends BodyComponent
@visibleForTesting @visibleForTesting
RampSensor.test({ RampSensor.test({
required this.type, required this.type,
required this.bloc,
}); });
/// Type for the sensor, to know if it's the one at the door or inside ramp. /// Type for the sensor, to know if it's the one at the door or inside ramp.
final RampSensorType type; final RampSensorType type;
// TODO(alestiago): Consider refactoring once the following is merged:
// https://github.com/flame-engine/flame/pull/1538
// ignore: public_member_api_docs
final RampSensorCubit bloc;
@override
void onRemove() {
bloc.close();
super.onRemove();
}
@override @override
Body createBody() { Body createBody() {
final shape = PolygonShape() final shape = PolygonShape()

@ -1,66 +0,0 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('RampSensorCubit', () {
final ball = Ball(baseColor: Colors.red);
blocTest<RampSensorCubit, RampSensorState>(
'onDoor emits [door]',
build: RampSensorCubit.new,
act: (bloc) => bloc.onDoor(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onDoor twice emits [door, door]',
build: RampSensorCubit.new,
act: (bloc) => bloc
..onDoor(ball)
..onDoor(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.door)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onInside emits [inside]',
build: RampSensorCubit.new,
act: (bloc) => bloc.onInside(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
],
);
blocTest<RampSensorCubit, RampSensorState>(
'onInside twice emits [inside,inside]',
build: RampSensorCubit.new,
act: (bloc) => bloc
..onInside(ball)
..onInside(ball),
expect: () => [
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
isA<RampSensorState>()
..having((state) => state.type, 'type', RampSensorType.inside)
..having((state) => state.ball, 'ball', ball),
],
);
});
}

@ -1,110 +0,0 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('RampSensorState', () {
test('same states are different even though they have same content', () {
final ball = Ball(baseColor: Colors.red);
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: ball,
);
final otherRampSensorState = RampSensorState(
type: RampSensorType.door,
ball: ball,
);
expect(
rampSensorState,
isNot(equals(otherRampSensorState)),
);
expect(
rampSensorState.type,
equals(otherRampSensorState.type),
);
expect(
rampSensorState.ball,
equals(otherRampSensorState.ball),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(
RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
),
isNotNull,
);
});
});
group('copyWith', () {
test(
'copies correctly '
'when no argument specified',
() {
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
);
final copiedRampSensorState = rampSensorState.copyWith();
expect(
copiedRampSensorState.type,
equals(rampSensorState.type),
);
expect(
copiedRampSensorState.ball,
equals(rampSensorState.ball),
);
},
);
test(
'copies correctly '
'when all arguments specified',
() {
final ball = Ball(baseColor: Colors.blue);
final rampSensorState = RampSensorState(
type: RampSensorType.door,
ball: Ball(baseColor: Colors.red),
);
final otherRampSensorState = RampSensorState(
type: RampSensorType.inside,
ball: ball,
);
final copiedRampSensorState = rampSensorState.copyWith(
type: RampSensorType.inside,
ball: ball,
);
expect(
rampSensorState,
isNot(equals(otherRampSensorState)),
);
expect(
copiedRampSensorState.type,
equals(otherRampSensorState.type),
);
expect(
copiedRampSensorState.ball,
equals(otherRampSensorState.ball),
);
},
);
});
});
}

@ -0,0 +1,98 @@
// ignore_for_file: prefer_const_constructors
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('SpaceshipRampCubit', () {
final ball = Ball(baseColor: Colors.red);
group('onDoor', () {
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits nothing if contains ball',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
balls: {ball},
),
act: (bloc) => bloc.onDoor(ball),
expect: () => <SpaceshipRampState>[],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits [{ball}, withoutBonus] if not contains ball',
build: SpaceshipRampCubit.new,
act: (bloc) => bloc.onDoor(ball),
expect: () => [
isA<SpaceshipRampState>()
..having(
(state) => state.balls,
'balls',
contains(ball),
)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withoutBonus,
),
],
);
});
group('onInside', () {
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits nothing if not contains ball',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
balls: {},
),
act: (bloc) => bloc.onInside(ball),
expect: () => <SpaceshipRampState>[],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits withoutBonus if contains ball '
'and hits less than 10 times',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
hits: 5,
balls: {ball},
),
act: (bloc) => bloc.onInside(ball),
expect: () => [
isA<SpaceshipRampState>()
..having((state) => state.hits, 'hits', 6)
..having((state) => state.balls, 'balls', isNot(contains(ball)))
..having((state) => state.shot, 'shot', true)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withoutBonus,
),
],
);
blocTest<SpaceshipRampCubit, SpaceshipRampState>(
'emits withBonus if contains ball and hits 10 times',
build: SpaceshipRampCubit.new,
seed: () => SpaceshipRampState.initial().copyWith(
hits: 9,
balls: {ball},
),
act: (bloc) => bloc.onInside(ball),
expect: () => [
isA<SpaceshipRampState>()
..having((state) => state.hits, 'hits', 10)
..having((state) => state.balls, 'balls', contains(ball))
..having((state) => state.shot, 'shot', false)
..having(
(state) => state.status,
'status',
SpaceshipRampStatus.withBonus,
),
],
);
});
});
}

@ -0,0 +1,126 @@
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/src/components/components.dart';
void main() {
group('SpaceshipRampState', () {
test('supports value equality', () {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
equals(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
),
);
});
group('constructor', () {
test('can be instantiated', () {
expect(
SpaceshipRampState(
hits: 0,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
isNotNull,
);
});
});
test(
'throws AssertionError '
'when hits is negative',
() {
expect(
() => SpaceshipRampState(
hits: -1,
balls: const {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
),
throwsAssertionError,
);
},
);
group('copyWith', () {
test(
'throws AssertionError '
'when hits is decreased',
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
() => rampState.copyWith(hits: rampState.hits - 1),
throwsAssertionError,
);
},
);
test(
'copies correctly '
'when no argument specified',
() {
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
expect(
rampState.copyWith(),
equals(rampState),
);
},
);
test(
'copies correctly '
'when all arguments specified',
() {
final ball = Ball(baseColor: Colors.black);
const rampState = SpaceshipRampState(
hits: 0,
balls: {},
shot: false,
status: SpaceshipRampStatus.withoutBonus,
);
final otherRampState = SpaceshipRampState(
hits: rampState.hits + 1,
balls: {ball},
shot: true,
status: SpaceshipRampStatus.withBonus,
);
expect(rampState, isNot(equals(otherRampState)));
expect(
rampState.copyWith(
hits: rampState.hits + 1,
balls: {ball},
shot: true,
status: SpaceshipRampStatus.withBonus,
),
equals(otherRampState),
);
},
);
});
});
}

@ -8,7 +8,7 @@ import 'package:pinball_components/pinball_components.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
class MockRampSensorCubit extends Mock implements RampSensorCubit {} class _MockSpaceshipRampCubit extends Mock implements SpaceshipRampCubit {}
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
@ -22,27 +22,23 @@ void main() {
]; ];
final flameTester = FlameTester(() => TestGame(assets)); final flameTester = FlameTester(() => TestGame(assets));
group('RampSensor', () { flameTester.test('closes bloc when removed', (game) async {
flameTester.test('closes bloc when removed', (game) async { final bloc = _MockSpaceshipRampCubit();
final bloc = MockRampSensorCubit(); whenListen(
whenListen( bloc,
bloc, const Stream<SpaceshipRampState>.empty(),
const Stream<RampSensorState>.empty(), initialState: SpaceshipRampState.initial(),
initialState: const RampSensorState.initial(), );
); when(bloc.close).thenAnswer((_) async {});
when(bloc.close).thenAnswer((_) async {});
final rampSensor = RampSensor.test( final ramp = SpaceshipRamp.test(
type: RampSensorType.door, bloc: bloc,
bloc: bloc, );
);
final parent = SpaceshipRamp.test(); await game.ensureAdd(ramp);
game.remove(ramp);
await game.ensureAdd(parent); await game.ready();
await parent.ensureAdd(rampSensor);
parent.remove(rampSensor); verify(bloc.close).called(1);
await game.ready();
verify(bloc.close).called(1);
});
}); });
} }

Loading…
Cancel
Save