Files
BlockSeasons/test/ui/game_screen_test.dart
T
airkjw 3138fc4b08 Add playable core UI: board painter, drag-and-drop, HUD, result overlay
CustomPainter board with gems/ghost/clear-flash, finger-lifted drag
with snap preview, combo text effect, HUD chips, phase overlays with
rescue stubs, demo stage. E2E widget test drives a real drag gesture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:19:34 +09:00

117 lines
3.8 KiB
Dart

import 'package:block_seasons/core/rng.dart';
import 'package:block_seasons/game/engine/piece_generator.dart';
import 'package:block_seasons/game/models/piece_library.dart';
import 'package:block_seasons/game/models/stage.dart';
import 'package:block_seasons/l10n/gen/app_localizations.dart';
import 'package:block_seasons/state/providers.dart';
import 'package:block_seasons/ui/screens/game_screen.dart';
import 'package:block_seasons/ui/widgets/board_widget.dart';
import 'package:block_seasons/ui/widgets/piece_painter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
final _stage = StageConfig.fromJson({
'id': 'drag_stage',
'seed': 11,
'moveLimit': 20,
'preset': const <Map<String, dynamic>>[],
'objectives': [
{'type': 'reachScore', 'target': 999999},
],
'stars': {
'two': {'movesLeft': 5},
'three': {'movesLeft': 10},
},
'generatorProfile': 'mid',
});
PieceGenerator _smallPool() => PieceGenerator(
SeededRng(3),
pool: [
PieceLibrary.byId('mono'),
PieceLibrary.byId('domino_h'),
PieceLibrary.byId('domino_v'),
],
);
void main() {
testWidgets('dragging a tray piece onto the board places it',
(tester) async {
final container = ProviderContainer();
addTearDown(container.dispose);
container
.read(gameSessionProvider.notifier)
.startStage(_stage, generator: _smallPool());
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: GameScreen(),
),
),
);
await tester.pumpAndSettle();
final view0 = container.read(gameSessionProvider)!;
final monoIndex = view0.tray.indexWhere((p) => p.id == 'mono');
expect(monoIndex, greaterThanOrEqualTo(0));
final boardRect = tester.getRect(find.byType(BoardWidget));
final cell = boardRect.width / 8;
// The dragged piece floats 70px above the finger, so aim the finger
// below the intended landing cell (0, 0).
final targetCenter = boardRect.topLeft + Offset(cell * 0.5, cell * 0.5);
final fingerEnd = targetCenter + Offset(0, 70 + cell / 2);
final start = tester.getCenter(find.byType(PieceWidget).at(monoIndex));
final gesture = await tester.startGesture(start);
await gesture.moveBy(const Offset(0, -30));
await tester.pump();
await gesture.moveTo(fingerEnd);
await tester.pump();
await gesture.up();
await tester.pumpAndSettle();
final view = container.read(gameSessionProvider)!;
expect(view.grid.isOccupied(0, 0), isTrue,
reason: 'mono should land on (0,0)');
expect(view.tray, hasLength(2));
expect(view.score, 1);
});
testWidgets('result overlay appears when out of moves', (tester) async {
final container = ProviderContainer();
addTearDown(container.dispose);
final oneMove = StageConfig.fromJson({
..._stage.toJson(),
'moveLimit': 1,
});
container
.read(gameSessionProvider.notifier)
.startStage(oneMove, generator: _smallPool());
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: GameScreen(),
),
),
);
await tester.pumpAndSettle();
container.read(gameSessionProvider.notifier).tryPlace(0, 0, 0);
await tester.pumpAndSettle();
expect(find.text('Out of moves'), findsOneWidget);
expect(find.text('+5 moves (ad)'), findsOneWidget);
});
}