diff --git a/.gitignore b/.gitignore index 5c70fae..5f40cf1 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ luac.out packpath vendor .phpunit.result.cache +.phpunit.cache diff --git a/lua/neotest-pest/init.lua b/lua/neotest-pest/init.lua index 488aed6..d8d8cf0 100644 --- a/lua/neotest-pest/init.lua +++ b/lua/neotest-pest/init.lua @@ -3,9 +3,9 @@ local logger = require('neotest.logging') local utils = require('neotest-pest.utils') local config = require('neotest-pest.config') local debug = logger.debug +local parameterized_tests = require('neotest-pest.parameterized-tests') ---@class neotest.Adapter ----@field name string local NeotestAdapter = { name = "neotest-pest" } ---Find the project root directory given a current directory to work from. @@ -72,7 +72,7 @@ function NeotestAdapter.is_test_file(file_path) end function NeotestAdapter.discover_positions(path) - local query = [[ + local query = [[ ((expression_statement (member_call_expression name: (name) @member_name (#eq? @member_name "group") @@ -80,23 +80,48 @@ function NeotestAdapter.discover_positions(path) ) @member )) @namespace.definition - ((function_call_expression - function: (name) @func_name (#match? @func_name "^(test|it)$") - arguments: (arguments . (argument (_ (string_content) @test.name))) - )) @test.definition + ((expression_statement + (function_call_expression + function: (name) @func_name (#match? @func_name "^(test|it)$") + arguments: (arguments . (argument (_ (string_content) @test.name))) + ) + )) @test.definition ((expression_statement (member_call_expression - object: (#eq? @test.definition) - name: (name) @member_call_name (#match? @member_call_name "^(with)$") + object: ((function_call_expression + function: (name) @func_name (#match? @func_name "^(test|it)$") + arguments: (arguments . (argument (_ (string_content) @test.name))) + )) + name: (name) @with_call (#match? @with_call "^(with)$") arguments: (arguments . (argument (array_creation_expression (array_element_initializer (array_creation_expression (array_element_initializer (_) @test.parameter .) ))))) ) - )) + )) @test.definition + ]] - return lib.treesitter.parse_positions(path, query, { - position_id = "require('neotest-pest.utils').make_test_id", + local positions = lib.treesitter.parse_positions(path, query, { + nested_tests = false, + build_position = "require('neotest-pest.utils').build_position", }) + + local parameterized_test_positions = + parameterized_tests.get_parameterized_test_positions(positions) + + if #parameterized_test_positions > 0 then + debug("enriching positions of " .. path) + parameterized_tests.enrich_positions_with_parameterized_tests( + positions:data().path, + parameterized_test_positions + ) + end + + debug('vvvvvvvvvvvvvvvvvvvvv') + debug(positions) + debug(parameterized_test_positions) + debug('^^^^^^^^^^^^^^^^^^^^^') + + return positions end ---@param args neotest.RunArgs diff --git a/lua/neotest-pest/parameterized-tests.lua b/lua/neotest-pest/parameterized-tests.lua new file mode 100644 index 0000000..428bacb --- /dev/null +++ b/lua/neotest-pest/parameterized-tests.lua @@ -0,0 +1,51 @@ +local debug = require("neotest.logging").debug +local pest_control = require("neotest-pest.pest-control") + +local M = {} + +function M.get_parameterized_test_positions(positions) + local parameterized_test_positions = {} + + for _, value in positions:iter_nodes() do + local data = value:data() + + if data.type == "test" and data.is_parameterized == true then + debug("Found a parameterized test") + parameterized_test_positions[#parameterized_test_positions + 1] = value + end + end + + return parameterized_test_positions +end + +function M.enrich_positions_with_parameterized_tests( + file_path, + parsed_parameterized_test_positions +) + local pest_test_discovery_output = pest_control.run_pest_test_discovery(file_path) + + -- if pest_test_discovery_output == nil then + -- return + -- end + + -- for _, value in pairs(parsed_parameterized_test_positions) do + -- local data = value:data() + + -- local parameterized_test_results_for_position = + -- get_test_ids_at_position(pest_test_discovery_output, data.range) + + -- for _, test_result in ipairs(parameterized_test_results_for_position) do + -- local new_data = { + -- id = test_result.keyid, + -- name = test_result.name, + -- path = data.path, + -- } + -- new_data.range = nil + + -- local new_pos = value:new(new_data, {}, value._key, {}, {}) + -- value:add_child(new_data.id, new_pos) + -- end + -- end +end + +return M diff --git a/lua/neotest-pest/pest-control.lua b/lua/neotest-pest/pest-control.lua new file mode 100644 index 0000000..c5eab82 --- /dev/null +++ b/lua/neotest-pest/pest-control.lua @@ -0,0 +1,37 @@ +local lib = require("neotest.lib") +local config = require("neotest-pest.config") +local debug = require('neotest.logging').debug + +local M = {} + +--- Synchronously runs `pest` with the --list-tests-xml option, +--- pointed at /dev/stdout so we can capture the xml it outputs. +--- +--- Because the command assumes it's writing a file, that output +--- contains some extra text at the end that is trimmed off, then +--- we can use the XML from there. +M.run_pest_test_discovery = function(file_path) + local command = config('pest_cmd') + + vim.list_extend(command, { + "--list-tests-xml", + "/dev/stdout", -- Since list-tests-xml requires a filename, write to stdout + "--colors=never", -- This makes it so that we can use a simple find/replace for the INFO text after the XML + file_path, + }) + + debug("Running command:") + debug(command) + local result = { lib.process.run(command, { stdout = true }) } + + if not result[2] then + debug("pest command failed somehow") + debug(result) + end + + debug("Command finished running:") + debug(result[2].stdout) +end + + +return M diff --git a/lua/neotest-pest/utils.lua b/lua/neotest-pest/utils.lua index ea80a68..813a6ee 100644 --- a/lua/neotest-pest/utils.lua +++ b/lua/neotest-pest/utils.lua @@ -20,6 +20,41 @@ M.make_test_id = function(position) return id end +local function get_match_type(captured_nodes) + if captured_nodes["test.name"] then + return "test" + end + if captured_nodes["namespace.name"] then + return "namespace" + end +end + +---Given a file path and the captured information for the treesitter query, build out the +---position information for enriching with parameterized tests and use later by neotest +---@param file_path string +---@param source string|integer +---@param captured_nodes table +---@return table|nil +M.build_position = function(file_path, source, captured_nodes) + local match_type = get_match_type(captured_nodes) + if not match_type then + return + end + + local name = vim.treesitter.get_node_text(captured_nodes[match_type .. ".name"], source) + local definition = captured_nodes[match_type .. ".definition"] + + logger.debug(captured_nodes) + + return { + type = match_type, + path = file_path, + name = name, + range = { definition:range() }, + is_parameterized = captured_nodes["with_call"] and true or false, + } +end + ---Recursively iterate through a deeply nested table to obtain specified keys ---@param data_table table ---@param key string diff --git a/tests/Examples/some/deep/nesting/NestingTest.php b/tests/Examples/some/deep/nesting/NestingTest.php index ab44d70..461a9ce 100644 --- a/tests/Examples/some/deep/nesting/NestingTest.php +++ b/tests/Examples/some/deep/nesting/NestingTest.php @@ -1,5 +1,5 @@ expect(fn () => true) - ->toBeTrue(); +// it('is true') +// ->expect(fn () => true) +// ->toBeTrue(); diff --git a/tests/Feature/UserTest.php b/tests/Feature/UserTest.php index c9ca67f..6cdcd52 100644 --- a/tests/Feature/UserTest.php +++ b/tests/Feature/UserTest.php @@ -4,49 +4,49 @@ * @var mixed $this */ -use TestProject\User; +// use TestProject\User; -uses()->group('file group'); +// uses()->group('file group'); -$makeUser = fn () => new User(18, 'John'); +// $makeUser = fn () => new User(18, 'John'); -beforeEach(function () use ($makeUser) { - $this->sut = $makeUser(); -}); +// beforeEach(function () use ($makeUser) { +// $this->sut = $makeUser(); +// }); -test('class constructor') - ->expect($makeUser) - ->name->toBe('John') - ->age->toBe(18) - ->favorite_movies->toBeEmpty(); +// test('class constructor') +// ->expect($makeUser) +// ->name->toBe('John') +// ->age->toBe(18) +// ->favorite_movies->toBeEmpty(); -test('tellName', function () { - expect($this->sut) - ->tellName()->toBeString()->toContain('John'); -}) - ->group('special tests'); +// test('tellName', function () { +// expect($this->sut) +// ->tellName()->toBeString()->toContain('John'); +// }) +// ->group('special tests'); -it('throws', function () { - throw new \Exception('oops!'); -}); +// it('throws', function () { +// throw new \Exception('oops!'); +// }); -it('is skipped')->skip(); +// it('is skipped')->skip(); -it('can tellAge') - ->expect($makeUser) - ->tellAge()->toBeString()->toContain('18'); +// it('can tellAge') +// ->expect($makeUser) +// ->tellAge()->toBeString()->toContain('18'); -it('is false')->expect(true)->toBeFalse(); +// it('is false')->expect(true)->toBeFalse(); -test('addFavoriteMovie') - ->expect($makeUser) - ->addFavoriteMovie('Avengers') - ->toBeTrue() - ->favorite_movies->toContain('Avengers')->toHaveLength(1); +// test('addFavoriteMovie') +// ->expect($makeUser) +// ->addFavoriteMovie('Avengers') +// ->toBeTrue() +// ->favorite_movies->toContain('Avengers')->toHaveLength(1); -test('removeFavoriteMovie') - ->expect($makeUser) - ->addFavoriteMovie('Avengers')->toBeTrue() - ->addFavoriteMovie('Justice League')->toBeTrue() - ->removeFavoriteMovie('Avengers')->toBeTrue() - ->favorite_movies->not->toContain('Avengers')->toHaveLength(1); +// test('removeFavoriteMovie') +// ->expect($makeUser) +// ->addFavoriteMovie('Avengers')->toBeTrue() +// ->addFavoriteMovie('Justice League')->toBeTrue() +// ->removeFavoriteMovie('Avengers')->toBeTrue() +// ->favorite_movies->not->toContain('Avengers')->toHaveLength(1); diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php index 7739dd4..9a16947 100644 --- a/tests/Unit/ExampleTest.php +++ b/tests/Unit/ExampleTest.php @@ -1,9 +1,12 @@ toBeTrue(); -}); +test('example', function ($value, $value2) { + expect($value)->toBeTrue(); +})->with([ + ["some string", false], + [[], true], +]); -test("double quote example", function() { - expect(true)->toBeTrue(); -}); +// test("double quote example", function () { +// expect(true)->toBeTrue(); +// });