local function ok(condition, message) if not condition then error(message or "assertion failed") end end vim.opt.runtimepath:append(vim.fn.getcwd()) package.path = package.path .. ";" .. vim.fn.getcwd() .. "/lua/?.lua;" .. vim.fn.getcwd() .. "/lua/?/init.lua" local function eq(actual, expected, message) if actual ~= expected then error(message or string.format("expected %s, got %s", tostring(expected), tostring(actual))) end end 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") for rel_path, content in pairs(structure) do write_file(root .. "/" .. rel_path, content) end fn(root) end local function test_is_test_file_with_mocha_dependency() with_project({ ["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]], }, function(root) local runner = require("test-samurai-mocha-runner") local bufnr = make_buffer(root .. "/test/sample.spec.js", "describe('x', function() {})") ok(runner.is_test_file(bufnr), "expected mocha project to be detected") end) end local function test_is_test_file_without_mocha_dependency() with_project({ ["package.json"] = [[{"devDependencies":{"jest":"29.0.0"}}]], }, function(root) local runner = require("test-samurai-mocha-runner") local bufnr = make_buffer(root .. "/test/sample.spec.js", "describe('x', function() {})") ok(not runner.is_test_file(bufnr), "expected non-mocha project to be ignored") end) end local function test_find_nearest_priorities() with_project({ ["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]], }, function(root) local runner = require("test-samurai-mocha-runner") local content = table.concat({ "describe(\"Math\", function() {", " it(\"adds\", function() {", " expect(1).to.equal(1)", " })", "", " it(\"subs\", function() {", " expect(1).to.equal(1)", " })", "})", "", "const value = 1", }, "\n") local bufnr = make_buffer(root .. "/test/math.spec.js", content) local spec_test = assert(runner.find_nearest(bufnr, 3, 0)) eq(spec_test.kind, "test", "expected test kind") eq(spec_test.full_name, "Math/adds", "expected full test name") local spec_suite = assert(runner.find_nearest(bufnr, 5, 0)) eq(spec_suite.kind, "suite", "expected suite kind") eq(spec_suite.full_name, "Math", "expected suite name") local spec_file = assert(runner.find_nearest(bufnr, 11, 0)) eq(spec_file.kind, "file", "expected file kind") end) end local function test_missing_package_json_errors() local runner = require("test-samurai-mocha-runner") 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) ok(spec == nil, "expected no spec without package.json") eq(err, "no package.json found", "expected package.json error") local command = runner.build_file_command(bufnr) eq(command.cmd[1], "echo") eq(command.cmd[2], "no package.json found") end local function test_command_building() with_project({ ["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]], }, function(root) local runner = require("test-samurai-mocha-runner") 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) eq(command.cmd[1], "npx") eq(command.cmd[2], "mocha") ok(vim.tbl_contains(command.cmd, "--reporter"), "expected reporter flag") ok(vim.tbl_contains(command.cmd, "json-stream"), "expected json-stream reporter") ok(vim.tbl_contains(command.cmd, "--grep"), "expected grep flag") end) end local function test_build_all_command_includes_glob() with_project({ ["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]], }, function(root) local runner = require("test-samurai-mocha-runner") local bufnr = make_buffer(root .. "/test/sample.spec.js", "") local command = runner.build_all_command(bufnr) ok(vim.tbl_contains(command.cmd, "test/**/*.{test,spec}.{t,j}s"), "expected test glob") end) end local function test_output_parser_and_locations() local runner = require("test-samurai-mocha-runner") local output_lines = { [=[["suite",{"title":"Math","fullTitle":"Math"}]]=], [=[["pass",{"title":"adds","fullTitle":"Math adds","file":"/tmp/math.spec.js"}]]=], [=[["pass",{"title":"adds","fullTitle":"Math adds","file":"/tmp/math.spec.js"}]]=], [=[["fail",{"title":"subs","fullTitle":"Math subs","file":"/tmp/math.spec.js","err":"oops","stack":"Error: oops\n at Context. (/tmp/math.spec.js:10:5)"}]]=], [=[["pending",{"title":"skips","fullTitle":"Math skips","file":"/tmp/math.spec.js"}]]=], [=[["suite end",{"title":"Math","fullTitle":"Math"}]]=], } 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 eq(aggregated.passes[1], "Math/adds", "expected pass name") eq(#aggregated.passes, 1, "expected no duplicate passes") eq(aggregated.failures[1], "Math/subs", "expected failure name") eq(aggregated.skips[1], "Math/skips", "expected skip name") local items = runner.collect_failed_locations(aggregated.failures, {}, "nearest") eq(items[1].filename, "/tmp/math.spec.js", "expected filename from stack") eq(items[1].lnum, 10, "expected line from stack") eq(items[1].col, 5, "expected col from stack") end local function test_failed_only_command() local runner = require("test-samurai-mocha-runner") 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") eq(command.cmd[1], "npx") ok(vim.tbl_contains(command.cmd, "--grep"), "expected grep for failed-only") local grep_index for idx, value in ipairs(command.cmd) do if value == "--grep" then grep_index = idx + 1 break end end ok(grep_index, "expected grep argument") ok(command.cmd[grep_index]:match("Math%.%*adds"), "expected grep pattern for first failure") end local function test_parse_results_and_test_output() local runner = require("test-samurai-mocha-runner") local output = table.concat({ [=[["suite",{"title":"Math","fullTitle":"Math"}]]=], [=[["fail",{"title":"subs","fullTitle":"Math subs","file":"/tmp/math.spec.js","err":"oops","stack":"Error: oops\n at Context. (/tmp/math.spec.js:10:5)\n at processImmediate"}]]=], [=[["suite end",{"title":"Math","fullTitle":"Math"}]]=], }, "\n") local results = runner.parse_results(output) eq(results.failures[1], "Math/subs", "expected failure from parse_results") local outputs = runner.parse_test_output(output) ok(outputs["Math/subs"], "expected test output for failure") local found = false for _, line in ipairs(outputs["Math/subs"]) do if line:match("oops") then found = true break end end ok(found, "expected error output line") end local function test_output_parser_on_complete_returns_nil() local runner = require("test-samurai-mocha-runner") local parser = runner.output_parser() local state = {} local output = [=[["pass",{"title":"adds","fullTitle":"Math adds","file":"/tmp/math.spec.js"}]]=] local results = parser.on_complete(output, state) ok(results == nil, "expected nil on_complete to avoid duplicate listings") end local tests = { test_is_test_file_with_mocha_dependency, test_is_test_file_without_mocha_dependency, test_find_nearest_priorities, test_missing_package_json_errors, test_command_building, test_build_all_command_includes_glob, test_output_parser_and_locations, test_failed_only_command, test_parse_results_and_test_output, test_output_parser_on_complete_returns_nil, } for _, test_fn in ipairs(tests) do test_fn() end