Skip to content

PHP/Pest: calls inside it()/test()/describe() closures emit no node and no calls edge (PE-80049) #71

Description

@filipechagas

Found while validating the fork on lawnstarter/signup-api for PE-80049 (https://lawnstarter.atlassian.net/browse/PE-80049). Graded shape: Q1 — "Who calls X?".

Summary

Calls made inside the closure argument to Pest's it() / test() / describe() produce no node and no calls edge. The identical call written in a classic PHPUnit test class resolves normally. There is no warning, so "who calls X" silently under-reports — often to zero — for any repo that uses Pest's functional style.

Impact on signup-api

220 of 237 test files in that repo use the Pest functional style. Only 16 files repo-wide contribute any calls edge originating in tests/, and those are almost entirely the 12 class-style holdouts.

Concretely, graphify affected "CreateProspect" --relation calls returns exactly one caller — the production mutation — and misses all 12 call sites in tests/Integration/internal/app/Actions/Prospects/CreateProspectTest.php, each a plain CreateProspect::make()->handle(...) at closure top level. This was the one graded comprehension question that failed for the repo.

Minimal reproduction

Three files:

app/Greeter.php

<?php
namespace App;
class Greeter
{
    public function greet(): string { return 'hi'; }
}

tests/GreeterPestTest.php

<?php
use App\Greeter;

it('greets from a pest closure', function () {
    $g = new Greeter();
    expect($g->greet())->toBe('hi');
});

tests/GreeterClassTest.php

<?php
namespace Tests;

use App\Greeter;
use PHPUnit\Framework\TestCase;

class GreeterClassTest extends TestCase
{
    public function test_it_greets(): void
    {
        $g = new Greeter();
        $this->assertSame('hi', $g->greet());
    }
}

Then:

graphify update .
graphify affected "Greeter" --relation calls

Actual

Affected nodes for Greeter
Relations: calls
Depth: 2
- .test_it_greets() [calls] tests/GreeterClassTest.php:L12

Expected

Both call sites listed — the Pest closure at tests/GreeterPestTest.php:L6 alongside the class-based one. Failing that, the closure body should at least be attributed to some node (e.g. a synthetic node named for the it() description) rather than dropped without a warning.

Notes

This is the "silent incompleteness" limit from the successor spike (Finding 4) landing on a very large surface: a consumer cannot distinguish "this action has no test callers" from "its test callers were refused".

Related but distinct from the TypeScript closure issues #63 and #68 — this is the PHP extractor, and the trigger is the closure being an argument to a global function call rather than a module-scope statement.

Environment: graphify 0.9.40, fork origin/v8 @ 5e9d95c11837aeabbbb5d90cb45544f49dc58ca4, cold cache.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions