Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ luac.out
packpath
vendor
.phpunit.result.cache
.phpunit.cache
47 changes: 36 additions & 11 deletions lua/neotest-pest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -72,31 +72,56 @@ 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")
arguments: (arguments . (argument (string (string_content) @namespace.name)))
) @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
Expand Down
51 changes: 51 additions & 0 deletions lua/neotest-pest/parameterized-tests.lua
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions lua/neotest-pest/pest-control.lua
Original file line number Diff line number Diff line change
@@ -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
35 changes: 35 additions & 0 deletions lua/neotest-pest/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions tests/Examples/some/deep/nesting/NestingTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?php

it('is true')
->expect(fn () => true)
->toBeTrue();
// it('is true')
// ->expect(fn () => true)
// ->toBeTrue();
70 changes: 35 additions & 35 deletions tests/Feature/UserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
15 changes: 9 additions & 6 deletions tests/Unit/ExampleTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<?php

test('example', function () {
expect(true)->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();
// });