import 'package:asciineverdie/src/core/engine/game_mutations.dart'; import 'package:asciineverdie/src/core/engine/progress_service.dart'; import 'package:asciineverdie/src/core/engine/reward_service.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/pq_config.dart'; import 'package:asciineverdie/src/core/storage/save_manager.dart'; import 'package:asciineverdie/src/core/storage/save_repository.dart'; import 'package:asciineverdie/src/core/storage/save_service.dart'; import 'package:asciineverdie/src/features/game/game_session_controller.dart'; import 'package:fake_async/fake_async.dart'; import 'package:flutter_test/flutter_test.dart'; class FakeSaveManager implements SaveManager { final List savedStates = []; (SaveOutcome, GameState?, bool) Function(String?)? onLoad; SaveOutcome saveOutcome = const SaveOutcome.success(); @override Future saveState( GameState state, { String? fileName, bool cheatsEnabled = false, }) async { savedStates.add(state); return saveOutcome; } @override Future<(SaveOutcome, GameState?, bool)> loadState({String? fileName}) async { if (onLoad != null) { return onLoad!(fileName); } return (const SaveOutcome.success(), null, false); } @override Future> listSaves() async => []; @override Future deleteSave({String? fileName}) async { return const SaveOutcome.success(); } @override Future saveExists({String? fileName}) async => false; } void main() { const config = PqConfig(); final mutations = GameMutations(config); final progressService = ProgressService( config: config, mutations: mutations, rewards: RewardService(mutations), ); GameSessionController buildController( FakeAsync async, FakeSaveManager saveManager, ) { return GameSessionController( progressService: progressService, saveManager: saveManager, tickInterval: const Duration(milliseconds: 10), now: () => DateTime.fromMillisecondsSinceEpoch(async.elapsed.inMilliseconds), ); } GameState sampleState() { return GameState.withSeed( seed: 1, traits: const Traits( name: 'Hero', race: 'Human', klass: 'Fighter', level: 1, motto: '', guild: '', ), stats: const Stats( str: 5, con: 5, dex: 5, intelligence: 5, wis: 5, cha: 5, hpMax: 10, mpMax: 8, ), progress: const ProgressState( task: ProgressBarState(position: 0, max: 50), quest: ProgressBarState(position: 0, max: 1000), plot: ProgressBarState(position: 0, max: 1000), exp: ProgressBarState(position: 0, max: 9999), encumbrance: ProgressBarState(position: 0, max: 1), currentTask: TaskInfo(caption: 'Battle', type: TaskType.kill), plotStageCount: 1, questCount: 0, ), ); } test('startNew runs loop and publishes state updates', () { fakeAsync((async) { final saveManager = FakeSaveManager(); final controller = buildController(async, saveManager); controller.startNew(sampleState(), isNewGame: false); async.flushMicrotasks(); expect(controller.status, GameSessionStatus.running); expect(controller.state, isNotNull); async.elapse(const Duration(milliseconds: 30)); async.flushMicrotasks(); expect(controller.state!.progress.task.position, greaterThan(0)); controller.pause(); async.flushMicrotasks(); expect(controller.status, GameSessionStatus.idle); }); }); test('loadAndStart surfaces save load errors', () { fakeAsync((async) { final saveManager = FakeSaveManager() ..onLoad = (_) => (const SaveOutcome.failure('boom'), null, false); final controller = buildController(async, saveManager); controller.loadAndStart(fileName: 'bad.pqf'); async.flushMicrotasks(); expect(controller.status, GameSessionStatus.error); expect(controller.error, 'boom'); }); }); test('pause saves on stop when requested', () { fakeAsync((async) { final saveManager = FakeSaveManager(); final controller = buildController(async, saveManager); controller.startNew(sampleState(), isNewGame: false); async.flushMicrotasks(); controller.pause(saveOnStop: true); async.flushMicrotasks(); expect(controller.status, GameSessionStatus.idle); expect(saveManager.savedStates.length, 1); }); }); }