From fd3952bedfb96693852e08fde401f85039fb3fc2 Mon Sep 17 00:00:00 2001 From: "M.Schirmer" Date: Fri, 9 Jan 2026 16:24:55 +0100 Subject: [PATCH] fix missing summary after TSamLast call --- README.md | 5 +- doc/test-samurai.txt | 8 +- lua/test-samurai/core.lua | 11 +- tests/test_samurai_core_spec.lua | 174 +++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 05c203c..61f1b72 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,12 @@ Additional keymaps: - `ss` -> filter the listing to `[ SKIP ] - ...` entries - `sa` -> clear the listing filter and show all entries - `tt` -> run the test under the cursor in the listing -- `cb` -> breaks test-command onto multiple lines +- `cb` -> breaks test-command onto multiple lines (clears search highlight) - `cj` -> joins test-command onto single line - `?` -> show help with TSam commands and standard keymaps in the Detail-Float +Before running any test command, test-samurai runs `:wall` to save all buffers. + ## Output UI - Output is shown in a floating container called **Testing-Float**. @@ -84,6 +86,7 @@ Additional keymaps: - ANSI color translation is only applied in the **Detail-Float**; the **Test-Listing-Float** shows raw text without ANSI translation. - `` hides the floating window; `TSamShowOutput` reopens it. - If no output is captured for a test, the **Detail-Float** shows `No output captured`. +- Summary lines (`TOTAL`/`DURATION`) are appended in the listing output, including `TSamLast`. ## Runner architecture diff --git a/doc/test-samurai.txt b/doc/test-samurai.txt index 866daca..d85ae83 100644 --- a/doc/test-samurai.txt +++ b/doc/test-samurai.txt @@ -58,7 +58,7 @@ Additional keymaps: ss Filter listing to [ SKIP ] only sa Show all listing entries (clear filter) tt Run the test under the cursor in the listing - cb breaks test-command onto multiple lines + cb breaks test-command onto multiple lines (clears search highlight) cj joins test-command onto single line o Jump to test location z Toggle Detail-Float full width @@ -67,6 +67,9 @@ Additional keymaps: Close Testing-Float Close Detail-Float (when focused) +Notes: + Buffers are saved via :wall before every test run. + OUTPUT UI *test-samurai-ui* @@ -80,6 +83,9 @@ the Detail-Float with a 20/80 split. ANSI colors are translated only inside the Detail-Float. If no output is captured for a test, the Detail-Float shows "No output captured". +Summary lines (TOTAL/DURATION) are rendered in the listing output, +including after :TSamLast. + RUNNER ARCHITECTURE *test-samurai-runners* diff --git a/lua/test-samurai/core.lua b/lua/test-samurai/core.lua index ec39041..383ff8e 100644 --- a/lua/test-samurai/core.lua +++ b/lua/test-samurai/core.lua @@ -76,7 +76,7 @@ local function help_lines() " Focus Test-Listing-Float", " z Toggle Detail-Float full width", " o Jump to test location", - " cb breaks test-command onto multiple lines", + " cb breaks test-command onto multiple lines (clears search highlight)", " cj joins test-command onto single line", " ? Show this help", "", @@ -91,6 +91,7 @@ local function help_lines() "", "Notes:", " No output captured -> shows placeholder text", + " Buffers are saved via :wall before every test run", } end @@ -1361,6 +1362,7 @@ end function M.listing_break_on_dashes() apply_listing_substitution([[%s/--/\\\r\t--/g]]) + vim.cmd("noh") end function M.listing_join_backslashes() @@ -1885,6 +1887,7 @@ end run_command = function(command, opts) local options = opts or {} + vim.cmd("wall") state.last_test_outputs = {} state.last_result_line_map = {} state.last_raw_output = nil @@ -1935,7 +1938,10 @@ run_command = function(command, opts) skips = {}, } local had_parsed_output = false - local summary_enabled = options.scope_kind == "file" or options.scope_kind == "all" or options.scope_kind == "nearest" + local summary_enabled = options.scope_kind == "file" + or options.scope_kind == "all" + or options.scope_kind == "nearest" + or options.scope_kind == "last" local summary = make_summary_tracker(summary_enabled) local result_counts = make_summary_tracker(true) state.last_border_kind = "default" @@ -2209,6 +2215,7 @@ function M.run_last() end run_command(command, { runner = runner, + scope_kind = state.last_scope_kind or "last", output_parser = parser or (runner and runner.parse_results), }) end diff --git a/tests/test_samurai_core_spec.lua b/tests/test_samurai_core_spec.lua index 91b8010..f3507f7 100644 --- a/tests/test_samurai_core_spec.lua +++ b/tests/test_samurai_core_spec.lua @@ -262,8 +262,24 @@ describe("test-samurai core (no bundled runners)", function() "gamma -- delta", }) + local cmd_calls = {} + local orig_cmd = vim.cmd + vim.cmd = function(cmd) + table.insert(cmd_calls, cmd) + return orig_cmd(cmd) + end + core.listing_break_on_dashes() + local has_noh = false + for _, cmd in ipairs(cmd_calls) do + if cmd == "noh" then + has_noh = true + break + end + end + assert.is_true(has_noh) + local broken = vim.api.nvim_buf_get_lines(buf, 0, -1, false) assert.same({ "alpha \\", @@ -279,6 +295,164 @@ describe("test-samurai core (no bundled runners)", function() "alpha -- beta", "gamma -- delta", }, joined) + + vim.cmd = orig_cmd + end) + + it("saves all buffers before running tests", function() + local runner = { + name = "test-runner-save-buffers", + } + + 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 = "TestA" } + end + + function runner.build_command(spec) + return { cmd = { "echo", "nearest" }, cwd = spec.cwd } + end + + function runner.parse_results(_output) + return { passes = {}, failures = {}, skips = {} } + end + + function runner.output_parser() + return { + on_line = function(_line, _state) + return nil + end, + 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 {} + end + + package.loaded["test-samurai-save-buffers-runner"] = runner + test_samurai.setup({ runner_modules = { "test-samurai-save-buffers-runner" } }) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, "/tmp/test_samurai_save_buffers.go") + vim.bo[bufnr].filetype = "go" + vim.api.nvim_set_current_buf(bufnr) + + local cmd_calls = {} + local orig_cmd = vim.cmd + vim.cmd = function(cmd) + table.insert(cmd_calls, cmd) + end + + 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 + vim.cmd = orig_cmd + + local has_wall = false + for _, cmd in ipairs(cmd_calls) do + if cmd == "wall" then + has_wall = true + break + end + end + assert.is_true(has_wall) + end) + + it("renders the summary in the listing after TSamLast", function() + local runner = { + name = "test-runner-last-summary", + } + + 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 = "TestA" } + 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 = { "TestA" }, failures = { "TestB" }, skips = {} } + end + + function runner.output_parser() + return { + on_line = function(_line, _state) + return nil + end, + 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 {} + end + + package.loaded["test-samurai-last-summary-runner"] = runner + test_samurai.setup({ runner_modules = { "test-samurai-last-summary-runner" } }) + + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, "/tmp/test_samurai_last_summary.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() + core.run_last() + + local listing_buf = vim.api.nvim_get_current_buf() + local lines = vim.api.nvim_buf_get_lines(listing_buf, 0, -1, false) + local joined = table.concat(lines, "\n") + assert.is_true(joined:find("TOTAL", 1, true) ~= nil) + + vim.fn.jobstart = orig_jobstart end) it("filters listing entries and restores them", function()