stick to the old and simple test-output layout for listing and detail view and disable hardtime-plugin on any testing-float

This commit is contained in:
2025-12-29 18:08:37 +01:00
parent 2ad3ded481
commit b916a11481
3 changed files with 918 additions and 123 deletions

View File

@@ -80,6 +80,4 @@
- Neue Features benötigen Tests
## Einschränkungen
- Failed-only: nur Go
- Lua Nearest pausiert
- Farbiger Output später

View File

@@ -22,6 +22,9 @@ local state = {
detail_buf = nil,
detail_win = nil,
detail_opening = false,
detail_full = false,
hardtime_refcount = 0,
hardtime_was_enabled = false,
autocmds_set = false,
}
@@ -31,6 +34,69 @@ local detail_ns = vim.api.nvim_create_namespace("TestSamuraiDetailAnsi")
local apply_border_kind
local close_container
local restore_listing_full
local close_detail_float
local function disable_container_maps(buf)
local opts = { buffer = buf, nowait = true, silent = true }
vim.keymap.set("n", "<C-j>", "<Nop>", opts)
vim.keymap.set("n", "<C-k>", "<Nop>", opts)
end
local function get_hardtime()
local ok, hardtime = pcall(require, "hardtime")
if not ok or type(hardtime) ~= "table" then
return nil
end
if type(hardtime.enable) ~= "function" or type(hardtime.disable) ~= "function" then
return nil
end
return hardtime
end
local function hardtime_disable()
local hardtime = get_hardtime()
if not hardtime then
return
end
if state.hardtime_refcount == 0 then
state.hardtime_was_enabled = hardtime.is_plugin_enabled == true
end
state.hardtime_refcount = state.hardtime_refcount + 1
if hardtime.is_plugin_enabled == true then
pcall(hardtime.disable)
end
end
local function hardtime_restore()
if state.hardtime_refcount == 0 then
return
end
state.hardtime_refcount = state.hardtime_refcount - 1
if state.hardtime_refcount > 0 then
return
end
if not state.hardtime_was_enabled then
return
end
local hardtime = get_hardtime()
if hardtime then
pcall(hardtime.enable)
end
state.hardtime_was_enabled = false
end
local function handle_ctrl_l_in_listing()
local next_key = vim.fn.getchar(0)
if next_key ~= 0 and next_key ~= -1 then
local char = vim.fn.nr2char(next_key)
if char == "l" then
M.expand_detail_full()
return
end
vim.api.nvim_feedkeys(char, "n", false)
end
M.focus_detail()
end
local function get_hl_fg(name)
local ok, hl = pcall(vim.api.nvim_get_hl, 0, { name = name, link = true })
@@ -87,21 +153,6 @@ local function ensure_output_autocmds()
local group = vim.api.nvim_create_augroup("TestSamuraiOutput", { clear = true })
vim.api.nvim_create_autocmd({ "BufEnter", "WinEnter" }, {
group = group,
callback = function()
if state.detail_opening then
return
end
local cur = vim.api.nvim_get_current_win()
local keep = (state.last_win and vim.api.nvim_win_is_valid(state.last_win) and cur == state.last_win)
or (state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) and cur == state.detail_win)
if not keep then
close_container()
end
end,
})
vim.api.nvim_create_autocmd("WinClosed", {
group = group,
callback = function(args)
@@ -109,14 +160,20 @@ local function ensure_output_autocmds()
if not closed then
return
end
if state.last_win and closed == state.last_win then
state.last_win = nil
close_container()
return
end
if state.detail_win and closed == state.detail_win then
state.detail_win = nil
restore_listing_full()
hardtime_restore()
return
end
if state.last_win and closed == state.last_win then
state.last_win = nil
hardtime_restore()
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
pcall(vim.api.nvim_win_close, state.detail_win, true)
state.detail_win = nil
end
return
end
end,
})
@@ -147,8 +204,12 @@ function M.setup()
state.last_result_line_map = {}
state.last_raw_output = nil
state.last_float = nil
state.last_win = nil
state.last_buf = nil
state.detail_opening = false
state.detail_opening = false
state.detail_full = false
state.hardtime_refcount = 0
state.hardtime_was_enabled = false
end
function M.reload_runners()
@@ -261,27 +322,43 @@ local function float_geometry()
return width, height, row, col
end
local function base_geometry()
local width, height, row, col = float_geometry()
local base = state.last_float or {}
return base.width or width, base.height or height, base.row or row, base.col or col
end
apply_border_kind = function(win, kind)
if not (win and vim.api.nvim_win_is_valid(win)) then
local target = win
if not target then
target = state.last_win
end
if not (target and vim.api.nvim_win_is_valid(target)) then
return
end
if kind == "pass" then
vim.api.nvim_win_set_option(win, "winhighlight", "FloatBorder:TestSamuraiBorderPass")
vim.api.nvim_win_set_option(target, "winhighlight", "FloatBorder:TestSamuraiBorderPass")
elseif kind == "fail" then
vim.api.nvim_win_set_option(win, "winhighlight", "FloatBorder:TestSamuraiBorderFail")
vim.api.nvim_win_set_option(target, "winhighlight", "FloatBorder:TestSamuraiBorderFail")
else
vim.api.nvim_win_set_option(win, "winhighlight", "")
vim.api.nvim_win_set_option(target, "winhighlight", "")
end
end
close_container = function()
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
pcall(vim.api.nvim_win_close, state.detail_win, true)
state.detail_win = nil
end
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
pcall(vim.api.nvim_win_close, state.last_win, true)
state.last_win = nil
end
end
close_detail_float = function()
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
pcall(vim.api.nvim_win_close, state.detail_win, true)
state.detail_win = nil
end
end
@@ -289,10 +366,7 @@ restore_listing_full = function()
if not (state.last_win and vim.api.nvim_win_is_valid(state.last_win)) then
return
end
local width = state.last_float and state.last_float.width or math.floor(vim.o.columns * 0.8)
local height = state.last_float and state.last_float.height or math.floor(vim.o.lines * 0.8)
local row = state.last_float and state.last_float.row or math.floor((vim.o.lines - height) / 2)
local col = state.last_float and state.last_float.col or math.floor((vim.o.columns - width) / 2)
local width, height, row, col = base_geometry()
vim.api.nvim_win_set_config(state.last_win, {
relative = "editor",
width = width,
@@ -302,18 +376,71 @@ restore_listing_full = function()
style = "minimal",
border = "rounded",
})
apply_border_kind(state.last_win, state.last_border_kind)
end
local function apply_split_layout(left_ratio)
if not (state.last_win and vim.api.nvim_win_is_valid(state.last_win)) then
return
end
if not (state.detail_win and vim.api.nvim_win_is_valid(state.detail_win)) then
return
end
local width, height, row, col = base_geometry()
local available = math.max(1, width - 2)
local left_width = math.floor(available * left_ratio)
if left_width < 1 then
left_width = 1
end
if left_width >= available then
left_width = math.max(1, available - 1)
end
local right_width = available - left_width
if right_width < 1 then
right_width = 1
if available > 1 then
left_width = available - right_width
end
end
local listing_border = "rounded"
local detail_col = col + left_width + 2
if left_ratio <= 0 then
left_width = 1
right_width = width
listing_border = "none"
detail_col = col
state.detail_full = true
else
state.detail_full = false
end
vim.api.nvim_win_set_config(state.last_win, {
relative = "editor",
width = left_width,
height = height,
row = row,
col = col,
style = "minimal",
border = listing_border,
})
vim.api.nvim_win_set_config(state.detail_win, {
relative = "editor",
width = right_width,
height = height,
row = row,
col = detail_col,
style = "minimal",
border = "rounded",
})
end
local function create_output_win(initial_lines)
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
pcall(vim.api.nvim_win_close, state.last_win, true)
state.last_win = nil
end
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
pcall(vim.api.nvim_win_close, state.detail_win, true)
state.detail_win = nil
end
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
pcall(vim.api.nvim_win_close, state.last_win, true)
state.last_win = nil
end
local buf = state.last_buf
if not (buf and vim.api.nvim_buf_is_valid(buf)) then
@@ -328,7 +455,7 @@ local function create_output_win(initial_lines)
local width, height, row, col = float_geometry()
state.last_float = { width = width, height = height, row = row, col = col }
local win = vim.api.nvim_open_win(buf, true, {
local listing = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width,
height = height,
@@ -337,6 +464,7 @@ local function create_output_win(initial_lines)
style = "minimal",
border = "rounded",
})
hardtime_disable()
vim.keymap.set("n", "<esc><esc>", function()
close_container()
@@ -344,12 +472,22 @@ local function create_output_win(initial_lines)
vim.keymap.set("n", "<cr>", function()
M.open_test_output_at_cursor()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-h>", function()
M.focus_listing()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-l>", function()
handle_ctrl_l_in_listing()
end, { buffer = buf, silent = true })
vim.keymap.set("n", "<leader>z", function()
M.toggle_detail_full()
end, { buffer = buf, nowait = true, silent = true })
disable_container_maps(buf)
state.last_win = win
state.last_win = listing
state.last_buf = buf
apply_border_kind(win, state.last_border_kind)
apply_border_kind(listing, state.last_border_kind)
return buf, win
return buf, listing
end
local function reopen_output_win()
@@ -378,6 +516,7 @@ local function reopen_output_win()
style = "minimal",
border = "rounded",
})
hardtime_disable()
vim.keymap.set("n", "<esc><esc>", function()
close_container()
@@ -385,6 +524,16 @@ local function reopen_output_win()
vim.keymap.set("n", "<cr>", function()
M.open_test_output_at_cursor()
end, { buffer = state.last_buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-h>", function()
M.focus_listing()
end, { buffer = state.last_buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-l>", function()
handle_ctrl_l_in_listing()
end, { buffer = state.last_buf, silent = true })
vim.keymap.set("n", "<leader>z", function()
M.toggle_detail_full()
end, { buffer = state.last_buf, nowait = true, silent = true })
disable_container_maps(state.last_buf)
state.last_win = win
apply_border_kind(win, state.last_border_kind)
@@ -684,6 +833,19 @@ local function ensure_detail_buf(lines)
vim.keymap.set("n", "<C-w>h", function()
M.focus_listing()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-h>", function()
M.focus_listing()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-l>", function()
M.focus_detail()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<leader>z", function()
M.toggle_detail_full()
end, { buffer = buf, nowait = true, silent = true })
vim.keymap.set("n", "<C-c>", function()
close_detail_float()
end, { buffer = buf, nowait = true, silent = true })
disable_container_maps(buf)
end
local clean_lines, highlights = parse_ansi_lines(normalize_output_lines(lines))
vim.api.nvim_buf_set_lines(buf, 0, -1, false, clean_lines)
@@ -691,21 +853,28 @@ local function ensure_detail_buf(lines)
return buf
end
local function open_detail_split(lines)
local function open_detail_split(lines, border_kind)
local buf = ensure_detail_buf(lines)
local base = state.last_float or {}
local width = base.width or math.floor(vim.o.columns * 0.8)
local height = base.height or math.floor(vim.o.lines * 0.8)
local row = base.row or math.floor((vim.o.lines - height) / 2)
local col = base.col or math.floor((vim.o.columns - width) / 2)
local left_width = math.max(1, math.floor(width * 0.2))
local right_width = width - left_width
if not (state.last_win and vim.api.nvim_win_is_valid(state.last_win)) then
return
end
local width, height, row, col = base_geometry()
local available = math.max(1, width - 2)
local left_width = math.floor(available * 0.25)
if left_width < 1 then
left_width = 1
end
if left_width >= available then
left_width = math.max(1, available - 1)
end
local right_width = available - left_width
if right_width < 1 then
right_width = 1
left_width = math.max(1, width - right_width)
if available > 1 then
left_width = available - right_width
end
end
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
vim.api.nvim_win_set_config(state.last_win, {
relative = "editor",
width = left_width,
@@ -715,19 +884,19 @@ local function open_detail_split(lines)
style = "minimal",
border = "rounded",
})
end
local right_cfg = {
relative = "editor",
width = right_width,
height = height,
row = row,
col = col + left_width,
col = col + left_width + 2,
style = "minimal",
border = "rounded",
}
local right = state.detail_win
local opening_detail = not (right and vim.api.nvim_win_is_valid(right))
if right and vim.api.nvim_win_is_valid(right) then
vim.api.nvim_win_set_buf(right, buf)
vim.api.nvim_win_set_config(right, right_cfg)
@@ -737,15 +906,24 @@ local function open_detail_split(lines)
state.detail_win = right
state.detail_opening = false
end
if opening_detail then
hardtime_disable()
end
apply_border_kind(right, border_kind)
state.detail_full = false
vim.api.nvim_set_current_win(right)
end
function M.open_test_output_at_cursor()
local cursor = vim.api.nvim_win_get_cursor(0)
local line = cursor[1]
local text = vim.api.nvim_get_current_line()
local status = text:match("^%[%s*(%u+)%s*%]%s*%-")
if status and status ~= "PASS" and status ~= "FAIL" then
return
end
local test_name = state.last_result_line_map[line]
if not test_name then
local text = vim.api.nvim_get_current_line()
test_name = text:match("^%[%s*[%u]+%s*%]%s*%-%s*(.+)$")
end
if not test_name then
@@ -791,15 +969,44 @@ function M.open_test_output_at_cursor()
vim.notify("[test-samurai] No output captured for " .. test_name, vim.log.levels.WARN)
return
end
open_detail_split(output)
local border_kind = status and status:lower() or nil
open_detail_split(output, border_kind)
end
function M.focus_listing()
if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
apply_split_layout(0.25)
end
vim.api.nvim_set_current_win(state.last_win)
end
end
function M.focus_detail()
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
vim.api.nvim_set_current_win(state.detail_win)
end
end
function M.expand_detail_full()
if state.detail_win and vim.api.nvim_win_is_valid(state.detail_win) then
apply_split_layout(0)
vim.api.nvim_set_current_win(state.detail_win)
end
end
function M.toggle_detail_full()
if not (state.detail_win and vim.api.nvim_win_is_valid(state.detail_win)) then
return
end
if state.detail_full then
apply_split_layout(0.25)
vim.api.nvim_set_current_win(state.last_win)
else
M.expand_detail_full()
end
end
local function run_cmd(cmd, cwd, handlers)
local h = handlers or {}

View File

@@ -1,6 +1,62 @@
local test_samurai = require("test-samurai")
local core = require("test-samurai.core")
local function close_output_container()
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
local attempts = 5
while attempts > 0 do
local float_win = nil
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative ~= "" then
float_win = win
break
end
end
if not float_win then
break
end
vim.api.nvim_set_current_win(float_win)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(20, function()
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative ~= "" then
return false
end
end
return true
end)
attempts = attempts - 1
end
end
local function has_nop_mapping(buf, lhs)
local check = { lhs }
if lhs == "<C-h>" then
table.insert(check, "<BS>")
end
if lhs == "<C-j>" then
table.insert(check, "<NL>")
end
for _, map in ipairs(vim.api.nvim_buf_get_keymap(buf, "n")) do
local matched = false
for _, key in ipairs(check) do
if (map.lhs or ""):lower() == key:lower() then
matched = true
break
end
end
if matched then
local rhs = map.rhs or ""
if rhs == "<Nop>" or rhs == "<nop>" or rhs == "" then
return true
end
end
end
return false
end
describe("test-samurai public API", function()
it("delegates show_output to core", function()
local called = false
@@ -98,6 +154,25 @@ describe("test-samurai output formatting", function()
test_samurai.setup()
end)
after_each(function()
close_output_container()
end)
local function find_listing_win()
local current = vim.api.nvim_get_current_win()
local cfg = vim.api.nvim_win_get_config(current)
if cfg.relative ~= "" then
return current
end
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local win_cfg = vim.api.nvim_win_get_config(win)
if win_cfg.relative ~= "" then
return win
end
end
return nil
end
it("formats JSON output as PASS/FAIL lines", function()
local json = vim.json.encode({
testResults = {
@@ -265,8 +340,9 @@ describe("test-samurai output formatting", function()
core.run_nearest()
local win = vim.api.nvim_get_current_win()
local hl = vim.api.nvim_win_get_option(win, "winhighlight")
local listing = find_listing_win()
assert.is_not_nil(listing)
local hl = vim.api.nvim_win_get_option(listing, "winhighlight")
vim.fn.jobstart = orig_jobstart
@@ -311,8 +387,9 @@ describe("test-samurai output formatting", function()
core.run_nearest()
local win = vim.api.nvim_get_current_win()
local hl = vim.api.nvim_win_get_option(win, "winhighlight")
local listing = find_listing_win()
assert.is_not_nil(listing)
local hl = vim.api.nvim_win_get_option(listing, "winhighlight")
vim.fn.jobstart = orig_jobstart
@@ -1406,6 +1483,10 @@ describe("test-samurai output detail view", function()
test_samurai.setup()
end)
after_each(function()
close_output_container()
end)
local function find_float_wins()
local wins = vim.api.nvim_tabpage_list_wins(0)
local out = {}
@@ -1418,10 +1499,9 @@ describe("test-samurai output detail view", function()
return out
end
local function find_non_float_win()
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative == "" then
local function find_detail_win(listing)
for _, win in ipairs(find_float_wins()) do
if win ~= listing then
return win
end
end
@@ -1467,21 +1547,17 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local wins = find_float_wins()
assert.equals(2, #wins)
local right = nil
for _, win in ipairs(wins) do
if win ~= output_win then
right = win
break
end
end
local right = find_detail_win(output_win)
assert.is_not_nil(right)
local left_cfg = vim.api.nvim_win_get_config(output_win)
local right_cfg = vim.api.nvim_win_get_config(right)
assert.is_true(left_cfg.border == "rounded" or type(left_cfg.border) == "table")
assert.is_true(right_cfg.border == "rounded" or type(right_cfg.border) == "table")
assert.equals(left_cfg.row, right_cfg.row)
assert.equals(left_cfg.height, right_cfg.height)
assert.equals(left_cfg.col + left_cfg.width, right_cfg.col)
assert.is_true(right_cfg.col >= left_cfg.col + left_cfg.width + 2)
local total_width = left_cfg.width + right_cfg.width
local expected_left = math.floor(total_width * 0.2)
local expected_left = math.floor(total_width * 0.25)
assert.is_true(math.abs(left_cfg.width - expected_left) <= 1)
local right_buf = vim.api.nvim_win_get_buf(right)
local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false)
@@ -1532,13 +1608,7 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local wins = find_float_wins()
local right = nil
for _, win in ipairs(wins) do
if win ~= output_win then
right = win
break
end
end
local right = find_detail_win(output_win)
assert.is_not_nil(right)
local right_buf = vim.api.nvim_win_get_buf(right)
vim.api.nvim_buf_set_lines(right_buf, 0, -1, false, { "old output" })
@@ -1547,13 +1617,7 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local updated = find_float_wins()
local updated_right = nil
for _, win in ipairs(updated) do
if win ~= output_win then
updated_right = win
break
end
end
local updated_right = find_detail_win(output_win)
assert.equals(right, updated_right)
local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false)
assert.are.same({
@@ -1601,13 +1665,7 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local wins = find_float_wins()
local right = nil
for _, win in ipairs(wins) do
if win ~= output_win then
right = win
break
end
end
local right = find_detail_win(output_win)
assert.is_not_nil(right)
local right_buf = vim.api.nvim_win_get_buf(right)
local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false)
@@ -1622,6 +1680,156 @@ describe("test-samurai output detail view", function()
vim.fn.jobstart = orig_jobstart
end)
it("opens detail output for PASS entries", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "PASS detail\n" }),
vim.json.encode({ Action = "pass", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 0, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_pass_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ PASS %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local wins = find_float_wins()
assert.equals(2, #wins)
local right = find_detail_win(output_win)
assert.is_not_nil(right)
local right_buf = vim.api.nvim_win_get_buf(right)
local detail = vim.api.nvim_buf_get_lines(right_buf, 0, -1, false)
assert.are.same({ "PASS detail" }, detail)
vim.fn.jobstart = orig_jobstart
end)
it("colors detail border based on selected result", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Pass", Output = "PASS detail\n" }),
vim.json.encode({ Action = "pass", Test = "TestFoo/Pass" }),
vim.json.encode({ Action = "output", Test = "TestFoo/Fail", Output = "FAIL detail\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Fail" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_border_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local pass_line = nil
local fail_line = nil
for i, line in ipairs(lines) do
if line:match("^%[ PASS %] %- ") then
pass_line = i
elseif line:match("^%[ FAIL %] %- ") then
fail_line = i
end
end
assert.is_not_nil(pass_line)
assert.is_not_nil(fail_line)
local output_win = vim.api.nvim_get_current_win()
vim.api.nvim_win_set_cursor(0, { pass_line, 0 })
core.open_test_output_at_cursor()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
local pass_hl = vim.api.nvim_win_get_option(detail_win, "winhighlight")
assert.equals("FloatBorder:TestSamuraiBorderPass", pass_hl)
vim.api.nvim_set_current_win(output_win)
vim.api.nvim_win_set_cursor(0, { fail_line, 0 })
core.open_test_output_at_cursor()
local fail_hl = vim.api.nvim_win_get_option(detail_win, "winhighlight")
assert.equals("FloatBorder:TestSamuraiBorderFail", fail_hl)
vim.fn.jobstart = orig_jobstart
end)
it("does not open detail output for SKIP entries", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "skip detail\n" }),
vim.json.encode({ Action = "skip", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 0, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_skip_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ SKIP %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local wins = find_float_wins()
assert.equals(1, #wins)
local right = find_detail_win(output_win)
assert.is_nil(right)
vim.fn.jobstart = orig_jobstart
end)
it("closes detail float and restores listing width", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
@@ -1660,13 +1868,7 @@ describe("test-samurai output detail view", function()
local wins = find_float_wins()
assert.equals(2, #wins)
local right = nil
for _, win in ipairs(wins) do
if win ~= output_win then
right = win
break
end
end
local right = find_detail_win(output_win)
assert.is_not_nil(right)
local left_cfg = vim.api.nvim_win_get_config(output_win)
local right_cfg = vim.api.nvim_win_get_config(right)
@@ -1676,13 +1878,400 @@ describe("test-samurai output detail view", function()
local remaining = find_float_wins()
assert.equals(1, #remaining)
local cfg = vim.api.nvim_win_get_config(remaining[1])
assert.equals(total_width, cfg.width)
local listing_cfg = vim.api.nvim_win_get_config(output_win)
assert.is_true(listing_cfg.border == "rounded" or type(listing_cfg.border) == "table")
assert.equals(total_width + 2, listing_cfg.width)
vim.fn.jobstart = orig_jobstart
end)
it("closes container when listing float is closed", function()
it("closes detail float with <C-c> and restores listing width", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_key_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local wins = find_float_wins()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
local left_cfg = vim.api.nvim_win_get_config(output_win)
local right_cfg = vim.api.nvim_win_get_config(detail_win)
local total_width = left_cfg.width + right_cfg.width
vim.api.nvim_set_current_win(detail_win)
local keys = vim.api.nvim_replace_termcodes("<C-c>", true, false, true)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(20, function()
return #find_float_wins() == 1
end)
local remaining = find_float_wins()
assert.equals(1, #remaining)
local listing_cfg = vim.api.nvim_win_get_config(output_win)
assert.equals(total_width + 2, listing_cfg.width)
vim.fn.jobstart = orig_jobstart
end)
it("disables ctrl-j/ctrl-k mappings in listing and detail buffers", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_ctrl_map_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local listing_buf = vim.api.nvim_get_current_buf()
assert.is_true(has_nop_mapping(listing_buf, "<C-j>"))
assert.is_true(has_nop_mapping(listing_buf, "<C-k>"))
local lines = vim.api.nvim_buf_get_lines(listing_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
local detail_buf = vim.api.nvim_win_get_buf(detail_win)
assert.is_true(has_nop_mapping(detail_buf, "<C-j>"))
assert.is_true(has_nop_mapping(detail_buf, "<C-k>"))
vim.fn.jobstart = orig_jobstart
end)
it("focuses listing with <C-h> from detail", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_focus_h_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
vim.api.nvim_set_current_win(detail_win)
local keys = vim.api.nvim_replace_termcodes("<C-h>", true, false, true)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(20, function()
return vim.api.nvim_get_current_win() == output_win
end)
assert.equals(output_win, vim.api.nvim_get_current_win())
vim.fn.jobstart = orig_jobstart
end)
it("focuses detail with <C-l> from listing when detail is visible", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_focus_l_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
vim.api.nvim_set_current_win(output_win)
local keys = vim.api.nvim_replace_termcodes("<C-l>", true, false, true)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(20, function()
return vim.api.nvim_get_current_win() == detail_win
end)
assert.equals(detail_win, vim.api.nvim_get_current_win())
vim.fn.jobstart = orig_jobstart
end)
it("expands detail to full width and toggles with <leader>z", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_full_width_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
local output_win = vim.api.nvim_get_current_win()
core.open_test_output_at_cursor()
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
local left_cfg = vim.api.nvim_win_get_config(output_win)
local right_cfg = vim.api.nvim_win_get_config(detail_win)
local total_width = left_cfg.width + right_cfg.width
vim.api.nvim_set_current_win(output_win)
local leader_keys = vim.api.nvim_replace_termcodes("<leader>z", true, false, true)
vim.api.nvim_feedkeys(leader_keys, "x", false)
vim.wait(20, function()
local updated_left = vim.api.nvim_win_get_config(output_win)
return updated_left.width <= 1
end)
local collapsed_left = vim.api.nvim_win_get_config(output_win)
local expanded_right = vim.api.nvim_win_get_config(detail_win)
assert.is_true(collapsed_left.width <= 1)
assert.is_true(expanded_right.width >= total_width - 1)
vim.api.nvim_set_current_win(detail_win)
local leader_toggle = vim.api.nvim_replace_termcodes("<leader>z", true, false, true)
vim.api.nvim_feedkeys(leader_toggle, "x", false)
vim.wait(20, function()
local updated_left = vim.api.nvim_win_get_config(output_win)
return updated_left.width > 1
end)
local toggle_left = vim.api.nvim_win_get_config(output_win)
local toggle_right = vim.api.nvim_win_get_config(detail_win)
local toggle_total = toggle_left.width + toggle_right.width
local toggle_expected = math.floor(toggle_total * 0.25)
assert.is_true(math.abs(toggle_left.width - toggle_expected) <= 1)
vim.fn.jobstart = orig_jobstart
end)
it("closes floats via <esc><esc>", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "pass", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 0, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_close_container_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local wins = find_float_wins()
assert.equals(1, #wins)
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(20, function()
return #find_float_wins() == 0
end)
local remaining = find_float_wins()
assert.equals(0, #remaining)
vim.fn.jobstart = orig_jobstart
end)
it("disables hardtime in listing/detail and restores on close", function()
local orig_hardtime = package.loaded["hardtime"]
local disable_calls = 0
local enable_calls = 0
local hardtime_state = true
local hardtime = {
is_plugin_enabled = true,
disable = function()
disable_calls = disable_calls + 1
hardtime_state = false
hardtime.is_plugin_enabled = false
end,
enable = function()
enable_calls = enable_calls + 1
hardtime_state = true
hardtime.is_plugin_enabled = true
end,
}
package.loaded["hardtime"] = hardtime
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
opts.on_stdout(1, {
vim.json.encode({ Action = "output", Test = "TestFoo/Sub", Output = "=== RUN TestFoo/Sub\n" }),
vim.json.encode({ Action = "fail", Test = "TestFoo/Sub" }),
}, nil)
end
if opts and opts.on_exit then
opts.on_exit(1, 1, nil)
end
return 1
end
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/output_detail_hardtime_test.go")
vim.bo[bufnr].filetype = "go"
vim.api.nvim_set_current_buf(bufnr)
test_samurai.test_file()
local out_buf = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
local target = nil
for i, line in ipairs(lines) do
if line:match("^%[ FAIL %] %- ") then
target = i
break
end
end
assert.is_not_nil(target)
vim.api.nvim_win_set_cursor(0, { target, 0 })
core.open_test_output_at_cursor()
assert.is_true(disable_calls >= 1)
assert.is_false(hardtime_state)
local keys = vim.api.nvim_replace_termcodes("<esc><esc>", true, false, true)
vim.api.nvim_feedkeys(keys, "x", false)
vim.wait(50, function()
return #find_float_wins() == 0
end)
assert.equals(1, enable_calls)
assert.is_true(hardtime_state)
vim.fn.jobstart = orig_jobstart
package.loaded["hardtime"] = orig_hardtime
end)
it("closes detail when listing float is closed", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
@@ -1726,7 +2315,7 @@ describe("test-samurai output detail view", function()
vim.fn.jobstart = orig_jobstart
end)
it("closes container when navigating out of floats", function()
it("keeps floats open when navigating out of floats", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
@@ -1761,17 +2350,24 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local non_float = find_non_float_win()
local non_float = nil
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
local cfg = vim.api.nvim_win_get_config(win)
if cfg.relative == "" then
non_float = win
break
end
end
assert.is_not_nil(non_float)
vim.api.nvim_set_current_win(non_float)
local remaining = find_float_wins()
assert.equals(0, #remaining)
assert.equals(2, #remaining)
vim.fn.jobstart = orig_jobstart
end)
it("keeps container open when focusing listing from detail", function()
it("keeps floats open when focusing listing from detail", function()
local orig_jobstart = vim.fn.jobstart
vim.fn.jobstart = function(_cmd, opts)
if opts and opts.on_stdout then
@@ -1808,13 +2404,7 @@ describe("test-samurai output detail view", function()
core.open_test_output_at_cursor()
local wins = find_float_wins()
local detail_win = nil
for _, win in ipairs(wins) do
if win ~= output_win then
detail_win = win
break
end
end
local detail_win = find_detail_win(output_win)
assert.is_not_nil(detail_win)
vim.api.nvim_set_current_win(detail_win)