diff --git a/lua/test-samurai/config.lua b/lua/test-samurai/config.lua index e13eddd..e5b7633 100644 --- a/lua/test-samurai/config.lua +++ b/lua/test-samurai/config.lua @@ -1,12 +1,7 @@ local M = {} local defaults = { - runner_modules = { - "test-samurai.runners.go", - "test-samurai.runners.js-jest", - "test-samurai.runners.js-mocha", - "test-samurai.runners.js-vitest", - }, + runner_modules = {}, } local current = vim.deepcopy(defaults) diff --git a/lua/test-samurai/core.lua b/lua/test-samurai/core.lua index 7b68fc1..2ca42dd 100644 --- a/lua/test-samurai/core.lua +++ b/lua/test-samurai/core.lua @@ -436,13 +436,6 @@ function M.get_runner_for_buf(bufnr) return nil end - if path:find(".test.", 1, true) or path:find(".spec.", 1, true) then - local ok, jsjest = pcall(require, "test-samurai.runners.js-jest") - if ok and type(jsjest) == "table" then - return jsjest - end - end - return nil end @@ -1915,7 +1908,7 @@ function M.run_nearest() local runner = M.get_runner_for_buf(bufnr) if not runner then - vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN) return end @@ -1959,7 +1952,7 @@ function M.run_file() local runner = M.get_runner_for_buf(bufnr) if not runner then - vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN) return end @@ -1992,7 +1985,7 @@ function M.run_all() local runner = M.get_runner_for_buf(bufnr) if not runner then - vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + vim.notify("[test-samurai] no runner installed for this kind of test", vim.log.levels.WARN) return end diff --git a/lua/test-samurai/runners/go.lua b/lua/test-samurai/runners/go.lua deleted file mode 100644 index 0f29e03..0000000 --- a/lua/test-samurai/runners/go.lua +++ /dev/null @@ -1,502 +0,0 @@ -local util = require("test-samurai.util") - -local runner = { - name = "go", -} - -local function find_block_end(lines, start_idx) - local depth = 0 - local started = false - for i = start_idx, #lines do - local line = lines[i] - for j = 1, #line do - local ch = line:sub(j, j) - if ch == "{" then - depth = depth + 1 - started = true - elseif ch == "}" then - if started then - depth = depth - 1 - if depth == 0 then - return i - 1 - end - end - end - end - end - return #lines - 1 -end - -local function find_test_functions(lines) - local funcs = {} - for i, line in ipairs(lines) do - local name = line:match("^%s*func%s+([%w_]+)%s*%(") - if not name then - name = line:match("^%s*func%s+%([^)]-%)%s+([%w_]+)%s*%(") - end - if name and line:find("%*testing%.T") then - local start_0 = i - 1 - local end_0 = find_block_end(lines, i) - table.insert(funcs, { - name = name, - start = start_0, - ["end"] = end_0, - }) - end - end - return funcs -end - -local function find_t_runs(lines, func) - local subtests = {} - for i = func.start + 1, func["end"] do - local line = lines[i + 1] - if line then - local name = line:match("t%.Run%(%s*['\"]([^'\"]+)['\"]") - if name then - local start_idx = i + 1 - local end_0 = find_block_end(lines, start_idx) - table.insert(subtests, { - name = name, - start = start_idx - 1, - ["end"] = end_0, - }) - end - end - end - return subtests -end - -local function escape_go_regex(s) - s = s or "" - return (s:gsub("([\\.^$|()%%[%]{}*+?%-])", "\\\\%1")) -end - -local function build_run_pattern(spec) - local name = spec.test_path or "" - local escaped = escape_go_regex(name) - if spec.scope == "function" then - return "^" .. escaped .. "($|/)" - else - return "^" .. escaped .. "$" - end -end - -local function build_pkg_arg(spec) - local file = spec.file - local cwd = spec.cwd - if not file or not cwd or file == "" or cwd == "" then - return "./..." - end - - local dir = vim.fs.dirname(file) - if dir == cwd then - return "./" - end - - if file:sub(1, #cwd) ~= cwd then - return "./..." - end - - local rel = dir:sub(#cwd + 2) - if not rel or rel == "" then - return "./" - end - - return "./" .. rel -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) - local path = util.get_buf_path(bufnr) - if not path or path == "" then - return false - end - return path:sub(-8) == "_test.go" -end - -function runner.find_nearest(bufnr, row, _col) - if not runner.is_test_file(bufnr) then - return nil, "not a Go test file" - end - - local lines = util.get_buf_lines(bufnr) - local funcs = find_test_functions(lines) - - local current - for _, f in ipairs(funcs) do - if row >= f.start and row <= f["end"] then - current = f - break - end - end - - if not current then - return nil, "cursor not inside a test function" - end - - local subtests = find_t_runs(lines, current) - local inside_sub - for _, sub in ipairs(subtests) do - if row >= sub.start and row <= sub["end"] then - inside_sub = sub - break - end - end - - local path = util.get_buf_path(bufnr) - local root = util.find_root(path, { "go.mod", ".git" }) - - if inside_sub then - local full = current.name .. "/" .. inside_sub.name - return { - file = path, - cwd = root, - test_path = full, - scope = "subtest", - func = current.name, - subtest = inside_sub.name, - } - else - return { - file = path, - cwd = root, - test_path = current.name, - scope = "function", - func = current.name, - } - end -end - -function runner.build_command(spec) - local pattern = build_run_pattern(spec) - local pkg = build_pkg_arg(spec) - local cmd = { "go", "test", "-json", pkg, "-run", pattern } - return { - cmd = cmd, - cwd = spec.cwd, - } -end - -function runner.build_file_command(bufnr) - local path = util.get_buf_path(bufnr) - if not path or path == "" then - return nil - end - local root = util.find_root(path, { "go.mod", ".git" }) - if not root or root == "" then - root = vim.loop.cwd() - end - local spec = { file = path, cwd = root } - local pkg = build_pkg_arg(spec) - local cmd = { "go", "test", "-json", pkg } - local lines = util.get_buf_lines(bufnr) - local funcs = find_test_functions(lines) - local names = {} - for _, fn in ipairs(funcs) do - table.insert(names, fn.name) - end - names = collect_unique(names) - if #names > 0 then - local pattern_parts = {} - for _, name in ipairs(names) do - table.insert(pattern_parts, escape_go_regex(name)) - end - local pattern = "^(" .. table.concat(pattern_parts, "|") .. ")$" - table.insert(cmd, "-run") - table.insert(cmd, pattern) - end - return { - cmd = cmd, - cwd = root, - } -end - -function runner.build_all_command(bufnr) - local path = util.get_buf_path(bufnr) - local root - if path and path ~= "" then - root = util.find_root(path, { "go.mod", ".git" }) - end - if not root or root == "" then - root = vim.loop.cwd() - end - local cmd = { "go", "test", "-json", "./..." } - return { - cmd = cmd, - cwd = root, - } -end - -function runner.parse_results(output) - if not output or output == "" then - return { passes = {}, failures = {}, skips = {}, display = { passes = {}, failures = {}, skips = {} } } - end - local passes = {} - local failures = {} - local skips = {} - local display = { passes = {}, failures = {}, 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) - local short = data.Test:match("([^/]+)$") or data.Test - table.insert(display.passes, short) - elseif data.Action == "fail" then - table.insert(failures, data.Test) - local short = data.Test:match("([^/]+)$") or data.Test - table.insert(display.failures, short) - elseif data.Action == "skip" then - table.insert(skips, data.Test) - local short = data.Test:match("([^/]+)$") or data.Test - table.insert(display.skips, short) - end - end - end - end - return { - passes = collect_unique(passes), - failures = collect_unique(failures), - skips = collect_unique(skips), - display = display, - } -end - -local function split_output_lines(text) - if not text or text == "" then - return {} - end - local lines = vim.split(text, "\n", { plain = true }) - if #lines > 0 and lines[#lines] == "" then - table.remove(lines, #lines) - end - return lines -end - -local function normalize_go_name(name) - if not name or name == "" then - return nil - end - return (name:gsub("%s+", "_")) -end - -local function add_location(target, key, file, line, label) - if not key or key == "" or not file or file == "" or not line then - return - end - local text = label or key - if not target[key] then - target[key] = {} - end - table.insert(target[key], { - filename = file, - lnum = line, - col = 1, - text = text, - }) -end - -local function collect_file_locations(file, target) - local ok, lines = pcall(vim.fn.readfile, file) - if not ok or type(lines) ~= "table" then - return - end - local funcs = find_test_functions(lines) - for _, fn in ipairs(funcs) do - add_location(target, fn.name, file, fn.start + 1, fn.name) - local normalized = normalize_go_name(fn.name) - if normalized and normalized ~= fn.name then - add_location(target, normalized, file, fn.start + 1, fn.name) - end - for _, sub in ipairs(find_t_runs(lines, fn)) do - local full = fn.name .. "/" .. sub.name - add_location(target, full, file, sub.start + 1, full) - local normalized_full = normalize_go_name(full) - if normalized_full and normalized_full ~= full then - add_location(target, normalized_full, file, sub.start + 1, full) - end - end - end -end - -local function collect_go_test_files(root) - if not root or root == "" then - root = vim.loop.cwd() - end - local files = vim.fn.globpath(root, "**/*_test.go", false, true) - if type(files) ~= "table" then - return {} - end - return files -end - -function runner.parse_test_output(output) - local out = {} - if not output or output == "" then - return out - end - for line in output:gmatch("[^\n]+") do - local ok, data = pcall(vim.json.decode, line) - if ok and type(data) == "table" and data.Action == "output" and data.Test and data.Output then - if not out[data.Test] then - out[data.Test] = {} - end - for _, item in ipairs(split_output_lines(data.Output)) do - table.insert(out[data.Test], item) - end - end - end - return out -end - - function runner.output_parser() - local seen_pass = {} - local seen_fail = {} - local failures = {} - local passes = {} - local skips = {} - local display = { passes = {}, failures = {}, 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 - local short = name:match("([^/]+)$") or name - if data.Action == "pass" and not seen_pass[name] then - seen_pass[name] = true - table.insert(passes, name) - table.insert(display.passes, short) - return { - passes = { name }, - failures = {}, - skips = {}, - display = { passes = { short }, 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) - table.insert(display.failures, short) - return { - passes = {}, - failures = { name }, - skips = {}, - display = { passes = {}, failures = { short }, skips = {} }, - failures_all = vim.deepcopy(failures), - } - elseif data.Action == "skip" and not seen_pass[name] then - seen_pass[name] = true - table.insert(skips, name) - table.insert(display.skips, short) - return { - passes = {}, - failures = {}, - skips = { name }, - display = { passes = {}, failures = {}, skips = { short } }, - 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 - -function runner.collect_failed_locations(failures, command, scope_kind) - if type(failures) ~= "table" or #failures == 0 then - return {} - end - local files = {} - if scope_kind == "all" then - files = collect_go_test_files(command and command.cwd or nil) - elseif command and command.file then - files = { command.file } - end - if #files == 0 then - return {} - end - local locations = {} - for _, file in ipairs(files) do - collect_file_locations(file, locations) - end - local items = {} - local seen = {} - local function add_locations(name, locs) - for _, loc in ipairs(locs or {}) do - local key = string.format("%s:%d:%s", loc.filename or "", loc.lnum or 0, loc.text or name or "") - if not seen[key] then - seen[key] = true - table.insert(items, loc) - end - end - end - for _, name in ipairs(failures) do - local direct = locations[name] - if direct then - add_locations(name, direct) - elseif not name:find("/", 1, true) then - for full, locs in pairs(locations) do - if full:sub(-#name - 1) == "/" .. name then - add_locations(full, locs) - end - end - end - end - return items -end - -return runner diff --git a/lua/test-samurai/runners/js-jest.lua b/lua/test-samurai/runners/js-jest.lua deleted file mode 100644 index b26a3f6..0000000 --- a/lua/test-samurai/runners/js-jest.lua +++ /dev/null @@ -1,8 +0,0 @@ -local js = require("test-samurai.runners.js") - -return js.new({ - name = "js-jest", - framework = "jest", - command = { "npx", "jest" }, - json_args = { "--json", "--verbose" }, -}) diff --git a/lua/test-samurai/runners/js-mocha.lua b/lua/test-samurai/runners/js-mocha.lua deleted file mode 100644 index d9813bf..0000000 --- a/lua/test-samurai/runners/js-mocha.lua +++ /dev/null @@ -1,9 +0,0 @@ -local js = require("test-samurai.runners.js") - -return js.new({ - name = "js-mocha", - framework = "mocha", - command = { "npx", "mocha" }, - all_glob = "test/**/*.test.js", - json_args = { "--reporter", "json-stream" }, -}) diff --git a/lua/test-samurai/runners/js-vitest.lua b/lua/test-samurai/runners/js-vitest.lua deleted file mode 100644 index 4d17b7f..0000000 --- a/lua/test-samurai/runners/js-vitest.lua +++ /dev/null @@ -1,8 +0,0 @@ -local js = require("test-samurai.runners.js") - -return js.new({ - name = "js-vitest", - framework = "vitest", - command = { "npx", "vitest" }, - json_args = { "--reporter", "tap-flat" }, -}) diff --git a/lua/test-samurai/runners/js.lua b/lua/test-samurai/runners/js.lua deleted file mode 100644 index 7f1cfc7..0000000 --- a/lua/test-samurai/runners/js.lua +++ /dev/null @@ -1,1241 +0,0 @@ -local util = require("test-samurai.util") - -local M = {} - -local default_filetypes = { - javascript = true, - javascriptreact = true, - typescript = true, - typescriptreact = true, -} - -local default_patterns = { ".test.", ".spec." } - -local function is_js_test_file(bufnr, filetypes, patterns) - local ft = vim.bo[bufnr].filetype - if not filetypes[ft] then - return false - end - local path = util.get_buf_path(bufnr) - if not path or path == "" then - return false - end - for _, pat in ipairs(patterns) do - if path:find(pat, 1, true) then - return true - end - end - 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 - for i = start_idx, #lines do - local line = lines[i] - for j = 1, #line do - local ch = line:sub(j, j) - if ch == "{" then - depth = depth + 1 - started = true - elseif ch == "}" then - if started then - depth = depth - 1 - if depth == 0 then - return i - 1 - end - end - end - end - end - return #lines - 1 -end - -local jest_config_files = { "jest.config.js", "jest.config.ts" } - -local function find_jest_root(file) - if not file or file == "" then - return util.find_root(file, { - "jest.config.js", - "jest.config.ts", - "vitest.config.ts", - "vitest.config.js", - "package.json", - "node_modules", - }) - end - - local dir = vim.fs.dirname(file) - local found = vim.fs.find(jest_config_files, { path = dir, upward = true }) - if found and #found > 0 then - local cfg = found[1] - local sep = package.config:sub(1, 1) - local marker = sep .. "test" .. sep .. ".bin" .. sep - local idx = cfg:find(marker, 1, true) - if idx then - local root = cfg:sub(1, idx - 1) - if root == "" then - root = sep - end - return root - end - return vim.fs.dirname(cfg) - end - - return util.find_root(file, { - "jest.config.js", - "jest.config.ts", - "vitest.config.ts", - "vitest.config.js", - "package.json", - "node_modules", - }) -end - -local function match_test_call(line) - local call, name = line:match("^%s*(it)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if call then - return call, name - end - call, name = line:match("^%s*(test)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if call then - return call, name - end - call, name = line:match("^%s*(describe)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if call and name then - return call, name - end - return nil, nil -end - -local function collect_js_structs(lines) - local describes = {} - local tests = {} - - for i, line in ipairs(lines) do - local dcall, dname = line:match("^%s*(describe)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if dcall and dname then - local start0 = i - 1 - local end0 = find_block_end(lines, i) - table.insert(describes, { - kind = dcall, - name = dname, - start = start0, - ["end"] = end0, - }) - else - local tcall, tname = line:match("^%s*(it)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if not tcall then - tcall, tname = line:match("^%s*(test)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - end - if tcall and tname then - local start0 = i - 1 - local end0 = find_block_end(lines, i) - table.insert(tests, { - kind = tcall, - name = tname, - start = start0, - ["end"] = end0, - }) - end - end - end - - return describes, tests -end - -local build_full_name -local shorten_js_name -local normalize_js_name - -local function add_location(target, name, file, line) - if not name or name == "" or not file or file == "" or not line then - return - end - if not target[name] then - target[name] = {} - end - table.insert(target[name], { - filename = file, - lnum = line, - col = 1, - text = name, - }) -end - -local function collect_js_locations(lines, file, target) - local describes, tests = collect_js_structs(lines) - for _, t in ipairs(tests) do - local full = build_full_name(lines, t.start + 1, t.name) - add_location(target, full, file, t.start + 1) - add_location(target, t.name, file, t.start + 1) - local short = shorten_js_name and shorten_js_name(full) or nil - if short and short ~= full then - add_location(target, short, file, t.start + 1) - end - end - for _, d in ipairs(describes) do - local full = build_full_name(lines, d.start + 1, d.name) - add_location(target, full, file, d.start + 1) - add_location(target, d.name, file, d.start + 1) - local short = shorten_js_name and shorten_js_name(full) or nil - if short and short ~= full then - add_location(target, short, file, d.start + 1) - end - end -end - -local function collect_js_test_files(root, patterns) - if not root or root == "" then - root = vim.loop.cwd() - end - local files = {} - local seen = {} - for _, pat in ipairs(patterns or {}) do - local glob = "**/*" .. pat .. "*" - local hits = vim.fn.globpath(root, glob, false, true) - if type(hits) == "table" then - for _, file in ipairs(hits) do - if not seen[file] then - seen[file] = true - table.insert(files, file) - end - end - end - end - return files -end - -build_full_name = function(lines, idx, leaf_name) - local parts = { leaf_name } - for i = idx - 1, 1, -1 do - local line = lines[i] - local call, name = line:match("^%s*(describe)%s*%(%s*['\"`]([^'\"`]+)['\"`]") - if call and name then - table.insert(parts, 1, name) - end - end - return table.concat(parts, " ") -end - -local function find_nearest_test(bufnr, row) - local lines = util.get_buf_lines(bufnr) - local describes, tests = collect_js_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) - return { - kind = t.kind, - name = t.name, - full_name = full, - line = t.start, - } - 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) - return { - kind = "describe", - name = best_describe.name, - full_name = full, - line = best_describe.start, - } - 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 call, name = match_test_call(line) - if call and name then - local full = build_full_name(lines, i, name) - return { - kind = call, - name = name, - full_name = full, - line = i - 1, - } - end - end - - return nil -end - -function M.new(opts) - local cfg = opts or {} - local runner = {} - - runner.name = cfg.name or "js" - 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 - for _, ft in ipairs(cfg.filetypes) do - runner.filetypes[ft] = true - end - else - runner.filetypes = vim.deepcopy(default_filetypes) - end - - runner.patterns = cfg.patterns or default_patterns - - function runner.is_test_file(bufnr) - return is_js_test_file(bufnr, runner.filetypes, runner.patterns) - end - - function runner.find_nearest(bufnr, row, _col) - if not runner.is_test_file(bufnr) then - return nil, "not a JS/TS test file" - end - local hit = find_nearest_test(bufnr, row) - if not hit then - return nil, "no test call found" - end - local path = util.get_buf_path(bufnr) - local root - if runner.framework == "jest" then - root = find_jest_root(path) - else - root = util.find_root(path, { - "jest.config.js", - "jest.config.ts", - "vitest.config.ts", - "vitest.config.js", - "package.json", - "node_modules", - }) - end - return { - file = path, - cwd = root, - framework = runner.framework, - test_name = hit.name, - full_name = hit.full_name, - kind = hit.kind, - } - end - - 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) - return cmd - end - - 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) - table.insert(cmd, spec.file) - return cmd - end - - 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) - return cmd - end - - function runner.build_command(spec) - local fw = runner.framework - local cmd - if fw == "jest" then - cmd = build_jest(spec) - elseif fw == "mocha" then - cmd = build_mocha(spec) - elseif fw == "vitest" then - cmd = build_vitest(spec) - else - cmd = vim.deepcopy(runner.command) - table.insert(cmd, spec.file) - end - return { - cmd = cmd, - cwd = spec.cwd, - } - end - - function runner.build_file_command(bufnr) - local path = util.get_buf_path(bufnr) - if not path or path == "" then - return nil - end - local root - if runner.framework == "jest" then - root = find_jest_root(path) - else - root = util.find_root(path, { - "jest.config.js", - "jest.config.ts", - "vitest.config.ts", - "vitest.config.js", - "package.json", - "node_modules", - }) - end - local cmd = vim.deepcopy(runner.command) - append_args(cmd, runner.json_args) - table.insert(cmd, path) - return { - cmd = cmd, - cwd = root, - } - end - - function runner.build_all_command(bufnr) - local path = util.get_buf_path(bufnr) - local root - if path and path ~= "" then - if runner.framework == "jest" then - root = find_jest_root(path) - else - root = util.find_root(path, { - "jest.config.js", - "jest.config.ts", - "vitest.config.ts", - "vitest.config.js", - "package.json", - "node_modules", - }) - end - end - if not root or root == "" then - 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 - return { - cmd = cmd, - cwd = root, - } - end - - local function parse_jest_results(data) - if type(data) ~= "table" then - return nil - end - local passes = {} - local failures = {} - local skips = {} - local display = { passes = {}, failures = {}, skips = {} } - for _, result in ipairs(data.testResults or {}) do - for _, assertion in ipairs(result.assertionResults or {}) do - local full = assertion.fullName or assertion.title - local short = assertion.title or assertion.fullName - if assertion.status == "passed" and full then - table.insert(passes, full) - if short then - table.insert(display.passes, short) - end - elseif assertion.status == "failed" and full then - table.insert(failures, full) - if short then - table.insert(display.failures, short) - end - elseif (assertion.status == "pending" or assertion.status == "skipped" or assertion.status == "todo") - and full then - table.insert(skips, full) - if short then - table.insert(display.skips, short) - end - end - end - end - return { passes = passes, failures = failures, skips = skips, display = display } - end - - local function split_output_lines(text) - if not text or text == "" then - return {} - end - local lines = vim.split(text, "\n", { plain = true }) - if #lines > 0 and lines[#lines] == "" then - table.remove(lines, #lines) - end - return lines - end - - local function append_output(out, name, text) - if not name or name == "" or not text or text == "" then - return - end - if not out[name] then - out[name] = {} - end - for _, line in ipairs(split_output_lines(text)) do - table.insert(out[name], line) - end - end - - local function decode_json_from_output(output) - local ok, data = pcall(vim.json.decode, output) - if ok and type(data) == "table" then - return data - end - if not output or output == "" then - return nil - end - local start_idx = output:find("{", 1, true) - if not start_idx then - return nil - end - local end_idx = nil - for i = #output, start_idx, -1 do - if output:sub(i, i) == "}" then - end_idx = i - break - end - end - if not end_idx then - return nil - end - local candidate = output:sub(start_idx, end_idx) - local ok2, data2 = pcall(vim.json.decode, candidate) - if ok2 and type(data2) == "table" then - return data2 - end - return nil - end - - local function collect_jest_failure_output(data) - local out = {} - for _, result in ipairs(data.testResults or {}) do - for _, assertion in ipairs(result.assertionResults or {}) do - if assertion.status == "failed" then - local name = assertion.fullName or assertion.title - for _, msg in ipairs(assertion.failureMessages or {}) do - append_output(out, name, msg) - end - end - end - end - return out - end - - local function collect_mocha_failure_output(output) - local out = {} - local ok, data = pcall(vim.json.decode, output) - if ok and type(data) == "table" then - for _, failure in ipairs(data.failures or {}) do - local name = failure.fullTitle or failure.title - local err = failure.err or failure - local message = nil - if type(err) == "table" then - message = err.stack or err.message - elseif type(err) == "string" then - message = err - end - append_output(out, name, message) - end - return out - end - for line in (output or ""):gmatch("[^\n]+") do - local ok_line, entry = pcall(vim.json.decode, line) - if ok_line and type(entry) == "table" then - local event = entry.event or entry[1] or entry["1"] - local payload = entry - if not entry.event then - payload = entry[2] or entry["2"] or {} - end - if event == "fail" then - local name = payload.fullTitle or payload.title - local err = payload.err or payload - local message = nil - if type(err) == "table" then - message = err.stack or err.message - elseif type(err) == "string" then - message = err - end - append_output(out, name, message) - end - end - end - return out - end - - local function parse_jest_like(output) - local ok, data = pcall(vim.json.decode, output) - if ok and type(data) == "table" then - return parse_jest_results(data) - end - if not output or output == "" then - return nil - end - local start_idx = output:find("{", 1, true) - if not start_idx then - return nil - end - local end_idx = nil - for i = #output, start_idx, -1 do - if output:sub(i, i) == "}" then - end_idx = i - break - end - end - if not end_idx then - return nil - end - local candidate = output:sub(start_idx, end_idx) - local ok2, data2 = pcall(vim.json.decode, candidate) - if not ok2 or type(data2) ~= "table" then - return nil - end - return parse_jest_results(data2) - end - - local jest_symbols = { - pass = string.char(0xE2, 0x9C, 0x93), - fail = string.char(0xE2, 0x9C, 0x95), - skip = string.char(0xE2, 0x97, 0x8B), - } - - normalize_js_name = function(name) - if not name or name == "" then - return nil - end - local out = name - out = out:gsub("%s*[%>›»]%s*", " ") - out = out:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "") - return out - end - - shorten_js_name = function(name) - if not name or name == "" then - return nil - end - local last = name:match(".*>%s*([^>]+)$") - if last then - return last:gsub("^%s+", ""):gsub("%s+$", "") - end - last = name:match(".*›%s*([^›]+)$") - if last then - return last:gsub("^%s+", ""):gsub("%s+$", "") - end - last = name:match(".*»%s*([^»]+)$") - if last then - return last:gsub("^%s+", ""):gsub("%s+$", "") - end - return name - end - - local function trim_jest_verbose_name(name) - if not name then - return nil - end - name = name:gsub("%s+%(%d+%.?%d*%s*ms%)%s*$", "") - name = name:gsub("%s+%(%d+%.?%d*%s*sec%)%s*$", "") - name = name:gsub("%s+%b()%s*$", "") - name = name:gsub("%s+$", "") - return name - end - - local function parse_jest_verbose_line(line) - if not line or line == "" then - return nil, nil - end - local name = line:match("^%s*" .. jest_symbols.pass .. "%s+(.+)$") - if name then - return "pass", trim_jest_verbose_name(name) - end - name = line:match("^%s*" .. jest_symbols.fail .. "%s+(.+)$") - if name then - return "fail", trim_jest_verbose_name(name) - end - name = line:match("^%s*" .. jest_symbols.skip .. "%s+(.+)$") - if name then - return "skip", trim_jest_verbose_name(name) - end - return nil, nil - end - - local function parse_mocha(output) - local ok, data = pcall(vim.json.decode, output) - if not ok or type(data) ~= "table" then - local passes = {} - local failures = {} - local skips = {} - local display = { passes = {}, failures = {}, skips = {} } - for line in (output or ""):gmatch("[^\n]+") do - local ok_line, entry = pcall(vim.json.decode, line) - if ok_line and type(entry) == "table" then - local event = entry.event or entry[1] or entry["1"] - local payload = entry - if not entry.event then - payload = entry[2] or entry["2"] or {} - end - local full = payload.fullTitle or payload.title - local short = payload.title or payload.fullTitle - if event == "pass" and full then - table.insert(passes, full) - if short then - table.insert(display.passes, short) - end - elseif event == "fail" and full then - table.insert(failures, full) - if short then - table.insert(display.failures, short) - end - elseif event == "pending" and full then - table.insert(skips, full) - if short then - table.insert(display.skips, short) - end - end - end - end - if #passes == 0 and #failures == 0 and #skips == 0 then - return nil - end - return { passes = passes, failures = failures, skips = skips, display = display } - end - local passes = {} - local failures = {} - local skips = {} - local display = { passes = {}, failures = {}, skips = {} } - if type(data.tests) == "table" then - for _, test in ipairs(data.tests) do - local full = test.fullTitle or test.title - local short = test.title or test.fullTitle - if test.state == "passed" and full then - table.insert(passes, full) - if short then - table.insert(display.passes, short) - end - elseif test.state == "failed" and full then - table.insert(failures, full) - if short then - table.insert(display.failures, short) - end - elseif test.state == "pending" and full then - table.insert(skips, full) - if short then - table.insert(display.skips, short) - end - end - end - elseif type(data.passes) == "table" or type(data.failures) == "table" then - for _, test in ipairs(data.passes or {}) do - local full = test.fullTitle or test.title - local short = test.title or test.fullTitle - if full then - table.insert(passes, full) - if short then - table.insert(display.passes, short) - end - end - end - for _, test in ipairs(data.failures or {}) do - local full = test.fullTitle or test.title - local short = test.title or test.fullTitle - if full then - table.insert(failures, full) - if short then - table.insert(display.failures, short) - end - end - end - for _, test in ipairs(data.pending or {}) do - local full = test.fullTitle or test.title - local short = test.title or test.fullTitle - if full then - table.insert(skips, full) - if short then - table.insert(display.skips, short) - end - end - end - end - return { passes = passes, failures = failures, skips = skips, display = display } - 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.parse_test_output(output) - if runner.framework == "mocha" then - return collect_mocha_failure_output(output) - end - local data = decode_json_from_output(output) - if not data then - return {} - end - return collect_jest_failure_output(data) - end - - function runner.output_parser() - local state = { raw = {}, done = false, saw_stream = false } - local failures = {} - local skips = {} - local jest_seen_pass = {} - local jest_seen_fail = {} - local jest_seen_skip = {} - local jest_json_collecting = false - local jest_json_buffer = {} - local jest_streamed = false - - local function reset_list(dst, src) - for i = #dst, 1, -1 do - dst[i] = nil - end - for _, item in ipairs(src or {}) do - if item and item ~= "" then - table.insert(dst, item) - end - end - end - - local function emit_jest_results(results, emit_output) - if not results then - return nil - end - local display = results.display or {} - if emit_output == false then - reset_list(failures, results.failures or {}) - return { - passes = {}, - failures = {}, - skips = {}, - failures_all = vim.deepcopy(failures), - } - end - local out = { - passes = {}, - failures = {}, - skips = {}, - failures_all = {}, - display = { passes = {}, failures = {}, skips = {} }, - } - for i, title in ipairs(results.passes or {}) do - if title and not jest_seen_pass[title] then - jest_seen_pass[title] = true - table.insert(out.passes, title) - local show = display.passes and display.passes[i] or title - table.insert(out.display.passes, show) - end - end - for i, title in ipairs(results.failures or {}) do - if title and not jest_seen_fail[title] then - jest_seen_fail[title] = true - table.insert(failures, title) - table.insert(out.failures, title) - local show = display.failures and display.failures[i] or title - table.insert(out.display.failures, show) - end - end - for i, title in ipairs(results.skips or {}) do - if title and not jest_seen_skip[title] then - jest_seen_skip[title] = true - table.insert(out.skips, title) - local show = display.skips and display.skips[i] or title - table.insert(out.display.skips, show) - end - end - out.failures_all = vim.deepcopy(failures) - if #out.passes == 0 and #out.failures == 0 and #out.skips == 0 then - return nil - end - return out - end - - local function feed_jest_json(line) - if not jest_json_collecting then - if not line:match("^%s*{") then - return nil - end - jest_json_collecting = true - jest_json_buffer = { line } - else - table.insert(jest_json_buffer, line) - end - local candidate = table.concat(jest_json_buffer, "\n") - local ok_buf, data_buf = pcall(vim.json.decode, candidate) - if ok_buf and type(data_buf) == "table" then - jest_json_collecting = false - jest_json_buffer = {} - return data_buf - end - return nil - end - - local function handle_jest_json(data) - local results = parse_jest_results(data) - if not results then - return nil - end - state.done = true - state.saw_stream = true - return emit_jest_results(results, not jest_streamed) - end - return { - on_line = function(line, _state) - if state.done then - return nil - end - if runner.framework == "jest" then - local kind, name = parse_jest_verbose_line(line) - if kind and name then - jest_streamed = true - local results = { passes = {}, failures = {}, skips = {} } - if kind == "pass" then - results.passes = { name } - elseif kind == "fail" then - results.failures = { name } - elseif kind == "skip" then - results.skips = { name } - end - results.display = { - passes = kind == "pass" and { name } or {}, - failures = kind == "fail" and { name } or {}, - skips = kind == "skip" and { name } or {}, - } - return emit_jest_results(results, true) - end - - local data = feed_jest_json(line) - if data then - return handle_jest_json(data) - end - 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" then - local event = data.event - local payload = data - if not event then - event = data[1] or data["1"] - payload = data[2] or data["2"] or {} - end - if event == "pass" and payload.fullTitle then - state.saw_stream = true - return { - passes = { payload.fullTitle }, - failures = {}, - skips = {}, - display = { - passes = { payload.title or payload.fullTitle }, - failures = {}, - skips = {}, - }, - failures_all = vim.deepcopy(failures), - } - elseif event == "fail" and payload.fullTitle then - state.saw_stream = true - table.insert(failures, payload.fullTitle) - return { - passes = {}, - failures = { payload.fullTitle }, - skips = {}, - display = { - passes = {}, - failures = { payload.title or payload.fullTitle }, - skips = {}, - }, - failures_all = vim.deepcopy(failures), - } - elseif event == "pending" and payload.fullTitle then - state.saw_stream = true - table.insert(skips, payload.fullTitle) - return { - passes = {}, - failures = {}, - skips = { payload.fullTitle }, - display = { - passes = {}, - failures = {}, - skips = { payload.title or payload.fullTitle }, - }, - failures_all = vim.deepcopy(failures), - } - elseif event == "start" or event == "end" then - state.saw_stream = true - return { - passes = {}, - failures = {}, - skips = {}, - 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) - state.saw_stream = true - return { - passes = {}, - failures = {}, - skips = { skip_title }, - display = { - passes = {}, - failures = {}, - skips = { shorten_js_name(skip_title) or skip_title }, - }, - failures_all = vim.deepcopy(failures), - } - end - ok_title = ok_title:gsub("%s+#%s+time=.*$", "") - state.saw_stream = true - return { - passes = { ok_title }, - failures = {}, - skips = {}, - display = { - passes = { shorten_js_name(ok_title) or 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=.*$", "") - local display_name = shorten_js_name(fail_title) - table.insert(failures, fail_title) - state.saw_stream = true - return { - passes = {}, - failures = { fail_title }, - skips = {}, - display = { - passes = {}, - failures = { display_name or 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 runner.framework == "jest" then - if state.done then - return nil - end - local results = parse_jest_like(output) - if results then - state.done = true - state.saw_stream = true - if _state and _state.scope_kind == "all" and jest_streamed then - return emit_jest_results(results, false) - end - return emit_jest_results(results, not jest_streamed) - end - return nil - end - if state.done or state.saw_stream 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 - if #failures == 1 then - table.insert(cmd, "--fgrep") - table.insert(cmd, failures[1]) - else - table.insert(cmd, "--grep") - table.insert(cmd, pattern) - end - 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 - - function runner.collect_failed_locations(failures, command, scope_kind) - if type(failures) ~= "table" or #failures == 0 then - return {} - end - local files = {} - if scope_kind == "all" then - files = collect_js_test_files(command and command.cwd or nil, runner.patterns) - else - local file = command and command.file or nil - if not file and command and type(command.cmd) == "table" then - file = find_test_file_arg(command.cmd) - end - if file then - files = { file } - end - end - if #files == 0 then - return {} - end - local locations = {} - for _, file in ipairs(files) do - local ok, lines = pcall(vim.fn.readfile, file) - if ok and type(lines) == "table" then - collect_js_locations(lines, file, locations) - end - end - local items = {} - local seen = {} - for _, name in ipairs(failures) do - local keys = { name } - if normalize_js_name then - local normalized = normalize_js_name(name) - if normalized and normalized ~= name then - table.insert(keys, normalized) - end - end - if shorten_js_name then - local short = shorten_js_name(name) - if short and short ~= name then - table.insert(keys, short) - end - end - for _, key_name in ipairs(keys) do - for _, loc in ipairs(locations[key_name] or {}) do - local key = string.format("%s:%d", loc.filename or "", loc.lnum or 0) - if not seen[key] then - seen[key] = true - table.insert(items, loc) - end - end - end - end - return items - end - - return runner -end - -return M diff --git a/tests/test_samurai_core_spec.lua b/tests/test_samurai_core_spec.lua index ad208ae..a0fbe01 100644 --- a/tests/test_samurai_core_spec.lua +++ b/tests/test_samurai_core_spec.lua @@ -1,81 +1,35 @@ local test_samurai = require("test-samurai") local core = require("test-samurai.core") -local util = require("test-samurai.util") +local config = require("test-samurai.config") -local orig_find_root = util.find_root -local orig_fs_stat = vim.loop.fs_stat -local orig_readfile = vim.fn.readfile - -describe("test-samurai core", function() +describe("test-samurai core (no bundled runners)", function() before_each(function() test_samurai.setup() - util.find_root = orig_find_root - vim.loop.fs_stat = orig_fs_stat - vim.fn.readfile = orig_readfile end) - it("selects Go runner for _test.go files", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_test.go") - local runner = core.get_runner_for_buf(bufnr) - assert.is_not_nil(runner) - assert.equals("go", runner.name) + it("defaults to an empty runner_modules list", function() + local cfg = config.get() + assert.is_true(type(cfg.runner_modules) == "table") + assert.equals(0, #cfg.runner_modules) end) - it("does not fallback to Go runner when no runners are configured", function() - test_samurai.setup({ runner_modules = {} }) + it("warns when no runner is installed for a test file", function() + local notified = {} + local orig_notify = vim.notify + vim.notify = function(msg, level) + table.insert(notified, { msg = msg, level = level }) + end + local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_name(bufnr, "/tmp/no_runner_test.go") - local runner = core.get_runner_for_buf(bufnr) - assert.is_nil(runner) - end) + vim.bo[bufnr].filetype = "go" + vim.api.nvim_set_current_buf(bufnr) - it("selects JS jest runner for *.test.ts files", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/foo.test.ts") - vim.bo[bufnr].filetype = "typescript" + core.run_nearest() - local runner = core.get_runner_for_buf(bufnr) - assert.is_not_nil(runner) - assert.equals("js-jest", runner.name) - end) + vim.notify = orig_notify - it("prefers mocha runner when mocha is in package.json", function() - util.find_root = function(_, _) - return "/tmp/mocha_proj" - end - - vim.loop.fs_stat = function(path) - if path == "/tmp/mocha_proj/package.json" then - return { type = "file" } - end - return nil - end - - vim.fn.readfile = function(path) - if path == "/tmp/mocha_proj/package.json" then - return { - "{", - ' "devDependencies": { "mocha": "^10.0.0" }', - "}", - } - end - return {} - end - - test_samurai.setup() - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/mocha_proj/foo.test.ts") - vim.bo[bufnr].filetype = "typescript" - - local runner = core.get_runner_for_buf(bufnr) - - util.find_root = orig_find_root - vim.loop.fs_stat = orig_fs_stat - vim.fn.readfile = orig_readfile - - assert.is_not_nil(runner) - assert.equals("js-mocha", runner.name) + assert.equals(1, #notified) + assert.equals("[test-samurai] no runner installed for this kind of test", notified[1].msg) end) end) diff --git a/tests/test_samurai_failed_only_spec.lua b/tests/test_samurai_failed_only_spec.lua deleted file mode 100644 index a9fc521..0000000 --- a/tests/test_samurai_failed_only_spec.lua +++ /dev/null @@ -1,339 +0,0 @@ -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", "--verbose", "/tmp/project/foo_failed_only.test.ts", "-t", "inner 2" }, - calls[1].cmd - ) - assert.are.same( - { "npx", "jest", "--json", "--verbose", "-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("uses go parser for failed-only output (no raw JSON)", function() - local json_line = vim.json.encode({ - Action = "fail", - Test = "TestHandleGet/returns_200", - }) - - local calls, orig_jobstart = stub_jobstart({ - exit_codes = { 1, 1 }, - stdout = { { json_line }, { json_line } }, - }) - - local bufnr = mkbuf("/tmp/project/foo_failed_only_output_test.go", "go", { - "package main", - "import \"testing\"", - "", - "func TestHandleGet(t *testing.T) {", - " t.Run(\"returns_200\", func(t *testing.T) {", - " -- inside test", - " })", - "}", - }) - - vim.api.nvim_set_current_buf(bufnr) - - core.run_all() - core.run_failed_only() - - 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 - - assert.equals(2, #calls) - local has_raw = false - for _, line in ipairs(lines) do - if line == json_line then - has_raw = true - break - end - end - assert.is_false(has_raw) - end) - - it("reruns failed mocha tests from json-stream array output without raw JSON", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-mocha", - }, - }) - - local fail_line = vim.json.encode({ - event = "fail", - fullTitle = "API :: /brands... GET: /", - }) - local start_line = vim.json.encode({ "start", { total = 1 } }) - local end_line = vim.json.encode({ "end", { tests = 0 } }) - - local calls, orig_jobstart = stub_jobstart({ - exit_codes = { 1, 1 }, - stdout = { { fail_line }, { start_line, end_line } }, - }) - - local bufnr = mkbuf("/tmp/project/brands.test.js", "javascript", { - 'describe("API :: /brands...", function() {', - ' it("GET: /", function() {', - " -- inside test", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 0 }) - - core.run_file() - core.run_failed_only() - - 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 - - assert.equals(2, #calls) - assert.are.same( - { "npx", "mocha", "--reporter", "json-stream", "/tmp/project/brands.test.js" }, - calls[1].cmd - ) - local failed_cmd = calls[2].cmd or {} - local saw_grep = false - local saw_fgrep = false - local saw_title = false - local plain_title = "API :: /brands... GET: /" - for _, arg in ipairs(failed_cmd) do - if arg == "--grep" then - saw_grep = true - elseif arg == "--fgrep" then - saw_fgrep = true - elseif arg == plain_title then - saw_title = true - end - end - assert.is_false(saw_grep) - assert.is_true(saw_fgrep) - assert.is_true(saw_title) - - local has_raw = false - for _, line in ipairs(lines) do - if line == start_line or line == end_line then - has_raw = true - break - end - end - assert.is_false(has_raw) - 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", "--verbose", "-t", "outer inner 2", "/tmp/project/foo_failed_only_last.test.ts" }, - calls[2].cmd - ) - end) -end) diff --git a/tests/test_samurai_go_spec.lua b/tests/test_samurai_go_spec.lua deleted file mode 100644 index 38cb86f..0000000 --- a/tests/test_samurai_go_spec.lua +++ /dev/null @@ -1,147 +0,0 @@ -local go_runner = require("test-samurai.runners.go") -local util = require("test-samurai.util") - -describe("test-samurai go runner", function() - it("detects Go test files by suffix", function() - local bufnr1 = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr1, "/tmp/go_suffix_test.go") - assert.is_true(go_runner.is_test_file(bufnr1)) - - local bufnr2 = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr2, "/tmp/go_main.go") - assert.is_false(go_runner.is_test_file(bufnr2)) - end) - - it("finds subtest when cursor is inside t.Run block", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/go_subtest_test.go") - local lines = { - "package main", - "import \"testing\"", - "", - "func TestFoo(t *testing.T) {", - " t.Run(\"first\", func(t *testing.T) {", - " -- inside first", - " })", - "", - " t.Run(\"second\", func(t *testing.T) {", - " -- inside second", - " })", - "}", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_fs_find = vim.fs.find - vim.fs.find = function(markers, opts) - return { "/tmp/go.mod" } - end - - local row_inside_first = 5 - local spec, err = go_runner.find_nearest(bufnr, row_inside_first, 0) - - vim.fs.find = orig_fs_find - - assert.is_nil(err) - assert.is_not_nil(spec) - assert.equals("TestFoo/first", spec.test_path) - assert.equals("subtest", spec.scope) - assert.is_true(spec.file:match("go_subtest_test%.go$") ~= nil) - assert.is_true(spec.cwd:match("tmp$") ~= nil) - end) - - it("falls back to whole test function when between subtests", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/go_between_test.go") - local lines = { - "package main", - "import \"testing\"", - "", - "func TestFoo(t *testing.T) {", - " t.Run(\"first\", func(t *testing.T) {", - " -- inside first", - " })", - "", - " t.Run(\"second\", func(t *testing.T) {", - " -- inside second", - " })", - "}", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_fs_find = vim.fs.find - vim.fs.find = function(markers, opts) - return { "/tmp/go.mod" } - end - - local row_between = 7 - local spec, err = go_runner.find_nearest(bufnr, row_between, 0) - - vim.fs.find = orig_fs_find - - assert.is_nil(err) - assert.is_not_nil(spec) - assert.equals("TestFoo", spec.test_path) - assert.equals("function", spec.scope) - assert.is_true(spec.file:match("go_between_test%.go$") ~= nil) - assert.is_true(spec.cwd:match("tmp$") ~= nil) - end) - - it("build_command uses current package and correct run pattern", function() - local spec_sub = { - file = "/tmp/project/pkg/foo_test.go", - cwd = "/tmp/project", - test_path = "TestFoo/first", - scope = "subtest", - } - - local cmd_spec_sub = go_runner.build_command(spec_sub) - assert.are.same( - { "go", "test", "-json", "./pkg", "-run", "^TestFoo/first$" }, - cmd_spec_sub.cmd - ) - - local spec_func = { - file = "/tmp/project/foo_test.go", - cwd = "/tmp/project", - test_path = "TestFoo", - scope = "function", - } - - local cmd_spec_func = go_runner.build_command(spec_func) - assert.are.same( - { "go", "test", "-json", "./", "-run", "^TestFoo($|/)" }, - cmd_spec_func.cmd - ) - end) - - it("build_file_command uses exact test names from current file", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/project/get_test.go") - local lines = { - "package main", - "import \"testing\"", - "", - "func TestHandleGet(t *testing.T) {", - " t.Run(\"returns_200\", func(t *testing.T) {", - " -- inside test", - " })", - "}", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_find_root = util.find_root - util.find_root = function(path, markers) - return "/tmp/project" - end - - local cmd_spec = go_runner.build_file_command(bufnr) - - util.find_root = orig_find_root - - assert.are.same( - { "go", "test", "-json", "./", "-run", "^(TestHandleGet)$" }, - cmd_spec.cmd - ) - assert.equals("/tmp/project", cmd_spec.cwd) - end) -end) diff --git a/tests/test_samurai_js_spec.lua b/tests/test_samurai_js_spec.lua deleted file mode 100644 index 46ba3aa..0000000 --- a/tests/test_samurai_js_spec.lua +++ /dev/null @@ -1,203 +0,0 @@ -local jest = require("test-samurai.runners.js-jest") -local mocha = require("test-samurai.runners.js-mocha") -local vitest = require("test-samurai.runners.js-vitest") -local util = require("test-samurai.util") - -describe("test-samurai js runner (jest)", function() - it("detects JS/TS test files by name and filetype", function() - local bufnr1 = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr1, "/tmp/foo_detect.test.ts") - vim.bo[bufnr1].filetype = "typescript" - assert.is_true(jest.is_test_file(bufnr1)) - - local bufnr2 = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr2, "/tmp/foo_detect.ts") - vim.bo[bufnr2].filetype = "typescript" - assert.is_false(jest.is_test_file(bufnr2)) - end) - - it("finds nearest it() call as test name and builds full_name when cursor is inside the test", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_nearest.test.ts") - vim.bo[bufnr].filetype = "typescript" - local lines = { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " -- inside 1", - " })", - "", - ' it("inner 2", function() {', - " -- inside 2", - " })", - "})", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_fs_find = vim.fs.find - vim.fs.find = function(names, opts) - return { "/tmp/package.json" } - end - - local row_inside_second = 6 - local spec, err = jest.find_nearest(bufnr, row_inside_second, 0) - - vim.fs.find = orig_fs_find - - 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("jest", spec.framework) - assert.is_true(spec.file:match("foo_nearest%.test%.ts$") ~= nil) - assert.is_true(spec.cwd:match("tmp$") ~= nil) - - local cmd_spec = jest.build_command(spec) - assert.are.same({ "npx", "jest", "--json", "--verbose", spec.file, "-t", "inner 2" }, cmd_spec.cmd) - end) - - it("returns describe block when cursor is between it() calls", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_between.test.ts") - vim.bo[bufnr].filetype = "typescript" - local lines = { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " -- inside 1", - " })", - "", - ' it("inner 2", function() {', - " -- inside 2", - " })", - "})", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_fs_find = vim.fs.find - vim.fs.find = function(names, opts) - return { "/tmp/package.json" } - end - - local row_between = 4 - local spec, err = jest.find_nearest(bufnr, row_between, 0) - - vim.fs.find = orig_fs_find - - assert.is_nil(err) - assert.is_not_nil(spec) - assert.equals("outer", spec.test_name) - assert.equals("outer", spec.full_name) - assert.equals("jest", spec.framework) - end) - - it("treats jest.config in test/.bin as project root parent", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/foo_binroot.test.ts") - vim.bo[bufnr].filetype = "typescript" - local lines = { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " -- inside 1", - " })", - "", - ' it("inner 2", function() {', - " -- inside 2", - " })", - "})", - } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - local orig_fs_find = vim.fs.find - vim.fs.find = function(names, opts) - return { "/tmp/test/.bin/jest.config.js" } - end - - local row_inside_second = 6 - local spec, err = jest.find_nearest(bufnr, row_inside_second, 0) - - vim.fs.find = orig_fs_find - - assert.is_nil(err) - assert.is_not_nil(spec) - assert.equals("/tmp", spec.cwd) - end) -end) - -describe("test-samurai js runner (mocha)", function() - it("builds mocha command with fgrep and full test title", 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 = mocha.build_command(spec) - - assert.are.same( - { "npx", "mocha", "--reporter", "json-stream", "--fgrep", "outer inner 2", spec.file }, - cmd_spec.cmd - ) - assert.equals("/tmp/project", cmd_spec.cwd) - end) - - it("builds mocha all command with default glob", function() - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/project/test/foo_all.test.js") - vim.bo[bufnr].filetype = "javascript" - - local orig_find_root = util.find_root - util.find_root = function(path, markers) - return "/tmp/project" - end - - local cmd_spec = mocha.build_all_command(bufnr) - - util.find_root = orig_find_root - - assert.are.same( - { "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 - ) - assert.equals("/tmp/project", cmd_spec.cwd) - end) -end) diff --git a/tests/test_samurai_last_spec.lua b/tests/test_samurai_last_spec.lua deleted file mode 100644 index 87f4d50..0000000 --- a/tests/test_samurai_last_spec.lua +++ /dev/null @@ -1,139 +0,0 @@ -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 capture_jobstart() - local calls = {} - local orig = vim.fn.jobstart - vim.fn.jobstart = function(cmd, opts) - table.insert(calls, { cmd = cmd, opts = opts }) - return 1 - end - return calls, orig -end - -describe("TSamLast", function() - before_each(function() - test_samurai.setup() - end) - - it("reruns last Go command", function() - local calls, orig_jobstart = capture_jobstart() - - local bufnr = mkbuf("/tmp/project/foo_test.go", "go", { - "package main", - "import \"testing\"", - "", - "func TestFoo(t *testing.T) {", - " t.Run(\"first\", func(t *testing.T) {", - " -- inside first", - " })", - "}", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 6, 0 }) - - core.run_nearest() - core.run_last() - - vim.fn.jobstart = orig_jobstart - - assert.equals(2, #calls) - assert.are.same({ "go", "test", "-json", "./", "-run", "^TestFoo/first$" }, calls[1].cmd) - assert.are.same(calls[1].cmd, calls[2].cmd) - assert.equals(calls[1].opts.cwd, calls[2].opts.cwd) - end) - - it("uses go parser for TSamLast output (no raw JSON)", function() - local json_line = vim.json.encode({ - Action = "fail", - Test = "TestHandleGet/returns_200", - }) - - local calls = {} - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(cmd, opts) - table.insert(calls, { cmd = cmd, opts = opts }) - if opts and opts.on_stdout then - opts.on_stdout(1, { json_line }, nil) - end - if opts and opts.on_exit then - opts.on_exit(1, 1, nil) - end - return 1 - end - - local bufnr = mkbuf("/tmp/project/foo_last_output_test.go", "go", { - "package main", - "import \"testing\"", - "", - "func TestHandleGet(t *testing.T) {", - " t.Run(\"returns_200\", func(t *testing.T) {", - " -- inside test", - " })", - "}", - }) - - vim.api.nvim_set_current_buf(bufnr) - - core.run_all() - core.run_last() - - 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 - - assert.equals(2, #calls) - local has_raw = false - for _, line in ipairs(lines) do - if line == json_line then - has_raw = true - break - end - end - assert.is_false(has_raw) - end) - - it("reruns last JS command", function() - local calls, orig_jobstart = capture_jobstart() - - local bufnr = mkbuf("/tmp/project/foo_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_last() - - vim.fn.jobstart = orig_jobstart - - assert.equals(2, #calls) - assert.are.same( - { "npx", "jest", "--json", "--verbose", "/tmp/project/foo_last.test.ts", "-t", "inner 2" }, - calls[1].cmd - ) - assert.are.same(calls[1].cmd, calls[2].cmd) - assert.equals(calls[1].opts.cwd, calls[2].opts.cwd) - end) -end) diff --git a/tests/test_samurai_output_spec.lua b/tests/test_samurai_output_spec.lua deleted file mode 100644 index b633420..0000000 --- a/tests/test_samurai_output_spec.lua +++ /dev/null @@ -1,2963 +0,0 @@ -local test_samurai = require("test-samurai") -local core = require("test-samurai.core") - -local function close_output_container() - local keys = vim.api.nvim_replace_termcodes("", 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 == "" then - table.insert(check, "") - end - if lhs == "" then - table.insert(check, "") - 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 == "" or rhs == "" 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 - local orig = core.show_output - - core.show_output = function() - called = true - end - - test_samurai.show_output() - - core.show_output = orig - - assert.is_true(called) - end) - - it("delegates test_nearest to core.run_nearest", function() - local called = false - local orig = core.run_nearest - - core.run_nearest = function() - called = true - end - - test_samurai.test_nearest() - - core.run_nearest = orig - - assert.is_true(called) - end) - - it("delegates test_file to core.run_file", function() - local called = false - local orig = core.run_file - - core.run_file = function() - called = true - end - - test_samurai.test_file() - - core.run_file = orig - - assert.is_true(called) - end) - - it("delegates test_all to core.run_all", function() - local called = false - local orig = core.run_all - - core.run_all = function() - called = true - end - - test_samurai.test_all() - - core.run_all = orig - - assert.is_true(called) - end) - - it("delegates test_last to core.run_last", function() - local called = false - local orig = core.run_last - - core.run_last = function() - called = true - end - - test_samurai.test_last() - - core.run_last = orig - - assert.is_true(called) - 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 listing fail navigation", function() - before_each(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 - - local function collect_fail_lines(buf) - local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) - local out = {} - for i, line in ipairs(lines) do - if line:match("^%[ FAIL %] %- ") then - table.insert(out, i) - end - end - return out - end - - local function press(keys) - local mapped = vim.api.nvim_replace_termcodes(keys, true, false, true) - vim.api.nvim_feedkeys(mapped, "x", false) - vim.wait(20) - end - - it("jumps to the next fail and wraps to the first", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "failed", title = "inner 1", fullName = "outer inner 1" }, - { status = "passed", title = "inner 2", fullName = "outer inner 2" }, - { status = "failed", title = "inner 3", fullName = "outer inner 3" }, - }, - }, - }, - }) - - 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_listing_next_fail.test.ts") - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " })", - ' it("inner 2", function() {', - " })", - ' it("inner 3", function() {', - " })", - "})", - }) - - core.run_nearest() - - local listing = find_listing_win() - assert.is_not_nil(listing) - vim.api.nvim_set_current_win(listing) - local listing_buf = vim.api.nvim_win_get_buf(listing) - local fails = collect_fail_lines(listing_buf) - assert.equals(2, #fails) - - vim.api.nvim_win_set_cursor(listing, { fails[1], 0 }) - press("nf") - assert.equals(fails[2], vim.api.nvim_win_get_cursor(listing)[1]) - press("nf") - assert.equals(fails[1], vim.api.nvim_win_get_cursor(listing)[1]) - - vim.fn.jobstart = orig_jobstart - end) - - it("jumps to the previous fail and wraps to the last", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "failed", title = "inner 1", fullName = "outer inner 1" }, - { status = "passed", title = "inner 2", fullName = "outer inner 2" }, - { status = "failed", title = "inner 3", fullName = "outer inner 3" }, - }, - }, - }, - }) - - 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_listing_prev_fail.test.ts") - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " })", - ' it("inner 2", function() {', - " })", - ' it("inner 3", function() {', - " })", - "})", - }) - - core.run_nearest() - - local listing = find_listing_win() - assert.is_not_nil(listing) - vim.api.nvim_set_current_win(listing) - local listing_buf = vim.api.nvim_win_get_buf(listing) - local fails = collect_fail_lines(listing_buf) - assert.equals(2, #fails) - - vim.api.nvim_win_set_cursor(listing, { fails[2], 0 }) - press("pf") - assert.equals(fails[1], vim.api.nvim_win_get_cursor(listing)[1]) - press("pf") - assert.equals(fails[2], vim.api.nvim_win_get_cursor(listing)[1]) - - vim.fn.jobstart = orig_jobstart - end) - - it("does nothing when there are no fail lines", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "passed", title = "inner 1", fullName = "outer inner 1" }, - { status = "skipped", 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, 0, nil) - end - return 1 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_listing_no_fail.test.ts") - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " })", - ' it("inner 2", function() {', - " })", - "})", - }) - - core.run_nearest() - - local listing = find_listing_win() - assert.is_not_nil(listing) - vim.api.nvim_set_current_win(listing) - local before = vim.api.nvim_win_get_cursor(listing)[1] - press("nf") - assert.equals(before, vim.api.nvim_win_get_cursor(listing)[1]) - press("pf") - assert.equals(before, vim.api.nvim_win_get_cursor(listing)[1]) - - vim.fn.jobstart = orig_jobstart - end) -end) - -describe("test-samurai output formatting", function() - before_each(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 = { - { - 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 ] - inner 1" then - has_pass = true - elseif line == "[ SKIP ] - inner skip" then - has_skip = true - elseif line == "[ FAIL ] - 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("applies PASS/FAIL/SKIP highlights to parsed output", 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_highlight_parsed.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 ns = vim.api.nvim_create_namespace("TestSamuraiResult") - local extmarks = vim.api.nvim_buf_get_extmarks(out_buf, ns, 0, -1, { details = true }) - - vim.fn.jobstart = orig_jobstart - - local groups = {} - for _, mark in ipairs(extmarks) do - local details = mark[4] or {} - if details.hl_group then - groups[details.hl_group] = true - end - end - - assert.is_true(groups.TestSamuraiResultPass) - assert.is_true(groups.TestSamuraiResultFail) - assert.is_true(groups.TestSamuraiResultSkip) - end) - - it("sets pass border when there are only passing tests", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "passed", title = "inner 1", fullName = "outer inner 1" }, - }, - }, - }, - }) - - 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, 0, nil) - end - return 1 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_border_pass.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", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 0 }) - - core.run_nearest() - - 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 - - assert.equals("FloatBorder:TestSamuraiBorderPass", hl) - end) - - it("sets fail border when there are failing tests", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { 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_border_fail.test.ts") - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 2", function() {', - " -- inside 2", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 0 }) - - core.run_nearest() - - 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 - - assert.equals("FloatBorder:TestSamuraiBorderFail", hl) - end) - - it("does not print raw JSON output for jest runs", function() - local json = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "passed", title = "inner 1", fullName = "outer inner 1" }, - }, - }, - }, - }) - - 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, 0, nil) - end - return 1 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_raw_json.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", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 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_raw_json = false - for _, line in ipairs(lines) do - if line == json then - has_raw_json = true - break - end - end - - assert.is_false(has_raw_json) - end) - - it("formats multi-line jest JSON output and ignores non-JSON lines", function() - local lines_out = { - "Some log line", - "{", - ' "testResults": [', - " {", - ' "assertionResults": [', - ' {"status":"passed","title":"inner 1","fullName":"outer inner 1"}', - " ]", - " }", - " ]", - "}", - } - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, lines_out, 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_multiline_json.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", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 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_raw_json = false - for _, line in ipairs(lines) do - if line == "[ PASS ] - inner 1" then - has_pass = true - elseif line == ' "testResults": [' then - has_raw_json = true - end - end - assert.is_true(has_pass) - assert.is_false(has_raw_json) - end) - - it("formats jest verbose output as PASS/FAIL/SKIP lines", function() - local check = string.char(0xE2, 0x9C, 0x93) - local cross = string.char(0xE2, 0x9C, 0x95) - local circle = string.char(0xE2, 0x97, 0x8B) - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, { - " PASS /tmp/output_verbose.test.ts", - " " .. check .. " inner 1 (5 ms)", - " " .. circle .. " inner skip (skipped)", - " " .. cross .. " inner 2 (1 ms)", - }, 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_verbose.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 - local has_raw_verbose = false - for _, line in ipairs(lines) do - if line == "[ PASS ] - inner 1" then - has_pass = true - elseif line == "[ SKIP ] - inner skip" then - has_skip = true - elseif line == "[ FAIL ] - inner 2" then - has_fail = true - elseif line:match("^%s*PASS%s+") and line ~= " PASS 1 - SKIPPED 1 - FAILED 1" then - has_raw_verbose = true - end - end - assert.is_true(has_pass) - assert.is_true(has_skip) - assert.is_true(has_fail) - assert.is_false(has_raw_verbose) - end) - - it("formats jest JSON output for TSamAll as full names", 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_fullname_all.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_all() - - 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 jest verbose output for TSamAll as short names", function() - local check = string.char(0xE2, 0x9C, 0x93) - local cross = string.char(0xE2, 0x9C, 0x95) - local circle = string.char(0xE2, 0x97, 0x8B) - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, { - " PASS /tmp/output_verbose_all.test.ts", - " " .. check .. " inner 1 (5 ms)", - " " .. circle .. " inner skip (skipped)", - " " .. cross .. " inner 2 (1 ms)", - }, 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_verbose_all.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_all() - - 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 - local has_raw_verbose = false - for _, line in ipairs(lines) do - if line == "[ PASS ] - inner 1" then - has_pass = true - elseif line == "[ SKIP ] - inner skip" then - has_skip = true - elseif line == "[ FAIL ] - inner 2" then - has_fail = true - elseif line:match("^%s*PASS%s+") and line ~= " PASS 1 - SKIPPED 1 - FAILED 1" then - has_raw_verbose = true - end - end - - assert.is_true(has_pass) - assert.is_true(has_skip) - assert.is_true(has_fail) - assert.is_false(has_raw_verbose) - end) - - it("adds a summary line for TSamFile output", 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, 0, nil) - end - return 1 - end - - local orig_hrtime = vim.loop.hrtime - local hr_calls = 0 - vim.loop.hrtime = function() - hr_calls = hr_calls + 1 - if hr_calls == 1 then - return 1000000000 - end - return 3500000000 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_file.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_file() - - 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 - vim.loop.hrtime = orig_hrtime - - local has_total = false - local has_breakdown = false - local has_duration = false - local saw_blank = false - for _, line in ipairs(lines) do - if line == "" then - saw_blank = true - elseif line == "TOTAL 3" then - has_total = true - elseif line == "DURATION 00m 02s" then - has_duration = true - elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then - has_breakdown = true - end - end - - assert.is_true(saw_blank) - assert.is_true(has_total) - assert.is_true(has_breakdown) - assert.is_true(has_duration) - end) - - it("adds a summary line for TSamAll output", 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, 0, nil) - end - return 1 - end - - local orig_hrtime = vim.loop.hrtime - local hr_calls = 0 - vim.loop.hrtime = function() - hr_calls = hr_calls + 1 - if hr_calls == 1 then - return 500000000 - end - return 2000000000 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_all.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_all() - - 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 - vim.loop.hrtime = orig_hrtime - - local has_total = false - local has_breakdown = false - local has_duration = false - local saw_blank = false - for _, line in ipairs(lines) do - if line == "" then - saw_blank = true - elseif line == "TOTAL 3" then - has_total = true - elseif line == "DURATION 00m 01s" then - has_duration = true - elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then - has_breakdown = true - end - end - - assert.is_true(saw_blank) - assert.is_true(has_total) - assert.is_true(has_breakdown) - assert.is_true(has_duration) - end) - - it("adds a summary line for TSamNearest output", 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, 0, nil) - end - return 1 - end - - local orig_hrtime = vim.loop.hrtime - local hr_calls = 0 - vim.loop.hrtime = function() - hr_calls = hr_calls + 1 - if hr_calls == 1 then - return 2000000000 - end - return 4500000000 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_nearest.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, { 3, 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 - vim.loop.hrtime = orig_hrtime - - local has_total = false - local has_breakdown = false - local has_duration = false - local saw_blank = false - for _, line in ipairs(lines) do - if line == "" then - saw_blank = true - elseif line == "TOTAL 3" then - has_total = true - elseif line == "DURATION 00m 02s" then - has_duration = true - elseif line == " PASS 1 - SKIPPED 1 - FAILED 1" then - has_breakdown = true - end - end - - assert.is_true(saw_blank) - assert.is_true(has_total) - assert.is_true(has_breakdown) - assert.is_true(has_duration) - end) - - it("applies summary highlights for TSamAll output", 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, 0, nil) - end - return 1 - end - - local orig_hrtime = vim.loop.hrtime - local hr_calls = 0 - vim.loop.hrtime = function() - hr_calls = hr_calls + 1 - if hr_calls == 1 then - return 1000000000 - end - return 2500000000 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_summary_highlight_all.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_all() - - local out_buf = vim.api.nvim_get_current_buf() - local ns = vim.api.nvim_create_namespace("TestSamuraiSummary") - local extmarks = vim.api.nvim_buf_get_extmarks(out_buf, ns, 0, -1, { details = true }) - - vim.fn.jobstart = orig_jobstart - vim.loop.hrtime = orig_hrtime - - local groups = {} - for _, mark in ipairs(extmarks) do - local details = mark[4] or {} - if details.hl_group then - groups[details.hl_group] = true - end - end - - assert.is_true(groups.TestSamuraiSummaryBold) - assert.is_true(groups.TestSamuraiSummaryPass) - assert.is_true(groups.TestSamuraiSummaryFail) - assert.is_true(groups.TestSamuraiSummarySkip) - end) - - it("derives summary highlight colors from diagnostic fallbacks", function() - local orig_get_hl = vim.api.nvim_get_hl - local orig_set_hl = vim.api.nvim_set_hl - local set_calls = {} - - vim.api.nvim_get_hl = function(_ns, opts) - if opts.name == "DiffAdd" or opts.name == "DiffDelete" then - return {} - end - if opts.name == "DiagnosticOk" then - return { fg = 111 } - end - if opts.name == "DiagnosticError" then - return { fg = 222 } - end - if opts.name == "DiagnosticInfo" then - return { fg = 333 } - end - if opts.name == "Normal" then - return { fg = 99 } - end - return {} - end - - vim.api.nvim_set_hl = function(_ns, name, opts) - set_calls[name] = opts - end - - core.setup() - - vim.api.nvim_get_hl = orig_get_hl - vim.api.nvim_set_hl = orig_set_hl - - assert.is_true(set_calls.TestSamuraiSummaryPass.fg == 111) - assert.is_true(set_calls.TestSamuraiSummaryFail.fg == 222) - assert.is_true(set_calls.TestSamuraiSummarySkip.fg == 333) - end) - - it("groups Go subtests under their parent in listing", function() - local json_lines = { - vim.json.encode({ Action = "pass", Test = "TestHandleGet/returns_200" }), - vim.json.encode({ Action = "fail", Test = "TestOther/returns_500" }), - vim.json.encode({ Action = "pass", Test = "TestHandleGet" }), - vim.json.encode({ Action = "skip", Test = "TestOther" }), - } - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, json_lines, 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_go_grouped_test.go") - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package main", - "import \"testing\"", - "", - "func TestHandleGet(t *testing.T) {", - " t.Run(\"returns_200\", func(t *testing.T) {})", - "}", - "", - "func TestOther(t *testing.T) {", - " t.Run(\"returns_500\", func(t *testing.T) {})", - "}", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 5, 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 idx_parent_1 = nil - local idx_sub_1 = nil - local idx_parent_2 = nil - local idx_sub_2 = nil - for i, line in ipairs(lines) do - if line == "[ PASS ] - TestHandleGet" then - idx_parent_1 = i - elseif line == "[ PASS ] - TestHandleGet/returns_200" then - idx_sub_1 = i - elseif line == "[ SKIP ] - TestOther" then - idx_parent_2 = i - elseif line == "[ FAIL ] - TestOther/returns_500" then - idx_sub_2 = i - end - end - - assert.is_not_nil(idx_parent_1) - assert.is_not_nil(idx_sub_1) - assert.is_not_nil(idx_parent_2) - assert.is_not_nil(idx_sub_2) - assert.is_true(idx_parent_1 < idx_sub_1) - assert.is_true(idx_parent_2 < idx_sub_2) - end) - - it("groups nested Go subtests under subtest parents in listing", function() - local json_lines = { - vim.json.encode({ Action = "pass", Test = "TestWriteJSON/returns_500_when/data_could_not_be_serialized_and_logs_it" }), - vim.json.encode({ Action = "pass", Test = "TestWriteJSON" }), - vim.json.encode({ Action = "pass", Test = "TestWriteJSON/returns_500_when" }), - vim.json.encode({ Action = "pass", Test = "TestWriteJSON/returns_500_when/error_at_writing_response_occurs_and_logs_it" }), - } - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, json_lines, 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_go_nested_test.go") - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package main", - "import \"testing\"", - "", - "func TestWriteJSON(t *testing.T) {", - " t.Run(\"returns_500_when\", func(t *testing.T) {", - " t.Run(\"data_could_not_be_serialized_and_logs_it\", func(t *testing.T) {})", - " t.Run(\"error_at_writing_response_occurs_and_logs_it\", func(t *testing.T) {})", - " })", - "}", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 5, 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 idx_parent = nil - local idx_mid = nil - local idx_child_1 = nil - local idx_child_2 = nil - for i, line in ipairs(lines) do - if line == "[ PASS ] - TestWriteJSON" then - idx_parent = i - elseif line == "[ PASS ] - TestWriteJSON/returns_500_when" then - idx_mid = i - elseif line == "[ PASS ] - TestWriteJSON/returns_500_when/data_could_not_be_serialized_and_logs_it" then - idx_child_1 = i - elseif line == "[ PASS ] - TestWriteJSON/returns_500_when/error_at_writing_response_occurs_and_logs_it" then - idx_child_2 = i - end - end - - assert.is_not_nil(idx_parent) - assert.is_not_nil(idx_mid) - assert.is_not_nil(idx_child_1) - assert.is_not_nil(idx_child_2) - assert.is_true(idx_parent < idx_mid) - assert.is_true(idx_mid < idx_child_1) - assert.is_true(idx_mid < idx_child_2) - end) - - it("does not print raw JSON output for mocha json-stream", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-mocha", - }, - }) - - local json_line = vim.json.encode({ - event = "pass", - fullTitle = "outer inner 1", - }) - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, { json_line }, 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_raw_json.test.js") - vim.bo[bufnr].filetype = "javascript" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " -- inside 1", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 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_raw_json = false - for _, line in ipairs(lines) do - if line == json_line then - has_raw_json = true - break - end - end - - assert.is_false(has_raw_json) - end) - - it("formats mocha json-stream array output as PASS/FAIL lines", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-mocha", - }, - }) - - local pass_line = vim.json.encode({ - "pass", - { - title = "GET: /", - fullTitle = "API :: /brands... GET: /", - }, - }) - - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(_cmd, opts) - if opts and opts.on_stdout then - opts.on_stdout(1, { pass_line }, 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_mocha_json_stream.test.js") - vim.bo[bufnr].filetype = "javascript" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("outer", function() {', - ' it("inner 1", function() {', - " -- inside 1", - " })", - "})", - }) - - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 3, 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_raw_json = false - for _, line in ipairs(lines) do - if line == "[ PASS ] - GET: /" then - has_pass = true - elseif line == pass_line then - has_raw_json = true - end - end - - assert.is_true(has_pass) - assert.is_false(has_raw_json) - end) - - it("does not print raw JSON when JSON arrives on stdout and stderr", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-jest", - }, - }) - - local json1 = vim.json.encode({ - testResults = { - { - assertionResults = { - { status = "passed", title = "inner 1", fullName = "outer inner 1" }, - }, - }, - }, - }) - local json2 = vim.json.encode({ - testResults = { - { - assertionResults = { - { 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, { json1 }, nil) - end - if opts and opts.on_stderr then - opts.on_stderr(1, { json2 }, 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_raw_json_both.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_raw_json = false - for _, line in ipairs(lines) do - if line == json1 or line == json2 then - has_raw_json = true - break - end - end - - assert.is_false(has_raw_json) - end) - - it("handles mixed mocha json-stream events and tracks failures for failed-only", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-mocha", - }, - }) - - local pass_line = vim.json.encode({ - event = "pass", - title = "inner 1", - fullTitle = "outer inner 1", - }) - local fail_line = vim.json.encode({ - event = "fail", - title = "inner 2", - fullTitle = "outer inner 2", - }) - - local job_calls = {} - local orig_jobstart = vim.fn.jobstart - vim.fn.jobstart = function(cmd, opts) - table.insert(job_calls, { cmd = cmd, opts = opts }) - if #job_calls == 1 then - if opts and opts.on_stdout then - opts.on_stdout(1, { pass_line, fail_line }, nil) - end - if opts and opts.on_exit then - opts.on_exit(1, 1, nil) - end - else - if opts and opts.on_exit then - opts.on_exit(1, 1, nil) - end - end - return 1 - end - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, "/tmp/output_mixed_json.test.js") - vim.bo[bufnr].filetype = "javascript" - 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) - - core.run_failed_only() - - vim.fn.jobstart = orig_jobstart - - local has_pass = false - local has_fail = false - for _, line in ipairs(lines) do - if line == "[ PASS ] - inner 1" then - has_pass = true - elseif line == "[ FAIL ] - inner 2" then - has_fail = true - end - end - assert.is_true(has_pass) - assert.is_true(has_fail) - - assert.is_true(#job_calls >= 2) - local failed_cmd = job_calls[2].cmd or {} - local saw_grep = false - local saw_fgrep = false - local saw_title = false - for _, arg in ipairs(failed_cmd) do - if arg == "--grep" then - saw_grep = true - elseif arg == "--fgrep" then - saw_fgrep = true - elseif arg == "outer inner 2" then - saw_title = true - end - end - assert.is_false(saw_grep) - assert.is_true(saw_fgrep) - assert.is_true(saw_title) - 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 ] - inner 1" then - has_pass = true - elseif line == "[ SKIP ] - inner skip" then - has_skip = true - elseif line == "[ FAIL ] - 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) - -describe("test-samurai output detail view", function() - before_each(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 = {} - for _, win in ipairs(wins) do - local cfg = vim.api.nvim_win_get_config(win) - if cfg.relative ~= "" then - table.insert(out, win) - end - end - return out - end - - local function find_detail_win(listing) - for _, win in ipairs(find_float_wins()) do - if win ~= listing then - return win - end - end - return nil - end - - it("opens failing test output in a right vsplit", 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 = "output", Test = "TestFoo/Sub", Output = " foo_test.go:10: expected\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_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() - assert.equals(2, #wins) - 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.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.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) - assert.are.same({ - "=== RUN TestFoo/Sub", - " foo_test.go:10: expected", - }, detail) - - vim.fn.jobstart = orig_jobstart - end) - - it("reuses an existing right split for test output", 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 = "output", Test = "TestFoo/Sub", Output = " foo_test.go:10: expected\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_reuse_test.go") - vim.bo[bufnr].filetype = "go" - vim.api.nvim_set_current_buf(bufnr) - - test_samurai.test_file() - local output_win = vim.api.nvim_get_current_win() - 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 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" }) - - vim.api.nvim_set_current_win(output_win) - core.open_test_output_at_cursor() - - local updated = find_float_wins() - 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({ - "=== RUN TestFoo/Sub", - " foo_test.go:10: expected", - }, detail) - - vim.fn.jobstart = orig_jobstart - end) - - it("translates ANSI codes into highlights for detail output", 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 = "TestAnsi/Sub", Output = "\27[31mFAIL\27[0m bad\n" }), - vim.json.encode({ Action = "fail", Test = "TestAnsi/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_ansi_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 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({ "FAIL bad" }, detail) - - local ns = vim.api.nvim_get_namespaces()["TestSamuraiDetailAnsi"] - assert.is_not_nil(ns) - local marks = vim.api.nvim_buf_get_extmarks(right_buf, ns, 0, -1, { details = true }) - assert.is_true(#marks > 0) - assert.is_not_nil(marks[1][4].hl_group) - - 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) - 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_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() - assert.equals(2, #wins) - 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) - local total_width = left_cfg.width + right_cfg.width - - vim.api.nvim_win_close(right, true) - - local remaining = find_float_wins() - assert.equals(1, #remaining) - 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 detail float with 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("", 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, "")) - assert.is_true(has_nop_mapping(listing_buf, "")) - - 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, "")) - assert.is_true(has_nop_mapping(detail_buf, "")) - - vim.fn.jobstart = orig_jobstart - end) - - it("focuses listing with 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("", 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 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("", 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 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("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("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 ", 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("", 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("schliesst den Test-Container und springt mit qn zum ersten Quickfix-Eintrag", 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 = "fail", Test = "TestFoo" }), - }, nil) - end - if opts and opts.on_exit then - opts.on_exit(1, 1, nil) - end - return 1 - end - - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_jump") - vim.fn.mkdir(root, "p") - local target = root .. "/output_detail_qn_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }, target) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, target) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - - test_samurai.test_file() - - local wins = find_float_wins() - assert.equals(1, #wins) - - vim.wait(20, function() - return #vim.fn.getqflist() > 0 - end) - - local keys = vim.api.nvim_replace_termcodes("qn", true, false, true) - vim.api.nvim_feedkeys(keys, "x", false) - vim.wait(20, function() - return #find_float_wins() == 0 - end) - - assert.equals(target, vim.api.nvim_buf_get_name(0)) - local cursor = vim.api.nvim_win_get_cursor(0) - assert.equals(3, cursor[1]) - - vim.fn.jobstart = orig_jobstart - end) - - it("schliesst den Test-Container und springt mit o zum Test der Listing-Zeile", 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" }), - }, nil) - end - if opts and opts.on_exit then - opts.on_exit(1, 0, nil) - end - return 1 - end - - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_jump") - vim.fn.mkdir(root, "p") - local target = root .. "/output_detail_o_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }, target) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, target) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - - test_samurai.test_file() - - local wins = find_float_wins() - assert.equals(1, #wins) - - local out_buf = vim.api.nvim_get_current_buf() - local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) - local target_line = nil - for i, line in ipairs(lines) do - if line:match("^%[ PASS %] %- ") then - target_line = i - break - end - end - assert.is_not_nil(target_line) - vim.api.nvim_win_set_cursor(0, { target_line, 0 }) - - local keys = vim.api.nvim_replace_termcodes("o", true, false, true) - vim.api.nvim_feedkeys(keys, "x", false) - vim.wait(20, function() - return #find_float_wins() == 0 - end) - - assert.equals(target, vim.api.nvim_buf_get_name(0)) - local cursor = vim.api.nvim_win_get_cursor(0) - assert.equals(3, cursor[1]) - - vim.fn.jobstart = orig_jobstart - end) - - it("ignoriert o auf Nicht-Ergebniszeilen im Listing", 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" }), - }, nil) - end - if opts and opts.on_exit then - opts.on_exit(1, 0, nil) - end - return 1 - end - - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_jump") - vim.fn.mkdir(root, "p") - local target = root .. "/output_detail_o_ignore_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }, target) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, target) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestFoo(t *testing.T) {", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - - test_samurai.test_file() - - local wins = find_float_wins() - assert.equals(1, #wins) - - vim.api.nvim_win_set_cursor(0, { 1, 0 }) - - local keys = vim.api.nvim_replace_termcodes("o", true, false, true) - vim.api.nvim_feedkeys(keys, "x", false) - vim.wait(20) - - local remaining = find_float_wins() - assert.equals(1, #remaining) - assert.is_true(vim.api.nvim_buf_get_name(0) ~= target) - - vim.fn.jobstart = orig_jobstart - end) - - it("oeffnet mit o den nicht geladenen Buffer fuer Mocha-TSamAll", function() - test_samurai.setup({ - runner_modules = { - "test-samurai.runners.js-mocha", - }, - }) - - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_mocha_jump") - local test_dir = root .. "/test" - vim.fn.mkdir(test_dir, "p") - vim.fn.writefile({ '{ "name": "mocha-jump" }' }, root .. "/package.json") - - local file1 = test_dir .. "/one.test.js" - local file2 = test_dir .. "/two.test.js" - vim.fn.writefile({ - 'describe("suite one", function() {', - ' it("test one", function() {', - " })", - "})", - }, file1) - vim.fn.writefile({ - 'describe("suite two", function() {', - ' it("test two", function() {', - " })", - "})", - }, file2) - - 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({ event = "pass", fullTitle = "suite one test one", title = "test one" }), - vim.json.encode({ event = "pass", fullTitle = "suite two test two", title = "test two" }), - }, 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, file1) - vim.bo[bufnr].filetype = "javascript" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - 'describe("suite one", function() {', - ' it("test one", function() {', - " })", - "})", - }) - vim.api.nvim_set_current_buf(bufnr) - - test_samurai.test_all() - - local out_buf = vim.api.nvim_get_current_buf() - local lines = vim.api.nvim_buf_get_lines(out_buf, 0, -1, false) - local target_line = nil - for i, line in ipairs(lines) do - if line == "[ PASS ] - suite two test two" then - target_line = i - break - end - end - assert.is_not_nil(target_line) - vim.api.nvim_win_set_cursor(0, { target_line, 0 }) - - local keys = vim.api.nvim_replace_termcodes("o", true, false, true) - vim.api.nvim_feedkeys(keys, "x", false) - vim.wait(20, function() - return #find_float_wins() == 0 - end) - - assert.equals(file2, vim.api.nvim_buf_get_name(0)) - local cursor = vim.api.nvim_win_get_cursor(0) - assert.equals(2, cursor[1]) - - 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("", 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 - 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_listing_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() - - vim.api.nvim_win_close(output_win, true) - - local remaining = find_float_wins() - assert.equals(0, #remaining) - - vim.fn.jobstart = orig_jobstart - end) - - 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 - 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_nav_close_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() - - 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(2, #remaining) - - vim.fn.jobstart = orig_jobstart - end) - - 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 - 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_listing_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) - vim.api.nvim_set_current_win(detail_win) - - core.focus_listing() - - assert.equals(output_win, vim.api.nvim_get_current_win()) - local remaining = find_float_wins() - assert.equals(2, #remaining) - - vim.fn.jobstart = orig_jobstart - end) -end) diff --git a/tests/test_samurai_quickfix_js_spec.lua b/tests/test_samurai_quickfix_js_spec.lua deleted file mode 100644 index 2a72a48..0000000 --- a/tests/test_samurai_quickfix_js_spec.lua +++ /dev/null @@ -1,97 +0,0 @@ -local test_samurai = require("test-samurai") -local core = require("test-samurai.core") - -local function close_output_container() - local keys = vim.api.nvim_replace_termcodes("", 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 stub_jobstart(opts_config) - local orig = vim.fn.jobstart - local config = opts_config or {} - vim.fn.jobstart = function(_cmd, opts) - local out = config.stdout 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 - if opts and opts.on_exit then - opts.on_exit(1, config.exit_code or 0, nil) - end - return 1 - end - return orig -end - -describe("test-samurai quickfix (js)", function() - before_each(function() - test_samurai.setup() - end) - - after_each(function() - close_output_container() - vim.fn.setqflist({}, "r") - end) - - it("mappt jest-verbose Failures auf die Zeile des Tests", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_js") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_qf.test.ts" - vim.fn.writefile({ - 'describe("outer", function() {', - ' it("inner 1", function() {', - " })", - "", - ' it("inner 2", function() {', - " })", - "})", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_set_current_buf(bufnr) - - local fail_symbol = string.char(0xE2, 0x9C, 0x95) - local orig_jobstart = stub_jobstart({ - exit_code = 1, - stdout = { " " .. fail_symbol .. " inner 2" }, - }) - - core.run_file() - - local qf = vim.fn.getqflist() - assert.equals(1, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(5, qf[1].lnum) - - vim.fn.jobstart = orig_jobstart - end) -end) diff --git a/tests/test_samurai_quickfix_spec.lua b/tests/test_samurai_quickfix_spec.lua deleted file mode 100644 index ea6009f..0000000 --- a/tests/test_samurai_quickfix_spec.lua +++ /dev/null @@ -1,409 +0,0 @@ -local test_samurai = require("test-samurai") -local core = require("test-samurai.core") - -local function close_output_container() - local keys = vim.api.nvim_replace_termcodes("", 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 stub_jobstart(opts_config) - local orig = vim.fn.jobstart - local idx = 0 - local config = opts_config or {} - vim.fn.jobstart = function(cmd, opts) - idx = idx + 1 - 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 - if opts and opts.on_exit then - opts.on_exit(1, code, nil) - end - return 1 - end - return orig -end - -describe("test-samurai quickfix", function() - before_each(function() - test_samurai.setup() - end) - - after_each(function() - close_output_container() - vim.fn.setqflist({}, "r") - end) - - it("fuellt die Quickfix-Liste mit Fehltests und leert sie bei Erfolg", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestFoo(t *testing.T) {", - ' t.Run("bar", func(t *testing.T) {', - " })", - "}", - "", - "func TestBaz(t *testing.T) {", - "}", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_set_current_buf(bufnr) - - local orig_jobstart = stub_jobstart({ - exit_codes = { 1, 0 }, - stdout = { - { - vim.json.encode({ Action = "fail", Test = "TestFoo/bar" }), - vim.json.encode({ Action = "fail", Test = "TestBaz" }), - }, - { vim.json.encode({ Action = "pass", Test = "TestFoo" }) }, - }, - }) - - core.run_file() - - local first = vim.fn.getqflist() - assert.equals(2, #first) - assert.equals(path, vim.fn.bufname(first[1].bufnr)) - assert.equals(4, first[1].lnum) - assert.equals(path, vim.fn.bufname(first[2].bufnr)) - assert.equals(8, first[2].lnum) - - close_output_container() - vim.api.nvim_set_current_buf(bufnr) - - core.run_file() - - local second = vim.fn.getqflist() - assert.equals(0, #second) - - vim.fn.jobstart = orig_jobstart - end) - - it("enthaelt bei Go auch Eltern- und Subtest-Failures im Quickfix", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_sub") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_sub_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run("evergreen", func(t *testing.T) {', - " })", - "", - ' t.Run("everred", func(t *testing.T) {', - " })", - "}", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run("evergreen", func(t *testing.T) {', - " })", - "", - ' t.Run("everred", func(t *testing.T) {', - " })", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 6, 0 }) - - local orig_jobstart = stub_jobstart({ - exit_codes = { 1 }, - stdout = { - { - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }), - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }), - }, - }, - }) - - core.run_nearest() - - local qf = vim.fn.getqflist() - assert.equals(2, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(path, vim.fn.bufname(qf[2].bufnr)) - local lines = { qf[1].lnum, qf[2].lnum } - table.sort(lines) - assert.are.same({ 3, 7 }, lines) - - vim.fn.jobstart = orig_jobstart - end) - - it("vereinigt Failures aus Parser und Scope fuer Go", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_union") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_union_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run(\"evergreen\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"everred\", func(t *testing.T) {', - " })", - "}", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run(\"evergreen\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"everred\", func(t *testing.T) {', - " })", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 6, 0 }) - - local go = require("test-samurai.runners.go") - local orig_parser = go.output_parser - go.output_parser = function() - local seen = 0 - return { - on_line = function() - seen = seen + 1 - if seen == 1 then - return { - passes = {}, - failures = { "TestAwesomeThing/everred" }, - skips = {}, - display = { passes = {}, failures = { "everred" }, skips = {} }, - failures_all = { "TestAwesomeThing" }, - } - end - return { - passes = {}, - failures = { "TestAwesomeThing" }, - skips = {}, - display = { passes = {}, failures = { "TestAwesomeThing" }, skips = {} }, - failures_all = { "TestAwesomeThing" }, - } - end, - on_complete = function() - return nil - end, - } - end - - local orig_jobstart = stub_jobstart({ - exit_codes = { 1 }, - stdout = { - { - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }), - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }), - }, - }, - }) - - core.run_nearest() - - local qf = vim.fn.getqflist() - assert.equals(2, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(path, vim.fn.bufname(qf[2].bufnr)) - local lines = { qf[1].lnum, qf[2].lnum } - table.sort(lines) - assert.are.same({ 3, 7 }, lines) - - vim.fn.jobstart = orig_jobstart - go.output_parser = orig_parser - end) - - it("nutzt Listing-Namen wenn Parser keine Failure-Liste liefert", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_listing") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_listing_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run(\"evergreen\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"everred\", func(t *testing.T) {', - " })", - "}", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestAwesomeThing(t *testing.T) {", - ' t.Run(\"evergreen\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"everred\", func(t *testing.T) {', - " })", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 6, 0 }) - - local go = require("test-samurai.runners.go") - local orig_parser = go.output_parser - go.output_parser = function() - local step = 0 - return { - on_line = function() - step = step + 1 - if step == 1 then - return { - passes = {}, - failures = {}, - skips = {}, - display = { passes = {}, failures = { "TestAwesomeThing" }, skips = {} }, - } - end - return { - passes = {}, - failures = {}, - skips = {}, - display = { passes = {}, failures = { "everred" }, skips = {} }, - } - end, - on_complete = function() - return nil - end, - } - end - - local orig_jobstart = stub_jobstart({ - exit_codes = { 1 }, - stdout = { - { - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing/everred" }), - vim.json.encode({ Action = "fail", Test = "TestAwesomeThing" }), - }, - }, - }) - - core.run_nearest() - - local qf = vim.fn.getqflist() - assert.equals(2, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(path, vim.fn.bufname(qf[2].bufnr)) - local lines = { qf[1].lnum, qf[2].lnum } - table.sort(lines) - assert.are.same({ 3, 7 }, lines) - - vim.fn.jobstart = orig_jobstart - go.output_parser = orig_parser - end) - - it("mappt Go-Subtests mit durch Unterstriche normalisierten Namen", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_go_norm") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_norm_test.go" - vim.fn.writefile({ - "package foo", - "", - "func TestHandleGet(t *testing.T) {", - ' t.Run(\"returns 200 with an list of all badges\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"returns 500 on any db error\", func(t *testing.T) {', - " })", - "}", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "go" - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { - "package foo", - "", - "func TestHandleGet(t *testing.T) {", - ' t.Run(\"returns 200 with an list of all badges\", func(t *testing.T) {', - " })", - "", - ' t.Run(\"returns 500 on any db error\", func(t *testing.T) {', - " })", - "}", - }) - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_win_set_cursor(0, { 6, 0 }) - - local orig_jobstart = stub_jobstart({ - exit_codes = { 1 }, - stdout = { - { - vim.json.encode({ - Action = "fail", - Test = "TestHandleGet/returns_500_on_any_db_error", - }), - vim.json.encode({ Action = "fail", Test = "TestHandleGet" }), - }, - }, - }) - - core.run_nearest() - - local qf = vim.fn.getqflist() - assert.equals(2, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(path, vim.fn.bufname(qf[2].bufnr)) - local lines = { qf[1].lnum, qf[2].lnum } - table.sort(lines) - assert.are.same({ 3, 7 }, lines) - - vim.fn.jobstart = orig_jobstart - end) -end) diff --git a/tests/test_samurai_quickfix_vitest_spec.lua b/tests/test_samurai_quickfix_vitest_spec.lua deleted file mode 100644 index 6377a28..0000000 --- a/tests/test_samurai_quickfix_vitest_spec.lua +++ /dev/null @@ -1,102 +0,0 @@ -local test_samurai = require("test-samurai") -local core = require("test-samurai.core") - -local function close_output_container() - local keys = vim.api.nvim_replace_termcodes("", 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 stub_jobstart(opts_config) - local orig = vim.fn.jobstart - local config = opts_config or {} - vim.fn.jobstart = function(_cmd, opts) - local out = config.stdout 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 - if opts and opts.on_exit then - opts.on_exit(1, config.exit_code or 0, nil) - end - return 1 - end - return orig -end - -describe("test-samurai quickfix (vitest)", function() - before_each(function() - test_samurai.setup() - end) - - after_each(function() - close_output_container() - vim.fn.setqflist({}, "r") - end) - - it("mappt tap-flat Failures mit >-Trenner auf die Testzeile", function() - local root = vim.fs.joinpath(vim.loop.cwd(), "tests", "tmp_qf_vitest") - vim.fn.mkdir(root, "p") - local path = root .. "/foo_qf.test.ts" - local pkg = root .. "/package.json" - vim.fn.writefile({ - "{", - ' "devDependencies": { "vitest": "^1.0.0" }', - "}", - }, pkg) - vim.fn.writefile({ - 'describe("outer", function() {', - ' it("inner 1", function() {', - " })", - "", - ' it("inner 2", function() {', - " })", - "})", - }, path) - - local bufnr = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr, path) - vim.bo[bufnr].filetype = "typescript" - vim.api.nvim_set_current_buf(bufnr) - - local orig_jobstart = stub_jobstart({ - exit_code = 1, - stdout = { "not ok 1 - outer > inner 2 # time=12.3ms" }, - }) - - core.run_file() - - local qf = vim.fn.getqflist() - assert.equals(1, #qf) - assert.equals(path, vim.fn.bufname(qf[1].bufnr)) - assert.equals(5, qf[1].lnum) - - vim.fn.jobstart = orig_jobstart - end) -end) diff --git a/tests/tmp_mocha_jump/package.json b/tests/tmp_mocha_jump/package.json deleted file mode 100644 index 4dcce0b..0000000 --- a/tests/tmp_mocha_jump/package.json +++ /dev/null @@ -1 +0,0 @@ -{ "name": "mocha-jump" } diff --git a/tests/tmp_mocha_jump/test/one.test.js b/tests/tmp_mocha_jump/test/one.test.js deleted file mode 100644 index 74d6755..0000000 --- a/tests/tmp_mocha_jump/test/one.test.js +++ /dev/null @@ -1,4 +0,0 @@ -describe("suite one", function() { - it("test one", function() { - }) -}) diff --git a/tests/tmp_mocha_jump/test/two.test.js b/tests/tmp_mocha_jump/test/two.test.js deleted file mode 100644 index 4c378d0..0000000 --- a/tests/tmp_mocha_jump/test/two.test.js +++ /dev/null @@ -1,4 +0,0 @@ -describe("suite two", function() { - it("test two", function() { - }) -}) diff --git a/tests/tmp_qf/foo_test.go b/tests/tmp_qf/foo_test.go deleted file mode 100644 index ef7bc0d..0000000 --- a/tests/tmp_qf/foo_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package foo - -func TestFoo(t *testing.T) { - t.Run("bar", func(t *testing.T) { - }) -} - -func TestBaz(t *testing.T) { -} diff --git a/tests/tmp_qf_go_listing/foo_listing_test.go b/tests/tmp_qf_go_listing/foo_listing_test.go deleted file mode 100644 index 9d9a9b1..0000000 --- a/tests/tmp_qf_go_listing/foo_listing_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package foo - -func TestAwesomeThing(t *testing.T) { - t.Run("evergreen", func(t *testing.T) { - }) - - t.Run("everred", func(t *testing.T) { - }) -} diff --git a/tests/tmp_qf_go_norm/foo_norm_test.go b/tests/tmp_qf_go_norm/foo_norm_test.go deleted file mode 100644 index a11d9e6..0000000 --- a/tests/tmp_qf_go_norm/foo_norm_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package foo - -func TestHandleGet(t *testing.T) { - t.Run("returns 200 with an list of all badges", func(t *testing.T) { - }) - - t.Run("returns 500 on any db error", func(t *testing.T) { - }) -} diff --git a/tests/tmp_qf_go_sub/foo_sub_test.go b/tests/tmp_qf_go_sub/foo_sub_test.go deleted file mode 100644 index 9d9a9b1..0000000 --- a/tests/tmp_qf_go_sub/foo_sub_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package foo - -func TestAwesomeThing(t *testing.T) { - t.Run("evergreen", func(t *testing.T) { - }) - - t.Run("everred", func(t *testing.T) { - }) -} diff --git a/tests/tmp_qf_go_union/foo_union_test.go b/tests/tmp_qf_go_union/foo_union_test.go deleted file mode 100644 index 9d9a9b1..0000000 --- a/tests/tmp_qf_go_union/foo_union_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package foo - -func TestAwesomeThing(t *testing.T) { - t.Run("evergreen", func(t *testing.T) { - }) - - t.Run("everred", func(t *testing.T) { - }) -} diff --git a/tests/tmp_qf_js/foo_qf.test.ts b/tests/tmp_qf_js/foo_qf.test.ts deleted file mode 100644 index c40a8ea..0000000 --- a/tests/tmp_qf_js/foo_qf.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -describe("outer", function() { - it("inner 1", function() { - }) - - it("inner 2", function() { - }) -}) diff --git a/tests/tmp_qf_jump/jump_test.go b/tests/tmp_qf_jump/jump_test.go deleted file mode 100644 index 48fbd08..0000000 --- a/tests/tmp_qf_jump/jump_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package foo - -func TestFoo(t *testing.T) {} diff --git a/tests/tmp_qf_jump/output_detail_o_ignore_test.go b/tests/tmp_qf_jump/output_detail_o_ignore_test.go deleted file mode 100644 index cddd917..0000000 --- a/tests/tmp_qf_jump/output_detail_o_ignore_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package foo - -func TestFoo(t *testing.T) { -} diff --git a/tests/tmp_qf_jump/output_detail_o_test.go b/tests/tmp_qf_jump/output_detail_o_test.go deleted file mode 100644 index cddd917..0000000 --- a/tests/tmp_qf_jump/output_detail_o_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package foo - -func TestFoo(t *testing.T) { -} diff --git a/tests/tmp_qf_jump/output_detail_qn_test.go b/tests/tmp_qf_jump/output_detail_qn_test.go deleted file mode 100644 index cddd917..0000000 --- a/tests/tmp_qf_jump/output_detail_qn_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package foo - -func TestFoo(t *testing.T) { -} diff --git a/tests/tmp_qf_vitest/foo_qf.test.ts b/tests/tmp_qf_vitest/foo_qf.test.ts deleted file mode 100644 index c40a8ea..0000000 --- a/tests/tmp_qf_vitest/foo_qf.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -describe("outer", function() { - it("inner 1", function() { - }) - - it("inner 2", function() { - }) -}) diff --git a/tests/tmp_qf_vitest/package.json b/tests/tmp_qf_vitest/package.json deleted file mode 100644 index d9e67ee..0000000 --- a/tests/tmp_qf_vitest/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "devDependencies": { "vitest": "^1.0.0" } -}