From 61f9a7dcb4d16c56bf9869e70507a5ded1f00629 Mon Sep 17 00:00:00 2001 From: "M.Schirmer" Date: Tue, 23 Dec 2025 23:35:40 +0100 Subject: [PATCH] add TSamFile and TSamAll command with default keymaps --- lua/test-samurai/core.lua | 123 +++++++++++++++++++------- lua/test-samurai/init.lua | 8 ++ lua/test-samurai/runners/go.lua | 34 +++++++ lua/test-samurai/runners/js-mocha.lua | 1 + lua/test-samurai/runners/js.lua | 57 ++++++++++++ plugin/test-samurai.lua | 24 ++++- tests/test_samurai_js_spec.lua | 28 +++++- tests/test_samurai_output_spec.lua | 47 +++++++++- 8 files changed, 281 insertions(+), 41 deletions(-) diff --git a/lua/test-samurai/core.lua b/lua/test-samurai/core.lua index 4d280be..72a52d0 100644 --- a/lua/test-samurai/core.lua +++ b/lua/test-samurai/core.lua @@ -250,6 +250,11 @@ local function append_lines(buf, new_lines) end local existing = vim.api.nvim_buf_line_count(buf) vim.api.nvim_buf_set_lines(buf, existing, existing, false, new_lines) + + if state.last_win and vim.api.nvim_win_is_valid(state.last_win) then + local total = vim.api.nvim_buf_line_count(buf) + vim.api.nvim_win_set_cursor(state.last_win, { total, 0 }) + end end local function run_cmd(cmd, cwd, handlers) @@ -292,40 +297,7 @@ local function run_cmd(cmd, cwd, handlers) }) end -function M.run_nearest() - local bufnr = vim.api.nvim_get_current_buf() - local pos = vim.api.nvim_win_get_cursor(0) - local row = pos[1] - 1 - local col = pos[2] - - local runner = M.get_runner_for_buf(bufnr) - if not runner then - vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) - return - end - - if type(runner.find_nearest) ~= "function" or type(runner.build_command) ~= "function" then - vim.notify("[test-samurai] Runner missing methods", vim.log.levels.ERROR) - return - end - - local ok, spec_or_err = pcall(runner.find_nearest, bufnr, row, col) - if not ok or not spec_or_err then - local msg = "[test-samurai] No test found" - if type(spec_or_err) == "string" then - msg = "[test-samurai] " .. spec_or_err - end - vim.notify(msg, vim.log.levels.WARN) - return - end - local spec = spec_or_err - - local ok_cmd, command = pcall(runner.build_command, spec) - if not ok_cmd or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then - vim.notify("[test-samurai] Runner failed to build command", vim.log.levels.ERROR) - return - end - +local function run_command(command) local cmd = command.cmd local cwd = command.cwd or vim.loop.cwd() @@ -378,6 +350,89 @@ function M.run_nearest() }) end +function M.run_nearest() + local bufnr = vim.api.nvim_get_current_buf() + local pos = vim.api.nvim_win_get_cursor(0) + local row = pos[1] - 1 + local col = pos[2] + + local runner = M.get_runner_for_buf(bufnr) + if not runner then + vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + return + end + + if type(runner.find_nearest) ~= "function" or type(runner.build_command) ~= "function" then + vim.notify("[test-samurai] Runner missing methods", vim.log.levels.ERROR) + return + end + + local ok, spec_or_err = pcall(runner.find_nearest, bufnr, row, col) + if not ok or not spec_or_err then + local msg = "[test-samurai] No test found" + if type(spec_or_err) == "string" then + msg = "[test-samurai] " .. spec_or_err + end + vim.notify(msg, vim.log.levels.WARN) + return + end + local spec = spec_or_err + + local ok_cmd, command = pcall(runner.build_command, spec) + if not ok_cmd or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then + vim.notify("[test-samurai] Runner failed to build command", vim.log.levels.ERROR) + return + end + + run_command(command) +end + +function M.run_file() + local bufnr = vim.api.nvim_get_current_buf() + + local runner = M.get_runner_for_buf(bufnr) + if not runner then + vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + return + end + + if type(runner.build_file_command) ~= "function" then + vim.notify("[test-samurai] Runner does not support file-level execution", vim.log.levels.WARN) + return + end + + local ok_cmd, command = pcall(runner.build_file_command, bufnr) + if not ok_cmd or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then + vim.notify("[test-samurai] Runner failed to build file command", vim.log.levels.ERROR) + return + end + + run_command(command) +end + +function M.run_all() + local bufnr = vim.api.nvim_get_current_buf() + + local runner = M.get_runner_for_buf(bufnr) + if not runner then + vim.notify("[test-samurai] No runner for this file", vim.log.levels.WARN) + return + end + + if type(runner.build_all_command) ~= "function" then + vim.notify("[test-samurai] Runner does not support project-level execution", vim.log.levels.WARN) + return + end + + local ok_cmd, command = pcall(runner.build_all_command, bufnr) + if not ok_cmd or not command or type(command.cmd) ~= "table" or #command.cmd == 0 then + vim.notify("[test-samurai] Runner failed to build all-tests command", vim.log.levels.ERROR) + return + end + + run_command(command) +end + function M.show_output() if not (state.last_buf and vim.api.nvim_buf_is_valid(state.last_buf)) then vim.notify("[test-samurai] No previous output", vim.log.levels.WARN) diff --git a/lua/test-samurai/init.lua b/lua/test-samurai/init.lua index 58ee7ec..91f2a4e 100644 --- a/lua/test-samurai/init.lua +++ b/lua/test-samurai/init.lua @@ -12,6 +12,14 @@ function M.test_nearest() core.run_nearest() end +function M.test_file() + core.run_file() +end + +function M.test_all() + core.run_all() +end + function M.show_output() core.show_output() end diff --git a/lua/test-samurai/runners/go.lua b/lua/test-samurai/runners/go.lua index a0d3550..06cf2ee 100644 --- a/lua/test-samurai/runners/go.lua +++ b/lua/test-samurai/runners/go.lua @@ -172,4 +172,38 @@ function runner.build_command(spec) } end +function runner.build_file_command(bufnr) + local path = util.get_buf_path(bufnr) + if not path or path == "" then + return nil + end + local root = util.find_root(path, { "go.mod", ".git" }) + if not root or root == "" then + root = vim.loop.cwd() + end + local spec = { file = path, cwd = root } + local pkg = build_pkg_arg(spec) + local cmd = { "go", "test", "-v", pkg } + return { + cmd = cmd, + cwd = root, + } +end + +function runner.build_all_command(bufnr) + local path = util.get_buf_path(bufnr) + local root + if path and path ~= "" then + root = util.find_root(path, { "go.mod", ".git" }) + end + if not root or root == "" then + root = vim.loop.cwd() + end + local cmd = { "go", "test", "-v", "./..." } + return { + cmd = cmd, + cwd = root, + } +end + return runner diff --git a/lua/test-samurai/runners/js-mocha.lua b/lua/test-samurai/runners/js-mocha.lua index c76f0dd..8664f3e 100644 --- a/lua/test-samurai/runners/js-mocha.lua +++ b/lua/test-samurai/runners/js-mocha.lua @@ -4,4 +4,5 @@ return js.new({ name = "js-mocha", framework = "mocha", command = { "npx", "mocha" }, + all_glob = "test/**/*.test.js", }) diff --git a/lua/test-samurai/runners/js.lua b/lua/test-samurai/runners/js.lua index d8de703..f471e78 100644 --- a/lua/test-samurai/runners/js.lua +++ b/lua/test-samurai/runners/js.lua @@ -220,6 +220,7 @@ function M.new(opts) runner.name = cfg.name or "js" runner.framework = cfg.framework or "jest" runner.command = cfg.command or { "npx", runner.framework } + runner.all_glob = cfg.all_glob runner.filetypes = {} if cfg.filetypes then @@ -312,6 +313,62 @@ function M.new(opts) } end + function runner.build_file_command(bufnr) + local path = util.get_buf_path(bufnr) + if not path or path == "" then + return nil + end + local root + if runner.framework == "jest" then + root = find_jest_root(path) + else + root = util.find_root(path, { + "jest.config.js", + "jest.config.ts", + "vitest.config.ts", + "vitest.config.js", + "package.json", + "node_modules", + }) + end + local cmd = vim.deepcopy(runner.command) + table.insert(cmd, path) + return { + cmd = cmd, + cwd = root, + } + end + + function runner.build_all_command(bufnr) + local path = util.get_buf_path(bufnr) + local root + if path and path ~= "" then + if runner.framework == "jest" then + root = find_jest_root(path) + else + root = util.find_root(path, { + "jest.config.js", + "jest.config.ts", + "vitest.config.ts", + "vitest.config.js", + "package.json", + "node_modules", + }) + end + end + if not root or root == "" then + root = vim.loop.cwd() + end + local cmd = vim.deepcopy(runner.command) + if runner.framework == "mocha" and runner.all_glob then + table.insert(cmd, runner.all_glob) + end + return { + cmd = cmd, + cwd = root, + } + end + return runner end diff --git a/plugin/test-samurai.lua b/plugin/test-samurai.lua index b72f987..fa8ccb1 100644 --- a/plugin/test-samurai.lua +++ b/plugin/test-samurai.lua @@ -3,14 +3,34 @@ if vim.g.loaded_test_samurai_plugin == 1 then end vim.g.loaded_test_samurai_plugin = 1 -vim.api.nvim_create_user_command("TestNearest", function() +vim.api.nvim_create_user_command("TSamNearest", function() require("test-samurai").test_nearest() end, { desc = "test-samurai: run nearest test" }) -vim.api.nvim_create_user_command("TsShowOutput", function() +vim.api.nvim_create_user_command("TSamShowOutput", function() require("test-samurai").show_output() end, { desc = "test-samurai: show last test output" }) +vim.api.nvim_create_user_command("TSamFile", function() + require("test-samurai").test_file() +end, { desc = "test-samurai: run all tests in current file" }) + +vim.api.nvim_create_user_command("TSamAll", function() + require("test-samurai").test_all() +end, { desc = "test-samurai: run all tests in project (per runner)" }) + +vim.keymap.set("n", "tn", function() + require("test-samurai").test_nearest() +end, { desc = "test-samurai: run nearest test" }) + vim.keymap.set("n", "to", function() require("test-samurai").show_output() end, { desc = "test-samurai: show last test output" }) + +vim.keymap.set("n", "tf", function() + require("test-samurai").test_file() +end, { desc = "test-samurai: run all tests in current file" }) + +vim.keymap.set("n", "ta", function() + require("test-samurai").test_all() +end, { desc = "test-samurai: run all tests in project (per runner)" }) diff --git a/tests/test_samurai_js_spec.lua b/tests/test_samurai_js_spec.lua index 74e5808..a4c4f84 100644 --- a/tests/test_samurai_js_spec.lua +++ b/tests/test_samurai_js_spec.lua @@ -1,5 +1,6 @@ local jest = require("test-samurai.runners.js-jest") local mocha = require("test-samurai.runners.js-mocha") +local util = require("test-samurai.util") describe("test-samurai js runner (jest)", function() it("detects JS/TS test files by name and filetype", function() @@ -36,8 +37,7 @@ describe("test-samurai js runner (jest)", function() return { "/tmp/package.json" } end - -- Cursor in der zweiten it()-Body - local row_inside_second = 6 -- 0-basiert -> Zeile mit "-- inside 2" + local row_inside_second = 6 local spec, err = jest.find_nearest(bufnr, row_inside_second, 0) vim.fs.find = orig_fs_find @@ -76,8 +76,7 @@ describe("test-samurai js runner (jest)", function() return { "/tmp/package.json" } end - -- Cursor auf der Leerzeile zwischen den beiden it()-Blöcken - local row_between = 4 -- 0-basiert -> leere Zeile zwischen den Tests + local row_between = 4 local spec, err = jest.find_nearest(bufnr, row_between, 0) vim.fs.find = orig_fs_find @@ -139,4 +138,25 @@ describe("test-samurai js runner (mocha)", function() ) assert.equals("/tmp/project", cmd_spec.cwd) end) + + it("builds mocha all command with default glob", function() + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(bufnr, "/tmp/project/test/foo_all.test.js") + vim.bo[bufnr].filetype = "javascript" + + local orig_find_root = util.find_root + util.find_root = function(path, markers) + return "/tmp/project" + end + + local cmd_spec = mocha.build_all_command(bufnr) + + util.find_root = orig_find_root + + assert.are.same( + { "npx", "mocha", "test/**/*.test.js" }, + cmd_spec.cmd + ) + assert.equals("/tmp/project", cmd_spec.cwd) + end) end) diff --git a/tests/test_samurai_output_spec.lua b/tests/test_samurai_output_spec.lua index a3d2272..541cf30 100644 --- a/tests/test_samurai_output_spec.lua +++ b/tests/test_samurai_output_spec.lua @@ -1,7 +1,7 @@ local test_samurai = require("test-samurai") local core = require("test-samurai.core") -describe("test-samurai output API", function() +describe("test-samurai public API", function() it("delegates show_output to core", function() local called = false local orig = core.show_output @@ -16,4 +16,49 @@ describe("test-samurai output API", function() assert.is_true(called) end) + + it("delegates test_nearest to core.run_nearest", function() + local called = false + local orig = core.run_nearest + + core.run_nearest = function() + called = true + end + + test_samurai.test_nearest() + + core.run_nearest = orig + + assert.is_true(called) + end) + + it("delegates test_file to core.run_file", function() + local called = false + local orig = core.run_file + + core.run_file = function() + called = true + end + + test_samurai.test_file() + + core.run_file = orig + + assert.is_true(called) + end) + + it("delegates test_all to core.run_all", function() + local called = false + local orig = core.run_all + + core.run_all = function() + called = true + end + + test_samurai.test_all() + + core.run_all = orig + + assert.is_true(called) + end) end)