Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHPUnit Test Support #13

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
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
36 changes: 36 additions & 0 deletions lua/neotest-pest/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ function NeotestAdapter.is_test_file(file_path)
end

function NeotestAdapter.discover_positions(path)
-- Tree-sitter query, as follows:
-- 1. Pest namespace definition
-- 2. Pest single test definition
-- 3. Pest parameterized test definition
-- 4. PHPUnit namespace definition
-- 5. PHPUnit test definition (With attributes)
-- 6. PHPUnit test definition (without attributes)
-- 7. PHPUnit test definition (with comment)
--
-- ;;scheme
local query = [[
((expression_statement
(member_call_expression
Expand All @@ -92,6 +102,32 @@ function NeotestAdapter.discover_positions(path)
arguments: (arguments . (argument (array_creation_expression (array_element_initializer (array_creation_expression (array_element_initializer (_) @test.parameter .) )))))
)
))

((class_declaration
name: (name) @namespace.name (#match? @namespace.name "Test")
)) @namespace.definition

((method_declaration
(attribute_list
(attribute_group
(attribute) @test_attribute (#match? @test_attribute "Test")
)
)
(
(visibility_modifier)
(name) @test.name
) @test.definition
))

((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, {
Expand Down
70 changes: 65 additions & 5 deletions lua/neotest-pest/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,68 @@ local logger = require("neotest.logging")
local M = {}
local separator = "::"

local function starts_with(str, start)
return str:sub(1, #start) == start
end

local function is_phpunit_test(position)
if starts_with(position.name, 'test') ~= true then
logger.debug("Test name doesn't start with test")
return false
end

if position.name:sub(5, 5) ~= string.upper(position.name:sub(5, 5)) then
logger.debug("Test name isn't camel case:")
logger.debug("'" ..
position.name:sub(5, 5) .. "' doesn't match '" .. string.upper(position.name:sub(5, 5)) .. "'")
return false
end

if string.find(position.name, " ", 1, true) then
logger.debug("Test name contains spaces")
return false
end

return true
end

---Generate an id which we can use to match Treesitter queries and Pest tests
---@param position neotest.Position The position to return an ID for
---@return string
M.make_test_id = function(position)
if is_phpunit_test(position) then
logger.debug("Test " .. position.name .. " appears to be a PHPUnit test.")

local pathparts = {}
local foundtests = false
for dir in position.path:gmatch("([^/]+)") do
if dir == "tests" then
foundtests = true
end

if foundtests then
table.insert(pathparts, dir)
end
end

local testName = position.name:gsub(" (.)", string.upper)

local id = table.concat(pathparts, "/") .. separator .. testName

logger.info("PHPUnit Position", { position })
logger.info("Treesitter id:", { id })

return id
end

-- 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.
local path = string.sub(position.path, string.len(vim.loop.cwd()) + 2)

local id = path .. separator .. position.name
logger.debug("Path to test file:", { position.path })
logger.debug("Pest Position", { position })
logger.debug("Treesitter id:", { id })

return id
Expand Down Expand Up @@ -67,11 +117,21 @@ 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 test_id = ""

if string.find(test_attr.file, "(", 1, true) and string.find(test_attr.file, ")::", 1, true) then
name = "test" .. name:gsub(" (.)", string.upper)
test_id = "t" .. test_attr.class:sub(2) .. ".php" .. separator .. name

-- 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 })
test_id = string.gsub(test_id, "\\", "/")

logger.debug("PHPUnit id: ", { test_id })
else
-- Difference to neotest-phpunit as of PHPUnit 10:
-- Pest's test IDs are in the format "path/to/test/file::test name"
test_id = string.gsub(test_attr.file, "(.*)::(.*)", "%1") .. separator .. name
logger.debug("Pest id:", { test_id })
end

local test_output = {
status = "passed",
Expand Down
26 changes: 26 additions & 0 deletions tests/Unit/ExamplePhpunitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Tests\Unit;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

class ExamplePhpunitTest extends TestCase
{
public function testBasicTest(): void
{
$this->assertTrue(true);
}

/** @test */
public function itAssertsTrue(): void
{
$this->assertTrue(true);
}

#[Test]
public function itAssertsTrueAgain(): void
{
$this->assertTrue(true);
}
}