add TSamFailedOnly command and change test output within the floating window
This commit is contained in:
@@ -6,7 +6,6 @@ local defaults = {
|
|||||||
"test-samurai.runners.js-jest",
|
"test-samurai.runners.js-jest",
|
||||||
"test-samurai.runners.js-mocha",
|
"test-samurai.runners.js-mocha",
|
||||||
"test-samurai.runners.js-vitest",
|
"test-samurai.runners.js-vitest",
|
||||||
"test-samurai.runners.lua-plenary",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ local state = {
|
|||||||
last_win = nil,
|
last_win = nil,
|
||||||
last_buf = nil,
|
last_buf = nil,
|
||||||
last_command = nil,
|
last_command = nil,
|
||||||
|
last_scope_command = nil,
|
||||||
|
last_scope_runner = nil,
|
||||||
|
last_scope_kind = nil,
|
||||||
|
last_scope_exit_code = nil,
|
||||||
|
last_scope_failures = nil,
|
||||||
autocmds_set = false,
|
autocmds_set = false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +57,11 @@ function M.setup()
|
|||||||
load_runners()
|
load_runners()
|
||||||
ensure_output_autocmds()
|
ensure_output_autocmds()
|
||||||
state.last_command = nil
|
state.last_command = nil
|
||||||
|
state.last_scope_command = nil
|
||||||
|
state.last_scope_runner = nil
|
||||||
|
state.last_scope_kind = nil
|
||||||
|
state.last_scope_exit_code = nil
|
||||||
|
state.last_scope_failures = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.reload_runners()
|
function M.reload_runners()
|
||||||
@@ -299,12 +309,45 @@ local function run_cmd(cmd, cwd, handlers)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run_command(command)
|
local function format_results(results)
|
||||||
|
local lines = {}
|
||||||
|
if type(results.passes) == "table" then
|
||||||
|
for _, title in ipairs(results.passes) do
|
||||||
|
table.insert(lines, "[ PASS ] - " .. title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if type(results.skips) == "table" then
|
||||||
|
for _, title in ipairs(results.skips) do
|
||||||
|
table.insert(lines, "[ SKIP ] - " .. title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if type(results.failures) == "table" then
|
||||||
|
for _, title in ipairs(results.failures) do
|
||||||
|
table.insert(lines, "[ FAIL ] - " .. title)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
local function run_command(command, opts)
|
||||||
|
local options = opts or {}
|
||||||
if command and type(command.cmd) == "table" and #command.cmd > 0 then
|
if command and type(command.cmd) == "table" and #command.cmd > 0 then
|
||||||
state.last_command = {
|
if options.save_last ~= false then
|
||||||
cmd = vim.deepcopy(command.cmd),
|
state.last_command = {
|
||||||
cwd = command.cwd,
|
cmd = vim.deepcopy(command.cmd),
|
||||||
}
|
cwd = command.cwd,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if options.track_scope then
|
||||||
|
state.last_scope_command = {
|
||||||
|
cmd = vim.deepcopy(command.cmd),
|
||||||
|
cwd = command.cwd,
|
||||||
|
}
|
||||||
|
state.last_scope_runner = options.runner
|
||||||
|
state.last_scope_kind = options.scope_kind
|
||||||
|
state.last_scope_exit_code = nil
|
||||||
|
state.last_scope_failures = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local cmd = command.cmd
|
local cmd = command.cmd
|
||||||
local cwd = command.cwd or vim.loop.cwd()
|
local cwd = command.cwd or vim.loop.cwd()
|
||||||
@@ -312,48 +355,126 @@ local function run_command(command)
|
|||||||
local header = "$ " .. table.concat(cmd, " ")
|
local header = "$ " .. table.concat(cmd, " ")
|
||||||
local buf = nil
|
local buf = nil
|
||||||
local has_output = false
|
local has_output = false
|
||||||
|
local parser = options.output_parser
|
||||||
|
if type(parser) == "function" then
|
||||||
|
parser = { on_complete = parser }
|
||||||
|
end
|
||||||
|
local parser_state = {}
|
||||||
|
local had_parsed_output = false
|
||||||
|
|
||||||
|
local output_lines = {}
|
||||||
|
|
||||||
|
local function collect_output(lines)
|
||||||
|
if not lines or #lines == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line ~= nil then
|
||||||
|
table.insert(output_lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_output_started()
|
||||||
|
if not buf then
|
||||||
|
buf = select(1, create_output_win({ header, "" }))
|
||||||
|
end
|
||||||
|
if not has_output then
|
||||||
|
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||||
|
if #cur > 0 and cur[#cur] == "[running...]" then
|
||||||
|
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
|
||||||
|
end
|
||||||
|
has_output = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handle_parsed(results)
|
||||||
|
if not results then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lines = format_results(results)
|
||||||
|
if #lines == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
ensure_output_started()
|
||||||
|
append_lines(buf, lines)
|
||||||
|
had_parsed_output = true
|
||||||
|
if options.track_scope then
|
||||||
|
if results.failures_all ~= nil then
|
||||||
|
state.last_scope_failures = results.failures_all
|
||||||
|
elseif results.failures ~= nil then
|
||||||
|
state.last_scope_failures = results.failures
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
run_cmd(cmd, cwd, {
|
run_cmd(cmd, cwd, {
|
||||||
on_start = function()
|
on_start = function()
|
||||||
buf = select(1, create_output_win({ header, "", "[running...]" }))
|
buf = select(1, create_output_win({ header, "", "[running...]" }))
|
||||||
end,
|
end,
|
||||||
on_stdout = function(lines)
|
on_stdout = function(lines)
|
||||||
if not buf then
|
if parser then
|
||||||
buf = select(1, create_output_win({ header, "" }))
|
collect_output(lines)
|
||||||
end
|
if parser.on_line then
|
||||||
if not has_output then
|
for _, line in ipairs(lines or {}) do
|
||||||
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
local ok_parse, results = pcall(parser.on_line, line, parser_state)
|
||||||
if #cur > 0 and cur[#cur] == "[running...]" then
|
if ok_parse then
|
||||||
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
|
handle_parsed(results)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
has_output = true
|
return
|
||||||
end
|
end
|
||||||
|
ensure_output_started()
|
||||||
append_lines(buf, lines)
|
append_lines(buf, lines)
|
||||||
end,
|
end,
|
||||||
on_stderr = function(lines)
|
on_stderr = function(lines)
|
||||||
if not buf then
|
if parser then
|
||||||
buf = select(1, create_output_win({ header, "" }))
|
collect_output(lines)
|
||||||
end
|
if parser.on_line then
|
||||||
if not has_output then
|
for _, line in ipairs(lines or {}) do
|
||||||
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
local ok_parse, results = pcall(parser.on_line, line, parser_state)
|
||||||
if #cur > 0 and cur[#cur] == "[running...]" then
|
if ok_parse then
|
||||||
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
|
handle_parsed(results)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
has_output = true
|
return
|
||||||
end
|
end
|
||||||
|
ensure_output_started()
|
||||||
append_lines(buf, lines)
|
append_lines(buf, lines)
|
||||||
end,
|
end,
|
||||||
on_exit = function(code)
|
on_exit = function(code)
|
||||||
if not buf then
|
if not buf then
|
||||||
buf = select(1, create_output_win({ header }))
|
buf = select(1, create_output_win({ header }))
|
||||||
end
|
end
|
||||||
if not has_output then
|
if parser and parser.on_complete then
|
||||||
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
local output = table.concat(output_lines, "\n")
|
||||||
if #cur > 0 and cur[#cur] == "[running...]" then
|
local ok_parse, results = pcall(parser.on_complete, output, parser_state)
|
||||||
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
|
if ok_parse then
|
||||||
|
handle_parsed(results)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
append_lines(buf, { "", "[exit code] " .. tostring(code) })
|
if parser then
|
||||||
|
if not had_parsed_output and #output_lines > 0 then
|
||||||
|
ensure_output_started()
|
||||||
|
append_lines(buf, output_lines)
|
||||||
|
elseif not has_output then
|
||||||
|
ensure_output_started()
|
||||||
|
end
|
||||||
|
append_lines(buf, { "", "[exit code] " .. tostring(code) })
|
||||||
|
else
|
||||||
|
if not has_output then
|
||||||
|
local cur = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||||
|
if #cur > 0 and cur[#cur] == "[running...]" then
|
||||||
|
vim.api.nvim_buf_set_lines(buf, #cur - 1, #cur, false, {})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
append_lines(buf, { "", "[exit code] " .. tostring(code) })
|
||||||
|
end
|
||||||
|
if options.track_scope then
|
||||||
|
state.last_scope_exit_code = code
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -405,7 +526,16 @@ function M.run_nearest()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
run_command(command)
|
local parser = runner.output_parser
|
||||||
|
if type(parser) == "function" then
|
||||||
|
parser = parser()
|
||||||
|
end
|
||||||
|
run_command(command, {
|
||||||
|
track_scope = true,
|
||||||
|
runner = runner,
|
||||||
|
scope_kind = "nearest",
|
||||||
|
output_parser = parser or runner.parse_results,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.run_file()
|
function M.run_file()
|
||||||
@@ -428,7 +558,16 @@ function M.run_file()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
run_command(command)
|
local parser = runner.output_parser
|
||||||
|
if type(parser) == "function" then
|
||||||
|
parser = parser()
|
||||||
|
end
|
||||||
|
run_command(command, {
|
||||||
|
track_scope = true,
|
||||||
|
runner = runner,
|
||||||
|
scope_kind = "file",
|
||||||
|
output_parser = parser or runner.parse_results,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.run_all()
|
function M.run_all()
|
||||||
@@ -451,7 +590,59 @@ function M.run_all()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
run_command(command)
|
local parser = runner.output_parser
|
||||||
|
if type(parser) == "function" then
|
||||||
|
parser = parser()
|
||||||
|
end
|
||||||
|
run_command(command, {
|
||||||
|
track_scope = true,
|
||||||
|
runner = runner,
|
||||||
|
scope_kind = "all",
|
||||||
|
output_parser = parser or runner.parse_results,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function build_failed_only_command()
|
||||||
|
if not (state.last_scope_command and type(state.last_scope_command.cmd) == "table") then
|
||||||
|
return nil, "[test-samurai] No previous scoped test command"
|
||||||
|
end
|
||||||
|
|
||||||
|
local failures = state.last_scope_failures or {}
|
||||||
|
if #failures == 0 then
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local runner = state.last_scope_runner
|
||||||
|
if not runner or type(runner.build_failed_command) ~= "function" then
|
||||||
|
local name = runner and runner.name or "unknown"
|
||||||
|
return nil, "[test-samurai] Runner does not support failed-only: " .. name
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok_build, command = pcall(runner.build_failed_command, state.last_scope_command, failures, state.last_scope_kind)
|
||||||
|
if not ok_build or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then
|
||||||
|
return nil, "[test-samurai] Runner failed to build failed-only command"
|
||||||
|
end
|
||||||
|
|
||||||
|
return command, nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.run_failed_only()
|
||||||
|
if not (state.last_scope_command and type(state.last_scope_command.cmd) == "table") then
|
||||||
|
vim.notify("[test-samurai] No previous scoped test command", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local command, err = build_failed_only_command()
|
||||||
|
if not command then
|
||||||
|
if not err then
|
||||||
|
M.run_last()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.notify(err, vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
run_command(command, { save_last = false })
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.show_output()
|
function M.show_output()
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ function M.test_last()
|
|||||||
core.run_last()
|
core.run_last()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.test_failed_only()
|
||||||
|
core.run_failed_only()
|
||||||
|
end
|
||||||
|
|
||||||
function M.show_output()
|
function M.show_output()
|
||||||
core.show_output()
|
core.show_output()
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -106,6 +106,18 @@ local function build_pkg_arg(spec)
|
|||||||
return "./" .. rel
|
return "./" .. rel
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function collect_unique(list)
|
||||||
|
local out = {}
|
||||||
|
local seen = {}
|
||||||
|
for _, item in ipairs(list) do
|
||||||
|
if item and item ~= "" and not seen[item] then
|
||||||
|
seen[item] = true
|
||||||
|
table.insert(out, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
function runner.is_test_file(bufnr)
|
function runner.is_test_file(bufnr)
|
||||||
local path = util.get_buf_path(bufnr)
|
local path = util.get_buf_path(bufnr)
|
||||||
if not path or path == "" then
|
if not path or path == "" then
|
||||||
@@ -170,7 +182,7 @@ end
|
|||||||
function runner.build_command(spec)
|
function runner.build_command(spec)
|
||||||
local pattern = build_run_pattern(spec)
|
local pattern = build_run_pattern(spec)
|
||||||
local pkg = build_pkg_arg(spec)
|
local pkg = build_pkg_arg(spec)
|
||||||
local cmd = { "go", "test", "-v", pkg, "-run", pattern }
|
local cmd = { "go", "test", "-json", pkg, "-run", pattern }
|
||||||
return {
|
return {
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
cwd = spec.cwd,
|
cwd = spec.cwd,
|
||||||
@@ -188,7 +200,7 @@ function runner.build_file_command(bufnr)
|
|||||||
end
|
end
|
||||||
local spec = { file = path, cwd = root }
|
local spec = { file = path, cwd = root }
|
||||||
local pkg = build_pkg_arg(spec)
|
local pkg = build_pkg_arg(spec)
|
||||||
local cmd = { "go", "test", "-v", pkg }
|
local cmd = { "go", "test", "-json", pkg }
|
||||||
return {
|
return {
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
cwd = root,
|
cwd = root,
|
||||||
@@ -204,11 +216,125 @@ function runner.build_all_command(bufnr)
|
|||||||
if not root or root == "" then
|
if not root or root == "" then
|
||||||
root = vim.loop.cwd()
|
root = vim.loop.cwd()
|
||||||
end
|
end
|
||||||
local cmd = { "go", "test", "-v", "./..." }
|
local cmd = { "go", "test", "-json", "./..." }
|
||||||
return {
|
return {
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
cwd = root,
|
cwd = root,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function runner.parse_results(output)
|
||||||
|
if not output or output == "" then
|
||||||
|
return { passes = {}, failures = {}, skips = {} }
|
||||||
|
end
|
||||||
|
local passes = {}
|
||||||
|
local failures = {}
|
||||||
|
local skips = {}
|
||||||
|
for line in output:gmatch("[^\n]+") do
|
||||||
|
local ok, data = pcall(vim.json.decode, line)
|
||||||
|
if ok and type(data) == "table" then
|
||||||
|
if data.Test and data.Test ~= "" then
|
||||||
|
if data.Action == "pass" then
|
||||||
|
table.insert(passes, data.Test)
|
||||||
|
elseif data.Action == "fail" then
|
||||||
|
table.insert(failures, data.Test)
|
||||||
|
elseif data.Action == "skip" then
|
||||||
|
table.insert(skips, data.Test)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
passes = collect_unique(passes),
|
||||||
|
failures = collect_unique(failures),
|
||||||
|
skips = collect_unique(skips),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.output_parser()
|
||||||
|
local seen_pass = {}
|
||||||
|
local seen_fail = {}
|
||||||
|
local failures = {}
|
||||||
|
local passes = {}
|
||||||
|
local skips = {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
on_line = function(line, _state)
|
||||||
|
local ok, data = pcall(vim.json.decode, line)
|
||||||
|
if not ok or type(data) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local name = data.Test
|
||||||
|
if not name or name == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
if data.Action == "pass" and not seen_pass[name] then
|
||||||
|
seen_pass[name] = true
|
||||||
|
table.insert(passes, name)
|
||||||
|
return {
|
||||||
|
passes = { name },
|
||||||
|
failures = {},
|
||||||
|
skips = {},
|
||||||
|
failures_all = vim.deepcopy(failures),
|
||||||
|
}
|
||||||
|
elseif data.Action == "fail" and not seen_fail[name] then
|
||||||
|
seen_fail[name] = true
|
||||||
|
table.insert(failures, name)
|
||||||
|
return {
|
||||||
|
passes = {},
|
||||||
|
failures = { name },
|
||||||
|
skips = {},
|
||||||
|
failures_all = vim.deepcopy(failures),
|
||||||
|
}
|
||||||
|
elseif data.Action == "skip" and not seen_pass[name] then
|
||||||
|
seen_pass[name] = true
|
||||||
|
table.insert(skips, name)
|
||||||
|
return {
|
||||||
|
passes = {},
|
||||||
|
failures = {},
|
||||||
|
skips = { name },
|
||||||
|
failures_all = vim.deepcopy(failures),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
on_complete = function(_output, _state)
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function runner.build_failed_command(last_command, failures, _scope_kind)
|
||||||
|
if not last_command or type(last_command.cmd) ~= "table" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local pattern_parts = {}
|
||||||
|
for _, name in ipairs(failures or {}) do
|
||||||
|
table.insert(pattern_parts, escape_go_regex(name))
|
||||||
|
end
|
||||||
|
if #pattern_parts == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local pattern = "^(" .. table.concat(pattern_parts, "|") .. ")$"
|
||||||
|
|
||||||
|
local cmd = {}
|
||||||
|
local skip_next = false
|
||||||
|
for _, arg in ipairs(last_command.cmd) do
|
||||||
|
if skip_next then
|
||||||
|
skip_next = false
|
||||||
|
elseif arg == "-run" then
|
||||||
|
skip_next = true
|
||||||
|
else
|
||||||
|
table.insert(cmd, arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(cmd, "-run")
|
||||||
|
table.insert(cmd, pattern)
|
||||||
|
|
||||||
|
return {
|
||||||
|
cmd = cmd,
|
||||||
|
cwd = last_command.cwd,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
return runner
|
return runner
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ return js.new({
|
|||||||
name = "js-jest",
|
name = "js-jest",
|
||||||
framework = "jest",
|
framework = "jest",
|
||||||
command = { "npx", "jest" },
|
command = { "npx", "jest" },
|
||||||
|
json_args = { "--json" },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ return js.new({
|
|||||||
framework = "mocha",
|
framework = "mocha",
|
||||||
command = { "npx", "mocha" },
|
command = { "npx", "mocha" },
|
||||||
all_glob = "test/**/*.test.js",
|
all_glob = "test/**/*.test.js",
|
||||||
|
json_args = { "--reporter", "json-stream" },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ return js.new({
|
|||||||
name = "js-vitest",
|
name = "js-vitest",
|
||||||
framework = "vitest",
|
framework = "vitest",
|
||||||
command = { "npx", "vitest" },
|
command = { "npx", "vitest" },
|
||||||
|
json_args = { "--reporter", "tap-flat" },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,6 +28,48 @@ local function is_js_test_file(bufnr, filetypes, patterns)
|
|||||||
return false
|
return false
|
||||||
end
|
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 function find_block_end(lines, start_idx)
|
||||||
local depth = 0
|
local depth = 0
|
||||||
local started = false
|
local started = false
|
||||||
@@ -221,6 +263,8 @@ function M.new(opts)
|
|||||||
runner.framework = cfg.framework or "jest"
|
runner.framework = cfg.framework or "jest"
|
||||||
runner.command = cfg.command or { "npx", runner.framework }
|
runner.command = cfg.command or { "npx", runner.framework }
|
||||||
runner.all_glob = cfg.all_glob
|
runner.all_glob = cfg.all_glob
|
||||||
|
runner.json_args = cfg.json_args or {}
|
||||||
|
runner.failed_only_flag = cfg.failed_only_flag
|
||||||
|
|
||||||
runner.filetypes = {}
|
runner.filetypes = {}
|
||||||
if cfg.filetypes then
|
if cfg.filetypes then
|
||||||
@@ -271,6 +315,7 @@ function M.new(opts)
|
|||||||
|
|
||||||
local function build_jest(spec)
|
local function build_jest(spec)
|
||||||
local cmd = vim.deepcopy(runner.command)
|
local cmd = vim.deepcopy(runner.command)
|
||||||
|
append_args(cmd, runner.json_args)
|
||||||
table.insert(cmd, spec.file)
|
table.insert(cmd, spec.file)
|
||||||
table.insert(cmd, "-t")
|
table.insert(cmd, "-t")
|
||||||
table.insert(cmd, spec.test_name)
|
table.insert(cmd, spec.test_name)
|
||||||
@@ -279,6 +324,7 @@ function M.new(opts)
|
|||||||
|
|
||||||
local function build_mocha(spec)
|
local function build_mocha(spec)
|
||||||
local cmd = vim.deepcopy(runner.command)
|
local cmd = vim.deepcopy(runner.command)
|
||||||
|
append_args(cmd, runner.json_args)
|
||||||
local target = spec.full_name or spec.test_name
|
local target = spec.full_name or spec.test_name
|
||||||
table.insert(cmd, "--fgrep")
|
table.insert(cmd, "--fgrep")
|
||||||
table.insert(cmd, target)
|
table.insert(cmd, target)
|
||||||
@@ -288,6 +334,7 @@ function M.new(opts)
|
|||||||
|
|
||||||
local function build_vitest(spec)
|
local function build_vitest(spec)
|
||||||
local cmd = vim.deepcopy(runner.command)
|
local cmd = vim.deepcopy(runner.command)
|
||||||
|
append_args(cmd, runner.json_args)
|
||||||
table.insert(cmd, spec.file)
|
table.insert(cmd, spec.file)
|
||||||
table.insert(cmd, "-t")
|
table.insert(cmd, "-t")
|
||||||
table.insert(cmd, spec.test_name)
|
table.insert(cmd, spec.test_name)
|
||||||
@@ -332,6 +379,7 @@ function M.new(opts)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
local cmd = vim.deepcopy(runner.command)
|
local cmd = vim.deepcopy(runner.command)
|
||||||
|
append_args(cmd, runner.json_args)
|
||||||
table.insert(cmd, path)
|
table.insert(cmd, path)
|
||||||
return {
|
return {
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
@@ -360,6 +408,7 @@ function M.new(opts)
|
|||||||
root = vim.loop.cwd()
|
root = vim.loop.cwd()
|
||||||
end
|
end
|
||||||
local cmd = vim.deepcopy(runner.command)
|
local cmd = vim.deepcopy(runner.command)
|
||||||
|
append_args(cmd, runner.json_args)
|
||||||
if runner.framework == "mocha" and runner.all_glob then
|
if runner.framework == "mocha" and runner.all_glob then
|
||||||
table.insert(cmd, runner.all_glob)
|
table.insert(cmd, runner.all_glob)
|
||||||
end
|
end
|
||||||
@@ -369,6 +418,226 @@ function M.new(opts)
|
|||||||
}
|
}
|
||||||
end
|
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
|
return runner
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
local util = require("test-samurai.util")
|
|
||||||
|
|
||||||
local runner = {
|
|
||||||
name = "lua-plenary",
|
|
||||||
framework = "plenary",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function is_lua_test_path(path)
|
|
||||||
if not path or path == "" then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if not path:match("%.lua$") then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
if path:match("_spec%.lua$") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
if path:match("/tests/") then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.is_test_file(bufnr)
|
|
||||||
local path = util.get_buf_path(bufnr)
|
|
||||||
return is_lua_test_path(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_repo_root(file)
|
|
||||||
local root = util.find_root(file, { ".git", "tests", "lua", "plugin" })
|
|
||||||
if not root or root == "" then
|
|
||||||
root = vim.loop.cwd()
|
|
||||||
end
|
|
||||||
return root
|
|
||||||
end
|
|
||||||
|
|
||||||
local function minimal_init_for(root)
|
|
||||||
return vim.fs.joinpath(root, "tests", "minimal_init.lua")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function escape_for_ex_double_quotes(s)
|
|
||||||
s = s or ""
|
|
||||||
s = s:gsub("\\", "\\\\")
|
|
||||||
s = s:gsub('"', '\\"')
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
|
|
||||||
local function count_keyword(line, kw)
|
|
||||||
local c = 0
|
|
||||||
local pat = "%f[%w]" .. kw .. "%f[%W]"
|
|
||||||
for _ in line:gmatch(pat) do
|
|
||||||
c = c + 1
|
|
||||||
end
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
local function find_end_lua_block(lines, start_idx)
|
|
||||||
local depth = 0
|
|
||||||
local started = false
|
|
||||||
for i = start_idx, #lines do
|
|
||||||
local line = lines[i]
|
|
||||||
local fnc = count_keyword(line, "function")
|
|
||||||
local endc = count_keyword(line, "end")
|
|
||||||
if fnc > 0 then
|
|
||||||
depth = depth + fnc
|
|
||||||
started = true
|
|
||||||
end
|
|
||||||
if started and endc > 0 then
|
|
||||||
depth = depth - endc
|
|
||||||
if depth <= 0 then
|
|
||||||
return i - 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return #lines - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
local function collect_lua_structs(lines)
|
|
||||||
local describes = {}
|
|
||||||
local tests = {}
|
|
||||||
|
|
||||||
for i, line in ipairs(lines) do
|
|
||||||
local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]")
|
|
||||||
if dname then
|
|
||||||
local start0 = i - 1
|
|
||||||
local end0 = find_end_lua_block(lines, i)
|
|
||||||
table.insert(describes, { kind = "describe", name = dname, start = start0, ["end"] = end0 })
|
|
||||||
else
|
|
||||||
local tname = line:match("^%s*it%s*%(%s*['\"`]([^'\"`]+)['\"`]")
|
|
||||||
if tname then
|
|
||||||
local start0 = i - 1
|
|
||||||
local end0 = find_end_lua_block(lines, i)
|
|
||||||
table.insert(tests, { kind = "it", name = tname, start = start0, ["end"] = end0 })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return describes, tests
|
|
||||||
end
|
|
||||||
|
|
||||||
local function build_full_name(lines, idx, leaf_name)
|
|
||||||
local parts = { leaf_name }
|
|
||||||
for i = idx - 1, 1, -1 do
|
|
||||||
local line = lines[i]
|
|
||||||
local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]")
|
|
||||||
if dname then
|
|
||||||
table.insert(parts, 1, dname)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return table.concat(parts, " ")
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.find_nearest(bufnr, row, _col)
|
|
||||||
if not runner.is_test_file(bufnr) then
|
|
||||||
return nil, "not a lua test file"
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = util.get_buf_lines(bufnr)
|
|
||||||
local describes, tests = collect_lua_structs(lines)
|
|
||||||
|
|
||||||
for _, t in ipairs(tests) do
|
|
||||||
if row >= t.start and row <= t["end"] then
|
|
||||||
local full = build_full_name(lines, t.start + 1, t.name)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
return { file = file, cwd = root, test_name = t.name, full_name = full, kind = "it" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local best_describe = nil
|
|
||||||
for _, d in ipairs(describes) do
|
|
||||||
if row >= d.start and row <= d["end"] then
|
|
||||||
if not best_describe or d.start >= best_describe.start then
|
|
||||||
best_describe = d
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if best_describe then
|
|
||||||
local full = build_full_name(lines, best_describe.start + 1, best_describe.name)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
return { file = file, cwd = root, test_name = best_describe.name, full_name = full, kind = "describe" }
|
|
||||||
end
|
|
||||||
|
|
||||||
local start = row + 1
|
|
||||||
if start > #lines then
|
|
||||||
start = #lines
|
|
||||||
elseif start < 1 then
|
|
||||||
start = 1
|
|
||||||
end
|
|
||||||
for i = start, 1, -1 do
|
|
||||||
local line = lines[i]
|
|
||||||
local tname = line:match("^%s*it%s*%(%s*['\"`]([^'\"`]+)['\"`]")
|
|
||||||
if tname then
|
|
||||||
local full = build_full_name(lines, i, tname)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
return { file = file, cwd = root, test_name = tname, full_name = full, kind = "it" }
|
|
||||||
end
|
|
||||||
local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]")
|
|
||||||
if dname then
|
|
||||||
local full = build_full_name(lines, i, dname)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
return { file = file, cwd = root, test_name = dname, full_name = full, kind = "describe" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil, "no test call found"
|
|
||||||
end
|
|
||||||
|
|
||||||
local function ex_plenary_busted_file(file, filter_name)
|
|
||||||
local ex = "PlenaryBustedFile " .. vim.fn.fnameescape(file)
|
|
||||||
if filter_name and filter_name ~= "" then
|
|
||||||
local f = escape_for_ex_double_quotes(filter_name)
|
|
||||||
ex = ex .. ' { busted_args = { "--filter", "' .. f .. '" } }'
|
|
||||||
end
|
|
||||||
return ex
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_command(spec)
|
|
||||||
local root = spec and spec.cwd or vim.loop.cwd()
|
|
||||||
local minit = minimal_init_for(root)
|
|
||||||
|
|
||||||
local cmd = {
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
minit,
|
|
||||||
"-c",
|
|
||||||
ex_plenary_busted_file(spec.file, spec.test_name),
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}
|
|
||||||
|
|
||||||
return { cmd = cmd, cwd = root }
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_file_command(bufnr)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
if not file or file == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
local minit = minimal_init_for(root)
|
|
||||||
|
|
||||||
local cmd = {
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
minit,
|
|
||||||
"-c",
|
|
||||||
ex_plenary_busted_file(file, nil),
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}
|
|
||||||
|
|
||||||
return { cmd = cmd, cwd = root }
|
|
||||||
end
|
|
||||||
|
|
||||||
function runner.build_all_command(bufnr)
|
|
||||||
local file = util.get_buf_path(bufnr)
|
|
||||||
local root = find_repo_root(file)
|
|
||||||
local minit = minimal_init_for(root)
|
|
||||||
|
|
||||||
local cmd = {
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
minit,
|
|
||||||
"-c",
|
|
||||||
"PlenaryBustedDirectory tests",
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}
|
|
||||||
|
|
||||||
return { cmd = cmd, cwd = root }
|
|
||||||
end
|
|
||||||
|
|
||||||
return runner
|
|
||||||
@@ -23,6 +23,10 @@ vim.api.nvim_create_user_command("TSamLast", function()
|
|||||||
require("test-samurai").test_last()
|
require("test-samurai").test_last()
|
||||||
end, { desc = "test-samurai: rerun last test command" })
|
end, { desc = "test-samurai: rerun last test command" })
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("TSamFailedOnly", function()
|
||||||
|
require("test-samurai").test_failed_only()
|
||||||
|
end, { desc = "test-samurai: rerun failed tests from last scoped run" })
|
||||||
|
|
||||||
vim.keymap.set("n", "<leader>tn", function()
|
vim.keymap.set("n", "<leader>tn", function()
|
||||||
require("test-samurai").test_nearest()
|
require("test-samurai").test_nearest()
|
||||||
end, { desc = "test-samurai: run nearest test" })
|
end, { desc = "test-samurai: run nearest test" })
|
||||||
@@ -42,3 +46,7 @@ end, { desc = "test-samurai: run all tests in project (per runner)" })
|
|||||||
vim.keymap.set("n", "<leader>tl", function()
|
vim.keymap.set("n", "<leader>tl", function()
|
||||||
require("test-samurai").test_last()
|
require("test-samurai").test_last()
|
||||||
end, { desc = "test-samurai: rerun last test command" })
|
end, { desc = "test-samurai: rerun last test command" })
|
||||||
|
|
||||||
|
vim.keymap.set("n", "<leader>te", function()
|
||||||
|
require("test-samurai").test_failed_only()
|
||||||
|
end, { desc = "test-samurai: rerun failed tests from last scoped run" })
|
||||||
|
|||||||
15
tests/test_samurai_commands_spec.lua
Normal file
15
tests/test_samurai_commands_spec.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
describe("test-samurai commands", function()
|
||||||
|
before_each(function()
|
||||||
|
vim.g.loaded_test_samurai_plugin = nil
|
||||||
|
vim.cmd("runtime plugin/test-samurai.lua")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("registers TSamFailedOnly command", function()
|
||||||
|
assert.equals(2, vim.fn.exists(":TSamFailedOnly"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("registers <leader>te keymap", function()
|
||||||
|
local map = vim.fn.maparg("<leader>te", "n")
|
||||||
|
assert.is_true(map ~= nil and map ~= "")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
225
tests/test_samurai_failed_only_spec.lua
Normal file
225
tests/test_samurai_failed_only_spec.lua
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
local test_samurai = require("test-samurai")
|
||||||
|
local core = require("test-samurai.core")
|
||||||
|
|
||||||
|
local function mkbuf(path, ft, lines)
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_name(bufnr, path)
|
||||||
|
vim.bo[bufnr].filetype = ft
|
||||||
|
if lines then
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||||
|
end
|
||||||
|
return bufnr
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stub_jobstart(opts_config)
|
||||||
|
local calls = {}
|
||||||
|
local orig = vim.fn.jobstart
|
||||||
|
local idx = 0
|
||||||
|
local config = opts_config or {}
|
||||||
|
vim.fn.jobstart = function(cmd, opts)
|
||||||
|
idx = idx + 1
|
||||||
|
table.insert(calls, { cmd = cmd, opts = opts })
|
||||||
|
local code = 0
|
||||||
|
if type(config.exit_codes) == "table" then
|
||||||
|
code = config.exit_codes[idx] or 0
|
||||||
|
elseif type(config.exit_codes) == "number" then
|
||||||
|
code = config.exit_codes
|
||||||
|
end
|
||||||
|
local out = config.stdout and config.stdout[idx] or nil
|
||||||
|
if out and opts and opts.on_stdout then
|
||||||
|
if type(out) == "string" then
|
||||||
|
out = { out }
|
||||||
|
end
|
||||||
|
opts.on_stdout(1, out, nil)
|
||||||
|
end
|
||||||
|
local err = config.stderr and config.stderr[idx] or nil
|
||||||
|
if err and opts and opts.on_stderr then
|
||||||
|
if type(err) == "string" then
|
||||||
|
err = { err }
|
||||||
|
end
|
||||||
|
opts.on_stderr(1, err, nil)
|
||||||
|
end
|
||||||
|
if opts and opts.on_exit then
|
||||||
|
opts.on_exit(1, code, nil)
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
return calls, orig
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("TSamFailedOnly", function()
|
||||||
|
before_each(function()
|
||||||
|
test_samurai.setup()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("reruns failed jest tests with --onlyFailures", function()
|
||||||
|
local json = vim.json.encode({
|
||||||
|
testResults = {
|
||||||
|
{
|
||||||
|
assertionResults = {
|
||||||
|
{ status = "passed", title = "inner 1", fullName = "outer inner 1" },
|
||||||
|
{ status = "failed", title = "inner 2", fullName = "outer inner 2" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local calls, orig_jobstart = stub_jobstart({
|
||||||
|
exit_codes = { 1, 0 },
|
||||||
|
stdout = { { json } },
|
||||||
|
})
|
||||||
|
|
||||||
|
local bufnr = mkbuf("/tmp/project/foo_failed_only.test.ts", "typescript", {
|
||||||
|
'describe("outer", function() {',
|
||||||
|
' it("inner 1", function() {',
|
||||||
|
" -- inside 1",
|
||||||
|
" })",
|
||||||
|
"",
|
||||||
|
' it("inner 2", function() {',
|
||||||
|
" -- inside 2",
|
||||||
|
" })",
|
||||||
|
"})",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
||||||
|
|
||||||
|
core.run_nearest()
|
||||||
|
core.run_failed_only()
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
assert.equals(2, #calls)
|
||||||
|
assert.are.same(
|
||||||
|
{ "npx", "jest", "--json", "/tmp/project/foo_failed_only.test.ts", "-t", "inner 2" },
|
||||||
|
calls[1].cmd
|
||||||
|
)
|
||||||
|
assert.are.same(
|
||||||
|
{ "npx", "jest", "--json", "-t", "outer inner 2", "/tmp/project/foo_failed_only.test.ts" },
|
||||||
|
calls[2].cmd
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("falls back to TSamLast when last run had no failures", function()
|
||||||
|
local json = vim.json.encode({
|
||||||
|
testResults = {
|
||||||
|
{
|
||||||
|
assertionResults = {
|
||||||
|
{ status = "passed", title = "inner 1", fullName = "outer inner 1" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local calls, orig_jobstart = stub_jobstart({
|
||||||
|
exit_codes = { 0, 0 },
|
||||||
|
stdout = { { json } },
|
||||||
|
})
|
||||||
|
|
||||||
|
local bufnr = mkbuf("/tmp/project/foo_failed_only_pass.test.ts", "typescript", {
|
||||||
|
'describe("outer", function() {',
|
||||||
|
' it("inner 1", function() {',
|
||||||
|
" -- inside 1",
|
||||||
|
" })",
|
||||||
|
"",
|
||||||
|
' it("inner 2", function() {',
|
||||||
|
" -- inside 2",
|
||||||
|
" })",
|
||||||
|
"})",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
||||||
|
|
||||||
|
core.run_nearest()
|
||||||
|
core.run_failed_only()
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
assert.equals(2, #calls)
|
||||||
|
assert.are.same(calls[1].cmd, calls[2].cmd)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("reruns failed go tests with -run regex", function()
|
||||||
|
local json_lines = {
|
||||||
|
vim.json.encode({ Action = "fail", Test = "TestFoo/first" }),
|
||||||
|
vim.json.encode({ Action = "fail", Test = "TestBar" }),
|
||||||
|
}
|
||||||
|
|
||||||
|
local calls, orig_jobstart = stub_jobstart({
|
||||||
|
exit_codes = { 1, 0 },
|
||||||
|
stdout = { json_lines },
|
||||||
|
})
|
||||||
|
|
||||||
|
local bufnr = mkbuf("/tmp/project/foo_failed_only_test.go", "go", {
|
||||||
|
"package main",
|
||||||
|
"import \"testing\"",
|
||||||
|
"",
|
||||||
|
"func TestFoo(t *testing.T) {",
|
||||||
|
" t.Run(\"first\", func(t *testing.T) {",
|
||||||
|
" -- inside first",
|
||||||
|
" })",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"func TestBar(t *testing.T) {",
|
||||||
|
" -- inside bar",
|
||||||
|
"}",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
|
||||||
|
core.run_all()
|
||||||
|
core.run_failed_only()
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
assert.equals(2, #calls)
|
||||||
|
assert.are.same({ "go", "test", "-json", "./..." }, calls[1].cmd)
|
||||||
|
assert.are.same({ "go", "test", "-json", "./...", "-run", "^(TestFoo/first|TestBar)$" }, calls[2].cmd)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("does not affect TSamLast history", function()
|
||||||
|
local json = vim.json.encode({
|
||||||
|
testResults = {
|
||||||
|
{
|
||||||
|
assertionResults = {
|
||||||
|
{ status = "failed", title = "inner 2", fullName = "outer inner 2" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local calls, orig_jobstart = stub_jobstart({
|
||||||
|
exit_codes = { 1, 1, 1 },
|
||||||
|
stdout = { { json } },
|
||||||
|
})
|
||||||
|
|
||||||
|
local bufnr = mkbuf("/tmp/project/foo_failed_only_last.test.ts", "typescript", {
|
||||||
|
'describe("outer", function() {',
|
||||||
|
' it("inner 1", function() {',
|
||||||
|
" -- inside 1",
|
||||||
|
" })",
|
||||||
|
"",
|
||||||
|
' it("inner 2", function() {',
|
||||||
|
" -- inside 2",
|
||||||
|
" })",
|
||||||
|
"})",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
||||||
|
|
||||||
|
core.run_nearest()
|
||||||
|
core.run_failed_only()
|
||||||
|
core.run_last()
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
assert.equals(3, #calls)
|
||||||
|
assert.are.same(calls[1].cmd, calls[3].cmd)
|
||||||
|
assert.are.same(
|
||||||
|
{ "npx", "jest", "--json", "-t", "outer inner 2", "/tmp/project/foo_failed_only_last.test.ts" },
|
||||||
|
calls[2].cmd
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@@ -95,7 +95,7 @@ describe("test-samurai go runner", function()
|
|||||||
|
|
||||||
local cmd_spec_sub = go_runner.build_command(spec_sub)
|
local cmd_spec_sub = go_runner.build_command(spec_sub)
|
||||||
assert.are.same(
|
assert.are.same(
|
||||||
{ "go", "test", "-v", "./pkg", "-run", "^TestFoo/first$" },
|
{ "go", "test", "-json", "./pkg", "-run", "^TestFoo/first$" },
|
||||||
cmd_spec_sub.cmd
|
cmd_spec_sub.cmd
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ describe("test-samurai go runner", function()
|
|||||||
|
|
||||||
local cmd_spec_func = go_runner.build_command(spec_func)
|
local cmd_spec_func = go_runner.build_command(spec_func)
|
||||||
assert.are.same(
|
assert.are.same(
|
||||||
{ "go", "test", "-v", "./", "-run", "^TestFoo($|/)" },
|
{ "go", "test", "-json", "./", "-run", "^TestFoo($|/)" },
|
||||||
cmd_spec_func.cmd
|
cmd_spec_func.cmd
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
local jest = require("test-samurai.runners.js-jest")
|
local jest = require("test-samurai.runners.js-jest")
|
||||||
local mocha = require("test-samurai.runners.js-mocha")
|
local mocha = require("test-samurai.runners.js-mocha")
|
||||||
|
local vitest = require("test-samurai.runners.js-vitest")
|
||||||
local util = require("test-samurai.util")
|
local util = require("test-samurai.util")
|
||||||
|
|
||||||
describe("test-samurai js runner (jest)", function()
|
describe("test-samurai js runner (jest)", function()
|
||||||
@@ -51,7 +52,7 @@ describe("test-samurai js runner (jest)", function()
|
|||||||
assert.is_true(spec.cwd:match("tmp$") ~= nil)
|
assert.is_true(spec.cwd:match("tmp$") ~= nil)
|
||||||
|
|
||||||
local cmd_spec = jest.build_command(spec)
|
local cmd_spec = jest.build_command(spec)
|
||||||
assert.are.same({ "npx", "jest", spec.file, "-t", "inner 2" }, cmd_spec.cmd)
|
assert.are.same({ "npx", "jest", "--json", spec.file, "-t", "inner 2" }, cmd_spec.cmd)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("returns describe block when cursor is between it() calls", function()
|
it("returns describe block when cursor is between it() calls", function()
|
||||||
@@ -133,7 +134,7 @@ describe("test-samurai js runner (mocha)", function()
|
|||||||
local cmd_spec = mocha.build_command(spec)
|
local cmd_spec = mocha.build_command(spec)
|
||||||
|
|
||||||
assert.are.same(
|
assert.are.same(
|
||||||
{ "npx", "mocha", "--fgrep", "outer inner 2", spec.file },
|
{ "npx", "mocha", "--reporter", "json-stream", "--fgrep", "outer inner 2", spec.file },
|
||||||
cmd_spec.cmd
|
cmd_spec.cmd
|
||||||
)
|
)
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||||
@@ -154,7 +155,47 @@ describe("test-samurai js runner (mocha)", function()
|
|||||||
util.find_root = orig_find_root
|
util.find_root = orig_find_root
|
||||||
|
|
||||||
assert.are.same(
|
assert.are.same(
|
||||||
{ "npx", "mocha", "test/**/*.test.js" },
|
{ "npx", "mocha", "--reporter", "json-stream", "test/**/*.test.js" },
|
||||||
|
cmd_spec.cmd
|
||||||
|
)
|
||||||
|
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("test-samurai js runner (vitest)", function()
|
||||||
|
it("builds vitest command with tap-flat reporter", function()
|
||||||
|
local spec = {
|
||||||
|
file = "/tmp/project/test/foo_nearest.test.ts",
|
||||||
|
cwd = "/tmp/project",
|
||||||
|
test_name = "inner 2",
|
||||||
|
full_name = "outer inner 2",
|
||||||
|
}
|
||||||
|
|
||||||
|
local cmd_spec = vitest.build_command(spec)
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
{ "npx", "vitest", "--reporter", "tap-flat", spec.file, "-t", "inner 2" },
|
||||||
|
cmd_spec.cmd
|
||||||
|
)
|
||||||
|
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("builds vitest all command with tap-flat reporter", function()
|
||||||
|
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/test/foo_all.test.ts")
|
||||||
|
vim.bo[bufnr].filetype = "typescript"
|
||||||
|
|
||||||
|
local orig_find_root = util.find_root
|
||||||
|
util.find_root = function(path, markers)
|
||||||
|
return "/tmp/project"
|
||||||
|
end
|
||||||
|
|
||||||
|
local cmd_spec = vitest.build_all_command(bufnr)
|
||||||
|
|
||||||
|
util.find_root = orig_find_root
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
{ "npx", "vitest", "--reporter", "tap-flat" },
|
||||||
cmd_spec.cmd
|
cmd_spec.cmd
|
||||||
)
|
)
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ describe("TSamLast", function()
|
|||||||
vim.fn.jobstart = orig_jobstart
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
assert.equals(2, #calls)
|
||||||
assert.are.same({ "go", "test", "-v", "./", "-run", "^TestFoo/first$" }, calls[1].cmd)
|
assert.are.same({ "go", "test", "-json", "./", "-run", "^TestFoo/first$" }, calls[1].cmd)
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
assert.are.same(calls[1].cmd, calls[2].cmd)
|
||||||
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
||||||
end)
|
end)
|
||||||
@@ -79,48 +79,10 @@ describe("TSamLast", function()
|
|||||||
|
|
||||||
assert.equals(2, #calls)
|
assert.equals(2, #calls)
|
||||||
assert.are.same(
|
assert.are.same(
|
||||||
{ "npx", "jest", "/tmp/project/foo_last.test.ts", "-t", "inner 2" },
|
{ "npx", "jest", "--json", "/tmp/project/foo_last.test.ts", "-t", "inner 2" },
|
||||||
calls[1].cmd
|
calls[1].cmd
|
||||||
)
|
)
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
assert.are.same(calls[1].cmd, calls[2].cmd)
|
||||||
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("reruns last Lua command", function()
|
|
||||||
local calls, orig_jobstart = capture_jobstart()
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/foo_last_spec.lua", "lua", {
|
|
||||||
"describe('outer', function()",
|
|
||||||
" it('inner 1', function()",
|
|
||||||
" local x = 1",
|
|
||||||
" end)",
|
|
||||||
"",
|
|
||||||
" it('inner 2', function()",
|
|
||||||
" local y = 2",
|
|
||||||
" end)",
|
|
||||||
"end)",
|
|
||||||
})
|
|
||||||
|
|
||||||
vim.api.nvim_set_current_buf(bufnr)
|
|
||||||
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
|
||||||
|
|
||||||
core.run_nearest()
|
|
||||||
core.run_last()
|
|
||||||
|
|
||||||
vim.fn.jobstart = orig_jobstart
|
|
||||||
|
|
||||||
assert.equals(2, #calls)
|
|
||||||
assert.are.same({
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
"/tmp/project/tests/minimal_init.lua",
|
|
||||||
"-c",
|
|
||||||
'PlenaryBustedFile /tmp/project/foo_last_spec.lua { busted_args = { "--filter", "inner 2" } }',
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}, calls[1].cmd)
|
|
||||||
assert.are.same(calls[1].cmd, calls[2].cmd)
|
|
||||||
assert.equals(calls[1].opts.cwd, calls[2].opts.cwd)
|
|
||||||
end)
|
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
local test_samurai = require("test-samurai")
|
|
||||||
local lua_runner = require("test-samurai.runners.lua-plenary")
|
|
||||||
local util = require("test-samurai.util")
|
|
||||||
|
|
||||||
local function mkbuf(path, ft, lines)
|
|
||||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_name(bufnr, path)
|
|
||||||
vim.bo[bufnr].filetype = ft
|
|
||||||
if lines then
|
|
||||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
|
||||||
end
|
|
||||||
return bufnr
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("test-samurai lua runner (plenary)", function()
|
|
||||||
it("detects lua spec files by suffix", function()
|
|
||||||
local bufnr = mkbuf("/tmp/test_samurai_lua_spec_unique_1_spec.lua", "lua")
|
|
||||||
assert.is_true(lua_runner.is_test_file(bufnr))
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("finds nearest it() when cursor is inside it block and builds filtered command", function()
|
|
||||||
local bufnr = mkbuf("/tmp/project/tests/test_samurai_lua_nearest_unique_2_spec.lua", "lua", {
|
|
||||||
"describe('outer', function()",
|
|
||||||
" it('inner 1', function()",
|
|
||||||
" local x = 1",
|
|
||||||
" end)",
|
|
||||||
"",
|
|
||||||
" it('inner 2', function()",
|
|
||||||
" local y = 2",
|
|
||||||
" end)",
|
|
||||||
"end)",
|
|
||||||
})
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function()
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local spec, err = lua_runner.find_nearest(bufnr, 6, 0)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("inner 2", spec.test_name)
|
|
||||||
assert.equals("outer inner 2", spec.full_name)
|
|
||||||
assert.equals("/tmp/project", spec.cwd)
|
|
||||||
|
|
||||||
local cmd_spec = lua_runner.build_command(spec)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
assert.are.same({
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
"/tmp/project/tests/minimal_init.lua",
|
|
||||||
"-c",
|
|
||||||
'PlenaryBustedFile ' .. spec.file .. ' { busted_args = { "--filter", "inner 2" } }',
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}, cmd_spec.cmd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("returns describe block when cursor is between it() calls", function()
|
|
||||||
local bufnr = mkbuf("/tmp/project/tests/test_samurai_lua_between_unique_3_spec.lua", "lua", {
|
|
||||||
"describe('outer', function()",
|
|
||||||
" it('inner 1', function()",
|
|
||||||
" local x = 1",
|
|
||||||
" end)",
|
|
||||||
"",
|
|
||||||
" it('inner 2', function()",
|
|
||||||
" local y = 2",
|
|
||||||
" end)",
|
|
||||||
"end)",
|
|
||||||
})
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function()
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local spec, err = lua_runner.find_nearest(bufnr, 4, 0)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.is_nil(err)
|
|
||||||
assert.is_not_nil(spec)
|
|
||||||
assert.equals("outer", spec.test_name)
|
|
||||||
assert.equals("outer", spec.full_name)
|
|
||||||
assert.equals("describe", spec.kind)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("builds all command via PlenaryBustedDirectory", function()
|
|
||||||
local bufnr = mkbuf("/tmp/project/tests/test_samurai_core_spec_unique_4_spec.lua", "lua")
|
|
||||||
|
|
||||||
local orig_find_root = util.find_root
|
|
||||||
util.find_root = function()
|
|
||||||
return "/tmp/project"
|
|
||||||
end
|
|
||||||
|
|
||||||
local cmd_spec = lua_runner.build_all_command(bufnr)
|
|
||||||
|
|
||||||
util.find_root = orig_find_root
|
|
||||||
|
|
||||||
assert.are.same({
|
|
||||||
"nvim",
|
|
||||||
"--headless",
|
|
||||||
"-u",
|
|
||||||
"/tmp/project/tests/minimal_init.lua",
|
|
||||||
"-c",
|
|
||||||
"PlenaryBustedDirectory tests",
|
|
||||||
"-c",
|
|
||||||
"qa",
|
|
||||||
}, cmd_spec.cmd)
|
|
||||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("core selects lua runner for *_spec.lua buffers", function()
|
|
||||||
test_samurai.setup({
|
|
||||||
runner_modules = {
|
|
||||||
"test-samurai.runners.lua-plenary",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
local bufnr = mkbuf("/tmp/project/tests/test_samurai_core_spec_unique_5_spec.lua", "lua")
|
|
||||||
|
|
||||||
local runner = require("test-samurai.core").get_runner_for_buf(bufnr)
|
|
||||||
assert.is_not_nil(runner)
|
|
||||||
assert.equals("lua-plenary", runner.name)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
@@ -76,4 +76,157 @@ describe("test-samurai public API", function()
|
|||||||
|
|
||||||
assert.is_true(called)
|
assert.is_true(called)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("delegates test_failed_only to core.run_failed_only", function()
|
||||||
|
local called = false
|
||||||
|
local orig = core.run_failed_only
|
||||||
|
|
||||||
|
core.run_failed_only = function()
|
||||||
|
called = true
|
||||||
|
end
|
||||||
|
|
||||||
|
test_samurai.test_failed_only()
|
||||||
|
|
||||||
|
core.run_failed_only = orig
|
||||||
|
|
||||||
|
assert.is_true(called)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("test-samurai output formatting", function()
|
||||||
|
before_each(function()
|
||||||
|
test_samurai.setup()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("formats JSON output as PASS/FAIL lines", function()
|
||||||
|
local json = vim.json.encode({
|
||||||
|
testResults = {
|
||||||
|
{
|
||||||
|
assertionResults = {
|
||||||
|
{ status = "passed", title = "inner 1", fullName = "outer inner 1" },
|
||||||
|
{ status = "skipped", title = "inner skip", fullName = "outer inner skip" },
|
||||||
|
{ status = "failed", title = "inner 2", fullName = "outer inner 2" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local orig_jobstart = vim.fn.jobstart
|
||||||
|
vim.fn.jobstart = function(_cmd, opts)
|
||||||
|
if opts and opts.on_stdout then
|
||||||
|
opts.on_stdout(1, { json }, 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_format.test.ts")
|
||||||
|
vim.bo[bufnr].filetype = "typescript"
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
||||||
|
'describe("outer", function() {',
|
||||||
|
' it("inner 1", function() {',
|
||||||
|
" -- inside 1",
|
||||||
|
" })",
|
||||||
|
"",
|
||||||
|
' it("inner 2", function() {',
|
||||||
|
" -- inside 2",
|
||||||
|
" })",
|
||||||
|
"})",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
||||||
|
|
||||||
|
core.run_nearest()
|
||||||
|
|
||||||
|
local out_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
local has_pass = false
|
||||||
|
local has_skip = false
|
||||||
|
local has_fail = false
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line == "[ PASS ] - outer inner 1" then
|
||||||
|
has_pass = true
|
||||||
|
elseif line == "[ SKIP ] - outer inner skip" then
|
||||||
|
has_skip = true
|
||||||
|
elseif line == "[ FAIL ] - outer inner 2" then
|
||||||
|
has_fail = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert.is_true(has_pass)
|
||||||
|
assert.is_true(has_skip)
|
||||||
|
assert.is_true(has_fail)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("formats TAP output as PASS/FAIL lines", function()
|
||||||
|
test_samurai.setup({
|
||||||
|
runner_modules = {
|
||||||
|
"test-samurai.runners.js-vitest",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local orig_jobstart = vim.fn.jobstart
|
||||||
|
vim.fn.jobstart = function(_cmd, opts)
|
||||||
|
if opts and opts.on_stdout then
|
||||||
|
opts.on_stdout(1, {
|
||||||
|
"TAP version 13",
|
||||||
|
"1..2",
|
||||||
|
"ok 1 - outer inner 1 # time=1.00ms",
|
||||||
|
"ok 2 - outer inner skip # SKIP not now",
|
||||||
|
"not ok 2 - outer inner 2 # time=2.00ms",
|
||||||
|
}, 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_format.tap.test.ts")
|
||||||
|
vim.bo[bufnr].filetype = "typescript"
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {
|
||||||
|
'describe("outer", function() {',
|
||||||
|
' it("inner 1", function() {',
|
||||||
|
" -- inside 1",
|
||||||
|
" })",
|
||||||
|
"",
|
||||||
|
' it("inner 2", function() {',
|
||||||
|
" -- inside 2",
|
||||||
|
" })",
|
||||||
|
"})",
|
||||||
|
})
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_buf(bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 7, 0 })
|
||||||
|
|
||||||
|
core.run_nearest()
|
||||||
|
|
||||||
|
local out_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false)
|
||||||
|
|
||||||
|
vim.fn.jobstart = orig_jobstart
|
||||||
|
|
||||||
|
local has_pass = false
|
||||||
|
local has_skip = false
|
||||||
|
local has_fail = false
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
if line == "[ PASS ] - outer inner 1" then
|
||||||
|
has_pass = true
|
||||||
|
elseif line == "[ SKIP ] - outer inner skip" then
|
||||||
|
has_skip = true
|
||||||
|
elseif line == "[ FAIL ] - outer inner 2" then
|
||||||
|
has_fail = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert.is_true(has_pass)
|
||||||
|
assert.is_true(has_skip)
|
||||||
|
assert.is_true(has_fail)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user