diff --git a/.phpactor.json b/.phpactor.json new file mode 100644 index 0000000..fa298f8 --- /dev/null +++ b/.phpactor.json @@ -0,0 +1,4 @@ +{ + "$schema": "/phpactor.schema.json", + "language_server_phpstan.enabled": false +} \ No newline at end of file diff --git a/README.md b/README.md index 424a1a8..d703687 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ -# neotest-pest +# neotest-phpunit -This plugin provides a [Pest](https://pestphp.com) adapter for the [Neotest](https://github.com/nvim-neotest/neotest) framework. +This plugin provides a [PHPUnit](https://phpunit.de) adapter for the [Neotest](https://github.com/nvim-neotest/neotest) framework. -This is a fork of `neotest-pest` originally by [@theutz](https://github.com/theutz/neotest-pest), with some fixes and updates: +This is a fork of `neotest-pest` originally by [@V13Axel](https://github.com/V13Axel/neotest-pest) modified for phpunit -- Updated to work with [Pest](https://pestphp.com) 2.0 - Support for (and automatic detection of) Laravel Sail - - Note: This also moves junit output files into `storage/app/` + - Note: This also moves phpunit output files into `storage/app/` - Parallel testing support -:warning: _Ive only focused on making this work for me. Please test against your Pest tests_ :warning: +:warning: _Ive only focused on making this work for me. Please test against your phpunit tests_ :warning: ## :package: Installation @@ -22,13 +21,13 @@ Here's an example using lazy.nvim: 'nvim-neotest/neotest', dependencies = { ..., - 'V13Axel/neotest-pest', + 'pnx/neotest-phpunit', }, config = function() require('neotest').setup({ ..., adapters = { - require('neotest-pest'), + require('neotest-phpunit'), } }) end @@ -48,13 +47,13 @@ adapters = { -- -- Default: { "vendor", "node_modules" } ignore_dirs = { "vendor", "node_modules" } - -- Ignore any projects containing "phpunit-only.tests" + -- Ignore any projects containing "no_phpunit" -- -- Default: {} - root_ignore_files = { "phpunit-only.tests" }, + root_ignore_files = { "no_phpunit" }, -- Specify suffixes for files that should be considered tests -- -- Default: { "Test.php" } - test_file_suffixes = { "Test.php", "_test.php", "PestTest.php" }, + test_file_suffixes = { "Test.php", "_test.php" }, -- Sail not properly detected? Explicitly enable it. -- -- Default: function() that checks for sail presence @@ -71,16 +70,12 @@ adapters = { -- Custom pest binary. -- -- Default: function that checks for sail presence - pest_cmd = "vendor/bin/pest", + phpunit_cmd = "vendor/bin/phpunit", - -- Run N tests in parallel, <=1 doesn't pass --parallel to pest at all + -- Run N tests in parallel, <=1 doesn't pass --parallel to phpunit at all -- -- Default: 0 parallel = 16 - -- Enable ["compact" output printer](https://pestphp.com/docs/optimizing-tests#content-compact-printer) - -- -- Default: false - compact = false, - -- Set a custom path for the results XML file, parsed by this adapter -- ------------------------------------------------------------------------------------ @@ -127,7 +122,7 @@ To test the full test suite run `lua require('neotest').run.run({ suite = true } ## :gift: Contributing -I'm just one guy, maintaining this in my spare time and when I can get to it. Please raise a PR if you are interested in adding new functionality or fixing any bugs. When submitting a bug, please include an example test that I can test against. +Please raise a PR if you are interested in adding new functionality or fixing any bugs. When submitting a bug, please include an example test that I can test against. To trigger the tests for the adapter, run: @@ -137,4 +132,4 @@ To trigger the tests for the adapter, run: ## :clap: Prior Art -This package is a fork of [neotest-pest](https://github.com/theutz/neotest-pest) by [@theutz](https://github.com/olimorris), which relied _heavily_ on [olimorris/neotest-phpunit](https://github.com/olimorris/neotest-phpunit) by [@olimorris](https://github.com/olimorris). +This package is a fork of [neotest-pest](https://github.com/V13Axel/neotest-pest) by [@V13Axel](https://github.com/V13Axel) which is a fork of [neotest-pest](https://github.com/theutz/neotest-pest) by [@theutz](https://github.com/olimorris), which relied _heavily_ on [olimorris/neotest-phpunit](https://github.com/olimorris/neotest-phpunit) by [@olimorris](https://github.com/olimorris). diff --git a/lua/neotest-pest/config.lua b/lua/neotest-phpunit/config.lua similarity index 87% rename from lua/neotest-pest/config.lua rename to lua/neotest-phpunit/config.lua index f14499f..6f421fd 100644 --- a/lua/neotest-pest/config.lua +++ b/lua/neotest-phpunit/config.lua @@ -11,13 +11,12 @@ end local M = { env = { root_ignore_files = {}, - root_files = { "tests/Pest.php" }, + root_files = { "tests/" }, ignore_dirs = { "vendor", "node_modules" }, test_file_suffixes = { "Test.php" }, sail_executable = "vendor/bin/sail", sail_project_path = "/var/www/html", parallel = 0, - compact = false, }, _sail_error = false, @@ -39,17 +38,17 @@ function M.env.is_parallel() return M('parallel') > 0 end -function M.env.pest_cmd() +function M.env.phpunit_cmd() if M('sail_enabled') then - return { "vendor/bin/sail", "bin", "pest" } + return { M('sail_executable'), "phpunit" } end - return { "vendor/bin/pest" } + return { "vendor/bin/phpunit" } end function M.env.results_path() if M('sail_enabled') then - return "storage/app/" .. os.date("pest-%Y%m%d-%H%M%S") + return "storage/app/" .. os.date("phpunit-%Y%m%d-%H%M%S") end return async.fn.tempname() diff --git a/lua/neotest-pest/init.lua b/lua/neotest-phpunit/init.lua similarity index 71% rename from lua/neotest-pest/init.lua rename to lua/neotest-phpunit/init.lua index caf72f2..04eae4e 100644 --- a/lua/neotest-pest/init.lua +++ b/lua/neotest-phpunit/init.lua @@ -1,12 +1,12 @@ local lib = require('neotest.lib') local logger = require('neotest.logging') -local utils = require('neotest-pest.utils') -local config = require('neotest-pest.config') +local utils = require('neotest-phpunit.utils') +local config = require('neotest-phpunit.config') local debug = logger.debug ---@class neotest.Adapter ---@field name string -local NeotestAdapter = { name = "neotest-pest" } +local NeotestAdapter = { name = "neotest-phpunit" } ---Find the project root directory given a current directory to work from. ---Should no root be found, the adapter can still be use dina non-project context @@ -73,29 +73,35 @@ end function NeotestAdapter.discover_positions(path) local query = [[ - ((expression_statement - (member_call_expression - name: (name) @member_name (#eq? @member_name "group") - arguments: (arguments . (argument (string (string_content) @namespace.name))) - ) @member - )) @namespace.definition + ((class_declaration + name: (name) @namespace.name (#match? @namespace.name "Test") + )) @namespace.definition - ((function_call_expression - function: (name) @func_name (#match? @func_name "^(test|it)$") - arguments: (arguments . (argument (_ (string_content) @test.name))) - )) @test.definition + ((method_declaration + (attribute_list + (attribute_group + (attribute) @test_attribute (#match? @test_attribute "Test") + ) + ) + ( + (visibility_modifier) + (name) @test.name + ) @test.definition + )) - ((expression_statement - (member_call_expression - object: (#eq? @test.definition) - name: (name) @member_call_name (#match? @member_call_name "^(with)$") - arguments: (arguments . (argument (array_creation_expression (array_element_initializer (array_creation_expression (array_element_initializer (_) @test.parameter .) ))))) - ) - )) - ]] + ((method_declaration + (name) @test.name (#match? @test.name "test") + )) @test.definition + + (((comment) @test_comment (#match? @test_comment "\\@test") . + (method_declaration + (name) @test.name + ) @test.definition + )) + ]] return lib.treesitter.parse_positions(path, query, { - position_id = "require('neotest-pest.utils').make_test_id", + position_id = "require('neotest-phpunit.utils').make_test_id", }) end @@ -108,7 +114,7 @@ function NeotestAdapter.build_spec(args) debug("Building spec for:", position) debug("Results path:", results_path) - local path = position.path; + local path = position.path if config('sail_enabled') then debug("Sail enabled, adjusting path") @@ -120,7 +126,7 @@ function NeotestAdapter.build_spec(args) end local command = vim.tbl_flatten({ - config('pest_cmd'), + config('phpunit_cmd'), path, "--log-junit=" .. results_path, }) @@ -129,24 +135,7 @@ function NeotestAdapter.build_spec(args) command = vim.tbl_flatten({ command, "--filter", - position.name, - }) - else - if config('is_parallel') then - command = vim.tbl_flatten({ - command, - "--parallel", - "--processes=" .. config('parallel'), - }) - end - end - - - if config('compact') == true then - info("Using compact output") - command = vim.tbl_flatten({ - command, - "--compact", + "::" .. position.name .. "( with data set .*)?$", }) end diff --git a/lua/neotest-pest/utils.lua b/lua/neotest-phpunit/utils.lua similarity index 50% rename from lua/neotest-pest/utils.lua rename to lua/neotest-phpunit/utils.lua index ea80a68..d30f364 100644 --- a/lua/neotest-pest/utils.lua +++ b/lua/neotest-phpunit/utils.lua @@ -1,21 +1,22 @@ local logger = require("neotest.logging") +local config = require('neotest-phpunit.config') local M = {} local separator = "::" ----Generate an id which we can use to match Treesitter queries and Pest tests +---Generate an id which we can use to match Treesitter queries and PHPUnit tests ---@param position neotest.Position The position to return an ID for +---@param namespace neotest.Position[] Any namespaces the position is within ---@return string M.make_test_id = function(position) - -- Treesitter ID needs to look like 'tests/Unit/ColsHelperTest.php::it returns the proper format' - -- which means it should include position.path. However, as of PHPUnit 10, position.path - -- includes the root directory of the project, which breaks the ID matching. - -- As such, we need to remove the root directory from the path. + -- Treesitter starts line numbers from 0 so we add 1 local path = string.sub(position.path, string.len(vim.loop.cwd()) + 2) + -- local id = position.path .. separator .. (tonumber(position.range[1]) + 1) + local id = path .. separator .. (tonumber(position.range[1]) + 1) - local id = path .. separator .. position.name - logger.debug("Path to test file:", { position.path }) - logger.debug("Treesitter id:", { id }) + logger.info("New path:", { path }) + logger.info("Path to test file:", { position.path }) + logger.info("Treesitter id:", { id }) return id end @@ -39,78 +40,55 @@ end ---Extract the failure messages from the tests ---@param tests table, ----@return boolean,table,table +---@return boolean|table local function errors_or_fails(tests) - local failed = false - local errors = {} - local fails = {} + local errors_fails = {} - iterate_key(tests, "error", errors) - iterate_key(tests, "failure", fails) + iterate_key(tests, "error", errors_fails) + iterate_key(tests, "failure", errors_fails) - if #errors > 0 or #fails > 0 then - failed = true + if #errors_fails == 0 then + return false end - return failed, errors, fails -end - -local function make_short_output(test_attr, status) - return string.upper(status) .. " | " .. test_attr.name + return errors_fails end ---Make the outputs for a given test ---@param test table ---@param output_file string ----@return string, table +---@return table local function make_outputs(test, output_file) - logger.debug("Pre-output test:", test) local test_attr = test["_attr"] or test[1]["_attr"] - local name = string.gsub(test_attr.name, "^it (.*)", "%1") + local root_dir = vim.loop.cwd() - -- Difference to neotest-phpunit as of PHPUnit 10: - -- Pest's test IDs are in the format "path/to/test/file::test name" - local test_id = string.gsub(test_attr.file, "(.*)::(.*)", "%1") .. separator .. name - logger.debug("Pest id:", { test_id }) + if config('sail_enabled') then + root_dir = config('sail_project_path') + end + local relfile = string.sub(test_attr.file, string.len(root_dir) + 2) + + local test_id = relfile .. separator .. test_attr.line + logger.info("PHPUnit id:", { test_id }) + + local classname = test_attr.classname or test_attr.class local test_output = { status = "passed", - short = make_short_output(test_attr, "passed"), + short = string.upper(classname) .. "\n-> " .. "PASSED" .. " - " .. test_attr.name, output_file = output_file, } - local test_failed, errors, fails = errors_or_fails(test) - + local test_failed = errors_or_fails(test) if test_failed then - logger.debug("test_failed:", { test_failed, errors, fails }) test_output.status = "failed" - - if #errors > 0 then - local message = errors[1][1] - test_output.short = make_short_output(test_attr, "error") .. "\n\n" .. message - test_output.errors = { - { - message = message - }, - } - elseif #fails > 0 then - local message = fails[1][1] - test_output.short = make_short_output(test_attr, "failed") .. "\n\n" .. message - test_output.errors = { - { - message = message - } - } - end + test_output.short = test_failed[1]["failure"] or test_failed[1]["errors"] + test_output.errors = { + { + line = test_attr.line, + }, + } end - if test['skipped'] then - test_output.status = "skipped" - test_output.short = make_short_output(test_attr, "skipped") - end - - logger.debug("test_output:", test_output) - return test_id, test_output end