fix test file detection
All checks were successful
tests / test (push) Successful in 9s

This commit is contained in:
2026-01-05 09:16:08 +01:00
parent 58ab97f757
commit aa343f67ca
4 changed files with 241 additions and 174 deletions

View File

@@ -237,3 +237,11 @@ jobs:
- name: Run tests
run: bash run_test.sh
```
## Zusaetzliche Runner-Guidelines (framework-agnostisch)
- **Testnamen-Konvention:** Runner sollen eine konsistente, dokumentierte Full-Name-Bildung verwenden (z. B. `Parent/Subtest`), inklusive Mehrfach-Nesting. Diese Konvention muss in `results.*`, `parse_test_output` und `collect_failed_locations` uebereinstimmen.
- **TSamNearest-Prioritaet:** Falls moeglich, gelten folgende Regeln: Test-Block > Describe/Context-Block > File-Command. Das Verhalten muss getestet werden (Cursor im Test, zwischen Tests, ausserhalb von Describe/Context).
- **Reporter-Payload-Schema:** Wenn ein Custom-Reporter verwendet wird, soll dessen JSON-Payload dokumentiert und stabil sein (z. B. `{ name, status, file, location, output }`), damit Parser/Quickfix/Detail-Output konsistent bleiben.
- **Failed-Only-Logik:** Failed-Only muss auf den letzten Fehlermeldungen basieren und nur die fehlerhaften Tests erneut ausfuehren. Die Pattern-Strategie (z. B. Titel-only vs. Full-Name) muss getestet werden.
- **CI-Installations-Snippet:** Die Neovim-Installation in CI soll als „authoritative snippet“ behandelt werden und in Runner-Repos 1:1 uebernommen werden.

View File

@@ -7,6 +7,7 @@ Main plugin: https://gitea.mschirmer.com/m13r/test-samurai.nvim
## Features
- Detects Jest test files (`*.test.*`, `*.spec.*`).
- Activates only when the nearest `package.json` lists `jest` in dependencies or devDependencies.
- Finds nearest `test`/`it` within `describe` blocks.
- Builds `npx jest` commands for nearest, file, all, and failed-only runs.
- Streams results via a custom Jest reporter using `onTestCaseResult`.

View File

@@ -38,6 +38,47 @@ local function find_root(path, markers)
return vim.fs.dirname(found[1])
end
local function find_nearest_package_root(path)
if not path or path == "" then
return nil
end
local dir = vim.fs.dirname(path)
local prev = nil
while dir and dir ~= prev do
local candidate = dir .. "/package.json"
if vim.fn.filereadable(candidate) == 1 then
return dir
end
prev = dir
dir = vim.fs.dirname(dir)
end
return nil
end
local function read_package_json(root)
if not root then
return nil
end
local ok, lines = pcall(vim.fn.readfile, root .. "/package.json")
if not ok then
return nil
end
local ok_decode, data = pcall(vim.json.decode, table.concat(lines, "\n"))
if not ok_decode then
return nil
end
return data
end
local function has_jest_dependency(pkg)
if not pkg or type(pkg) ~= "table" then
return false
end
local deps = pkg.dependencies or {}
local dev_deps = pkg.devDependencies or {}
return deps.jest ~= nil or dev_deps.jest ~= nil
end
local function count_char(line, ch)
local count = 0
for i = 1, #line do
@@ -346,7 +387,12 @@ end
function runner.is_test_file(bufnr)
local path = get_buf_path(bufnr)
return test_file_path(path)
if not test_file_path(path) then
return false
end
local root = find_nearest_package_root(path)
local pkg = read_package_json(root)
return has_jest_dependency(pkg)
end
function runner.find_nearest(bufnr, row, _col)
@@ -354,7 +400,7 @@ function runner.find_nearest(bufnr, row, _col)
if not path or path == "" then
return nil, "no file name"
end
if not test_file_path(path) then
if not runner.is_test_file(bufnr) then
return nil, "not a jest test file"
end
local lines = get_buf_lines(bufnr)

View File

@@ -1,5 +1,37 @@
local runner = require("test-samurai-jest-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 with_project(package_json, fn)
local root = vim.fn.tempname()
vim.fn.mkdir(root, "p")
root = vim.loop.fs_realpath(root) or root
if package_json then
write_file(root .. "/package.json", package_json)
end
fn(root)
end
local JEST_PACKAGE = [[
{
"devDependencies": {
"jest": "^29.0.0"
}
}
]]
local NO_JEST_PACKAGE = [[
{
"devDependencies": {
"mocha": "^10.0.0"
}
}
]]
local function get_reporter_path()
local source = debug.getinfo(runner.build_command, "S").source
if source:sub(1, 1) == "@" then
@@ -10,23 +42,34 @@ local function get_reporter_path()
end
describe("test-samurai-jest-runner", function()
it("detects Jest test files by suffix", function()
it("detects Jest test files by suffix and package.json", function()
with_project(JEST_PACKAGE, function(root)
local bufnr1 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr1, "/tmp/example.test.js")
vim.api.nvim_buf_set_name(bufnr1, root .. "/example.test.js")
assert.is_true(runner.is_test_file(bufnr1))
local bufnr2 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr2, "/tmp/example.spec.tsx")
vim.api.nvim_buf_set_name(bufnr2, root .. "/example.spec.tsx")
assert.is_true(runner.is_test_file(bufnr2))
local bufnr3 = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr3, "/tmp/example.js")
vim.api.nvim_buf_set_name(bufnr3, root .. "/example.js")
assert.is_false(runner.is_test_file(bufnr3))
end)
end)
it("rejects test files when jest dependency is missing", function()
with_project(NO_JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, root .. "/example.test.js")
assert.is_false(runner.is_test_file(bufnr))
end)
end)
it("finds nearest test with describe hierarchy", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/math.test.js")
vim.api.nvim_buf_set_name(bufnr, root .. "/math.test.js")
local lines = {
"describe('Math', () => {",
" test('adds', () => {",
@@ -36,27 +79,22 @@ describe("test-samurai-jest-runner", function()
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/package.json" }
end
local spec, err = runner.find_nearest(bufnr, 2, 0)
vim.fs.find = orig_fs_find
assert.is_nil(err)
assert.equals("adds", spec.test_name)
assert.equals("Math/adds", spec.full_name)
assert.equals("Math adds", spec.jest_name)
assert.are.same({ "Math", "adds" }, spec.jest_parts)
assert.is_true(spec.file:match("math%.test%.js$") ~= nil)
assert.equals("/tmp", spec.cwd)
assert.equals(root, spec.cwd)
end)
end)
it("handles multiline describe and it declarations", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/multiline.test.js")
vim.api.nvim_buf_set_name(bufnr, root .. "/multiline.test.js")
local lines = {
"describe(",
" '<Header/>',",
@@ -77,24 +115,19 @@ describe("test-samurai-jest-runner", function()
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/package.json" }
end
local spec, err = runner.find_nearest(bufnr, 9, 0)
vim.fs.find = orig_fs_find
assert.is_nil(err)
assert.equals("the teaser links", spec.test_name)
assert.equals("<Header/>/renders properly.../the teaser links", spec.full_name)
assert.are.same({ "<Header/>", "renders properly...", "the teaser links" }, spec.jest_parts)
end)
end)
it("uses describe block when cursor is between tests", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/between.test.js")
vim.api.nvim_buf_set_name(bufnr, root .. "/between.test.js")
local lines = {
"describe('<Header/>', () => {",
" describe('renders properly...', () => {",
@@ -110,44 +143,32 @@ describe("test-samurai-jest-runner", function()
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/package.json" }
end
local spec, err = runner.find_nearest(bufnr, 5, 0)
vim.fs.find = orig_fs_find
assert.is_nil(err)
assert.equals("renders properly...", spec.test_name)
assert.equals("<Header/>/renders properly...", spec.full_name)
assert.are.same({ "<Header/>", "renders properly..." }, spec.jest_parts)
assert.equals("describe", spec.kind)
end)
end)
it("falls back to file command when outside any describe", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/outside.test.js")
vim.api.nvim_buf_set_name(bufnr, root .. "/outside.test.js")
local lines = {
"const value = 1",
"function helper() { return value }",
}
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/package.json" }
end
local spec, err = runner.find_nearest(bufnr, 1, 0)
assert.is_nil(err)
assert.equals("file", spec.kind)
local cmd_spec = runner.build_command(spec)
vim.fs.find = orig_fs_find
assert.are.same(
{
"npx",
@@ -162,6 +183,7 @@ describe("test-samurai-jest-runner", function()
)
assert.is_true(cmd_spec.cmd[#cmd_spec.cmd]:match("outside%.test%.js$") ~= nil)
end)
end)
it("build_command uses npx jest with reporter and pattern", function()
local spec = {
@@ -218,18 +240,12 @@ describe("test-samurai-jest-runner", function()
end)
it("build_file_command scopes to file", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/foo.test.ts")
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/project/package.json" }
end
vim.api.nvim_buf_set_name(bufnr, root .. "/foo.test.ts")
local cmd_spec = runner.build_file_command(bufnr)
vim.fs.find = orig_fs_find
assert.are.same(
{
"npx",
@@ -238,26 +254,21 @@ describe("test-samurai-jest-runner", function()
"--reporters",
get_reporter_path(),
"--runTestsByPath",
"/tmp/project/foo.test.ts",
root .. "/foo.test.ts",
},
cmd_spec.cmd
)
assert.equals("/tmp/project", cmd_spec.cwd)
assert.equals(root, cmd_spec.cwd)
end)
end)
it("build_all_command runs project tests", function()
with_project(JEST_PACKAGE, function(root)
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/bar.test.ts")
local orig_fs_find = vim.fs.find
vim.fs.find = function(_, _)
return { "/tmp/project/package.json" }
end
vim.api.nvim_buf_set_name(bufnr, root .. "/bar.test.ts")
local cmd_spec = runner.build_all_command(bufnr)
vim.fs.find = orig_fs_find
assert.are.same(
{
"npx",
@@ -268,7 +279,8 @@ describe("test-samurai-jest-runner", function()
},
cmd_spec.cmd
)
assert.equals("/tmp/project", cmd_spec.cwd)
assert.equals(root, cmd_spec.cwd)
end)
end)
it("build_failed_command narrows to failed tests", function()