diff --git a/.gitignore b/.gitignore index 69f397a..c33696b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ metals.sbt # Other .DS_Store +.nvim-test-state +.nvimlog diff --git a/README.md b/README.md index bc813bf..748661e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Support levels below describe **test execution + result reporting** in neotest. | Library | Test type | Build tool | Support | Notes | |---------|-----------|------------|---------|-------| | ScalaTest | `AnyFunSuite`, `AnyFreeSpec`, `AnyFlatSpec` | `sbt` | **Full** | Stable path via JUnit XML reports. | -| ScalaTest | `AnyFunSuite`, `AnyFreeSpec`, `AnyFlatSpec` | `bloop` | **Limited** | Can run, but report timing can lag (results may appear from previous run). | +| ScalaTest | `AnyFunSuite`, `AnyFreeSpec`, `AnyFlatSpec` | `bloop` | **Limited** | Uses stdout parsing for results (with additional JUnit report flags passed to runner); matching is best-effort vs XML. | | munit | `FunSuite` | `sbt` | **Full** | Stable path via JUnit XML reports. | | munit | `FunSuite` | `bloop` | **Limited** | Uses stdout parsing; works for common output, but parser-based matching is inherently less stable than XML. | | specs2 | `mutable.Specification` | `sbt` | **Limited** | General execution works, but single-test selection can still run a larger scope/spec. | @@ -38,9 +38,9 @@ Support levels below describe **test execution + result reporting** in neotest. | specs2 | text spec (`s2""" ... """`) | `sbt` | **Limited** | Execution works, but fine-grained single-test runs are limited. | | specs2 | text spec (`s2""" ... """`) | `bloop` | **Limited** | Same single-test limits plus stdout parsing constraints. | | zio-test | `ZIOSpecDefault` | `sbt` | **Full** | Stable path via JUnit XML reports. | -| zio-test | `ZIOSpecDefault` | `bloop` | **Limited** | Parallel suite output can interleave and break reliable parsing; `@@ TestAspect.sequential` is the practical workaround. | +| zio-test | `ZIOSpecDefault` | `bloop` | **Not supported** | Automatically forced to `sbt` (`bloop` execution is disabled for this framework). | | uTest | `TestSuite` | `sbt` | **Full** | Works for run/result flow; debug single-test remains constrained by uTest selector limitations. | -| uTest | `TestSuite` | `bloop` | **Not supported** | Known issue: bloop may not discover/run uTest suites (`No test suites were run`). | +| uTest | `TestSuite` | `bloop` | **Not supported** | Known issue: uTest suites can't be discovered by bloop; tests will be run by sbt. | > Recommendation: prefer `sbt` for stability. Use `bloop` when speed matters and current framework limitations are acceptable. diff --git a/lua/neotest-scala/build.lua b/lua/neotest-scala/build.lua index 095fc76..4b2fa1f 100644 --- a/lua/neotest-scala/build.lua +++ b/lua/neotest-scala/build.lua @@ -110,9 +110,11 @@ function M.get_tool_from_build_target_info(build_target_info) for _, value in ipairs(values) do local value_l = string.lower(tostring(value)) - if value_l:find("/.bloop/", 1, true) + if + value_l:find("/.bloop/", 1, true) or value_l:find("\\.bloop\\", 1, true) - or value_l:find("bloop%-bsp%-clients%-classes") then + or value_l:find("bloop%-bsp%-clients%-classes") + then return true end end @@ -122,13 +124,14 @@ function M.get_tool_from_build_target_info(build_target_info) local scala_classpath = build_target_info["Scala Classpath"] local scala_classes_directory = build_target_info["Scala Classes Directory"] - if (type(scala_classpath) == "table" and #scala_classpath > 0) - or (type(scala_classes_directory) == "table" and #scala_classes_directory > 0) then + if + (type(scala_classpath) == "table" and #scala_classpath > 0) + or (type(scala_classes_directory) == "table" and #scala_classes_directory > 0) + then return "sbt" end - if has_bloop_path(build_target_info["Classes Directory"]) - or has_bloop_path(build_target_info["Classpath"]) then + if has_bloop_path(build_target_info["Classes Directory"]) or has_bloop_path(build_target_info["Classpath"]) then return "bloop" end diff --git a/lua/neotest-scala/framework.lua b/lua/neotest-scala/framework.lua index 2d66f47..3ac1808 100644 --- a/lua/neotest-scala/framework.lua +++ b/lua/neotest-scala/framework.lua @@ -22,7 +22,7 @@ local FRAMEWORK_MARKERS = { specs2 = { "org%.specs2", "extends%s+Specification", - "s2\"\"\"", + 's2"""', }, utest = { "import%s+utest", @@ -155,7 +155,11 @@ function M.select_framework_tree(opts) local is_better = best_score == nil or score > best_score or (score == best_score and test_count > best_test_count) - or (score == best_score and test_count == best_test_count and namespace_count > best_namespace_count) + or ( + score == best_score + and test_count == best_test_count + and namespace_count > best_namespace_count + ) if is_better then best_score = score diff --git a/lua/neotest-scala/framework/scalatest/init.lua b/lua/neotest-scala/framework/scalatest/init.lua index 7787a69..54bb9ef 100644 --- a/lua/neotest-scala/framework/scalatest/init.lua +++ b/lua/neotest-scala/framework/scalatest/init.lua @@ -58,7 +58,7 @@ function M.discover_positions(opts) arguments: (arguments (string) @test.name)) )) @test.definition ]] - elseif style == "freespec" then + elseif style == "freespec" then -- FreeSpec: "name" - { } and "name" in { } query = [[ (object_definition @@ -75,11 +75,11 @@ function M.discover_positions(opts) right: (_) ) @test.definition ]] - else - -- FlatSpec: - -- "A Stack" should "pop values" in { } - -- it should "throw..." in { } - query = [[ + else + -- FlatSpec: + -- "A Stack" should "pop values" in { } + -- it should "throw..." in { } + query = [[ (object_definition name: (identifier) @namespace.name ) @namespace.definition diff --git a/lua/neotest-scala/framework/specs2/textspec.lua b/lua/neotest-scala/framework/specs2/textspec.lua index 618349d..1a5a33a 100644 --- a/lua/neotest-scala/framework/specs2/textspec.lua +++ b/lua/neotest-scala/framework/specs2/textspec.lua @@ -103,8 +103,7 @@ function M.discover_positions(opts) local package_name = utils.get_package_name(path) or "" -- Find the class/object name - local class_name = content:match("class%s+([%w_]+)%s*extends") - or content:match("object%s+([%w_]+)%s*extends") + local class_name = content:match("class%s+([%w_]+)%s*extends") or content:match("object%s+([%w_]+)%s*extends") if not class_name then class_name = "Unknown" end diff --git a/lua/neotest-scala/init.lua b/lua/neotest-scala/init.lua index ef9f2bf..f3652b8 100644 --- a/lua/neotest-scala/init.lua +++ b/lua/neotest-scala/init.lua @@ -22,6 +22,10 @@ local adapter = { name = "neotest-scala" } adapter.root = lib.files.match_root_pattern("build.sbt") +---This is a placeholder for the args function, +---it will be overridden by passing a function or a table to the adapter setup opts. +---The function receives an object with the path of the test file, build target info, project name and framework, +---and should return an array of strings with extra arguments to pass to the test command. ---@param _ neotest-scala.AdapterArgsContext ---@return string[] local function get_args(_) @@ -30,7 +34,6 @@ end local cache_build_info = true - ---@async ---@param file_path string ---@return boolean diff --git a/lua/neotest-scala/metals.lua b/lua/neotest-scala/metals.lua index c870748..00a3cf5 100644 --- a/lua/neotest-scala/metals.lua +++ b/lua/neotest-scala/metals.lua @@ -270,7 +270,9 @@ end function M.cleanup() for key, task in pairs(running_tasks) do if task and task.cancel then - pcall(function() task:cancel() end) + pcall(function() + task:cancel() + end) end end running_tasks = {} diff --git a/lua/neotest-scala/results.lua b/lua/neotest-scala/results.lua index d90be1f..1e96877 100644 --- a/lua/neotest-scala/results.lua +++ b/lua/neotest-scala/results.lua @@ -101,7 +101,13 @@ function M.collect(spec, result, node) }) if not test_result then - vim.print("[neotest-scala] Framework '" .. framework.name .. "' returned no result for position '" .. position.id .. "'") + vim.print( + "[neotest-scala] Framework '" + .. framework.name + .. "' returned no result for position '" + .. position.id + .. "'" + ) test_result = { status = TEST_FAILED } end diff --git a/lua/neotest-scala/utils.lua b/lua/neotest-scala/utils.lua index ef97586..c08f05c 100644 --- a/lua/neotest-scala/utils.lua +++ b/lua/neotest-scala/utils.lua @@ -20,7 +20,6 @@ function M.has_nested_tests(test) return #test:children() > 0 end - ---Extract the highest line number for the given file from stacktrace ---ScalaTest stacktraces have multiple file references (class def, test method, etc.) ---We want the highest line number which corresponds to the actual test assertion diff --git a/tests/build_tool_switch_spec.lua b/tests/build_tool_switch_spec.lua index 3c9093e..89d4610 100644 --- a/tests/build_tool_switch_spec.lua +++ b/tests/build_tool_switch_spec.lua @@ -340,6 +340,26 @@ describe("build tool switch behavior", function() end) describe("metals buildTargetChanged handler", function() + local original_get_client_by_id + local original_schedule + local original_handler + + before_each(function() + original_get_client_by_id = vim.lsp.get_client_by_id + original_schedule = vim.schedule + original_handler = vim.lsp.handlers["metals/buildTargetChanged"] + end) + + after_each(function() + vim.lsp.get_client_by_id = original_get_client_by_id + vim.schedule = original_schedule + vim.lsp.handlers["metals/buildTargetChanged"] = original_handler + local ok, metals = pcall(require, "neotest-scala.metals") + if ok and metals.cleanup then + metals.cleanup() + end + end) + it("chains previous handler and invalidates cache for the metals root", function() local metals = require("neotest-scala.metals") metals.cleanup() @@ -357,8 +377,6 @@ describe("build tool switch behavior", function() invalidated_root = root_path end) - local original_get_client_by_id = vim.lsp.get_client_by_id - local original_schedule = vim.schedule vim.lsp.get_client_by_id = function(_) return { config = { root_dir = "/tmp/project" } } end @@ -381,9 +399,6 @@ describe("build tool switch behavior", function() metals.cleanup() assert.are.equal(previous, vim.lsp.handlers["metals/buildTargetChanged"]) - - vim.lsp.get_client_by_id = original_get_client_by_id - vim.schedule = original_schedule end) end) end) diff --git a/tests/minimal_init.lua b/tests/minimal_init.lua index 03b80c6..611c66b 100644 --- a/tests/minimal_init.lua +++ b/tests/minimal_init.lua @@ -9,6 +9,10 @@ --- -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal_init.lua'}" local root = vim.fn.getcwd() +local state_home = root .. "/.nvim-test-state" + +vim.env.XDG_STATE_HOME = state_home +vim.fn.mkdir(state_home, "p") -- Add the plugin itself to runtimepath vim.opt.runtimepath:prepend(root)