diff --git a/README.md b/README.md index a4822e0..8448708 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Additional keymaps: - `fn` -> [F]ind [N]ext failed test in listing (wraps to the first, opens Detail-Float, works in Detail-Float) - `fp` -> [F]ind [P]revious failed test in listing (wraps to the last, opens Detail-Float, works in Detail-Float) - `ff` -> [F]ind [F]irst list entry (opens Detail-Float, works in Detail-Float) - - `o` -> jump to the test location + - `o` -> jump to the test location (works in Detail-Float) - `qn` -> close the testing floats and jump to the first quickfix entry - Listing filters: - `sf` -> filter the listing to `[ FAIL ] - ...` entries diff --git a/doc/test-samurai.txt b/doc/test-samurai.txt index ccf1f9b..34d4803 100644 --- a/doc/test-samurai.txt +++ b/doc/test-samurai.txt @@ -55,7 +55,7 @@ Listing navigation: fn [F]ind [N]ext failed test in listing (opens Detail-Float; works in Detail-Float) fp [F]ind [P]revious failed test in listing (opens Detail-Float; works in Detail-Float) ff [F]ind [F]irst list entry (opens Detail-Float; works in Detail-Float) - o Jump to test location + o Jump to test location (works in Detail-Float) qn Close floats + jump to the first quickfix entry Listing filters: sf Filter listing to [ FAIL ] only diff --git a/lua/test-samurai/core.lua b/lua/test-samurai/core.lua index eb1f739..05a0251 100644 --- a/lua/test-samurai/core.lua +++ b/lua/test-samurai/core.lua @@ -70,7 +70,7 @@ local function help_lines() " fn [F]ind [N]ext failed test in listing (opens Detail-Float; works in Detail-Float)", " fp [F]ind [P]revious failed test in listing (opens Detail-Float; works in Detail-Float)", " ff [F]ind [F]irst list entry (opens Detail-Float; works in Detail-Float)", - " o Jump to test location", + " o Jump to test location (works in Detail-Float)", " qn Close floats + jump to first quickfix entry", "", "Listing filters:", @@ -469,9 +469,27 @@ local function restore_trigger_location() end local function jump_to_listing_test() - local cursor = vim.api.nvim_win_get_cursor(0) - local line = cursor[1] - local text = vim.api.nvim_get_current_line() + local line = nil + local text = nil + local buf = vim.api.nvim_get_current_buf() + if state.detail_buf and buf == state.detail_buf then + if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then + return + end + line = state.detail_line + if not line then + return + end + local lines = vim.api.nvim_buf_get_lines(state.last_buf, line - 1, line, false) + text = lines and lines[1] or nil + else + local cursor = vim.api.nvim_win_get_cursor(0) + line = cursor[1] + text = vim.api.nvim_get_current_line() + end + if not line or not text then + return + end local status = text:match("^%[%s*(%u+)%s*%]%s*%-") if status ~= "PASS" and status ~= "FAIL" and status ~= "SKIP" then return @@ -1340,6 +1358,46 @@ local function parse_go_output_from_raw(output) return out end +local function set_detail_maps(buf) + vim.keymap.set("n", "", function() + close_container_and_restore() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "h", function() + M.focus_listing() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "", function() + M.focus_listing() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "", function() + M.focus_detail() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "z", function() + M.toggle_detail_full() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "fn", function() + jump_listing_and_open("next") + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "fp", function() + jump_listing_and_open("prev") + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "ff", function() + jump_listing_and_open("first") + end, { buffer = buf, nowait = true, silent = true, desc = "[F]ind [F]irst list entry" }) + vim.keymap.set("n", "", function() + close_detail_float() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "qn", function() + jump_to_first_quickfix() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "o", function() + jump_to_listing_test() + end, { buffer = buf, nowait = true, silent = true }) + vim.keymap.set("n", "?", function() + M.show_help() + end, { buffer = buf, nowait = true, silent = true }) + disable_container_maps(buf) +end + local function ensure_detail_buf(lines) local buf = state.detail_buf if not (buf and vim.api.nvim_buf_is_valid(buf)) then @@ -1349,41 +1407,8 @@ local function ensure_detail_buf(lines) vim.api.nvim_buf_set_option(buf, "swapfile", false) vim.api.nvim_buf_set_option(buf, "filetype", "test-samurai-output") state.detail_buf = buf - vim.keymap.set("n", "", function() - close_container_and_restore() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "h", function() - M.focus_listing() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "", function() - M.focus_listing() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "", function() - M.focus_detail() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "z", function() - M.toggle_detail_full() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "fn", function() - jump_listing_and_open("next") - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "fp", function() - jump_listing_and_open("prev") - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "ff", function() - jump_listing_and_open("first") - end, { buffer = buf, nowait = true, silent = true, desc = "[F]ind [F]irst list entry" }) - vim.keymap.set("n", "", function() - close_detail_float() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "qn", function() - jump_to_first_quickfix() - end, { buffer = buf, nowait = true, silent = true }) - vim.keymap.set("n", "?", function() - M.show_help() - end, { buffer = buf, nowait = true, silent = true }) - disable_container_maps(buf) end + set_detail_maps(buf) local clean_lines, highlights = parse_ansi_lines(normalize_output_lines(lines)) vim.api.nvim_buf_set_lines(buf, 0, -1, false, clean_lines) vim.api.nvim_buf_clear_namespace(buf, help_ns, 0, -1) diff --git a/tests/test_samurai_core_spec.lua b/tests/test_samurai_core_spec.lua index a5edb02..66d5c85 100644 --- a/tests/test_samurai_core_spec.lua +++ b/tests/test_samurai_core_spec.lua @@ -1358,4 +1358,115 @@ describe("test-samurai core (no bundled runners)", function() local detail_lines = vim.api.nvim_buf_get_lines(refreshed_buf, 0, -1, false) assert.same({ "", "No output captured" }, detail_lines) end) + + it("allows o from the detail float to jump to the test location", function() + local runner = { + name = "test-runner-detail-o", + } + + function runner.is_test_file(_bufnr) + return true + end + + function runner.find_nearest(bufnr, _row, _col) + return { file = vim.api.nvim_buf_get_name(bufnr), cwd = vim.loop.cwd(), test_name = "TestJump" } + end + + function runner.build_command(spec) + return { cmd = { "echo", "nearest" }, cwd = spec.cwd } + end + + function runner.build_file_command(_bufnr) + return { cmd = { "echo", "file" } } + end + + function runner.build_all_command(_bufnr) + return { cmd = { "echo", "all" } } + end + + function runner.build_failed_command(last_command, _failures, _scope_kind) + return { cmd = { "echo", "failed" }, cwd = last_command and last_command.cwd or nil } + end + + function runner.parse_results(_output) + return { passes = {}, failures = { "TestJump" }, skips = {} } + end + + function runner.output_parser() + return { + on_complete = function(output, _state) + return runner.parse_results(output) + end, + } + end + + function runner.parse_test_output(_output) + return {} + end + + function runner.collect_failed_locations(_failures, _command, _scope_kind) + return { { filename = "/tmp/test_jump_detail.go", lnum = 2, col = 3 } } + end + + package.loaded["test-samurai-detail-o-runner"] = runner + test_samurai.setup({ runner_modules = { "test-samurai-detail-o-runner" } }) + + local target_buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(target_buf, "/tmp/test_jump_detail.go") + vim.api.nvim_buf_set_lines(target_buf, 0, -1, false, { "line1", "line2", "line3" }) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, "/tmp/test_runner_detail_o.go") + vim.bo[bufnr].filetype = "go" + vim.api.nvim_set_current_buf(bufnr) + + local orig_jobstart = vim.fn.jobstart + vim.fn.jobstart = function(_cmd, opts) + if opts.on_exit then + opts.on_exit(nil, 0, nil) + end + return 1 + end + + core.run_nearest() + + vim.fn.jobstart = orig_jobstart + + local listing_buf = vim.api.nvim_get_current_buf() + assert.is_true(vim.bo[listing_buf].filetype == "test-samurai-output") + + local lines = vim.api.nvim_buf_get_lines(listing_buf, 0, -1, false) + local fail_entry = nil + for i, line in ipairs(lines) do + if line:match("^%[ FAIL %] %- ") then + fail_entry = i + break + end + end + assert.is_true(fail_entry ~= nil) + + vim.api.nvim_win_set_cursor(0, { fail_entry, 0 }) + core.open_test_output_at_cursor() + + local detail_buf = vim.api.nvim_get_current_buf() + local maps = vim.api.nvim_buf_get_keymap(detail_buf, "n") + local found = nil + for _, map in ipairs(maps) do + if type(map.lhs) == "string" and map.lhs:sub(-1) == "o" then + found = map + break + end + end + assert.is_true(found ~= nil) + + assert.is_true(type(found.callback) == "function") + found.callback() + + local current = vim.api.nvim_get_current_buf() + local name = vim.api.nvim_buf_get_name(current) + assert.is_true(name:sub(-#"/tmp/test_jump_detail.go") == "/tmp/test_jump_detail.go") + local cursor = vim.api.nvim_win_get_cursor(0) + assert.equals(2, cursor[1]) + assert.equals(2, cursor[2]) + end) end)