package.path = "plugin/subminer/?.lua;" .. package.path local session_bindings = require("session_bindings") local function assert_true(condition, message) if condition then return end error(message) end local artifact_path = ".tmp/test-plugin-session-bindings.json" local is_windows = package.config:sub(1, 1) == "\\" local mkdir_cmd = is_windows and "mkdir .tmp >NUL 2>NUL" or "mkdir -p .tmp" os.execute(mkdir_cmd) local handle = assert(io.open(artifact_path, "w")) handle:write("__SESSION_BINDINGS__") handle:close() local recorded = { bindings = {}, removed = {}, async_calls = {}, mpv_commands = {}, osd = {}, } local mp = {} function mp.add_forced_key_binding(keys, name, fn) recorded.bindings[#recorded.bindings + 1] = { keys = keys, name = name, fn = fn, } end function mp.remove_key_binding(name) recorded.removed[#recorded.removed + 1] = name end function mp.commandv(...) recorded.mpv_commands[#recorded.mpv_commands + 1] = { ... } end function mp.add_timeout(seconds, callback) return { seconds = seconds, callback = callback, killed = false, kill = function(self) self.killed = true end, } end function mp.osd_message(message) recorded.osd[#recorded.osd + 1] = message end local ctx = { mp = mp, utils = { parse_json = function(raw) if raw ~= "__SESSION_BINDINGS__" then return nil, "unexpected artifact" end return { numericSelectionTimeoutMs = 3000, bindings = { { key = { code = "KeyS", modifiers = { "ctrl", "shift" }, }, actionType = "session-action", actionId = "mineSentenceMultiple", }, { key = { code = "Space", modifiers = {}, }, actionType = "mpv-command", command = { "cycle", "pause" }, }, { key = { code = "KeyF", modifiers = {}, }, actionType = "mpv-command", command = { "cycle", "fullscreen" }, }, { key = { code = "KeyJ", modifiers = {}, }, actionType = "mpv-command", command = { "cycle", "sid" }, }, { key = { code = "KeyJ", modifiers = { "shift" }, }, actionType = "mpv-command", command = { "cycle", "secondary-sid" }, }, { key = { code = "ArrowRight", modifiers = {}, }, actionType = "mpv-command", command = { "seek", 5 }, }, { key = { code = "ArrowLeft", modifiers = {}, }, actionType = "mpv-command", command = { "seek", -5 }, }, { key = { code = "ArrowUp", modifiers = {}, }, actionType = "mpv-command", command = { "seek", 60 }, }, { key = { code = "ArrowDown", modifiers = {}, }, actionType = "mpv-command", command = { "seek", -60 }, }, { key = { code = "KeyH", modifiers = { "shift" }, }, actionType = "mpv-command", command = { "sub-seek", -1 }, }, { key = { code = "KeyL", modifiers = { "shift" }, }, actionType = "mpv-command", command = { "sub-seek", 1 }, }, { key = { code = "BracketRight", modifiers = { "shift" }, }, actionType = "session-action", actionId = "shiftSubDelayNextLine", }, { key = { code = "BracketLeft", modifiers = { "shift" }, }, actionType = "session-action", actionId = "shiftSubDelayPrevLine", }, { key = { code = "KeyC", modifiers = { "ctrl", "alt" }, }, actionType = "session-action", actionId = "openYoutubePicker", }, { key = { code = "KeyP", modifiers = { "ctrl", "alt" }, }, actionType = "session-action", actionId = "openPlaylistBrowser", }, { key = { code = "KeyH", modifiers = { "ctrl", "shift" }, }, actionType = "session-action", actionId = "replayCurrentSubtitle", }, { key = { code = "KeyL", modifiers = { "ctrl", "shift" }, }, actionType = "session-action", actionId = "playNextSubtitle", }, { key = { code = "KeyQ", modifiers = {}, }, actionType = "mpv-command", command = { "quit" }, }, { key = { code = "KeyW", modifiers = { "ctrl" }, }, actionType = "mpv-command", command = { "quit" }, }, { key = { code = "KeyA", modifiers = { "alt", "meta" }, }, actionType = "session-action", actionId = "openCharacterDictionary", }, }, }, nil end, }, state = { binary_path = "/tmp/subminer", session_binding_names = {}, session_numeric_binding_names = {}, session_numeric_selection = nil, }, process = { check_binary_available = function() return true end, run_binary_command_async = function(args) recorded.async_calls[#recorded.async_calls + 1] = args end, }, environment = { resolve_session_bindings_artifact_path = function() return artifact_path end, }, log = { subminer_log = function() end, show_osd = function(message) recorded.osd[#recorded.osd + 1] = message end, }, } local bindings = session_bindings.create(ctx) assert_true(bindings.register_bindings(), "session bindings should register") local function find_binding(keys) for _, binding in ipairs(recorded.bindings) do if binding.keys == keys then return binding end end return nil end local starter = find_binding("Ctrl+S") assert_true(starter ~= nil, "multi-mine starter binding should be registered") local expected_mpv_bindings = { { keys = "SPACE", command = { "cycle", "pause" } }, { keys = "f", command = { "cycle", "fullscreen" } }, { keys = "j", command = { "cycle", "sid" } }, { keys = "J", command = { "cycle", "secondary-sid" } }, { keys = "RIGHT", command = { "seek", 5 } }, { keys = "LEFT", command = { "seek", -5 } }, { keys = "UP", command = { "seek", 60 } }, { keys = "DOWN", command = { "seek", -60 } }, { keys = "H", command = { "sub-seek", -1 } }, { keys = "L", command = { "sub-seek", 1 } }, { keys = "q", command = { "quit" } }, { keys = "Ctrl+w", command = { "quit" } }, } for _, expected in ipairs(expected_mpv_bindings) do local binding = find_binding(expected.keys) assert_true(binding ~= nil, "default mpv binding should register " .. expected.keys) binding.fn() local command = recorded.mpv_commands[#recorded.mpv_commands] assert_true(command ~= nil, "default mpv binding should invoke mpv command " .. expected.keys) for index, value in ipairs(expected.command) do assert_true(command[index] == value, "default mpv command mismatch for " .. expected.keys) end end local expected_cli_bindings = { { keys = "Shift+]", flag = "--shift-sub-delay-next-line" }, { keys = "Shift+[", flag = "--shift-sub-delay-prev-line" }, { keys = "Ctrl+Alt+c", flag = "--open-youtube-picker" }, { keys = "Ctrl+Alt+p", flag = "--open-playlist-browser" }, { keys = "Ctrl+H", flag = "--replay-current-subtitle" }, { keys = "Ctrl+L", flag = "--play-next-subtitle" }, } for _, expected in ipairs(expected_cli_bindings) do local binding = find_binding(expected.keys) assert_true(binding ~= nil, "default session action should register " .. expected.keys) binding.fn() local cli_call = recorded.async_calls[#recorded.async_calls] assert_true(cli_call ~= nil, "default session action should invoke CLI " .. expected.keys) assert_true(cli_call[2] == expected.flag, "default session action should pass " .. expected.flag) end local play_next = find_binding("Ctrl+L") assert_true(play_next ~= nil, "play-next subtitle binding should use mpv shifted-letter form") local subtitle_jump = find_binding("L") assert_true(subtitle_jump ~= nil, "shifted subtitle jump binding should use mpv uppercase letter form") play_next.fn() local play_next_call = recorded.async_calls[#recorded.async_calls] assert_true(play_next_call ~= nil, "play-next binding should invoke CLI action") assert_true(play_next_call[2] == "--play-next-subtitle", "play-next binding should pass CLI flag") local character_dictionary = find_binding("Alt+Meta+a") assert_true(character_dictionary ~= nil, "character dictionary binding should be registered") character_dictionary.fn() local character_dictionary_call = recorded.async_calls[#recorded.async_calls] assert_true(character_dictionary_call ~= nil, "character dictionary binding should invoke CLI action") assert_true( character_dictionary_call[2] == "--open-character-dictionary", "character dictionary binding should pass CLI flag" ) starter.fn() local modified_digit = nil for _, binding in ipairs(recorded.bindings) do if binding.keys == "Ctrl+Shift+3" then modified_digit = binding break end end assert_true(modified_digit ~= nil, "numeric selection should bind Ctrl+Shift+3") modified_digit.fn() local call = recorded.async_calls[#recorded.async_calls] assert_true(call ~= nil, "modified digit should invoke CLI action") assert_true(call[1] == "/tmp/subminer", "CLI action should use configured binary") assert_true(call[2] == "--mine-sentence-count", "CLI action should mine sentence count") assert_true(call[3] == "3", "CLI action should pass selected count") print("plugin session binding regression tests: OK")