This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,157 +42,147 @@ local function get_reporter_path()
|
||||
end
|
||||
|
||||
describe("test-samurai-jest-runner", function()
|
||||
it("detects Jest test files by suffix", function()
|
||||
local bufnr1 = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr1, "/tmp/example.test.js")
|
||||
assert.is_true(runner.is_test_file(bufnr1))
|
||||
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, 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")
|
||||
assert.is_true(runner.is_test_file(bufnr2))
|
||||
local bufnr2 = vim.api.nvim_create_buf(false, true)
|
||||
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")
|
||||
assert.is_false(runner.is_test_file(bufnr3))
|
||||
local bufnr3 = vim.api.nvim_create_buf(false, true)
|
||||
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()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/math.test.js")
|
||||
local lines = {
|
||||
"describe('Math', () => {",
|
||||
" test('adds', () => {",
|
||||
" expect(1 + 1).toBe(2)",
|
||||
" })",
|
||||
"})",
|
||||
}
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, root .. "/math.test.js")
|
||||
local lines = {
|
||||
"describe('Math', () => {",
|
||||
" test('adds', () => {",
|
||||
" expect(1 + 1).toBe(2)",
|
||||
" })",
|
||||
"})",
|
||||
}
|
||||
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)
|
||||
|
||||
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.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(root, spec.cwd)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("handles multiline describe and it declarations", function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/multiline.test.js")
|
||||
local lines = {
|
||||
"describe(",
|
||||
" '<Header/>',",
|
||||
" () => {",
|
||||
" describe(",
|
||||
" 'renders properly...',",
|
||||
" () => {",
|
||||
" it(",
|
||||
" 'the teaser links',",
|
||||
" async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" }",
|
||||
" )",
|
||||
" }",
|
||||
" )",
|
||||
" }",
|
||||
")",
|
||||
}
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, root .. "/multiline.test.js")
|
||||
local lines = {
|
||||
"describe(",
|
||||
" '<Header/>',",
|
||||
" () => {",
|
||||
" describe(",
|
||||
" 'renders properly...',",
|
||||
" () => {",
|
||||
" it(",
|
||||
" 'the teaser links',",
|
||||
" async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" }",
|
||||
" )",
|
||||
" }",
|
||||
" )",
|
||||
" }",
|
||||
")",
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
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()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/between.test.js")
|
||||
local lines = {
|
||||
"describe('<Header/>', () => {",
|
||||
" describe('renders properly...', () => {",
|
||||
" it('the logo', async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" })",
|
||||
" ",
|
||||
" it('the teaser links', async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" })",
|
||||
" })",
|
||||
"})",
|
||||
}
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, root .. "/between.test.js")
|
||||
local lines = {
|
||||
"describe('<Header/>', () => {",
|
||||
" describe('renders properly...', () => {",
|
||||
" it('the logo', async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" })",
|
||||
" ",
|
||||
" it('the teaser links', async () => {",
|
||||
" expect(true).toBe(true)",
|
||||
" })",
|
||||
" })",
|
||||
"})",
|
||||
}
|
||||
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)
|
||||
|
||||
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)
|
||||
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()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/outside.test.js")
|
||||
local lines = {
|
||||
"const value = 1",
|
||||
"function helper() { return value }",
|
||||
}
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
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 spec, err = runner.find_nearest(bufnr, 1, 0)
|
||||
assert.is_nil(err)
|
||||
assert.equals("file", spec.kind)
|
||||
local cmd_spec = runner.build_command(spec)
|
||||
|
||||
local cmd_spec = runner.build_command(spec)
|
||||
|
||||
vim.fs.find = orig_fs_find
|
||||
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
"--runTestsByPath",
|
||||
cmd_spec.cmd[#cmd_spec.cmd],
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
assert.is_true(cmd_spec.cmd[#cmd_spec.cmd]:match("outside%.test%.js$") ~= nil)
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
"--runTestsByPath",
|
||||
cmd_spec.cmd[#cmd_spec.cmd],
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
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()
|
||||
@@ -218,57 +240,47 @@ describe("test-samurai-jest-runner", function()
|
||||
end)
|
||||
|
||||
it("build_file_command scopes to file", function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/foo.test.ts")
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, root .. "/foo.test.ts")
|
||||
|
||||
local orig_fs_find = vim.fs.find
|
||||
vim.fs.find = function(_, _)
|
||||
return { "/tmp/project/package.json" }
|
||||
end
|
||||
local cmd_spec = runner.build_file_command(bufnr)
|
||||
|
||||
local cmd_spec = runner.build_file_command(bufnr)
|
||||
|
||||
vim.fs.find = orig_fs_find
|
||||
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
"--runTestsByPath",
|
||||
"/tmp/project/foo.test.ts",
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
"--runTestsByPath",
|
||||
root .. "/foo.test.ts",
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
assert.equals(root, cmd_spec.cwd)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("build_all_command runs project tests", function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, "/tmp/project/bar.test.ts")
|
||||
with_project(JEST_PACKAGE, function(root)
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_name(bufnr, root .. "/bar.test.ts")
|
||||
|
||||
local orig_fs_find = vim.fs.find
|
||||
vim.fs.find = function(_, _)
|
||||
return { "/tmp/project/package.json" }
|
||||
end
|
||||
local cmd_spec = runner.build_all_command(bufnr)
|
||||
|
||||
local cmd_spec = runner.build_all_command(bufnr)
|
||||
|
||||
vim.fs.find = orig_fs_find
|
||||
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
assert.equals("/tmp/project", cmd_spec.cwd)
|
||||
assert.are.same(
|
||||
{
|
||||
"npx",
|
||||
"jest",
|
||||
"--testLocationInResults",
|
||||
"--reporters",
|
||||
get_reporter_path(),
|
||||
},
|
||||
cmd_spec.cmd
|
||||
)
|
||||
assert.equals(root, cmd_spec.cwd)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("build_failed_command narrows to failed tests", function()
|
||||
|
||||
Reference in New Issue
Block a user