Files
SubMiner/scripts/test-plugin-session-bindings.lua
T
sudacode d48d880ba3 feat(config): show default keybindings in generated example config (#64)
- Expand `keybindings` array in `config.example.jsonc` to list all built-in defaults instead of `[]`
- Inject `DEFAULT_KEYBINDINGS` into template when resolved config has an empty keybindings array
- Add regression tests for template parity, default binding compile/action mapping, overlay keyboard dispatch, and mpv plugin registration/dispatch
- Add fullscreen binding to docs shortcut tables and clarify keybindings can target mpv commands or session actions
2026-05-12 23:11:51 -07:00

363 lines
9.2 KiB
Lua

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")