| 1 | import 'dart:io'; |
| 2 | |
| 3 | import 'package:flutter/material.dart'; |
| 4 | import 'package:flutter_test/flutter_test.dart'; |
| 5 | import 'package:gba_core/gba_core.dart'; |
| 6 | import 'package:splashy_gba/main.dart'; |
| 7 | import 'package:splashy_gba/src/emulator/game_pad.dart'; |
| 8 | import 'package:splashy_gba/src/library/game_library.dart'; |
| 9 | import 'package:splashy_gba/src/library/home_screen.dart'; |
| 10 | |
| 11 | /// A freely licensed test ROM (jsmolka/gba-tests, MIT), fetched by |
| 12 | /// `dart run tool/fetch_test_roms.dart` in packages/gba_core. |
| 13 | const String testRom = 'packages/gba_core/test/roms/jsmolka/arm.gba' |
| 14 | |
| 15 | /// Lets real file IO and isolates progress, and the fake clock (snack bar |
| 16 | /// timers, animations) advance, until [finder] matches or 10 s pass. |
| 17 | Future<void> pumpUntil( |
| 18 | for (var i = 0 |
| 19 | await tester.runAsync( |
| 20 | () => Future<void> |
| 21 | ); |
| 22 | await tester.pump(const |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | void main() { |
| 27 | late Directory root; |
| 28 | |
| 29 | setUp(() => root = Directory |
| 30 | tearDown(() => root.deleteSync |
| 31 | |
| 32 | group('home screen', () { |
| 33 | testWidgets('an empty library explains how to add a game', (tester) async { |
| 34 | await tester.pumpWidget(SplashyGbaApp |
| 35 | await pumpUntil(tester, find. |
| 36 | expect(find.text('No games yet' |
| 37 | expect(find.textContaining('No games or BIOS files' |
| 38 | expect(find.text('Add game' |
| 39 | }); |
| 40 | |
| 41 | testWidgets('adding a ROM lists it; a non-ROM is refused', (tester) async { |
| 42 | final notRom = File( |
| 43 | var next = (path: testRom, |
| 44 | final library = GameLibrary( |
| 45 | await tester.pumpWidget( |
| 46 | MaterialApp( |
| 47 | home: HomeScreen(library: library, |
| 48 | ), |
| 49 | ); |
| 50 | await pumpUntil(tester, find. |
| 51 | |
| 52 | await tester.tap(find.text |
| 53 | await pumpUntil(tester, find. |
| 54 | expect(find.text('GBA Tests' |
| 55 | expect(find.textContaining('1337' |
| 56 | expect(File('${ |
| 57 | |
| 58 | next = (path: notRom.path, name: |
| 59 | await tester.tap(find.text |
| 60 | final refused = find.textContaining |
| 61 | await pumpUntil(tester, refused); |
| 62 | expect(refused, findsOneWidget); |
| 63 | }); |
| 64 | }); |
| 65 | |
| 66 | group('game pad layout', () { |
| 67 | const portrait = Size( |
| 68 | final portraitScreen = Rect. |
| 69 | |
| 70 | test('each button presses its key', () { |
| 71 | final layout = GamePadLayout. |
| 72 | for (final button in layout. |
| 73 | expect( |
| 74 | layout.keysAt(button.rect.center) |
| 75 | button.key, |
| 76 | reason: button.label, |
| 77 | ); |
| 78 | } |
| 79 | }); |
| 80 | |
| 81 | test('the D-pad reads eight directions and has a dead zone', () { |
| 82 | final layout = GamePadLayout. |
| 83 | final center = layout.dpad. |
| 84 | final r = layout.dpad. |
| 85 | expect(layout.keysAt(center), |
| 86 | expect(layout.keysAt(center + |
| 87 | expect(layout.keysAt(center + |
| 88 | expect(layout.keysAt(center + |
| 89 | expect(layout.keysAt(center + |
| 90 | expect( |
| 91 | layout.keysAt(center + Offset |
| 92 | GbaKey.right | GbaKey |
| 93 | ); |
| 94 | expect( |
| 95 | layout.keysAt(center + Offset |
| 96 | GbaKey.left | GbaKey |
| 97 | ); |
| 98 | }); |
| 99 | |
| 100 | test('controls stay clear of the picture in both orientations', () { |
| 101 | const landscape = Size( |
| 102 | final landscapeScreen = Rect. |
| 103 | center: const Offset( |
| 104 | width: 448 / 1.0, |
| 105 | height: 448 / 1.5, |
| 106 | ); |
| 107 | for (final (size, screen) in |
| 108 | (portrait, portraitScreen), |
| 109 | (landscape, landscapeScreen), |
| 110 | ]) { |
| 111 | final layout = GamePadLayout. |
| 112 | // Nothing may sit under the close and pause buttons. |
| 113 | final topBar = Rect. |
| 114 | if (size.width > size.height) { |
| 115 | for (final button in layout. |
| 116 | expect(button.rect.overlaps |
| 117 | } |
| 118 | } |
| 119 | expect(layout.dpad.overlaps |
| 120 | for (final button in layout. |
| 121 | expect( |
| 122 | button.rect.overlaps(screen), |
| 123 | isFalse, |
| 124 | reason: '${button.label |
| 125 | ); |
| 126 | expect( |
| 127 | Offset.zero & size, |
| 128 | predicate<Rect>((r) => r |
| 129 | ); |
| 130 | } |
| 131 | } |
| 132 | }); |
| 133 | }); |
| 134 | } |