diff --git a/lib/game/components/jetpack_ramp.dart b/lib/game/components/jetpack_ramp.dart index b31ac6e7..f769e39c 100644 --- a/lib/game/components/jetpack_ramp.dart +++ b/lib/game/components/jetpack_ramp.dart @@ -48,13 +48,13 @@ class JetpackRamp extends Component with HasGameRef { orientation: RampOrientation.down, rotation: radians(15), ) - ..initialPosition = position + Vector2(-11, 1) + ..initialPosition = position + Vector2(-25.7, 25) ..layer = Layer.opening; final rightOpening = JetpackRampOpening( orientation: RampOrientation.down, rotation: radians(-9), ) - ..initialPosition = position + Vector2(20.5, 3.4) + ..initialPosition = position + Vector2(-10, 26.2) ..layer = Layer.opening; await addAll([ @@ -97,13 +97,11 @@ class JetpackRampOpening extends RampOpening { RampOrientation get orientation => _orientation; @override - Shape get shape { - final area = PolygonShape()..setAsBoxXY(_size.x, _size.y); - // TODO(alestiago): Use shape.rotate() once it's implemented. - for (final vertex in area.vertices) { - vertex.rotate(_rotation); - } - - return area; - } + Shape get shape => PolygonShape() + ..setAsBox( + _size.x, + _size.y, + initialPosition, + _rotation, + ); } diff --git a/lib/game/components/launcher_ramp.dart b/lib/game/components/launcher_ramp.dart index 0c6b703d..5cf2ff95 100644 --- a/lib/game/components/launcher_ramp.dart +++ b/lib/game/components/launcher_ramp.dart @@ -42,7 +42,7 @@ class LauncherRamp extends Component with HasGameRef { ..layer = _layer; final curvedPath = Pathway.arc( color: const Color.fromARGB(255, 251, 255, 0), - center: position + Vector2(-28.8, -6), + center: position + Vector2(-24, -6), radius: _radius, angle: _angle, width: _width, diff --git a/lib/game/components/layer.dart b/lib/game/components/layer.dart index bbeedb7d..6616e781 100644 --- a/lib/game/components/layer.dart +++ b/lib/game/components/layer.dart @@ -17,13 +17,20 @@ mixin Layered on BodyComponent { _layer = value; if (!isLoaded) { // TODO(alestiago): Use loaded.whenComplete once provided. - mounted.whenComplete(() => layer = value); + mounted.whenComplete(() { + layer = value; + _applyMaskbits(); + }); } else { - for (final fixture in body.fixtures) { - fixture - ..filterData.categoryBits = layer.maskBits - ..filterData.maskBits = layer.maskBits; - } + _applyMaskbits(); + } + } + + void _applyMaskbits() { + for (final fixture in body.fixtures) { + fixture + ..filterData.categoryBits = layer.maskBits + ..filterData.maskBits = layer.maskBits; } } } diff --git a/lib/game/components/ramp_opening.dart b/lib/game/components/ramp_opening.dart index e8f926af..2032fb7c 100644 --- a/lib/game/components/ramp_opening.dart +++ b/lib/game/components/ramp_opening.dart @@ -1,6 +1,7 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; /// Indicates the orientation of a ramp entrance/exit. @@ -64,17 +65,18 @@ abstract class RampOpening extends BodyComponent with InitialPosition, Layered { class RampOpeningBallContactCallback extends ContactCallback { /// Collection of balls inside ramp pathway. - final _ballsInside = {}; + @visibleForTesting + final ballsInside = {}; @override void begin(Ball ball, Opening opening, Contact _) { late final Layer layer; - if (!_ballsInside.contains(ball)) { + if (!ballsInside.contains(ball)) { layer = opening.pathwayLayer; - _ballsInside.add(ball); + ballsInside.add(ball); } else { layer = Layer.board; - _ballsInside.remove(ball); + ballsInside.remove(ball); } ball.layer = layer; diff --git a/test/game/components/ball_test.dart b/test/game/components/ball_test.dart index 1da1bcaa..82e3672e 100644 --- a/test/game/components/ball_test.dart +++ b/test/game/components/ball_test.dart @@ -72,13 +72,13 @@ void main() { ); flameTester.test( - 'has default filter maskBits', + 'has all as default filter maskBits', (game) async { final ball = Ball(); await game.ensureAdd(ball); final fixture = ball.body.fixtures[0]; - expect(fixture.filterData.maskBits, equals(Layer.board.maskBits)); + expect(fixture.filterData.maskBits, equals(Layer.all.maskBits)); }, ); }); @@ -164,7 +164,7 @@ void main() { final fixture = ball.body.fixtures[0]; expect(fixture.filterData.categoryBits, equals(1)); - expect(fixture.filterData.maskBits, equals(Layer.board.maskBits)); + expect(fixture.filterData.maskBits, equals(Layer.all.maskBits)); ball.layer = newLayer; diff --git a/test/game/components/jetpack_ramp_test.dart b/test/game/components/jetpack_ramp_test.dart index e0037156..731d9b26 100644 --- a/test/game/components/jetpack_ramp_test.dart +++ b/test/game/components/jetpack_ramp_test.dart @@ -29,21 +29,6 @@ void main() { }, ); - group('constructor', () { - flameTester.test( - 'positions correctly', - (game) async { - final position = Vector2.all(10); - final ramp = JetpackRamp( - position: position, - ); - await game.ensureAdd(ramp); - - expect(ramp.position, equals(position)); - }, - ); - }); - group('children', () { flameTester.test( 'has only one Pathway.arc', @@ -86,9 +71,8 @@ void main() { (game) async { final position = Vector2.all(10); final ramp = JetpackRampOpening( - position: position, orientation: RampOrientation.down, - ); + )..initialPosition = position; await game.ready(); await game.ensureAdd(ramp); @@ -96,13 +80,4 @@ void main() { }, ); }); - - group('JetpackRampOpeningBallContactCallback', () { - test('has no ball inside on creation', () { - expect( - JetpackRampOpeningBallContactCallback().ballsInside, - equals({}), - ); - }); - }); } diff --git a/test/game/components/launcher_ramp_test.dart b/test/game/components/launcher_ramp_test.dart index 5aa56027..7d38f6c7 100644 --- a/test/game/components/launcher_ramp_test.dart +++ b/test/game/components/launcher_ramp_test.dart @@ -82,9 +82,8 @@ void main() { (game) async { final position = Vector2.all(10); final ramp = LauncherRampOpening( - position: position, orientation: RampOrientation.down, - ); + )..initialPosition = position; await game.ready(); await game.ensureAdd(ramp); @@ -92,13 +91,4 @@ void main() { }, ); }); - - group('LauncherRampOpeningBallContactCallback', () { - test('has no ball inside on creation', () { - expect( - LauncherRampOpeningBallContactCallback().ballsInside, - equals({}), - ); - }); - }); } diff --git a/test/game/components/layer_test.dart b/test/game/components/layer_test.dart index aed5a49a..acb4300a 100644 --- a/test/game/components/layer_test.dart +++ b/test/game/components/layer_test.dart @@ -1,50 +1,7 @@ // ignore_for_file: cascade_invocations -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart'; import 'package:pinball/game/game.dart'; -import '../../helpers/helpers.dart'; - -class TestRampOpening extends RampOpening { - TestRampOpening({ - required Vector2 position, - required RampOrientation orientation, - required Layer pathwayLayer, - required Layer openingLayer, - }) : _orientation = orientation, - super( - position: position, - pathwayLayer: pathwayLayer, - openingLayer: openingLayer, - ); - - final RampOrientation _orientation; - - @override - RampOrientation get orientation => _orientation; - - @override - Shape get shape => PolygonShape() - ..set([ - Vector2(0, 0), - Vector2(0, 1), - Vector2(1, 1), - Vector2(1, 0), - ]); -} - -class TestRampOpeningBallContactCallback - extends RampOpeningBallContactCallback { - TestRampOpeningBallContactCallback() : super(); - - final _ballsInside = {}; - - @override - Set get ballsInside => _ballsInside; -} - void main() { group('Layer', () { test('has four values', () { @@ -80,271 +37,4 @@ void main() { expect(Layer.launcher.maskBits, equals(0x0005)); }); }); - - group('RampOpening', () { - TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballGameTest.create); - - flameTester.test( - 'loads correctly', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - await game.ready(); - await game.ensureAdd(ramp); - - expect(game.contains(ramp), isTrue); - }, - ); - - group('body', () { - flameTester.test( - 'positions correctly', - (game) async { - final position = Vector2.all(10); - final ramp = TestRampOpening( - position: position, - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - await game.ensureAdd(ramp); - - game.contains(ramp); - expect(ramp.body.position, position); - }, - ); - - flameTester.test( - 'is static', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - await game.ensureAdd(ramp); - - expect(ramp.body.bodyType, equals(BodyType.static)); - }, - ); - - group('fixture', () { - const pathwayLayer = Layer.jetpack; - const openingLayer = Layer.opening; - - flameTester.test( - 'exists', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: pathwayLayer, - openingLayer: openingLayer, - ); - await game.ensureAdd(ramp); - - expect(ramp.body.fixtures[0], isA()); - }, - ); - - flameTester.test( - 'shape is a polygon', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: pathwayLayer, - openingLayer: openingLayer, - ); - await game.ensureAdd(ramp); - - final fixture = ramp.body.fixtures[0]; - expect(fixture.shape.shapeType, equals(ShapeType.polygon)); - }, - ); - - flameTester.test( - 'is sensor', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: pathwayLayer, - openingLayer: openingLayer, - ); - await game.ensureAdd(ramp); - - final fixture = ramp.body.fixtures[0]; - expect(fixture.isSensor, isTrue); - }, - ); - - flameTester.test( - 'sets filter categoryBits correctly', - (game) async { - final ramp = TestRampOpening( - position: Vector2.zero(), - orientation: RampOrientation.down, - pathwayLayer: pathwayLayer, - openingLayer: openingLayer, - ); - - await game.ensureAdd(ramp); - - final fixture = ramp.body.fixtures[0]; - expect( - fixture.filterData.categoryBits, - equals(ramp.openingLayer.maskBits), - ); - }, - ); - }); - }); - }); - - group('RampOpeningBallContactCallback', () { - test( - 'a ball enters from bottom into a down oriented path and keeps inside, ' - 'is saved into collection and set maskBits to path', () { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - position: Vector2(0, 10), - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - final callback = TestRampOpeningBallContactCallback(); - - when(() => body.position).thenReturn(Vector2(0, 20)); - when(() => ball.body).thenReturn(body); - expect(callback._ballsInside.isEmpty, isTrue); - - callback.begin(ball, area, MockContact()); - - expect(callback._ballsInside.length, equals(1)); - expect(callback._ballsInside.first, ball); - verify(() => ball.layer = Layer.jetpack).called(1); - - callback.end(ball, area, MockContact()); - - verifyNever(() => ball.layer = Layer.board); - }); - - test( - 'a ball enters from up into an up oriented path and keeps inside, ' - 'is saved into collection and set maskBits to path', () { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - position: Vector2(0, 10), - orientation: RampOrientation.up, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - final callback = TestRampOpeningBallContactCallback(); - - when(() => body.position).thenReturn(Vector2.zero()); - when(() => ball.body).thenReturn(body); - expect(callback._ballsInside.isEmpty, isTrue); - - callback.begin(ball, area, MockContact()); - - expect(callback._ballsInside.length, equals(1)); - expect(callback._ballsInside.first, ball); - verify(() => ball.layer = Layer.jetpack).called(1); - - callback.end(ball, area, MockContact()); - - verifyNever(() => ball.layer = Layer.board); - }); - - test( - 'a ball enters into a down oriented path but falls again outside, ' - 'is removed from collection and set maskBits to collide all', () { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - position: Vector2(0, 10), - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - final callback = TestRampOpeningBallContactCallback(); - - when(() => body.position).thenReturn(Vector2.zero()); - when(() => ball.body).thenReturn(body); - expect(callback._ballsInside.isEmpty, isTrue); - - callback.begin(ball, area, MockContact()); - - expect(callback._ballsInside.length, equals(1)); - expect(callback._ballsInside.first, ball); - verify(() => ball.layer = Layer.jetpack).called(1); - - callback.end(ball, area, MockContact()); - - verify(() => ball.layer = Layer.board); - }); - - test( - 'a ball exits from inside a down oriented path, ' - 'is removed from collection and set maskBits to collide all', () { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - position: Vector2(0, 10), - orientation: RampOrientation.down, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - final callback = TestRampOpeningBallContactCallback() - ..ballsInside.add(ball); - - when(() => body.position).thenReturn(Vector2.zero()); - when(() => ball.body).thenReturn(body); - - callback.begin(ball, area, MockContact()); - - expect(callback._ballsInside.isEmpty, isTrue); - verify(() => ball.layer = Layer.board).called(1); - - callback.end(ball, area, MockContact()); - - verify(() => ball.layer = Layer.board).called(1); - }); - - test( - 'a ball exits from inside an up oriented path, ' - 'is removed from collection and set maskBits to collide all', () { - final ball = MockBall(); - final body = MockBody(); - final area = TestRampOpening( - position: Vector2(0, 10), - orientation: RampOrientation.up, - pathwayLayer: Layer.jetpack, - openingLayer: Layer.board, - ); - final callback = TestRampOpeningBallContactCallback() - ..ballsInside.add(ball); - - when(() => body.position).thenReturn(Vector2(0, 20)); - when(() => ball.body).thenReturn(body); - - callback.begin(ball, area, MockContact()); - - expect(callback._ballsInside.isEmpty, isTrue); - verify(() => ball.layer = Layer.board).called(1); - - callback.end(ball, area, MockContact()); - - verify(() => ball.layer = Layer.board).called(1); - }); - }); } diff --git a/test/game/components/pathway_test.dart b/test/game/components/pathway_test.dart index 7fb7b4e5..678fb3e3 100644 --- a/test/game/components/pathway_test.dart +++ b/test/game/components/pathway_test.dart @@ -6,24 +6,41 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball/game/game.dart'; +import '../../helpers/helpers.dart'; + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(Forge2DGame.new); + final flameTester = FlameTester(PinballGameTest.create); group('Pathway', () { const width = 50.0; group('straight', () { + flameTester.test( + 'loads correctly', + (game) async { + final pathway = Pathway.straight( + start: Vector2(10, 10), + end: Vector2(20, 20), + width: width, + ); + await game.ready(); + await game.ensureAdd(pathway); + + expect(game.contains(pathway), isTrue); + }, + ); + group('color', () { flameTester.test( 'has transparent color by default when no color is specified', (game) async { - await game.ready(); final pathway = Pathway.straight( start: Vector2(10, 10), end: Vector2(20, 20), width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(game.contains(pathway), isTrue); @@ -38,7 +55,6 @@ void main() { flameTester.test( 'has a color when is specified', (game) async { - await game.ready(); const defaultColor = Colors.blue; final pathway = Pathway.straight( @@ -47,6 +63,7 @@ void main() { end: Vector2(20, 20), width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(game.contains(pathway), isTrue); @@ -56,31 +73,16 @@ void main() { ); }); - flameTester.test( - 'loads correctly', - (game) async { - await game.ready(); - final pathway = Pathway.straight( - start: Vector2(10, 10), - end: Vector2(20, 20), - width: width, - ); - await game.ensureAdd(pathway); - - expect(game.contains(pathway), isTrue); - }, - ); - group('body', () { flameTester.test( 'is static', (game) async { - await game.ready(); final pathway = Pathway.straight( start: Vector2(10, 10), end: Vector2(20, 20), width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(pathway.body.bodyType, equals(BodyType.static)); @@ -92,13 +94,13 @@ void main() { flameTester.test( 'has only one ChainShape when singleWall is true', (game) async { - await game.ready(); final pathway = Pathway.straight( start: Vector2(10, 10), end: Vector2(20, 20), width: width, singleWall: true, ); + await game.ready(); await game.ensureAdd(pathway); expect(pathway.body.fixtures.length, 1); @@ -111,12 +113,12 @@ void main() { flameTester.test( 'has two ChainShape when singleWall is false (default)', (game) async { - await game.ready(); final pathway = Pathway.straight( start: Vector2(10, 10), end: Vector2(20, 20), width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(pathway.body.fixtures.length, 2); @@ -156,8 +158,7 @@ void main() { start: Vector2(10, 10), end: Vector2(20, 20), width: width, - layer: layer, - ); + )..layer = layer; await game.ready(); await game.ensureAdd(pathway); @@ -177,13 +178,13 @@ void main() { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final pathway = Pathway.arc( center: Vector2.zero(), width: width, radius: 100, angle: math.pi / 2, ); + await game.ready(); await game.ensureAdd(pathway); expect(game.contains(pathway), isTrue); @@ -194,13 +195,13 @@ void main() { flameTester.test( 'is static', (game) async { - await game.ready(); final pathway = Pathway.arc( center: Vector2.zero(), width: width, radius: 100, angle: math.pi / 2, ); + await game.ready(); await game.ensureAdd(pathway); expect(pathway.body.bodyType, equals(BodyType.static)); @@ -220,11 +221,11 @@ void main() { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final pathway = Pathway.bezierCurve( controlPoints: controlPoints, width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(game.contains(pathway), isTrue); @@ -235,11 +236,11 @@ void main() { flameTester.test( 'is static', (game) async { - await game.ready(); final pathway = Pathway.bezierCurve( controlPoints: controlPoints, width: width, ); + await game.ready(); await game.ensureAdd(pathway); expect(pathway.body.bodyType, equals(BodyType.static)); diff --git a/test/game/components/ramp_opening_test.dart b/test/game/components/ramp_opening_test.dart new file mode 100644 index 00000000..0f0a909b --- /dev/null +++ b/test/game/components/ramp_opening_test.dart @@ -0,0 +1,338 @@ +// ignore_for_file: cascade_invocations +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockingjay/mockingjay.dart'; +import 'package:pinball/game/game.dart'; + +import '../../helpers/helpers.dart'; + +class TestRampOpening extends RampOpening { + TestRampOpening({ + required RampOrientation orientation, + required Layer pathwayLayer, + }) : _orientation = orientation, + super( + pathwayLayer: pathwayLayer, + ); + + final RampOrientation _orientation; + + @override + RampOrientation get orientation => _orientation; + + @override + Shape get shape => PolygonShape() + ..set([ + Vector2(0, 0), + Vector2(0, 1), + Vector2(1, 1), + Vector2(1, 0), + ]); +} + +class TestRampOpeningBallContactCallback + extends RampOpeningBallContactCallback { + TestRampOpeningBallContactCallback() : super(); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGameTest.create); + + group('RampOpening', () { + TestWidgetsFlutterBinding.ensureInitialized(); + final flameTester = FlameTester(PinballGameTest.create); + + flameTester.test( + 'loads correctly', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2.zero() + ..layer = Layer.board; + await game.ready(); + await game.ensureAdd(ramp); + + expect(game.contains(ramp), isTrue); + }, + ); + + group('body', () { + flameTester.test( + 'positions correctly', + (game) async { + final position = Vector2.all(10); + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = position + ..layer = Layer.board; + await game.ensureAdd(ramp); + + game.contains(ramp); + expect(ramp.body.position, position); + }, + ); + + flameTester.test( + 'is static', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2.zero() + ..layer = Layer.board; + await game.ensureAdd(ramp); + + expect(ramp.body.bodyType, equals(BodyType.static)); + }, + ); + + group('fixture', () { + const pathwayLayer = Layer.jetpack; + const openingLayer = Layer.opening; + + flameTester.test( + 'exists', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: pathwayLayer, + ) + ..initialPosition = Vector2.zero() + ..layer = openingLayer; + await game.ensureAdd(ramp); + + expect(ramp.body.fixtures[0], isA()); + }, + ); + + flameTester.test( + 'shape is a polygon', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: pathwayLayer, + ) + ..initialPosition = Vector2.zero() + ..layer = openingLayer; + await game.ensureAdd(ramp); + + final fixture = ramp.body.fixtures[0]; + expect(fixture.shape.shapeType, equals(ShapeType.polygon)); + }, + ); + + flameTester.test( + 'is sensor', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: pathwayLayer, + ) + ..initialPosition = Vector2.zero() + ..layer = openingLayer; + await game.ensureAdd(ramp); + + final fixture = ramp.body.fixtures[0]; + expect(fixture.isSensor, isTrue); + }, + ); + + flameTester.test( + 'sets filter categoryBits correctly', + (game) async { + final ramp = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: pathwayLayer, + ) + ..initialPosition = Vector2.zero() + ..layer = openingLayer; + + await game.ready(); + await game.ensureAdd(ramp); + + final fixture = ramp.body.fixtures[0]; + expect( + fixture.filterData.categoryBits, + equals(ramp.layer.maskBits), + ); + }, + ); + }); + }); + }); + + group('RampOpeningBallContactCallback', () { + test('has no ball inside on creation', () { + expect( + RampOpeningBallContactCallback().ballsInside, + equals({}), + ); + }); + + flameTester.test( + 'a ball enters from bottom into a down oriented path and keeps inside, ' + 'is saved into collection and set maskBits to path', (game) async { + final ball = MockBall(); + final body = MockBody(); + final area = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2(0, 10) + ..layer = Layer.board; + final callback = TestRampOpeningBallContactCallback(); + + when(() => ball.body).thenReturn(body); + when(() => body.position).thenReturn(Vector2(0, 20)); + + await game.ready(); + await game.ensureAdd(area); + + expect(callback.ballsInside.isEmpty, isTrue); + + callback.begin(ball, area, MockContact()); + + expect(callback.ballsInside.length, equals(1)); + expect(callback.ballsInside.first, ball); + verify(() => ball.layer = Layer.jetpack).called(1); + + callback.end(ball, area, MockContact()); + + verifyNever(() => ball.layer = Layer.board); + }); + + flameTester.test( + 'a ball enters from up into an up oriented path and keeps inside, ' + 'is saved into collection and set maskBits to path', (game) async { + final ball = MockBall(); + final body = MockBody(); + final area = TestRampOpening( + orientation: RampOrientation.up, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2(0, 10) + ..layer = Layer.board; + final callback = TestRampOpeningBallContactCallback(); + + when(() => body.position).thenReturn(Vector2.zero()); + when(() => ball.body).thenReturn(body); + + await game.ready(); + await game.ensureAdd(area); + + expect(callback.ballsInside.isEmpty, isTrue); + + callback.begin(ball, area, MockContact()); + + expect(callback.ballsInside.length, equals(1)); + expect(callback.ballsInside.first, ball); + verify(() => ball.layer = Layer.jetpack).called(1); + + callback.end(ball, area, MockContact()); + + verifyNever(() => ball.layer = Layer.board); + }); + + flameTester.test( + 'a ball enters into a down oriented path but falls again outside, ' + 'is removed from collection and set maskBits to collide all', + (game) async { + final ball = MockBall(); + final body = MockBody(); + final area = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2(0, 10) + ..layer = Layer.board; + final callback = TestRampOpeningBallContactCallback(); + + when(() => body.position).thenReturn(Vector2.zero()); + when(() => ball.body).thenReturn(body); + + await game.ready(); + await game.ensureAdd(area); + + expect(callback.ballsInside.isEmpty, isTrue); + + callback.begin(ball, area, MockContact()); + + expect(callback.ballsInside.length, equals(1)); + expect(callback.ballsInside.first, ball); + verify(() => ball.layer = Layer.jetpack).called(1); + + callback.end(ball, area, MockContact()); + + verify(() => ball.layer = Layer.board); + }); + + flameTester.test( + 'a ball exits from inside a down oriented path, ' + 'is removed from collection and set maskBits to collide all', + (game) async { + final ball = MockBall(); + final body = MockBody(); + final area = TestRampOpening( + orientation: RampOrientation.down, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2(0, 10) + ..layer = Layer.board; + final callback = TestRampOpeningBallContactCallback() + ..ballsInside.add(ball); + + when(() => body.position).thenReturn(Vector2.zero()); + when(() => ball.body).thenReturn(body); + + await game.ready(); + await game.ensureAdd(area); + + callback.begin(ball, area, MockContact()); + + expect(callback.ballsInside.isEmpty, isTrue); + verify(() => ball.layer = Layer.board).called(1); + + callback.end(ball, area, MockContact()); + + verify(() => ball.layer = Layer.board).called(1); + }); + + flameTester.test( + 'a ball exits from inside an up oriented path, ' + 'is removed from collection and set maskBits to collide all', + (game) async { + final ball = MockBall(); + final body = MockBody(); + final area = TestRampOpening( + orientation: RampOrientation.up, + pathwayLayer: Layer.jetpack, + ) + ..initialPosition = Vector2(0, 10) + ..layer = Layer.board; + final callback = TestRampOpeningBallContactCallback() + ..ballsInside.add(ball); + + when(() => ball.body).thenReturn(body); + when(() => body.position).thenReturn(Vector2(0, 20)); + + await game.ready(); + await game.ensureAdd(area); + + callback.begin(ball, area, MockContact()); + + expect(callback.ballsInside.isEmpty, isTrue); + verify(() => ball.layer = Layer.board).called(1); + + callback.end(ball, area, MockContact()); + + verify(() => ball.layer = Layer.board).called(1); + }); + }); +}