From 7218ed0c4c4e0e56b096a79ea2d0794d56a1c992 Mon Sep 17 00:00:00 2001 From: "M.Schirmer" Date: Sun, 4 Jan 2026 13:49:41 +0100 Subject: [PATCH] change test runner to plenary.busted --- run_test.sh | 3 +- tests/minimal_init.lua | 15 ++ tests/run.lua | 252 ------------------------------- tests/test_mocha_runner_spec.lua | 200 ++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 254 deletions(-) create mode 100644 tests/minimal_init.lua delete mode 100644 tests/run.lua create mode 100644 tests/test_mocha_runner_spec.lua diff --git a/run_test.sh b/run_test.sh index 719706e..9ce4250 100755 --- a/run_test.sh +++ b/run_test.sh @@ -1,4 +1,3 @@ #!/usr/bin/env bash -set -euo pipefail -nvim --headless -u NONE -c "lua dofile('tests/run.lua')" -c "qa" +nvim --headless -u tests/minimal_init.lua -c "PlenaryBustedDirectory tests" -c qa diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua new file mode 100644 index 0000000..f1bfe85 --- /dev/null +++ b/tests/minimal_init.lua @@ -0,0 +1,15 @@ +vim.cmd("set rtp^=.") + +local data_path = vim.fn.stdpath("data") +local plenary_paths = { + data_path .. "/site/pack/packer/start/plenary.nvim", + data_path .. "/lazy/plenary.nvim", +} + +for _, path in ipairs(plenary_paths) do + if vim.fn.isdirectory(path) == 1 then + vim.cmd("set rtp^=" .. path) + end +end + +vim.cmd("runtime! plugin/plenary.vim") diff --git a/tests/run.lua b/tests/run.lua deleted file mode 100644 index 82080fe..0000000 --- a/tests/run.lua +++ /dev/null @@ -1,252 +0,0 @@ -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 diff --git a/tests/test_mocha_runner_spec.lua b/tests/test_mocha_runner_spec.lua new file mode 100644 index 0000000..44f5046 --- /dev/null +++ b/tests/test_mocha_runner_spec.lua @@ -0,0 +1,200 @@ +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 test > suite > file priority", function() + with_project({ + ["package.json"] = [[{"devDependencies":{"mocha":"10.0.0"}}]], + }, function(root) + 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)) + assert.equals("test", spec_test.kind) + assert.equals("Math/adds", spec_test.full_name) + + local spec_suite = assert(runner.find_nearest(bufnr, 5, 0)) + assert.equals("suite", spec_suite.kind) + assert.equals("Math", spec_suite.full_name) + + local spec_file = assert(runner.find_nearest(bufnr, 11, 0)) + assert.equals("file", spec_file.kind) + 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 mocha json-stream 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, "--reporter")) + assert.is_true(vim.tbl_contains(command.cmd, "json-stream")) + 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 = { + [=["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 + 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) + 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({ + [=["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:gsub("\n", "]\n[") .. "]") + assert.equals("Math/subs", results.failures[1]) + + local outputs = runner.parse_test_output("[" .. output:gsub("\n", "]\n[") .. "]") + assert.is_true(outputs["Math/subs"] ~= nil) + end) + + it("does not return results on complete", function() + 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) + assert.is_nil(results) + end) +end)