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() 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,spec}.{t,j}s")) 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. (/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("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 }, error = { message = "oops", stack = "Error: oops\n at Context. (/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 }, }), 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) end) it("does not return results on complete", 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.is_nil(results) end) end)