feat: Add tests for Wolf3dGuiApp and refactor game data picker management
- Introduced unit tests for the Wolf3dGuiApp to ensure proper directory configuration and audio management. - Refactored the GameDataPickerManager to streamline directory and file selection processes. - Removed obsolete GameDataPickerManager and Wolf3dAppManager classes to simplify the architecture. - Enhanced the Wolf3dFlutterEngine to improve game version handling during save loading. - Updated existing tests to reflect changes in the game data management structure. - Removed unnecessary dependencies and cleaned up the codebase for better maintainability. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,195 +0,0 @@
|
||||
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<void> debugSoundTest() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(Music music) {}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(SoundEffect effect) {}
|
||||
|
||||
@override
|
||||
void playSoundEffectId(int sfxId) {}
|
||||
|
||||
@override
|
||||
Future<void> 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<String> lastAdditionalDirectories = <String>[];
|
||||
|
||||
@override
|
||||
Future<Wolf3dFlutterEngine> init({
|
||||
String? directory,
|
||||
Iterable<String>? additionalDirectories,
|
||||
}) async {
|
||||
initCallCount++;
|
||||
lastDirectory = directory;
|
||||
lastAdditionalDirectories =
|
||||
additionalDirectories?.toList(growable: false) ?? <String>[];
|
||||
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>[
|
||||
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, <String>['/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>[
|
||||
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<void>(<Future<void>>[
|
||||
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');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,83 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.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;
|
||||
|
||||
@override
|
||||
Future<void> debugSoundTest() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(Music music) {}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(SoundEffect effect) {}
|
||||
|
||||
@override
|
||||
void playSoundEffectId(int sfxId) {}
|
||||
|
||||
@override
|
||||
Future<void> stopAllAudio() async {}
|
||||
|
||||
@override
|
||||
void stopMusic() {}
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class _CountingAudio implements EngineAudio {
|
||||
@override
|
||||
WolfensteinData? activeGame;
|
||||
|
||||
int stopAllAudioCallCount = 0;
|
||||
int disposeCallCount = 0;
|
||||
|
||||
@override
|
||||
Future<void> debugSoundTest() async {}
|
||||
|
||||
@override
|
||||
Future<void> init() async {}
|
||||
|
||||
@override
|
||||
void playLevelMusic(Music music) {}
|
||||
|
||||
@override
|
||||
void playMenuMusic() {}
|
||||
|
||||
@override
|
||||
void playSoundEffect(SoundEffect effect) {}
|
||||
|
||||
@override
|
||||
void playSoundEffectId(int sfxId) {}
|
||||
|
||||
@override
|
||||
Future<void> stopAllAudio() async {
|
||||
stopAllAudioCallCount++;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 1));
|
||||
}
|
||||
|
||||
@override
|
||||
void stopMusic() {}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposeCallCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('DefaultGameDataDirectoryPersistence', () {
|
||||
test('saves and loads configured directory', () async {
|
||||
@@ -217,37 +143,4 @@ void main() {
|
||||
expect(await persistence.load(), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Wolf3dApp forwards configured directory to no-data screen', (
|
||||
tester,
|
||||
) async {
|
||||
final wolf3d = Wolf3dFlutterEngine(audioBackend: _NoopAudio())
|
||||
..configuredDataDirectory = '/tmp/wolf-data';
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Wolf3dApp(engine: wolf3d),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.textContaining('Configured data directory:'), findsOneWidget);
|
||||
expect(find.textContaining('/tmp/wolf-data'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Wolf3dApp dispose path shuts down audio', (tester) async {
|
||||
final audio = _CountingAudio();
|
||||
final wolf3d = Wolf3dFlutterEngine(audioBackend: audio);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Wolf3dApp(engine: wolf3d),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(const MaterialApp(home: SizedBox.shrink()));
|
||||
await tester.pump(const Duration(milliseconds: 10));
|
||||
|
||||
expect(audio.stopAllAudioCallCount, 1);
|
||||
expect(audio.disposeCallCount, 1);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -66,6 +67,33 @@ void main() {
|
||||
expect(wolf3d.isDebugEnabled, isTrue);
|
||||
});
|
||||
|
||||
test(
|
||||
'init without configured external data does not load bundled games',
|
||||
() async {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'wolf3d-empty-config-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await tempDir.exists()) {
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
final persistence = DefaultGameDataDirectoryPersistence(
|
||||
filePath: '${tempDir.path}/settings.json',
|
||||
);
|
||||
final wolf3d = Wolf3dFlutterEngine(
|
||||
audioBackend: _NoopAudio(),
|
||||
dataDirectoryPersistence: persistence,
|
||||
);
|
||||
|
||||
await wolf3d.init();
|
||||
|
||||
expect(wolf3d.configuredDataDirectory, isNull);
|
||||
expect(wolf3d.availableGames, isEmpty);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('GameScreen hides debug FAB when debug mode is disabled', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
Reference in New Issue
Block a user