Files
test-samurai-mocha-runner/tests/test_mocha_runner_spec.lua
M.Schirmer 2a1bac55b2
All checks were successful
tests / test (push) Successful in 8s
fix listing for errors that occurs within beforeEach
2026-01-08 08:56:13 +01:00

456 lines
16 KiB
Lua

local runner = require("test-samurai-mocha-runner")
local function write_file(path, content)
local dir = vim.fn.fnamemodify(path, ":h")
vim.fn.mkdir(dir, "p")
vim.fn.writefile(vim.split(content, "\n"), path)
end
local function make_buffer(path, content)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, path)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.split(content, "\n"))
return bufnr
end
local function with_project(structure, fn)
local root = vim.fn.tempname()
vim.fn.mkdir(root, "p")
root = vim.loop.fs_realpath(root) or root
for rel_path, content in pairs(structure) do
write_file(root .. "/" .. rel_path, content)
end
fn(root)
end
describe("test-samurai-mocha-runner", function()
before_each(function()
runner.setup()
end)
it("detects mocha test files via package.json", function()
with_project({
["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]],
}, function(root)
local bufnr = make_buffer(root .. "/test/sample.spec.js", "describe('x', function() {})")
assert.is_true(runner.is_test_file(bufnr))
end)
end)
it("rejects non-mocha projects", function()
with_project({
["package.json"] = [[{"devDependencies":{"jest":"29.0.0"}}]],
}, function(root)
local bufnr = make_buffer(root .. "/test/sample.spec.js", "describe('x', function() {})")
assert.is_false(runner.is_test_file(bufnr))
end)
end)
it("finds nearest with precise scope selection", function()
with_project({
["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]],
}, function(root)
local lines = {
"// #1",
"describe(\"outer\", () => {",
"",
" it(\"on stage 1\"); // #2",
"",
" it(\"i2\", () => { // #3",
" // #4",
" }); // #5",
"",
" // #6",
" describe(\"level 2\", () => { // #7",
" it(\"i3\", () => {",
" // #10",
" });",
"",
" // #8",
" it(\"i4\", () => {",
" // ...",
" });",
" }); // #9",
"});",
}
local content = table.concat(lines, "\n")
local bufnr = make_buffer(root .. "/test/math.spec.js", content)
local markers = {}
for i, line in ipairs(lines) do
local mark = line:match("//%s*#(%d+)")
if mark then
markers[tonumber(mark)] = i
end
end
local spec_file = assert(runner.find_nearest(bufnr, markers[1], 0))
assert.equals("file", spec_file.kind)
local spec_pending = assert(runner.find_nearest(bufnr, markers[2], 0))
assert.equals("test", spec_pending.kind)
assert.equals("outer/on stage 1", spec_pending.full_name)
local spec_i2_line = assert(runner.find_nearest(bufnr, markers[3], 0))
assert.equals("test", spec_i2_line.kind)
assert.equals("outer/i2", spec_i2_line.full_name)
local spec_i2_body = assert(runner.find_nearest(bufnr, markers[4], 0))
assert.equals("test", spec_i2_body.kind)
assert.equals("outer/i2", spec_i2_body.full_name)
local spec_i2_close = assert(runner.find_nearest(bufnr, markers[5], 0))
assert.equals("test", spec_i2_close.kind)
assert.equals("outer/i2", spec_i2_close.full_name)
local spec_outer = assert(runner.find_nearest(bufnr, markers[6], 0))
assert.equals("suite", spec_outer.kind)
assert.equals("outer", spec_outer.full_name)
local spec_inner_line = assert(runner.find_nearest(bufnr, markers[7], 0))
assert.equals("suite", spec_inner_line.kind)
assert.equals("outer/level 2", spec_inner_line.full_name)
local spec_inner_body = assert(runner.find_nearest(bufnr, markers[8], 0))
assert.equals("suite", spec_inner_body.kind)
assert.equals("outer/level 2", spec_inner_body.full_name)
local spec_inner_close = assert(runner.find_nearest(bufnr, markers[9], 0))
assert.equals("suite", spec_inner_close.kind)
assert.equals("outer/level 2", spec_inner_close.full_name)
local spec_i3 = assert(runner.find_nearest(bufnr, markers[10], 0))
assert.equals("test", spec_i3.kind)
assert.equals("outer/level 2/i3", spec_i3.full_name)
end)
end)
it("returns error when no package.json found", function()
local content = table.concat({
"describe(\"Math\", function() {",
" it(\"adds\", function() {",
" expect(1).to.equal(1)",
" })",
"})",
}, "\n")
local bufnr = make_buffer("/tmp/no-root.test.js", content)
local spec, err = runner.find_nearest(bufnr, 2, 0)
assert.is_nil(spec)
assert.equals("no package.json found", err)
local command = runner.build_file_command(bufnr)
assert.are.same({ "echo", "no package.json found" }, command.cmd)
end)
it("builds command with custom ui and reporter and grep", function()
with_project({
["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]],
}, function(root)
local spec = {
file = root .. "/test/math.spec.js",
cwd = root,
kind = "test",
mocha_full_title = "Math adds",
full_name = "Math/adds",
}
local command = runner.build_command(spec)
assert.equals("npx", command.cmd[1])
assert.equals("mocha", command.cmd[2])
assert.is_true(vim.tbl_contains(command.cmd, "--ui"))
assert.is_true(vim.tbl_contains(command.cmd, "bdd-with-location"))
assert.is_true(vim.tbl_contains(command.cmd, "--require"))
assert.is_true(vim.tbl_contains(command.cmd, "--reporter"))
local joined = table.concat(command.cmd, " ")
assert.is_true(joined:match("scripts/bdd%-with%-location%.cjs") ~= nil)
assert.is_true(joined:match("scripts/mocha%-ndjson%-reporter%.cjs") ~= nil)
assert.is_true(vim.tbl_contains(command.cmd, "--grep"))
end)
end)
it("builds TSamAll with test glob", function()
with_project({
["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]],
}, function(root)
local bufnr = make_buffer(root .. "/test/sample.spec.js", "")
local command = runner.build_all_command(bufnr)
assert.is_true(vim.tbl_contains(command.cmd, "test/**/*.test.js"))
end)
end)
it("allows overriding the TSamAll test glob via setup", function()
with_project({
["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]],
}, function(root)
runner.setup({ all_test_glob = "spec/**/*_spec.js" })
local bufnr = make_buffer(root .. "/test/sample.spec.js", "")
local command = runner.build_all_command(bufnr)
assert.is_true(vim.tbl_contains(command.cmd, "spec/**/*_spec.js"))
end)
end)
it("streams results without duplicates", function()
local output_lines = {
vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 5, column = 3 },
}),
vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 5, column = 3 },
}),
vim.json.encode({
event = "test",
status = "failed",
title = "subs",
fullTitle = "Math subs",
titlePath = { "Math", "subs" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 10, column = 5 },
error = { message = "oops", stack = "Error: oops\n at Context.<anonymous> (/tmp/math.spec.js:10:5)" },
}),
vim.json.encode({
event = "test",
status = "pending",
title = "skips",
fullTitle = "Math skips",
titlePath = { "Math", "skips" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 15, column = 2 },
}),
}
local parser = runner.output_parser()
local state = {}
local aggregated = { passes = {}, failures = {}, skips = {} }
for _, line in ipairs(output_lines) do
local results = parser.on_line(line, state)
if results then
for _, name in ipairs(results.passes or {}) do
table.insert(aggregated.passes, name)
end
for _, name in ipairs(results.failures or {}) do
table.insert(aggregated.failures, name)
end
for _, name in ipairs(results.skips or {}) do
table.insert(aggregated.skips, name)
end
end
end
assert.equals("Math/adds", aggregated.passes[1])
assert.equals(1, #aggregated.passes)
assert.equals("Math/subs", aggregated.failures[1])
assert.equals("Math/skips", aggregated.skips[1])
local items = runner.collect_failed_locations(aggregated.failures, {}, "nearest")
assert.equals("/tmp/math.spec.js", items[1].filename)
assert.equals(10, items[1].lnum)
assert.equals(5, items[1].col)
local pass_items = runner.collect_failed_locations({ "Math/adds" }, {}, "nearest")
assert.equals("/tmp/math.spec.js", pass_items[1].filename)
assert.equals(5, pass_items[1].lnum)
assert.equals(3, pass_items[1].col)
local skip_items = runner.collect_failed_locations({ "Math/skips" }, {}, "nearest")
assert.equals("/tmp/math.spec.js", skip_items[1].filename)
assert.equals(15, skip_items[1].lnum)
assert.equals(2, skip_items[1].col)
end)
it("captures hook failures in results and output", function()
local output = vim.json.encode({
event = "hook",
status = "failed",
hookType = "beforeEach",
titlePath = { "Math", "adds" },
fullTitle = "Math adds",
file = "/tmp/math.spec.js",
error = {
message = "boom",
stack = "Error: boom\n at Context.<anonymous> (/tmp/math.spec.js:5:3)",
},
})
local results = runner.parse_results(output)
assert.are.same({ "beforeEach: Math/adds" }, results.failures)
local outputs = runner.parse_test_output(output)
assert.is_table(outputs["beforeEach: Math/adds"])
assert.is_true(table.concat(outputs["beforeEach: Math/adds"], "\n"):match("boom") ~= nil)
local items = runner.collect_failed_locations(results.failures)
assert.are.same({
{ filename = "/tmp/math.spec.js", lnum = 5, col = 3, text = "boom" },
}, items)
end)
it("parses mocha hook failures emitted as test events", function()
local output = vim.json.encode({
event = "test",
status = "failed",
title = "\"before each\" hook for \"findAllInquiries()\"",
fullTitle = "BlacklistRepository... \"before each\" hook for \"findAllInquiries()\"",
titlePath = { "BlacklistRepository...", "\"before each\" hook for \"findAllInquiries()\"" },
file = "/tmp/BlacklistRepository.test.js",
location = nil,
error = {
message = "missing dbName",
stack = "Error: missing dbName\n at Context.<anonymous> (/tmp/BlacklistRepository.test.js:10:25)",
},
})
local results = runner.parse_results(output)
assert.are.same({
"BlacklistRepository.../\"before each\" hook for \"findAllInquiries()\"",
}, results.failures)
local outputs = runner.parse_test_output(output)
assert.is_true(table.concat(outputs[results.failures[1]], "\n"):match("missing dbName") ~= nil)
local items = runner.collect_failed_locations(results.failures)
assert.are.same({
{ filename = "/tmp/BlacklistRepository.test.js", lnum = 10, col = 25, text = "missing dbName" },
}, items)
end)
it("handles null location fields in ndjson output", function()
local output = [[{"event":"test","status":"failed","title":"\"before each\" hook for \"findAllInquiries()\"","fullTitle":"BlacklistRepository... \"before each\" hook for \"findAllInquiries()\"","titlePath":["BlacklistRepository...","\"before each\" hook for \"findAllInquiries()\""],"file":"/tmp/BlacklistRepository.test.js","location":null,"error":{"name":"Error","message":"missing dbName","stack":"Error: missing dbName\n at Context.<anonymous> (/tmp/BlacklistRepository.test.js:10:25)"}}]]
local results = runner.parse_results(output)
assert.are.same({
"BlacklistRepository.../\"before each\" hook for \"findAllInquiries()\"",
}, results.failures)
end)
it("writes debug logs when configured", function()
local log_path = vim.fn.tempname()
runner.setup({ debug_log_path = log_path })
local parser = runner.output_parser()
local state = {}
local line = vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 3, column = 2 },
})
parser.on_line(line, state)
parser.on_complete(line, state)
local lines = vim.fn.readfile(log_path)
assert.is_true(#lines >= 2)
assert.is_true(lines[1]:match("on_line_raw") ~= nil)
assert.is_true(lines[2]:match("on_complete_raw") ~= nil)
end)
it("builds failed-only command with grep pattern", function()
runner._last_mocha_titles = {
["Math/adds"] = "Math adds",
["Math/subs"] = "Math subs",
}
local command = runner.build_failed_command({ cwd = "/tmp" }, { "Math/adds", "Math/subs" }, "file")
assert.equals("npx", command.cmd[1])
assert.is_true(vim.tbl_contains(command.cmd, "--grep"))
local grep_index
for idx, value in ipairs(command.cmd) do
if value == "--grep" then
grep_index = idx + 1
break
end
end
assert.is_true(command.cmd[grep_index]:match("Math%.%*adds") ~= nil)
end)
it("parses results and per-test output", function()
local output = table.concat({
vim.json.encode({
event = "test",
status = "failed",
title = "subs",
fullTitle = "Math subs",
titlePath = { "Math", "subs" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 10, column = 5 },
output = { "log: before", "log: after" },
error = {
message = "oops",
stack = "Error: oops\n at Context.<anonymous> (/tmp/math.spec.js:10:5)\n at processImmediate",
},
}),
vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 4, column = 2 },
output = { "adds log" },
}),
vim.json.encode({
event = "test",
status = "skipped",
title = "skips",
fullTitle = "Math skips",
titlePath = { "Math", "skips" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 6, column = 2 },
}),
}, "\n")
local results = runner.parse_results(output)
assert.equals("Math/subs", results.failures[1])
local outputs = runner.parse_test_output(output)
assert.is_true(outputs["Math/subs"] ~= nil)
assert.is_true(outputs["Math/adds"] ~= nil)
assert.is_true(outputs["Math/skips"] ~= nil)
assert.equals("log: before", outputs["Math/subs"][1])
assert.equals("log: after", outputs["Math/subs"][2])
assert.equals("adds log", outputs["Math/adds"][1])
assert.equals("skipped", outputs["Math/skips"][1])
end)
it("returns results on complete when no streaming state exists", function()
local parser = runner.output_parser()
local state = {}
local output = vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 3, column = 2 },
})
local results = parser.on_complete(output, state)
assert.are.same({ "Math/adds" }, results.passes)
end)
it("does not return results on complete when streaming already handled", function()
local parser = runner.output_parser()
local state = {}
local line = vim.json.encode({
event = "test",
status = "passed",
title = "adds",
fullTitle = "Math adds",
titlePath = { "Math", "adds" },
file = "/tmp/math.spec.js",
location = { file = "/tmp/math.spec.js", line = 3, column = 2 },
})
parser.on_line(line, state)
local results = parser.on_complete(line, state)
assert.is_nil(results)
end)
end)