local util = require("test-samurai.util") local runner = { name = "lua-plenary", framework = "plenary", } local function is_lua_test_path(path) if not path or path == "" then return false end if not path:match("%.lua$") then return false end if path:match("_spec%.lua$") then return true end if path:match("/tests/") then return true end return false end function runner.is_test_file(bufnr) local path = util.get_buf_path(bufnr) return is_lua_test_path(path) end local function find_repo_root(file) local root = util.find_root(file, { ".git", "tests", "lua", "plugin" }) if not root or root == "" then root = vim.loop.cwd() end return root end local function minimal_init_for(root) return vim.fs.joinpath(root, "tests", "minimal_init.lua") end local function escape_for_ex_double_quotes(s) s = s or "" s = s:gsub("\\", "\\\\") s = s:gsub('"', '\\"') return s end local function count_keyword(line, kw) local c = 0 local pat = "%f[%w]" .. kw .. "%f[%W]" for _ in line:gmatch(pat) do c = c + 1 end return c end local function find_end_lua_block(lines, start_idx) local depth = 0 local started = false for i = start_idx, #lines do local line = lines[i] local fnc = count_keyword(line, "function") local endc = count_keyword(line, "end") if fnc > 0 then depth = depth + fnc started = true end if started and endc > 0 then depth = depth - endc if depth <= 0 then return i - 1 end end end return #lines - 1 end local function collect_lua_structs(lines) local describes = {} local tests = {} for i, line in ipairs(lines) do local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]") if dname then local start0 = i - 1 local end0 = find_end_lua_block(lines, i) table.insert(describes, { kind = "describe", name = dname, start = start0, ["end"] = end0 }) else local tname = line:match("^%s*it%s*%(%s*['\"`]([^'\"`]+)['\"`]") if tname then local start0 = i - 1 local end0 = find_end_lua_block(lines, i) table.insert(tests, { kind = "it", name = tname, start = start0, ["end"] = end0 }) end end end return describes, tests end local function build_full_name(lines, idx, leaf_name) local parts = { leaf_name } for i = idx - 1, 1, -1 do local line = lines[i] local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]") if dname then table.insert(parts, 1, dname) end end return table.concat(parts, " ") end function runner.find_nearest(bufnr, row, _col) if not runner.is_test_file(bufnr) then return nil, "not a lua test file" end local lines = util.get_buf_lines(bufnr) local describes, tests = collect_lua_structs(lines) for _, t in ipairs(tests) do if row >= t.start and row <= t["end"] then local full = build_full_name(lines, t.start + 1, t.name) local file = util.get_buf_path(bufnr) local root = find_repo_root(file) return { file = file, cwd = root, test_name = t.name, full_name = full, kind = "it" } end end local best_describe = nil for _, d in ipairs(describes) do if row >= d.start and row <= d["end"] then if not best_describe or d.start >= best_describe.start then best_describe = d end end end if best_describe then local full = build_full_name(lines, best_describe.start + 1, best_describe.name) local file = util.get_buf_path(bufnr) local root = find_repo_root(file) return { file = file, cwd = root, test_name = best_describe.name, full_name = full, kind = "describe" } end local start = row + 1 if start > #lines then start = #lines elseif start < 1 then start = 1 end for i = start, 1, -1 do local line = lines[i] local tname = line:match("^%s*it%s*%(%s*['\"`]([^'\"`]+)['\"`]") if tname then local full = build_full_name(lines, i, tname) local file = util.get_buf_path(bufnr) local root = find_repo_root(file) return { file = file, cwd = root, test_name = tname, full_name = full, kind = "it" } end local dname = line:match("^%s*describe%s*%(%s*['\"`]([^'\"`]+)['\"`]") if dname then local full = build_full_name(lines, i, dname) local file = util.get_buf_path(bufnr) local root = find_repo_root(file) return { file = file, cwd = root, test_name = dname, full_name = full, kind = "describe" } end end return nil, "no test call found" end local function ex_plenary_busted_file(file, filter_name) local ex = "PlenaryBustedFile " .. vim.fn.fnameescape(file) if filter_name and filter_name ~= "" then local f = escape_for_ex_double_quotes(filter_name) ex = ex .. ' { busted_args = { "--filter", "' .. f .. '" } }' end return ex end function runner.build_command(spec) local root = spec and spec.cwd or vim.loop.cwd() local minit = minimal_init_for(root) local cmd = { "nvim", "--headless", "-u", minit, "-c", ex_plenary_busted_file(spec.file, spec.test_name), "-c", "qa", } return { cmd = cmd, cwd = root } end function runner.build_file_command(bufnr) local file = util.get_buf_path(bufnr) if not file or file == "" then return nil end local root = find_repo_root(file) local minit = minimal_init_for(root) local cmd = { "nvim", "--headless", "-u", minit, "-c", ex_plenary_busted_file(file, nil), "-c", "qa", } return { cmd = cmd, cwd = root } end function runner.build_all_command(bufnr) local file = util.get_buf_path(bufnr) local root = find_repo_root(file) local minit = minimal_init_for(root) local cmd = { "nvim", "--headless", "-u", minit, "-c", "PlenaryBustedDirectory tests", "-c", "qa", } return { cmd = cmd, cwd = root } end return runner