add TSamFailedOnly command and change test output within the floating window

This commit is contained in:
2025-12-25 16:40:50 +01:00
parent cbc3e201ae
commit 1e2e881acd
17 changed files with 1074 additions and 448 deletions

View File

@@ -28,6 +28,48 @@ local function is_js_test_file(bufnr, filetypes, patterns)
return false
end
local function append_args(cmd, args)
if not args or #args == 0 then
return
end
for _, arg in ipairs(args) do
table.insert(cmd, arg)
end
end
local function escape_regex(s)
s = s or ""
return (s:gsub("([\\.^$|()%%[%]{}*+?%-])", "\\\\%1"))
end
local function build_pattern(names)
local parts = {}
for _, name in ipairs(names or {}) do
if name and name ~= "" then
table.insert(parts, escape_regex(name))
end
end
if #parts == 0 then
return nil
end
return table.concat(parts, "|")
end
local function find_test_file_arg(cmd)
if not cmd then
return nil
end
for i = #cmd, 1, -1 do
local arg = cmd[i]
if type(arg) == "string" and arg:sub(1, 1) ~= "-" then
if arg:match("%.test%.[jt]sx?$") or arg:match("%.spec%.[jt]sx?$") then
return arg
end
end
end
return nil
end
local function find_block_end(lines, start_idx)
local depth = 0
local started = false
@@ -221,6 +263,8 @@ function M.new(opts)
runner.framework = cfg.framework or "jest"
runner.command = cfg.command or { "npx", runner.framework }
runner.all_glob = cfg.all_glob
runner.json_args = cfg.json_args or {}
runner.failed_only_flag = cfg.failed_only_flag
runner.filetypes = {}
if cfg.filetypes then
@@ -271,6 +315,7 @@ function M.new(opts)
local function build_jest(spec)
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
table.insert(cmd, spec.file)
table.insert(cmd, "-t")
table.insert(cmd, spec.test_name)
@@ -279,6 +324,7 @@ function M.new(opts)
local function build_mocha(spec)
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
local target = spec.full_name or spec.test_name
table.insert(cmd, "--fgrep")
table.insert(cmd, target)
@@ -288,6 +334,7 @@ function M.new(opts)
local function build_vitest(spec)
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
table.insert(cmd, spec.file)
table.insert(cmd, "-t")
table.insert(cmd, spec.test_name)
@@ -332,6 +379,7 @@ function M.new(opts)
})
end
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
table.insert(cmd, path)
return {
cmd = cmd,
@@ -360,6 +408,7 @@ function M.new(opts)
root = vim.loop.cwd()
end
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
if runner.framework == "mocha" and runner.all_glob then
table.insert(cmd, runner.all_glob)
end
@@ -369,6 +418,226 @@ function M.new(opts)
}
end
local function parse_jest_like(output)
local ok, data = pcall(vim.json.decode, output)
if not ok or type(data) ~= "table" then
return nil
end
local passes = {}
local failures = {}
local skips = {}
for _, result in ipairs(data.testResults or {}) do
for _, assertion in ipairs(result.assertionResults or {}) do
local title = assertion.fullName or assertion.title
if assertion.status == "passed" and title then
table.insert(passes, title)
elseif assertion.status == "failed" and title then
table.insert(failures, title)
elseif (assertion.status == "pending" or assertion.status == "skipped" or assertion.status == "todo")
and title then
table.insert(skips, title)
end
end
end
return { passes = passes, failures = failures, skips = skips }
end
local function parse_mocha(output)
local ok, data = pcall(vim.json.decode, output)
if not ok or type(data) ~= "table" then
return nil
end
local passes = {}
local failures = {}
local skips = {}
if type(data.tests) == "table" then
for _, test in ipairs(data.tests) do
local title = test.fullTitle or test.title
if test.state == "passed" and title then
table.insert(passes, title)
elseif test.state == "failed" and title then
table.insert(failures, title)
elseif test.state == "pending" and title then
table.insert(skips, title)
end
end
elseif type(data.passes) == "table" or type(data.failures) == "table" then
for _, test in ipairs(data.passes or {}) do
local title = test.fullTitle or test.title
if title then
table.insert(passes, title)
end
end
for _, test in ipairs(data.failures or {}) do
local title = test.fullTitle or test.title
if title then
table.insert(failures, title)
end
end
for _, test in ipairs(data.pending or {}) do
local title = test.fullTitle or test.title
if title then
table.insert(skips, title)
end
end
end
return { passes = passes, failures = failures, skips = skips }
end
local function parse_output(output)
if runner.framework == "mocha" then
return parse_mocha(output)
end
return parse_jest_like(output)
end
function runner.parse_results(output)
return parse_output(output)
end
function runner.output_parser()
local state = { raw = {}, done = false }
local failures = {}
local skips = {}
return {
on_line = function(line, _state)
if state.done then
return nil
end
if runner.framework == "mocha" and runner.json_args then
local uses_stream = false
for i = 1, #runner.json_args - 1 do
if runner.json_args[i] == "--reporter" and runner.json_args[i + 1] == "json-stream" then
uses_stream = true
break
end
end
if uses_stream then
local ok, data = pcall(vim.json.decode, line)
if ok and type(data) == "table" and data.event then
if data.event == "pass" and data.fullTitle then
return {
passes = { data.fullTitle },
failures = {},
skips = {},
failures_all = vim.deepcopy(failures),
}
elseif data.event == "fail" and data.fullTitle then
table.insert(failures, data.fullTitle)
return {
passes = {},
failures = { data.fullTitle },
skips = {},
failures_all = vim.deepcopy(failures),
}
elseif data.event == "pending" and data.fullTitle then
table.insert(skips, data.fullTitle)
return {
passes = {},
failures = {},
skips = { data.fullTitle },
failures_all = vim.deepcopy(failures),
}
end
end
return nil
end
end
if runner.framework == "vitest" and runner.json_args then
local uses_tap = false
for i = 1, #runner.json_args - 1 do
if runner.json_args[i] == "--reporter" and (runner.json_args[i + 1] == "tap" or runner.json_args[i + 1] == "tap-flat") then
uses_tap = true
break
end
end
if uses_tap then
local ok_title = line:match("^ok%s+%d+%s+%-%s+(.+)")
if ok_title then
local skip_title = ok_title:match("^(.-)%s+#%s+SKIP.*$")
if skip_title then
skip_title = skip_title:gsub("%s+$", "")
table.insert(skips, skip_title)
return {
passes = {},
failures = {},
skips = { skip_title },
failures_all = vim.deepcopy(failures),
}
end
ok_title = ok_title:gsub("%s+#%s+time=.*$", "")
return {
passes = { ok_title },
failures = {},
skips = {},
failures_all = vim.deepcopy(failures),
}
end
local fail_title = line:match("^not ok%s+%d+%s+%-%s+(.+)")
if fail_title then
fail_title = fail_title:gsub("%s+#%s+time=.*$", "")
table.insert(failures, fail_title)
return {
passes = {},
failures = { fail_title },
skips = {},
failures_all = vim.deepcopy(failures),
}
end
return nil
end
end
table.insert(state.raw, line)
local output = table.concat(state.raw, "\n")
local results = parse_output(output)
if results then
state.done = true
end
return results
end,
on_complete = function(output, _state)
if state.done then
return nil
end
local results = parse_output(output)
if results then
state.done = true
end
return results
end,
}
end
function runner.build_failed_command(last_command, failures, scope_kind)
local pattern = build_pattern(failures)
if not pattern then
return nil
end
local cmd = vim.deepcopy(runner.command)
append_args(cmd, runner.json_args)
if runner.framework == "mocha" then
table.insert(cmd, "--grep")
table.insert(cmd, pattern)
else
table.insert(cmd, "-t")
table.insert(cmd, pattern)
end
if scope_kind ~= "all" and last_command then
local file = find_test_file_arg(last_command.cmd)
if file then
table.insert(cmd, file)
end
end
return {
cmd = cmd,
cwd = last_command and last_command.cwd or nil,
}
end
return runner
end