gemba 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/THIRD_PARTY_NOTICES +37 -2
- data/assets/placeholder_boxart.png +0 -0
- data/bin/gemba +2 -2
- data/ext/gemba/extconf.rb +23 -1
- data/ext/gemba/gemba_ext.c +436 -2
- data/ext/gemba/gemba_ext.h +2 -0
- data/gemba.gemspec +5 -3
- data/lib/gemba/achievements/achievement.rb +23 -0
- data/lib/gemba/achievements/backend.rb +186 -0
- data/lib/gemba/achievements/cache.rb +70 -0
- data/lib/gemba/achievements/credentials_presenter.rb +142 -0
- data/lib/gemba/achievements/fake_backend.rb +205 -0
- data/lib/gemba/achievements/null_backend.rb +11 -0
- data/lib/gemba/achievements/offline_backend.rb +168 -0
- data/lib/gemba/achievements/retro_achievements/backend.rb +453 -0
- data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
- data/lib/gemba/achievements/retro_achievements/ping_worker.rb +27 -0
- data/lib/gemba/achievements.rb +19 -0
- data/lib/gemba/achievements_window.rb +556 -0
- data/lib/gemba/app_controller.rb +1015 -0
- data/lib/gemba/bios.rb +54 -0
- data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
- data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
- data/lib/gemba/boxart_fetcher.rb +79 -0
- data/lib/gemba/bus_emitter.rb +13 -0
- data/lib/gemba/child_window.rb +24 -1
- data/lib/gemba/cli/commands/config_cmd.rb +83 -0
- data/lib/gemba/cli/commands/decode.rb +154 -0
- data/lib/gemba/cli/commands/patch.rb +78 -0
- data/lib/gemba/cli/commands/play.rb +78 -0
- data/lib/gemba/cli/commands/record.rb +114 -0
- data/lib/gemba/cli/commands/replay.rb +161 -0
- data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
- data/lib/gemba/cli/commands/version.rb +22 -0
- data/lib/gemba/cli.rb +52 -364
- data/lib/gemba/config.rb +134 -1
- data/lib/gemba/data/gb_games.json +1 -0
- data/lib/gemba/data/gb_md5.json +1 -0
- data/lib/gemba/data/gba_games.json +1 -0
- data/lib/gemba/data/gba_md5.json +1 -0
- data/lib/gemba/data/gbc_games.json +1 -0
- data/lib/gemba/data/gbc_md5.json +1 -0
- data/lib/gemba/emulator_frame.rb +1060 -0
- data/lib/gemba/event_bus.rb +48 -0
- data/lib/gemba/frame_stack.rb +60 -0
- data/lib/gemba/game_index.rb +84 -0
- data/lib/gemba/game_picker_frame.rb +268 -0
- data/lib/gemba/gamepad_map.rb +103 -0
- data/lib/gemba/headless.rb +6 -5
- data/lib/gemba/headless_player.rb +33 -3
- data/lib/gemba/help_window.rb +61 -0
- data/lib/gemba/hotkey_map.rb +3 -1
- data/lib/gemba/input_recorder.rb +107 -0
- data/lib/gemba/input_replayer.rb +119 -0
- data/lib/gemba/keyboard_map.rb +90 -0
- data/lib/gemba/locales/en.yml +97 -5
- data/lib/gemba/locales/ja.yml +97 -5
- data/lib/gemba/main_window.rb +56 -0
- data/lib/gemba/modal_stack.rb +81 -0
- data/lib/gemba/patcher_window.rb +223 -0
- data/lib/gemba/platform/gb.rb +21 -0
- data/lib/gemba/platform/gba.rb +21 -0
- data/lib/gemba/platform/gbc.rb +23 -0
- data/lib/gemba/platform.rb +20 -0
- data/lib/gemba/platform_open.rb +19 -0
- data/lib/gemba/recorder.rb +4 -3
- data/lib/gemba/replay_player.rb +691 -0
- data/lib/gemba/rom_info.rb +57 -0
- data/lib/gemba/rom_info_window.rb +16 -3
- data/lib/gemba/rom_library.rb +106 -0
- data/lib/gemba/rom_overrides.rb +47 -0
- data/lib/gemba/rom_patcher/bps.rb +161 -0
- data/lib/gemba/rom_patcher/ips.rb +101 -0
- data/lib/gemba/rom_patcher/ups.rb +118 -0
- data/lib/gemba/rom_patcher.rb +109 -0
- data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
- data/lib/gemba/runtime.rb +59 -26
- data/lib/gemba/save_state_manager.rb +4 -7
- data/lib/gemba/save_state_picker.rb +17 -4
- data/lib/gemba/session_logger.rb +64 -0
- data/lib/gemba/settings/audio_tab.rb +77 -0
- data/lib/gemba/settings/gamepad_tab.rb +351 -0
- data/lib/gemba/settings/hotkeys_tab.rb +259 -0
- data/lib/gemba/settings/paths.rb +11 -0
- data/lib/gemba/settings/recording_tab.rb +83 -0
- data/lib/gemba/settings/save_states_tab.rb +91 -0
- data/lib/gemba/settings/system_tab.rb +362 -0
- data/lib/gemba/settings/video_tab.rb +318 -0
- data/lib/gemba/settings_window.rb +162 -1036
- data/lib/gemba/version.rb +1 -1
- data/lib/gemba/virtual_keyboard.rb +19 -0
- data/lib/gemba.rb +2 -12
- data/test/achievements_window/test_bulk_sync.rb +218 -0
- data/test/achievements_window/test_bus_events.rb +125 -0
- data/test/achievements_window/test_close_confirmation.rb +201 -0
- data/test/achievements_window/test_initial_state.rb +164 -0
- data/test/achievements_window/test_sorting.rb +227 -0
- data/test/achievements_window/test_tree_rendering.rb +133 -0
- data/test/fixtures/fake_bios.bin +0 -0
- data/test/fixtures/pong.gba +0 -0
- data/test/fixtures/test.gb +0 -0
- data/test/fixtures/test.gbc +0 -0
- data/test/fixtures/test_quicksave.ss +0 -0
- data/test/screenshots/no_focus.png +0 -0
- data/test/shared/teek_test_worker.rb +17 -1
- data/test/shared/tk_test_helper.rb +91 -4
- data/test/support/achievements_window_helpers.rb +18 -0
- data/test/support/fake_core.rb +25 -0
- data/test/support/fake_ra_runtime.rb +74 -0
- data/test/support/fake_requester.rb +68 -0
- data/test/support/player_helpers.rb +20 -5
- data/test/test_achievement.rb +32 -0
- data/test/{test_player.rb → test_app_controller.rb} +353 -85
- data/test/test_bios.rb +123 -0
- data/test/test_boxart_fetcher.rb +150 -0
- data/test/test_cli.rb +17 -265
- data/test/test_cli_config.rb +64 -0
- data/test/test_cli_decode.rb +97 -0
- data/test/test_cli_patch.rb +58 -0
- data/test/test_cli_play.rb +213 -0
- data/test/test_cli_ra.rb +175 -0
- data/test/test_cli_record.rb +69 -0
- data/test/test_cli_replay.rb +72 -0
- data/test/test_cli_sync_requester.rb +152 -0
- data/test/test_cli_version.rb +27 -0
- data/test/test_config.rb +2 -3
- data/test/test_config_ra.rb +69 -0
- data/test/test_core.rb +62 -1
- data/test/test_credentials_presenter.rb +192 -0
- data/test/test_event_bus.rb +100 -0
- data/test/test_fake_backend_achievements.rb +130 -0
- data/test/test_fake_backend_auth.rb +68 -0
- data/test/test_game_index.rb +77 -0
- data/test/test_game_picker_frame.rb +310 -0
- data/test/test_gamepad_map.rb +1 -3
- data/test/test_headless_player.rb +17 -3
- data/test/test_help_window.rb +82 -0
- data/test/test_hotkey_map.rb +22 -1
- data/test/test_input_recorder.rb +179 -0
- data/test/test_input_replay_determinism.rb +113 -0
- data/test/test_input_replayer.rb +162 -0
- data/test/test_keyboard_map.rb +1 -3
- data/test/test_libretro_backend.rb +41 -0
- data/test/test_locale.rb +1 -1
- data/test/test_logging.rb +123 -0
- data/test/test_null_backend.rb +42 -0
- data/test/test_offline_backend.rb +116 -0
- data/test/test_overlay_renderer.rb +1 -1
- data/test/test_platform.rb +149 -0
- data/test/test_ra_backend.rb +313 -0
- data/test/test_ra_backend_unlock_gate.rb +56 -0
- data/test/test_recorder.rb +0 -3
- data/test/test_replay_player.rb +316 -0
- data/test/test_rom_info.rb +149 -0
- data/test/test_rom_overrides.rb +86 -0
- data/test/test_rom_patcher.rb +382 -0
- data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
- data/test/test_save_state_manager.rb +2 -4
- data/test/test_settings_audio.rb +107 -0
- data/test/test_settings_hotkeys.rb +83 -66
- data/test/test_settings_recording.rb +49 -0
- data/test/test_settings_save_states.rb +97 -0
- data/test/test_settings_system.rb +133 -0
- data/test/test_settings_video.rb +450 -0
- data/test/test_settings_window.rb +76 -507
- data/test/test_tip_service.rb +6 -6
- data/test/test_toast_overlay.rb +1 -1
- data/test/test_virtual_events.rb +156 -0
- data/test/test_virtual_keyboard.rb +1 -1
- data/vendor/rcheevos/CHANGELOG.md +495 -0
- data/vendor/rcheevos/LICENSE +21 -0
- data/vendor/rcheevos/Package.swift +33 -0
- data/vendor/rcheevos/README.md +67 -0
- data/vendor/rcheevos/include/module.modulemap +70 -0
- data/vendor/rcheevos/include/rc_api_editor.h +296 -0
- data/vendor/rcheevos/include/rc_api_info.h +280 -0
- data/vendor/rcheevos/include/rc_api_request.h +77 -0
- data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
- data/vendor/rcheevos/include/rc_api_user.h +262 -0
- data/vendor/rcheevos/include/rc_client.h +877 -0
- data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
- data/vendor/rcheevos/include/rc_consoles.h +138 -0
- data/vendor/rcheevos/include/rc_error.h +59 -0
- data/vendor/rcheevos/include/rc_export.h +100 -0
- data/vendor/rcheevos/include/rc_hash.h +200 -0
- data/vendor/rcheevos/include/rc_runtime.h +148 -0
- data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
- data/vendor/rcheevos/include/rc_util.h +51 -0
- data/vendor/rcheevos/include/rcheevos.h +8 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
- data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
- data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
- data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
- data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
- data/vendor/rcheevos/src/rc_client.c +6941 -0
- data/vendor/rcheevos/src/rc_client_external.c +281 -0
- data/vendor/rcheevos/src/rc_client_external.h +177 -0
- data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
- data/vendor/rcheevos/src/rc_client_internal.h +409 -0
- data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
- data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
- data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
- data/vendor/rcheevos/src/rc_compat.c +251 -0
- data/vendor/rcheevos/src/rc_compat.h +121 -0
- data/vendor/rcheevos/src/rc_libretro.c +915 -0
- data/vendor/rcheevos/src/rc_libretro.h +98 -0
- data/vendor/rcheevos/src/rc_util.c +199 -0
- data/vendor/rcheevos/src/rc_version.c +11 -0
- data/vendor/rcheevos/src/rc_version.h +32 -0
- data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
- data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
- data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
- data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
- data/vendor/rcheevos/src/rcheevos/format.c +330 -0
- data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
- data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
- data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
- data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
- data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
- data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
- data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
- data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
- data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
- data/vendor/rcheevos/src/rcheevos/value.c +935 -0
- data/vendor/rcheevos/src/rhash/aes.c +480 -0
- data/vendor/rcheevos/src/rhash/aes.h +49 -0
- data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
- data/vendor/rcheevos/src/rhash/hash.c +1402 -0
- data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
- data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
- data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
- data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
- data/vendor/rcheevos/src/rhash/md5.c +382 -0
- data/vendor/rcheevos/src/rhash/md5.h +91 -0
- data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
- data/vendor/rcheevos/test/libretro.h +205 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
- data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
- data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
- data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
- data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
- data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
- data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
- data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
- data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
- data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
- data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
- data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
- data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
- data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
- data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
- data/vendor/rcheevos/test/rhash/data.c +657 -0
- data/vendor/rcheevos/test/rhash/data.h +32 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
- data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
- data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
- data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
- data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
- data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
- data/vendor/rcheevos/test/test.c +113 -0
- data/vendor/rcheevos/test/test_framework.h +205 -0
- data/vendor/rcheevos/test/test_rc_client.c +10509 -0
- data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
- data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
- data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
- data/vendor/rcheevos/test/test_types.natvis +9 -0
- data/vendor/rcheevos/validator/validator.c +658 -0
- data/vendor/rcheevos/validator/validator.vcxproj +152 -0
- data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
- metadata +274 -11
- data/lib/gemba/input_mappings.rb +0 -214
- data/lib/gemba/player.rb +0 -1525
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "shared/tk_test_helper"
|
|
5
|
+
|
|
6
|
+
class TestHelpWindow < Minitest::Test
|
|
7
|
+
include TeekTestHelper
|
|
8
|
+
|
|
9
|
+
# HelpWindow is wm transient to '.'. The TestWorker withdraws '.' after each
|
|
10
|
+
# test, so a transient child can't be shown unless we deiconify '.' first.
|
|
11
|
+
# app.show deiconifies the root window for tests that check visibility.
|
|
12
|
+
|
|
13
|
+
def test_visible_after_show
|
|
14
|
+
assert_tk_app("help window is visible after show") do
|
|
15
|
+
require "gemba/headless"
|
|
16
|
+
|
|
17
|
+
hotkeys = Struct.new(:m) { def key_for(a) = Gemba::HotkeyMap::DEFAULTS[a] }.new(nil)
|
|
18
|
+
win = Gemba::HelpWindow.new(app: app, hotkeys: hotkeys)
|
|
19
|
+
app.show
|
|
20
|
+
win.show
|
|
21
|
+
app.update # flush pending Tk map events before checking visibility
|
|
22
|
+
|
|
23
|
+
assert win.visible?, "help window should be visible after show"
|
|
24
|
+
|
|
25
|
+
win.hide
|
|
26
|
+
app.command(:destroy, Gemba::HelpWindow::TOP) rescue nil
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_hidden_after_hide
|
|
31
|
+
assert_tk_app("help window is hidden after hide") do
|
|
32
|
+
require "gemba/headless"
|
|
33
|
+
|
|
34
|
+
hotkeys = Struct.new(:m) { def key_for(a) = Gemba::HotkeyMap::DEFAULTS[a] }.new(nil)
|
|
35
|
+
win = Gemba::HelpWindow.new(app: app, hotkeys: hotkeys)
|
|
36
|
+
app.show
|
|
37
|
+
win.show
|
|
38
|
+
win.hide
|
|
39
|
+
|
|
40
|
+
refute win.visible?, "help window should not be visible after hide"
|
|
41
|
+
|
|
42
|
+
app.command(:destroy, Gemba::HelpWindow::TOP) rescue nil
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_rows_show_action_labels
|
|
47
|
+
assert_tk_app("help window rows show translated action labels") do
|
|
48
|
+
require "gemba/headless"
|
|
49
|
+
|
|
50
|
+
hotkeys = Struct.new(:m) { def key_for(a) = Gemba::HotkeyMap::DEFAULTS[a] }.new(nil)
|
|
51
|
+
win = Gemba::HelpWindow.new(app: app, hotkeys: hotkeys)
|
|
52
|
+
win.show
|
|
53
|
+
|
|
54
|
+
text = app.command("#{Gemba::HelpWindow::TOP}.f.row_pause.act", :cget, '-text')
|
|
55
|
+
assert_equal 'Pause', text, "pause row should show 'Pause' label"
|
|
56
|
+
|
|
57
|
+
win.hide
|
|
58
|
+
app.command(:destroy, Gemba::HelpWindow::TOP) rescue nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_rows_show_key_display
|
|
63
|
+
assert_tk_app("help window rows show formatted key names") do
|
|
64
|
+
require "gemba/headless"
|
|
65
|
+
|
|
66
|
+
hotkeys = Struct.new(:m) { def key_for(a) = Gemba::HotkeyMap::DEFAULTS[a] }.new(nil)
|
|
67
|
+
win = Gemba::HelpWindow.new(app: app, hotkeys: hotkeys)
|
|
68
|
+
win.show
|
|
69
|
+
|
|
70
|
+
# pause default is 'p'
|
|
71
|
+
text = app.command("#{Gemba::HelpWindow::TOP}.f.row_pause.key", :cget, '-text')
|
|
72
|
+
assert_equal 'p', text, "pause row key should show 'p'"
|
|
73
|
+
|
|
74
|
+
# quick_save default is 'F5'
|
|
75
|
+
text = app.command("#{Gemba::HelpWindow::TOP}.f.row_quick_save.key", :cget, '-text')
|
|
76
|
+
assert_equal 'F5', text, "quick_save row key should show 'F5'"
|
|
77
|
+
|
|
78
|
+
win.hide
|
|
79
|
+
app.command(:destroy, Gemba::HelpWindow::TOP) rescue nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/test/test_hotkey_map.rb
CHANGED
|
@@ -29,7 +29,7 @@ end
|
|
|
29
29
|
|
|
30
30
|
class TestHotkeyMap < Minitest::Test
|
|
31
31
|
def setup
|
|
32
|
-
require "gemba/
|
|
32
|
+
require "gemba/headless"
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def make_map(hotkey_data = {})
|
|
@@ -393,4 +393,25 @@ class TestHotkeyMap < Minitest::Test
|
|
|
393
393
|
map, = make_map
|
|
394
394
|
assert_equal :record, map.action_for('F10')
|
|
395
395
|
end
|
|
396
|
+
|
|
397
|
+
# -- Open ROM action -------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
def test_open_rom_in_actions
|
|
400
|
+
assert_includes Gemba::HotkeyMap::ACTIONS, :open_rom
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def test_open_rom_default_is_ctrl_o
|
|
404
|
+
map, = make_map
|
|
405
|
+
assert_equal ['Control', 'o'], map.key_for(:open_rom)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def test_open_rom_dispatches_on_ctrl_o
|
|
409
|
+
map, = make_map
|
|
410
|
+
assert_equal :open_rom, map.action_for('o', modifiers: Set.new(['Control']))
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def test_open_rom_plain_o_does_not_dispatch
|
|
414
|
+
map, = make_map
|
|
415
|
+
assert_nil map.action_for('o')
|
|
416
|
+
end
|
|
396
417
|
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/headless"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
|
|
8
|
+
class TestInputRecorder < Minitest::Test
|
|
9
|
+
TEST_ROM = File.expand_path("fixtures/test.gba", __dir__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# -- Lifecycle ---------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
def test_start_and_stop
|
|
15
|
+
Dir.mktmpdir do |dir|
|
|
16
|
+
path = File.join(dir, "test.gir")
|
|
17
|
+
|
|
18
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
19
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
20
|
+
refute rec.recording?
|
|
21
|
+
rec.start
|
|
22
|
+
assert rec.recording?
|
|
23
|
+
rec.capture(0)
|
|
24
|
+
rec.stop
|
|
25
|
+
refute rec.recording?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
assert File.exist?(path)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_double_start_raises
|
|
33
|
+
Dir.mktmpdir do |dir|
|
|
34
|
+
path = File.join(dir, "test.gir")
|
|
35
|
+
|
|
36
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
37
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
38
|
+
rec.start
|
|
39
|
+
assert_raises(RuntimeError) { rec.start }
|
|
40
|
+
rec.stop
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_stop_without_start_is_safe
|
|
46
|
+
Dir.mktmpdir do |dir|
|
|
47
|
+
path = File.join(dir, "test.gir")
|
|
48
|
+
|
|
49
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
50
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
51
|
+
rec.stop # should not raise
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# -- Anchor state ------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def test_anchor_state_created
|
|
59
|
+
Dir.mktmpdir do |dir|
|
|
60
|
+
path = File.join(dir, "test.gir")
|
|
61
|
+
|
|
62
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
63
|
+
player.step(10) # advance so state has content
|
|
64
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
65
|
+
rec.start
|
|
66
|
+
rec.capture(0)
|
|
67
|
+
rec.stop
|
|
68
|
+
|
|
69
|
+
state_path = path.sub(/\.gir\z/, '.state')
|
|
70
|
+
assert File.exist?(state_path), "anchor .state file should exist"
|
|
71
|
+
assert_operator File.size(state_path), :>, 0
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# -- Header ------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def test_header_format
|
|
79
|
+
Dir.mktmpdir do |dir|
|
|
80
|
+
path = File.join(dir, "test.gir")
|
|
81
|
+
|
|
82
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
83
|
+
core = player.core
|
|
84
|
+
rec = Gemba::InputRecorder.new(path, core: core)
|
|
85
|
+
rec.start
|
|
86
|
+
rec.capture(0)
|
|
87
|
+
rec.stop
|
|
88
|
+
|
|
89
|
+
lines = File.readlines(path)
|
|
90
|
+
assert_match(/^# GEMBA INPUT RECORDING v1$/, lines[0])
|
|
91
|
+
assert_match(/^# rom_checksum: \d+$/, lines[1])
|
|
92
|
+
assert_match(/^# game_code: .+$/, lines[2])
|
|
93
|
+
assert_match(/^# frame_count: \d+$/, lines[3])
|
|
94
|
+
assert_match(/^# anchor_state: .+\.state$/, lines[4])
|
|
95
|
+
assert_equal "---\n", lines[5]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_header_frame_count_updated_on_clean_stop
|
|
101
|
+
Dir.mktmpdir do |dir|
|
|
102
|
+
path = File.join(dir, "test.gir")
|
|
103
|
+
|
|
104
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
105
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
106
|
+
rec.start
|
|
107
|
+
5.times { |i| rec.capture(i) }
|
|
108
|
+
rec.stop
|
|
109
|
+
|
|
110
|
+
lines = File.readlines(path)
|
|
111
|
+
assert_equal "# frame_count: 0000000005\n", lines[3]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# -- Bitmask data ------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def test_captures_correct_bitmasks
|
|
119
|
+
Dir.mktmpdir do |dir|
|
|
120
|
+
path = File.join(dir, "test.gir")
|
|
121
|
+
|
|
122
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
123
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
124
|
+
rec.start
|
|
125
|
+
rec.capture(0x000) # nothing
|
|
126
|
+
rec.capture(Gemba::KEY_A) # 0x001
|
|
127
|
+
rec.capture(Gemba::KEY_UP | Gemba::KEY_A) # 0x041
|
|
128
|
+
rec.capture(0x3FF) # all buttons
|
|
129
|
+
rec.stop
|
|
130
|
+
|
|
131
|
+
lines = File.readlines(path)
|
|
132
|
+
bitmasks = lines[6..] # after header (6 lines: 5 header + ---)
|
|
133
|
+
assert_equal "000\n", bitmasks[0]
|
|
134
|
+
assert_equal "001\n", bitmasks[1]
|
|
135
|
+
assert_equal "041\n", bitmasks[2]
|
|
136
|
+
assert_equal "3ff\n", bitmasks[3]
|
|
137
|
+
assert_equal 4, rec.frame_count
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_bitmask_masked_to_10_bits
|
|
143
|
+
Dir.mktmpdir do |dir|
|
|
144
|
+
path = File.join(dir, "test.gir")
|
|
145
|
+
|
|
146
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
147
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
148
|
+
rec.start
|
|
149
|
+
rec.capture(0xFFFF) # upper bits should be masked off
|
|
150
|
+
rec.stop
|
|
151
|
+
|
|
152
|
+
lines = File.readlines(path)
|
|
153
|
+
assert_equal "3ff\n", lines[6]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# -- Flush behavior ----------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
def test_periodic_flush_writes_data
|
|
161
|
+
Dir.mktmpdir do |dir|
|
|
162
|
+
path = File.join(dir, "test.gir")
|
|
163
|
+
|
|
164
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
165
|
+
rec = Gemba::InputRecorder.new(path, core: player.core)
|
|
166
|
+
rec.start
|
|
167
|
+
|
|
168
|
+
# Write exactly FLUSH_INTERVAL frames to trigger a flush
|
|
169
|
+
Gemba::InputRecorder::FLUSH_INTERVAL.times { rec.capture(0) }
|
|
170
|
+
|
|
171
|
+
# File should have data on disk even before stop
|
|
172
|
+
size_before_stop = File.size(path)
|
|
173
|
+
assert_operator size_before_stop, :>, 0
|
|
174
|
+
|
|
175
|
+
rec.stop
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/headless"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
|
|
8
|
+
class TestInputReplayDeterminism < Minitest::Test
|
|
9
|
+
PONG_ROM = File.expand_path("fixtures/pong.gba", __dir__)
|
|
10
|
+
TEST_ROM = File.expand_path("fixtures/test.gba", __dir__)
|
|
11
|
+
|
|
12
|
+
# Record inputs against pong, then replay from anchor state.
|
|
13
|
+
# Video buffer after replay must match the original recording frame-for-frame.
|
|
14
|
+
def test_deterministic_replay
|
|
15
|
+
Dir.mktmpdir do |dir|
|
|
16
|
+
gir_path = File.join(dir, "pong.gir")
|
|
17
|
+
frames = 120 # ~2 seconds of gameplay
|
|
18
|
+
|
|
19
|
+
# -- Phase 1: Record --
|
|
20
|
+
final_video = nil
|
|
21
|
+
Gemba::HeadlessPlayer.open(PONG_ROM) do |player|
|
|
22
|
+
# Let the game boot for a few frames
|
|
23
|
+
player.step(30)
|
|
24
|
+
|
|
25
|
+
core = player.core
|
|
26
|
+
rec = Gemba::InputRecorder.new(gir_path, core: core)
|
|
27
|
+
rec.start
|
|
28
|
+
|
|
29
|
+
# Play some inputs: Start to begin, then Up, A, idle, Down+B
|
|
30
|
+
inputs = []
|
|
31
|
+
inputs.concat([Gemba::KEY_START] * 30)
|
|
32
|
+
inputs.concat([Gemba::KEY_UP] * 30)
|
|
33
|
+
inputs.concat([Gemba::KEY_A] * 30)
|
|
34
|
+
inputs.concat([0] * 30)
|
|
35
|
+
inputs.concat([Gemba::KEY_DOWN | Gemba::KEY_B] * 30)
|
|
36
|
+
|
|
37
|
+
inputs.each do |mask|
|
|
38
|
+
rec.capture(mask)
|
|
39
|
+
core.set_keys(mask)
|
|
40
|
+
core.run_frame
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
rec.stop
|
|
44
|
+
final_video = core.video_buffer_argb.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
assert File.exist?(gir_path), ".gir file should exist"
|
|
48
|
+
assert File.exist?(gir_path.sub(/\.gir\z/, '.state')), ".state file should exist"
|
|
49
|
+
|
|
50
|
+
# -- Phase 2: Replay --
|
|
51
|
+
replay_video = nil
|
|
52
|
+
Gemba::HeadlessPlayer.open(PONG_ROM) do |player|
|
|
53
|
+
player.replay(gir_path)
|
|
54
|
+
replay_video = player.video_buffer_argb.dup
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# -- Phase 3: Compare --
|
|
58
|
+
assert_equal final_video.bytesize, replay_video.bytesize,
|
|
59
|
+
"video buffer sizes should match"
|
|
60
|
+
assert_equal final_video, replay_video,
|
|
61
|
+
"video buffer after replay should be identical to recording"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Replay with the wrong ROM should raise ChecksumMismatch.
|
|
66
|
+
def test_replay_wrong_rom_raises
|
|
67
|
+
skip "test.gba fixture missing" unless File.exist?(TEST_ROM)
|
|
68
|
+
|
|
69
|
+
Dir.mktmpdir do |dir|
|
|
70
|
+
gir_path = File.join(dir, "pong.gir")
|
|
71
|
+
|
|
72
|
+
# Record against pong
|
|
73
|
+
Gemba::HeadlessPlayer.open(PONG_ROM) do |player|
|
|
74
|
+
player.step(5)
|
|
75
|
+
core = player.core
|
|
76
|
+
rec = Gemba::InputRecorder.new(gir_path, core: core)
|
|
77
|
+
rec.start
|
|
78
|
+
rec.capture(0)
|
|
79
|
+
rec.stop
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Try to replay against test.gba — wrong ROM
|
|
83
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
84
|
+
assert_raises(Gemba::InputReplayer::ChecksumMismatch) do
|
|
85
|
+
player.replay(gir_path)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Replay with block yields each frame.
|
|
92
|
+
def test_replay_yields_frames
|
|
93
|
+
Dir.mktmpdir do |dir|
|
|
94
|
+
gir_path = File.join(dir, "pong.gir")
|
|
95
|
+
|
|
96
|
+
Gemba::HeadlessPlayer.open(PONG_ROM) do |player|
|
|
97
|
+
player.step(5)
|
|
98
|
+
core = player.core
|
|
99
|
+
rec = Gemba::InputRecorder.new(gir_path, core: core)
|
|
100
|
+
rec.start
|
|
101
|
+
[0x001, 0x041, 0x000].each { |m| rec.capture(m) }
|
|
102
|
+
rec.stop
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
collected = []
|
|
106
|
+
Gemba::HeadlessPlayer.open(PONG_ROM) do |player|
|
|
107
|
+
player.replay(gir_path) { |mask, idx| collected << [mask, idx] }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
assert_equal [[0x001, 0], [0x041, 1], [0x000, 2]], collected
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/headless"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
|
|
8
|
+
class TestInputReplayer < Minitest::Test
|
|
9
|
+
TEST_ROM = File.expand_path("fixtures/test.gba", __dir__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# -- Round-trip: record then replay ------------------------------------------
|
|
13
|
+
|
|
14
|
+
def test_round_trip_frame_count
|
|
15
|
+
Dir.mktmpdir do |dir|
|
|
16
|
+
gir_path = File.join(dir, "test.gir")
|
|
17
|
+
record(gir_path, [0x000, 0x001, 0x041, 0x3FF])
|
|
18
|
+
|
|
19
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
20
|
+
assert_equal 4, replayer.frame_count
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_round_trip_bitmasks
|
|
25
|
+
Dir.mktmpdir do |dir|
|
|
26
|
+
gir_path = File.join(dir, "test.gir")
|
|
27
|
+
inputs = [0x000, 0x001, 0x041, 0x3FF]
|
|
28
|
+
record(gir_path, inputs)
|
|
29
|
+
|
|
30
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
31
|
+
inputs.each_with_index do |expected, i|
|
|
32
|
+
assert_equal expected, replayer.bitmask_at(i), "frame #{i}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_each_bitmask
|
|
38
|
+
Dir.mktmpdir do |dir|
|
|
39
|
+
gir_path = File.join(dir, "test.gir")
|
|
40
|
+
inputs = [0x001, 0x041, 0x000]
|
|
41
|
+
record(gir_path, inputs)
|
|
42
|
+
|
|
43
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
44
|
+
collected = []
|
|
45
|
+
replayer.each_bitmask { |mask, idx| collected << [mask, idx] }
|
|
46
|
+
|
|
47
|
+
assert_equal [[0x001, 0], [0x041, 1], [0x000, 2]], collected
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# -- Header parsing ----------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
def test_rom_checksum_parsed
|
|
54
|
+
Dir.mktmpdir do |dir|
|
|
55
|
+
gir_path = File.join(dir, "test.gir")
|
|
56
|
+
record(gir_path, [0])
|
|
57
|
+
|
|
58
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
59
|
+
assert_kind_of Integer, replayer.rom_checksum
|
|
60
|
+
assert_operator replayer.rom_checksum, :>, 0
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_game_code_parsed
|
|
65
|
+
Dir.mktmpdir do |dir|
|
|
66
|
+
gir_path = File.join(dir, "test.gir")
|
|
67
|
+
record(gir_path, [0])
|
|
68
|
+
|
|
69
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
70
|
+
assert_equal "AGB-BGBE", replayer.game_code
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_anchor_state_path
|
|
75
|
+
Dir.mktmpdir do |dir|
|
|
76
|
+
gir_path = File.join(dir, "test.gir")
|
|
77
|
+
record(gir_path, [0])
|
|
78
|
+
|
|
79
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
80
|
+
expected = File.join(dir, "test.state")
|
|
81
|
+
assert_equal expected, replayer.anchor_state_path
|
|
82
|
+
assert File.exist?(replayer.anchor_state_path)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# -- Validation --------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def test_validate_passes_with_matching_rom
|
|
89
|
+
Dir.mktmpdir do |dir|
|
|
90
|
+
gir_path = File.join(dir, "test.gir")
|
|
91
|
+
record(gir_path, [0])
|
|
92
|
+
|
|
93
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
94
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
95
|
+
replayer.validate!(player.core)
|
|
96
|
+
# should not raise
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_validate_raises_on_checksum_mismatch
|
|
102
|
+
Dir.mktmpdir do |dir|
|
|
103
|
+
gir_path = File.join(dir, "test.gir")
|
|
104
|
+
# Write a fake .gir with a bogus checksum
|
|
105
|
+
File.write(gir_path, <<~GIR)
|
|
106
|
+
# GEMBA INPUT RECORDING v1
|
|
107
|
+
# rom_checksum: 99999
|
|
108
|
+
# game_code: FAKE
|
|
109
|
+
# frame_count: 1
|
|
110
|
+
# anchor_state: test.state
|
|
111
|
+
---
|
|
112
|
+
000
|
|
113
|
+
GIR
|
|
114
|
+
|
|
115
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
116
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
117
|
+
assert_raises(Gemba::InputReplayer::ChecksumMismatch) do
|
|
118
|
+
replayer.validate!(player.core)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# -- Crash resilience --------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
def test_truncated_file_loads_available_frames
|
|
127
|
+
Dir.mktmpdir do |dir|
|
|
128
|
+
gir_path = File.join(dir, "test.gir")
|
|
129
|
+
# Simulate a crash: header says 0 frames but body has 3
|
|
130
|
+
File.write(gir_path, <<~GIR)
|
|
131
|
+
# GEMBA INPUT RECORDING v1
|
|
132
|
+
# rom_checksum: 12345
|
|
133
|
+
# game_code: TEST
|
|
134
|
+
# frame_count: 0
|
|
135
|
+
# anchor_state: test.state
|
|
136
|
+
---
|
|
137
|
+
001
|
|
138
|
+
041
|
|
139
|
+
000
|
|
140
|
+
GIR
|
|
141
|
+
|
|
142
|
+
replayer = Gemba::InputReplayer.new(gir_path)
|
|
143
|
+
assert_equal 3, replayer.frame_count
|
|
144
|
+
assert_equal 0x001, replayer.bitmask_at(0)
|
|
145
|
+
assert_equal 0x041, replayer.bitmask_at(1)
|
|
146
|
+
assert_equal 0x000, replayer.bitmask_at(2)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
def record(gir_path, bitmasks)
|
|
153
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
154
|
+
player.step(5) # advance a few frames for meaningful state
|
|
155
|
+
core = player.core
|
|
156
|
+
rec = Gemba::InputRecorder.new(gir_path, core: core)
|
|
157
|
+
rec.start
|
|
158
|
+
bitmasks.each { |m| rec.capture(m) }
|
|
159
|
+
rec.stop
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/test/test_keyboard_map.rb
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "minitest/autorun"
|
|
4
|
-
require "gemba"
|
|
5
|
-
require_relative "../lib/gemba/config"
|
|
6
|
-
require_relative "../lib/gemba/input_mappings"
|
|
4
|
+
require "gemba/headless"
|
|
7
5
|
require_relative "support/input_mocks"
|
|
8
6
|
|
|
9
7
|
class TestKeyboardMap < Minitest::Test
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
|
|
6
|
+
class TestLibretroBackend < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
Gemba::GameIndex.reset!
|
|
9
|
+
@backend = Gemba::BoxartFetcher::LibretroBackend.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_url_for_known_gba_game
|
|
13
|
+
url = @backend.url_for("AGB-AXVE")
|
|
14
|
+
assert_match %r{thumbnails\.libretro\.com}, url
|
|
15
|
+
assert_match %r{Nintendo%20-%20Game%20Boy%20Advance}, url
|
|
16
|
+
assert_match %r{Named_Boxarts}, url
|
|
17
|
+
assert_match %r{Pokemon}, url
|
|
18
|
+
assert url.end_with?(".png")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_url_for_unknown_game_returns_nil
|
|
22
|
+
assert_nil @backend.url_for("AGB-ZZZZ")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_url_for_unknown_platform_returns_nil
|
|
26
|
+
assert_nil @backend.url_for("XYZ-AAAA")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_url_encodes_special_characters
|
|
30
|
+
# Games with special chars (parentheses, ampersands, etc.) should be encoded
|
|
31
|
+
url = @backend.url_for("AGB-AXVE")
|
|
32
|
+
refute_includes url, " " # no raw spaces
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_url_for_gb_game
|
|
36
|
+
url = @backend.url_for("DMG-APAU")
|
|
37
|
+
if url # GB data is sparse
|
|
38
|
+
assert_match %r{Nintendo%20-%20Game%20Boy/}, url
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/test/test_locale.rb
CHANGED