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,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/achievements"
|
|
6
|
+
require_relative "support/fake_core"
|
|
7
|
+
|
|
8
|
+
class TestFakeBackendAchievements < Minitest::Test
|
|
9
|
+
ADDR = 0x02000000
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
@backend = Gemba::Achievements::FakeBackend.new
|
|
13
|
+
@backend.add_achievement(id: 'test', title: 'Test', description: 'desc', points: 5) do |mem|
|
|
14
|
+
mem.call(ADDR) == 0x42
|
|
15
|
+
end
|
|
16
|
+
@core = FakeCore.new
|
|
17
|
+
@unlocked = []
|
|
18
|
+
@backend.on_unlock { |ach| @unlocked << ach }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_no_unlock_when_condition_false
|
|
22
|
+
@core.poke(ADDR, 0x00)
|
|
23
|
+
@backend.do_frame(@core)
|
|
24
|
+
assert_empty @unlocked
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_unlock_fires_when_condition_becomes_true
|
|
28
|
+
@core.poke(ADDR, 0x42)
|
|
29
|
+
@backend.do_frame(@core)
|
|
30
|
+
assert_equal 1, @unlocked.size
|
|
31
|
+
assert_equal 'test', @unlocked.first.id
|
|
32
|
+
assert_equal 'Test', @unlocked.first.title
|
|
33
|
+
assert_equal 5, @unlocked.first.points
|
|
34
|
+
assert @unlocked.first.earned?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_unlock_fires_only_once_rising_edge
|
|
38
|
+
@core.poke(ADDR, 0x42)
|
|
39
|
+
@backend.do_frame(@core)
|
|
40
|
+
@backend.do_frame(@core)
|
|
41
|
+
assert_equal 1, @unlocked.size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_no_second_unlock_after_condition_cycles
|
|
45
|
+
@core.poke(ADDR, 0x42)
|
|
46
|
+
@backend.do_frame(@core)
|
|
47
|
+
@core.poke(ADDR, 0x00)
|
|
48
|
+
@backend.do_frame(@core)
|
|
49
|
+
@core.poke(ADDR, 0x42)
|
|
50
|
+
@backend.do_frame(@core)
|
|
51
|
+
assert_equal 1, @unlocked.size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_fires_on_first_true_frame
|
|
55
|
+
@core.poke(ADDR, 0x42)
|
|
56
|
+
@backend.do_frame(@core)
|
|
57
|
+
assert_equal 1, @unlocked.size
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_no_unlock_on_wrong_value
|
|
61
|
+
@core.poke(ADDR, 0x01)
|
|
62
|
+
@backend.do_frame(@core)
|
|
63
|
+
assert_empty @unlocked
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_achievement_list_reflects_earned_state
|
|
67
|
+
assert_equal 1, @backend.total_count
|
|
68
|
+
assert_equal 0, @backend.earned_count
|
|
69
|
+
|
|
70
|
+
@core.poke(ADDR, 0x42)
|
|
71
|
+
@backend.do_frame(@core)
|
|
72
|
+
|
|
73
|
+
assert_equal 1, @backend.earned_count
|
|
74
|
+
assert @backend.achievement_list.first.earned?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_multiple_achievements_independent
|
|
78
|
+
@backend.add_achievement(id: 'two', title: 'Two', description: '', points: 10) do |mem|
|
|
79
|
+
mem.call(ADDR + 1) == 0xFF
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
@core.poke(ADDR, 0x42)
|
|
83
|
+
@backend.do_frame(@core)
|
|
84
|
+
assert_equal 1, @unlocked.size
|
|
85
|
+
assert_equal 'test', @unlocked.first.id
|
|
86
|
+
|
|
87
|
+
@core.poke(ADDR + 1, 0xFF)
|
|
88
|
+
@backend.do_frame(@core)
|
|
89
|
+
assert_equal 2, @unlocked.size
|
|
90
|
+
assert_equal 'two', @unlocked.last.id
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_reset_earned_allows_re_unlock
|
|
94
|
+
@core.poke(ADDR, 0x42)
|
|
95
|
+
@backend.do_frame(@core)
|
|
96
|
+
assert_equal 1, @backend.earned_count
|
|
97
|
+
|
|
98
|
+
@backend.reset_earned
|
|
99
|
+
assert_equal 0, @backend.earned_count
|
|
100
|
+
|
|
101
|
+
@unlocked.clear
|
|
102
|
+
@core.poke(ADDR, 0x00)
|
|
103
|
+
@backend.do_frame(@core)
|
|
104
|
+
@core.poke(ADDR, 0x42)
|
|
105
|
+
@backend.do_frame(@core)
|
|
106
|
+
assert_equal 1, @unlocked.size
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_load_game_resets_earned
|
|
110
|
+
@core.poke(ADDR, 0x42)
|
|
111
|
+
@backend.do_frame(@core)
|
|
112
|
+
assert_equal 1, @backend.earned_count
|
|
113
|
+
|
|
114
|
+
@backend.load_game(@core)
|
|
115
|
+
assert_equal 0, @backend.earned_count
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_multiple_unlock_callbacks_all_fire
|
|
119
|
+
other = []
|
|
120
|
+
@backend.on_unlock { |ach| other << ach.id }
|
|
121
|
+
@core.poke(ADDR, 0x42)
|
|
122
|
+
@backend.do_frame(@core)
|
|
123
|
+
assert_equal ['test'], @unlocked.map(&:id)
|
|
124
|
+
assert_equal ['test'], other
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def test_enabled
|
|
128
|
+
assert @backend.enabled?
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
require "gemba/achievements"
|
|
6
|
+
|
|
7
|
+
class TestFakeBackendAuth < Minitest::Test
|
|
8
|
+
def test_any_nonempty_creds_succeed_by_default
|
|
9
|
+
b = Gemba::Achievements::FakeBackend.new
|
|
10
|
+
statuses = []
|
|
11
|
+
b.on_auth_change { |status, _| statuses << status }
|
|
12
|
+
b.login_with_token(username: 'alice', token: 'anything')
|
|
13
|
+
assert b.authenticated?
|
|
14
|
+
assert_equal [:ok], statuses
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_empty_username_fails
|
|
18
|
+
b = Gemba::Achievements::FakeBackend.new
|
|
19
|
+
b.login_with_token(username: '', token: 'tok')
|
|
20
|
+
refute b.authenticated?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_empty_token_fails
|
|
24
|
+
b = Gemba::Achievements::FakeBackend.new
|
|
25
|
+
b.login_with_token(username: 'alice', token: '')
|
|
26
|
+
refute b.authenticated?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_restricted_correct_pair_succeeds
|
|
30
|
+
b = Gemba::Achievements::FakeBackend.new(valid_username: 'alice', valid_token: 'secret')
|
|
31
|
+
b.login_with_token(username: 'alice', token: 'secret')
|
|
32
|
+
assert b.authenticated?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_restricted_wrong_user_fails
|
|
36
|
+
b = Gemba::Achievements::FakeBackend.new(valid_username: 'alice', valid_token: 'secret')
|
|
37
|
+
statuses = []
|
|
38
|
+
errors = []
|
|
39
|
+
b.on_auth_change { |s, e| statuses << s; errors << e }
|
|
40
|
+
b.login_with_token(username: 'bob', token: 'secret')
|
|
41
|
+
refute b.authenticated?
|
|
42
|
+
assert_equal [:error], statuses
|
|
43
|
+
refute_nil errors.first
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_restricted_wrong_token_fails
|
|
47
|
+
b = Gemba::Achievements::FakeBackend.new(valid_username: 'alice', valid_token: 'secret')
|
|
48
|
+
b.login_with_token(username: 'alice', token: 'wrong')
|
|
49
|
+
refute b.authenticated?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_logout_clears_auth
|
|
53
|
+
b = Gemba::Achievements::FakeBackend.new
|
|
54
|
+
b.login_with_token(username: 'alice', token: 'tok')
|
|
55
|
+
assert b.authenticated?
|
|
56
|
+
b.logout
|
|
57
|
+
refute b.authenticated?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_multiple_auth_callbacks_all_fired
|
|
61
|
+
b = Gemba::Achievements::FakeBackend.new
|
|
62
|
+
results = []
|
|
63
|
+
b.on_auth_change { |s, _| results << "cb1:#{s}" }
|
|
64
|
+
b.on_auth_change { |s, _| results << "cb2:#{s}" }
|
|
65
|
+
b.login_with_token(username: 'u', token: 't')
|
|
66
|
+
assert_equal ['cb1:ok', 'cb2:ok'], results
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
require "gemba/headless"
|
|
5
|
+
|
|
6
|
+
class TestGameIndex < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
Gemba::GameIndex.reset!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# -- lookup (by game code) -----------------------------------------------
|
|
12
|
+
|
|
13
|
+
def test_lookup_known_gba_game
|
|
14
|
+
name = Gemba::GameIndex.lookup("AGB-AXVE")
|
|
15
|
+
assert_includes name, "Pokemon"
|
|
16
|
+
assert_includes name, "Ruby"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_lookup_returns_nil_for_unknown_code
|
|
20
|
+
assert_nil Gemba::GameIndex.lookup("AGB-ZZZZ")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_lookup_returns_nil_for_nil_input
|
|
24
|
+
assert_nil Gemba::GameIndex.lookup(nil)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_lookup_returns_nil_for_empty_string
|
|
28
|
+
assert_nil Gemba::GameIndex.lookup("")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_lookup_returns_nil_for_unknown_platform_prefix
|
|
32
|
+
assert_nil Gemba::GameIndex.lookup("XYZ-AAAA")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_lookup_known_gbc_game
|
|
36
|
+
# Pokemon Gold is a well-known GBC title
|
|
37
|
+
name = Gemba::GameIndex.lookup("CGB-BYEE")
|
|
38
|
+
assert_includes name, "Pokemon" if name # only assert content if present
|
|
39
|
+
# At minimum it must not raise
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# -- lookup_by_md5 -------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def test_lookup_by_md5_returns_nil_for_nil_md5
|
|
45
|
+
assert_nil Gemba::GameIndex.lookup_by_md5(nil, "gba")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_lookup_by_md5_returns_nil_for_empty_md5
|
|
49
|
+
assert_nil Gemba::GameIndex.lookup_by_md5("", "gba")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_lookup_by_md5_returns_nil_for_unknown_platform
|
|
53
|
+
assert_nil Gemba::GameIndex.lookup_by_md5("abc123", "snes")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_lookup_by_md5_returns_nil_for_unknown_digest
|
|
57
|
+
assert_nil Gemba::GameIndex.lookup_by_md5("0" * 32, "gba")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_lookup_by_md5_is_case_insensitive
|
|
61
|
+
# Use a real digest present in gba_md5.json so both lookups must return a non-nil title
|
|
62
|
+
md5 = "0007d212d9b76a466c7ca003d50c8c74"
|
|
63
|
+
lower = Gemba::GameIndex.lookup_by_md5(md5.downcase, "gba")
|
|
64
|
+
upper = Gemba::GameIndex.lookup_by_md5(md5.upcase, "gba")
|
|
65
|
+
refute_nil lower, "lowercase MD5 lookup must return a title"
|
|
66
|
+
assert_equal lower, upper
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# -- reset! / caching ----------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def test_reset_clears_cache_and_reloads
|
|
72
|
+
Gemba::GameIndex.lookup("AGB-AXVE")
|
|
73
|
+
Gemba::GameIndex.reset!
|
|
74
|
+
name = Gemba::GameIndex.lookup("AGB-AXVE")
|
|
75
|
+
assert_includes name, "Pokemon"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "minitest/autorun"
|
|
4
|
+
require_relative "shared/tk_test_helper"
|
|
5
|
+
|
|
6
|
+
class TestGamePickerFrame < Minitest::Test
|
|
7
|
+
include TeekTestHelper
|
|
8
|
+
|
|
9
|
+
# Each test gets a fresh picker. Since Teek::TestWorker persists across tests,
|
|
10
|
+
# we must destroy .game_picker at the end of each test so the next test can
|
|
11
|
+
# recreate it cleanly (ttk::frame fails if the path already exists).
|
|
12
|
+
def cleanup_picker(app)
|
|
13
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_empty_library_shows_all_hollow_cards
|
|
17
|
+
assert_tk_app("empty library shows all hollow cards") do
|
|
18
|
+
require "gemba/headless"
|
|
19
|
+
|
|
20
|
+
lib = Struct.new(:roms) { def all = roms }.new([])
|
|
21
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
22
|
+
picker.show
|
|
23
|
+
|
|
24
|
+
16.times do |i|
|
|
25
|
+
title = app.command(".game_picker.card#{i}.title", :cget, '-text')
|
|
26
|
+
assert_equal '', title, "Card #{i} title should be empty"
|
|
27
|
+
|
|
28
|
+
img = app.command(".game_picker.card#{i}.img", :cget, '-image')
|
|
29
|
+
assert_equal 'boxart_placeholder', img, "Card #{i} should show placeholder image"
|
|
30
|
+
|
|
31
|
+
bg = app.command(".game_picker.card#{i}", :cget, '-bg')
|
|
32
|
+
refute_equal '#2a2a2a', bg, "Card #{i} should not have populated background"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
picker.cleanup
|
|
36
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_populated_card_shows_title_and_platform
|
|
41
|
+
assert_tk_app("populated card shows title and platform text") do
|
|
42
|
+
require "gemba/headless"
|
|
43
|
+
|
|
44
|
+
rom = { 'title' => 'Pokemon Ruby', 'platform' => 'gba',
|
|
45
|
+
'game_code' => 'AGB-AXVE', 'path' => '/games/ruby.gba' }
|
|
46
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
47
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
48
|
+
picker.show
|
|
49
|
+
|
|
50
|
+
assert_equal 'Pokemon - Ruby Version (USA, Europe)', app.command('.game_picker.card0.title', :cget, '-text')
|
|
51
|
+
assert_equal 'GBA', app.command('.game_picker.card0.plat', :cget, '-text')
|
|
52
|
+
|
|
53
|
+
picker.cleanup
|
|
54
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_platform_is_uppercased
|
|
59
|
+
assert_tk_app("platform label is uppercased") do
|
|
60
|
+
require "gemba/headless"
|
|
61
|
+
|
|
62
|
+
rom = { 'title' => 'Tetris', 'platform' => 'gbc', 'path' => '/games/tetris.gbc' }
|
|
63
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
64
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
65
|
+
picker.show
|
|
66
|
+
|
|
67
|
+
assert_equal 'GBC', app.command('.game_picker.card0.plat', :cget, '-text')
|
|
68
|
+
|
|
69
|
+
picker.cleanup
|
|
70
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_title_falls_back_to_rom_id_when_title_missing
|
|
75
|
+
assert_tk_app("title falls back to rom_id when title key absent") do
|
|
76
|
+
require "gemba/headless"
|
|
77
|
+
|
|
78
|
+
rom = { 'rom_id' => 'MY-ROM', 'platform' => 'gba', 'path' => '/games/x.gba' }
|
|
79
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
80
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
81
|
+
picker.show
|
|
82
|
+
|
|
83
|
+
assert_equal 'MY-ROM', app.command('.game_picker.card0.title', :cget, '-text')
|
|
84
|
+
|
|
85
|
+
picker.cleanup
|
|
86
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_populated_card_background_differs_from_hollow
|
|
91
|
+
assert_tk_app("populated card has different background color than hollow card") do
|
|
92
|
+
require "gemba/headless"
|
|
93
|
+
|
|
94
|
+
rom = { 'title' => 'Test Game', 'platform' => 'gba', 'path' => '/games/test.gba' }
|
|
95
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
96
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
97
|
+
picker.show
|
|
98
|
+
|
|
99
|
+
pop_bg = app.command('.game_picker.card0', :cget, '-bg')
|
|
100
|
+
hollow_bg = app.command('.game_picker.card1', :cget, '-bg')
|
|
101
|
+
|
|
102
|
+
assert_equal '#2a2a2a', pop_bg, "Populated card background"
|
|
103
|
+
refute_equal pop_bg, hollow_bg, "Hollow card should differ from populated"
|
|
104
|
+
|
|
105
|
+
picker.cleanup
|
|
106
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_multiple_roms_populate_correct_cards_in_order
|
|
111
|
+
assert_tk_app("multiple ROMs fill cards in order; remainder are hollow") do
|
|
112
|
+
require "gemba/headless"
|
|
113
|
+
|
|
114
|
+
roms = [
|
|
115
|
+
{ 'title' => 'Alpha', 'platform' => 'gba', 'path' => '/a.gba' },
|
|
116
|
+
{ 'title' => 'Beta', 'platform' => 'gbc', 'path' => '/b.gbc' },
|
|
117
|
+
]
|
|
118
|
+
lib = Struct.new(:roms) { def all = roms }.new(roms)
|
|
119
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
120
|
+
picker.show
|
|
121
|
+
|
|
122
|
+
assert_equal 'Alpha', app.command('.game_picker.card0.title', :cget, '-text')
|
|
123
|
+
assert_equal 'GBA', app.command('.game_picker.card0.plat', :cget, '-text')
|
|
124
|
+
assert_equal 'Beta', app.command('.game_picker.card1.title', :cget, '-text')
|
|
125
|
+
assert_equal 'GBC', app.command('.game_picker.card1.plat', :cget, '-text')
|
|
126
|
+
assert_equal '', app.command('.game_picker.card2.title', :cget, '-text')
|
|
127
|
+
|
|
128
|
+
picker.cleanup
|
|
129
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_pre_cached_boxart_shown_immediately_without_network
|
|
134
|
+
assert_tk_app("pre-cached boxart image is set on the card without any network fetch") do
|
|
135
|
+
require "gemba/headless"
|
|
136
|
+
require "gemba/headless"
|
|
137
|
+
require "tmpdir"
|
|
138
|
+
require "fileutils"
|
|
139
|
+
|
|
140
|
+
game_code = 'AGB-AXVE'
|
|
141
|
+
tmpdir = Dir.mktmpdir('picker_test')
|
|
142
|
+
cache_dir = File.join(tmpdir, game_code)
|
|
143
|
+
FileUtils.mkdir_p(cache_dir)
|
|
144
|
+
# Re-use the placeholder PNG — it's a real PNG Tk already loads successfully
|
|
145
|
+
FileUtils.cp(Gemba::GamePickerFrame::PLACEHOLDER_PNG, File.join(cache_dir, 'boxart.png'))
|
|
146
|
+
|
|
147
|
+
# Backend that would return a URL, but the cache hit means it's never called
|
|
148
|
+
backend = Struct.new(:url) { def url_for(_) = url }.new('https://example.com/fake.png')
|
|
149
|
+
fetcher = Gemba::BoxartFetcher.new(app: app, cache_dir: tmpdir, backend: backend)
|
|
150
|
+
|
|
151
|
+
rom = { 'title' => 'Pokemon Ruby', 'platform' => 'gba',
|
|
152
|
+
'game_code' => game_code, 'path' => '/fake.gba' }
|
|
153
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
154
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib, boxart_fetcher: fetcher)
|
|
155
|
+
picker.show
|
|
156
|
+
|
|
157
|
+
img_name = app.command('.game_picker.card0.img', :cget, '-image')
|
|
158
|
+
assert_equal "boxart_#{game_code}", img_name,
|
|
159
|
+
"Card should display cached boxart image, not the placeholder"
|
|
160
|
+
|
|
161
|
+
FileUtils.rm_rf(tmpdir)
|
|
162
|
+
picker.cleanup
|
|
163
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def test_no_fetcher_leaves_placeholder_on_card
|
|
168
|
+
assert_tk_app("card with game_code but no fetcher stays on placeholder") do
|
|
169
|
+
require "gemba/headless"
|
|
170
|
+
|
|
171
|
+
rom = { 'title' => 'Some Game', 'platform' => 'gba',
|
|
172
|
+
'game_code' => 'AGB-TEST', 'path' => '/games/some.gba' }
|
|
173
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
174
|
+
# No boxart_fetcher passed
|
|
175
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
176
|
+
picker.show
|
|
177
|
+
|
|
178
|
+
img_name = app.command('.game_picker.card0.img', :cget, '-image')
|
|
179
|
+
assert_equal 'boxart_placeholder', img_name
|
|
180
|
+
|
|
181
|
+
picker.cleanup
|
|
182
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# -- Quick Load context menu ------------------------------------------------
|
|
187
|
+
# Menu entry indices: 0=Play, 1=Quick Load, 2=Set Boxart, 3=separator, 4=Remove
|
|
188
|
+
|
|
189
|
+
def test_quick_load_disabled_when_no_save_state
|
|
190
|
+
assert_tk_app("quick load menu entry is disabled when no .ss file exists") do
|
|
191
|
+
require "gemba/headless"
|
|
192
|
+
require "tmpdir"
|
|
193
|
+
|
|
194
|
+
Dir.mktmpdir("picker_qs_test") do |tmpdir|
|
|
195
|
+
rom_id = "AGB-TEST-DEADBEEF"
|
|
196
|
+
rom = { 'title' => 'Test Game', 'platform' => 'gba',
|
|
197
|
+
'rom_id' => rom_id, 'game_code' => 'AGB-TEST', 'path' => '/games/test.gba' }
|
|
198
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
199
|
+
|
|
200
|
+
Gemba.user_config.states_dir = tmpdir
|
|
201
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
202
|
+
picker.show
|
|
203
|
+
app.show
|
|
204
|
+
app.update
|
|
205
|
+
|
|
206
|
+
# Suppress tk_popup so the right-click binding configures menu entries
|
|
207
|
+
# without posting the menu (no platform grab → no blocking app.update).
|
|
208
|
+
override_tk_popup do
|
|
209
|
+
app.tcl_eval("event generate .game_picker.card0 <Button-3> -x 10 -y 10")
|
|
210
|
+
app.update
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# index 1 = Quick Load
|
|
214
|
+
state = app.tcl_eval(".game_picker.card0.ctx entrycget 1 -state")
|
|
215
|
+
assert_equal 'disabled', state
|
|
216
|
+
|
|
217
|
+
picker.cleanup
|
|
218
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def test_quick_load_enabled_when_save_state_exists
|
|
224
|
+
assert_tk_app("quick load menu entry is enabled when .ss file exists") do
|
|
225
|
+
require "gemba/headless"
|
|
226
|
+
require "tmpdir"
|
|
227
|
+
require "fileutils"
|
|
228
|
+
|
|
229
|
+
fixture = File.expand_path("test/fixtures/test_quicksave.ss")
|
|
230
|
+
|
|
231
|
+
Dir.mktmpdir("picker_qs_test") do |tmpdir|
|
|
232
|
+
rom_id = "AGB-TEST-DEADBEEF"
|
|
233
|
+
slot = Gemba.user_config.quick_save_slot
|
|
234
|
+
state_dir = File.join(tmpdir, rom_id)
|
|
235
|
+
FileUtils.mkdir_p(state_dir)
|
|
236
|
+
FileUtils.cp(fixture, File.join(state_dir, "state#{slot}.ss"))
|
|
237
|
+
|
|
238
|
+
rom = { 'title' => 'Test Game', 'platform' => 'gba',
|
|
239
|
+
'rom_id' => rom_id, 'game_code' => 'AGB-TEST', 'path' => '/games/test.gba' }
|
|
240
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
241
|
+
|
|
242
|
+
Gemba.user_config.states_dir = tmpdir
|
|
243
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
244
|
+
picker.show
|
|
245
|
+
app.show
|
|
246
|
+
app.update
|
|
247
|
+
|
|
248
|
+
override_tk_popup do
|
|
249
|
+
app.tcl_eval("event generate .game_picker.card0 <Button-3> -x 10 -y 10")
|
|
250
|
+
app.update
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# index 1 = Quick Load
|
|
254
|
+
state = app.tcl_eval(".game_picker.card0.ctx entrycget 1 -state")
|
|
255
|
+
assert_equal 'normal', state
|
|
256
|
+
|
|
257
|
+
picker.cleanup
|
|
258
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def test_quick_load_emits_rom_quick_load_event
|
|
264
|
+
assert_tk_app("clicking quick load emits :rom_quick_load with path and slot") do
|
|
265
|
+
require "gemba/headless"
|
|
266
|
+
require "tmpdir"
|
|
267
|
+
require "fileutils"
|
|
268
|
+
|
|
269
|
+
fixture = File.expand_path("test/fixtures/test_quicksave.ss")
|
|
270
|
+
|
|
271
|
+
Dir.mktmpdir("picker_qs_test") do |tmpdir|
|
|
272
|
+
rom_id = "AGB-TEST-DEADBEEF"
|
|
273
|
+
slot = Gemba.user_config.quick_save_slot
|
|
274
|
+
state_dir = File.join(tmpdir, rom_id)
|
|
275
|
+
FileUtils.mkdir_p(state_dir)
|
|
276
|
+
FileUtils.cp(fixture, File.join(state_dir, "state#{slot}.ss"))
|
|
277
|
+
|
|
278
|
+
rom_path = '/games/test.gba'
|
|
279
|
+
rom = { 'title' => 'Test Game', 'platform' => 'gba',
|
|
280
|
+
'rom_id' => rom_id, 'game_code' => 'AGB-TEST', 'path' => rom_path }
|
|
281
|
+
lib = Struct.new(:roms) { def all = roms }.new([rom])
|
|
282
|
+
|
|
283
|
+
received = nil
|
|
284
|
+
Gemba.bus.on(:rom_quick_load) { |**kwargs| received = kwargs }
|
|
285
|
+
|
|
286
|
+
Gemba.user_config.states_dir = tmpdir
|
|
287
|
+
picker = Gemba::GamePickerFrame.new(app: app, rom_library: lib)
|
|
288
|
+
picker.show
|
|
289
|
+
app.show
|
|
290
|
+
app.update
|
|
291
|
+
|
|
292
|
+
override_tk_popup do
|
|
293
|
+
app.tcl_eval("event generate .game_picker.card0 <Button-3> -x 10 -y 10")
|
|
294
|
+
app.update
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Menu was built but not shown — invoke the Quick Load entry directly.
|
|
298
|
+
# index 1 = Quick Load
|
|
299
|
+
app.tcl_eval(".game_picker.card0.ctx invoke 1")
|
|
300
|
+
app.update
|
|
301
|
+
|
|
302
|
+
assert_equal rom_path, received[:path]
|
|
303
|
+
assert_equal slot, received[:slot]
|
|
304
|
+
|
|
305
|
+
picker.cleanup
|
|
306
|
+
app.command(:destroy, '.game_picker') rescue nil
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
data/test/test_gamepad_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 TestGamepadMap < Minitest::Test
|
|
@@ -7,9 +7,6 @@ require "tmpdir"
|
|
|
7
7
|
class TestHeadlessPlayer < Minitest::Test
|
|
8
8
|
TEST_ROM = File.expand_path("fixtures/test.gba", __dir__)
|
|
9
9
|
|
|
10
|
-
def setup
|
|
11
|
-
skip "Run: ruby gemba/scripts/generate_test_rom.rb" unless File.exist?(TEST_ROM)
|
|
12
|
-
end
|
|
13
10
|
|
|
14
11
|
# -- Lifecycle ---------------------------------------------------------------
|
|
15
12
|
|
|
@@ -202,4 +199,21 @@ class TestHeadlessPlayer < Minitest::Test
|
|
|
202
199
|
assert_equal 0, player.rewind_count
|
|
203
200
|
end
|
|
204
201
|
end
|
|
202
|
+
|
|
203
|
+
# -- BIOS loading -----------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
FAKE_BIOS = File.expand_path("fixtures/fake_bios.bin", __dir__)
|
|
206
|
+
|
|
207
|
+
def test_bios_not_loaded_by_default
|
|
208
|
+
Gemba::HeadlessPlayer.open(TEST_ROM) do |player|
|
|
209
|
+
refute player.core.bios_loaded?
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_bios_loaded_when_path_given
|
|
214
|
+
skip "fake_bios.bin not present" unless File.exist?(FAKE_BIOS)
|
|
215
|
+
Gemba::HeadlessPlayer.open(TEST_ROM, bios_path: FAKE_BIOS) do |player|
|
|
216
|
+
assert player.core.bios_loaded?
|
|
217
|
+
end
|
|
218
|
+
end
|
|
205
219
|
end
|