import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:gba_core/gba_core.dart'; import 'package:splashy_gba/main.dart'; import 'package:splashy_gba/src/emulator/game_pad.dart'; import 'package:splashy_gba/src/library/game_library.dart'; import 'package:splashy_gba/src/library/home_screen.dart'; /// A freely licensed test ROM (jsmolka/gba-tests, MIT), fetched by /// `dart run tool/fetch_test_roms.dart` in packages/gba_core. const String testRom = 'packages/gba_core/test/roms/jsmolka/arm.gba'; /// Lets real file IO and isolates progress, and the fake clock (snack bar /// timers, animations) advance, until [finder] matches or 10 s pass. Future pumpUntil(WidgetTester tester, Finder finder) async { for (var i = 0; i < 100 && finder.evaluate().isEmpty; i++) { await tester.runAsync( () => Future.delayed(const Duration(milliseconds: 50)), ); await tester.pump(const Duration(milliseconds: 100)); } } void main() { late Directory root; setUp(() => root = Directory.systemTemp.createTempSync('splashy_test')); tearDown(() => root.deleteSync(recursive: true)); group('home screen', () { testWidgets('an empty library explains how to add a game', (tester) async { await tester.pumpWidget(SplashyGbaApp(library: GameLibrary(root))); await pumpUntil(tester, find.text('No games yet')); expect(find.text('No games yet'), findsOneWidget); expect(find.textContaining('No games or BIOS files'), findsOneWidget); expect(find.text('Add game'), findsOneWidget); }); testWidgets('adding a ROM lists it; a non-ROM is refused', (tester) async { final notRom = File('${root.path}/notes.txt')..writeAsStringSync('hello'); var next = (path: testRom, name: 'arm.gba'); final library = GameLibrary(Directory('${root.path}/library')); await tester.pumpWidget( MaterialApp( home: HomeScreen(library: library, pickFile: () async => next), ), ); await pumpUntil(tester, find.text('No games yet')); await tester.tap(find.text('Add game')); await pumpUntil(tester, find.text('GBA Tests')); expect(find.text('GBA Tests'), findsOneWidget); expect(find.textContaining('1337'), findsOneWidget); expect(File('${root.path}/library/roms/arm.gba').existsSync(), isTrue); next = (path: notRom.path, name: 'notes.txt'); await tester.tap(find.text('Add game')); final refused = find.textContaining('too small to be a Game Boy'); await pumpUntil(tester, refused); expect(refused, findsOneWidget); }); }); group('game pad layout', () { const portrait = Size(400, 800); final portraitScreen = Rect.fromLTWH(0, 48, 400, 400 / 1.5); test('each button presses its key', () { final layout = GamePadLayout.compute(portrait, portraitScreen); for (final button in layout.buttons) { expect( layout.keysAt(button.rect.center), button.key, reason: button.label, ); } }); test('the D-pad reads eight directions and has a dead zone', () { final layout = GamePadLayout.compute(portrait, portraitScreen); final center = layout.dpad.center; final r = layout.dpad.width / 2 * 0.8; expect(layout.keysAt(center), 0); expect(layout.keysAt(center + Offset(r, 0)), GbaKey.right); expect(layout.keysAt(center + Offset(0, -r)), GbaKey.up); expect(layout.keysAt(center + Offset(-r, 0)), GbaKey.left); expect(layout.keysAt(center + Offset(0, r)), GbaKey.down); expect( layout.keysAt(center + Offset(r, r) * 0.7), GbaKey.right | GbaKey.down, ); expect( layout.keysAt(center + Offset(-r, -r) * 0.7), GbaKey.left | GbaKey.up, ); }); test('controls stay clear of the picture in both orientations', () { const landscape = Size(800, 400); final landscapeScreen = Rect.fromCenter( center: const Offset(400, 200), width: 448 / 1.0, height: 448 / 1.5, ); for (final (size, screen) in [ (portrait, portraitScreen), (landscape, landscapeScreen), ]) { final layout = GamePadLayout.compute(size, screen, topReserved: 48); // Nothing may sit under the close and pause buttons. final topBar = Rect.fromLTWH(0, 0, size.width, 48); if (size.width > size.height) { for (final button in layout.buttons) { expect(button.rect.overlaps(topBar), isFalse, reason: button.label); } } expect(layout.dpad.overlaps(screen), isFalse); for (final button in layout.buttons) { expect( button.rect.overlaps(screen), isFalse, reason: '${button.label} in $size', ); expect( Offset.zero & size, predicate((r) => r.contains(button.rect.center)), ); } } }); }); }