import 'package:file_selector/file_selector.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; class _NoopAudio implements EngineAudio { @override WolfensteinData? activeGame; int stopAllAudioCallCount = 0; int disposeCallCount = 0; @override Future debugSoundTest() async {} @override Future init() async {} @override void playLevelMusic(Music music) {} @override void playMenuMusic() {} @override void playSoundEffect(SoundEffect effect) {} @override void playSoundEffectId(int sfxId) {} @override Future stopAllAudio() async { stopAllAudioCallCount++; } @override void stopMusic() {} @override void dispose() { disposeCallCount++; } } class _RecordingEngine extends Wolf3dFlutterEngine { _RecordingEngine({required _NoopAudio audio}) : super(audioBackend: audio); int initCallCount = 0; String? lastDirectory; List lastAdditionalDirectories = []; @override Future init({ String? directory, Iterable? additionalDirectories, }) async { initCallCount++; lastDirectory = directory; lastAdditionalDirectories = additionalDirectories?.toList(growable: false) ?? []; return this; } } void main() { group('GameDataPickerManager', () { test('pickGameDataDirectory reloads selected directory', () async { final engine = _RecordingEngine(audio: _NoopAudio()); int notifyCount = 0; final manager = GameDataPickerManager( engine: engine, pickDirectory: ({String? confirmButtonText}) async => ' /tmp/wolf ', ); await manager.pickGameDataDirectory( notifyChanged: () => notifyCount++, ); expect(notifyCount, 2); expect(manager.isLoadingGameData, isFalse); expect(manager.pickerError, isNull); expect(engine.initCallCount, 1); expect(engine.lastDirectory, '/tmp/wolf'); expect(engine.lastAdditionalDirectories, isEmpty); }); test('pickGameDataDirectory ignores empty selection', () async { final engine = _RecordingEngine(audio: _NoopAudio()); final manager = GameDataPickerManager( engine: engine, pickDirectory: ({String? confirmButtonText}) async => ' ', ); await manager.pickGameDataDirectory(notifyChanged: () {}); expect(engine.initCallCount, 0); expect(manager.pickerError, isNull); }); test( 'pickGameDataFiles deduplicates directories and forwards extras', () async { final engine = _RecordingEngine(audio: _NoopAudio()); final manager = GameDataPickerManager( engine: engine, pickFiles: () async => [ XFile('/a/one/VSWAP.WL6'), XFile('/a/one/MAPHEAD.WL6'), XFile('/a/two/VSWAP.WL1'), ], ); await manager.pickGameDataFiles(notifyChanged: () {}); expect(engine.initCallCount, 1); expect(engine.lastDirectory, '/a/one'); expect(engine.lastAdditionalDirectories, ['/a/two']); expect(manager.pickerError, isNull); }, ); test('pickGameDataFiles reports missing directory paths', () async { final engine = _RecordingEngine(audio: _NoopAudio()); final manager = GameDataPickerManager( engine: engine, pickFiles: () async => [ XFile('VSWAP.WL6'), XFile('MAPHEAD.WL6'), ], ); await manager.pickGameDataFiles(notifyChanged: () {}); expect(engine.initCallCount, 0); expect( manager.pickerError, 'Selected files do not expose local filesystem paths.', ); }); test('pickGameDataDirectory captures thrown picker error', () async { final engine = _RecordingEngine(audio: _NoopAudio()); final manager = GameDataPickerManager( engine: engine, pickDirectory: ({String? confirmButtonText}) async { throw StateError('picker failed'); }, ); await manager.pickGameDataDirectory(notifyChanged: () {}); expect(engine.initCallCount, 0); expect( manager.pickerError, contains('Unable to load selected directory'), ); expect(manager.isLoadingGameData, isFalse); }); }); group('Wolf3dAppManager', () { test('ensureAudioShutdown only executes once', () async { final audio = _NoopAudio(); final engine = _RecordingEngine(audio: audio); final manager = Wolf3dAppManager(engine: engine); await Future.wait(>[ manager.ensureAudioShutdown(), manager.ensureAudioShutdown(), ]); expect(audio.stopAllAudioCallCount, 1); expect(audio.disposeCallCount, 1); }); test('forwards picker state from injected picker manager', () async { final engine = _RecordingEngine(audio: _NoopAudio()); final pickerManager = GameDataPickerManager( engine: engine, pickDirectory: ({String? confirmButtonText}) async => '/tmp/wolf', ); final manager = Wolf3dAppManager( engine: engine, pickerManager: pickerManager, ); await manager.pickGameDataDirectory(notifyChanged: () {}); expect(manager.isLoadingGameData, isFalse); expect(manager.pickerError, isNull); expect(engine.lastDirectory, '/tmp/wolf'); }); }); }