mirror of
https://github.com/pestphp/pest.git
synced 2026-04-23 23:47:30 +02:00
wip
This commit is contained in:
@ -8,6 +8,8 @@ use Closure;
|
|||||||
use Pest\Exceptions\DatasetArgumentsMismatch;
|
use Pest\Exceptions\DatasetArgumentsMismatch;
|
||||||
use Pest\Panic;
|
use Pest\Panic;
|
||||||
use Pest\Plugins\Tia;
|
use Pest\Plugins\Tia;
|
||||||
|
use Pest\Plugins\Tia\BladeEdges;
|
||||||
|
use Pest\Plugins\Tia\Recorder;
|
||||||
use Pest\Preset;
|
use Pest\Preset;
|
||||||
use Pest\Support\ChainableClosure;
|
use Pest\Support\ChainableClosure;
|
||||||
use Pest\Support\Container;
|
use Pest\Support\Container;
|
||||||
@ -315,6 +317,16 @@ trait Testable
|
|||||||
|
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
// TIA blade-edge recording (Laravel-only). Runs right after
|
||||||
|
// `parent::setUp()` so the Laravel app exists and the View
|
||||||
|
// facade is bound; idempotent against the current app instance
|
||||||
|
// so the 774-test suite doesn't stack 774 composers when Laravel
|
||||||
|
// keeps the same app across tests.
|
||||||
|
$recorder = Container::getInstance()->get(Recorder::class);
|
||||||
|
if ($recorder instanceof Recorder) {
|
||||||
|
BladeEdges::arm($recorder);
|
||||||
|
}
|
||||||
|
|
||||||
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1];
|
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename)[1];
|
||||||
|
|
||||||
if ($this->__beforeEach instanceof Closure) {
|
if ($this->__beforeEach instanceof Closure) {
|
||||||
|
|||||||
92
src/Plugins/Tia/BladeEdges.php
Normal file
92
src/Plugins/Tia/BladeEdges.php
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Plugins\Tia;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Laravel-only collaborator: during record mode, attributes every
|
||||||
|
* rendered Blade view to the currently-running test.
|
||||||
|
*
|
||||||
|
* Why this exists: the coverage driver only sees compiled view files
|
||||||
|
* under `storage/framework/views/<hash>.php`, not the `.blade.php`
|
||||||
|
* source. Without a dedicated hook TIA has no edges for blade files,
|
||||||
|
* so it leans on the Laravel WatchDefault's broad "any .blade.php
|
||||||
|
* change → every feature test" fallback. Safe but noisy — editing a
|
||||||
|
* single partial re-runs the whole suite.
|
||||||
|
*
|
||||||
|
* With this armed at record time, each test's edge set grows to
|
||||||
|
* include the precise `.blade.php` files it rendered (directly or
|
||||||
|
* through `@include`, layouts, components, Livewire, Inertia root
|
||||||
|
* views — anything that goes through Laravel's view factory fires
|
||||||
|
* `View::composer('*')`). Replay then invalidates exactly the tests
|
||||||
|
* that rendered the changed template.
|
||||||
|
*
|
||||||
|
* Implementation note: everything Laravel-touching goes through
|
||||||
|
* string class names, `class_exists`, and `method_exists` so Pest
|
||||||
|
* core doesn't pull `illuminate/container` into its `require`.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class BladeEdges
|
||||||
|
{
|
||||||
|
private const string CONTAINER_CLASS = '\\Illuminate\\Container\\Container';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App-scoped marker that makes `arm()` idempotent. Tests call it
|
||||||
|
* from every `setUp()`, and Laravel reuses the same app instance
|
||||||
|
* across tests in most configurations — without this guard we'd
|
||||||
|
* stack one composer per test and replay every one of them on
|
||||||
|
* every view render.
|
||||||
|
*/
|
||||||
|
private const string MARKER = 'pest.tia.blade-edges-armed';
|
||||||
|
|
||||||
|
public static function arm(Recorder $recorder): void
|
||||||
|
{
|
||||||
|
if (! $recorder->isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$containerClass = self::CONTAINER_CLASS;
|
||||||
|
|
||||||
|
if (! class_exists($containerClass)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var object $app */
|
||||||
|
$app = $containerClass::getInstance();
|
||||||
|
|
||||||
|
if (! method_exists($app, 'bound') || ! method_exists($app, 'make') || ! method_exists($app, 'instance')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($app->bound(self::MARKER)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $app->bound('view')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$app->instance(self::MARKER, true);
|
||||||
|
|
||||||
|
$factory = $app->make('view');
|
||||||
|
|
||||||
|
if (! is_object($factory) || ! method_exists($factory, 'composer')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$factory->composer('*', static function (object $view) use ($recorder): void {
|
||||||
|
if (! method_exists($view, 'getPath')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var mixed $path */
|
||||||
|
$path = $view->getPath();
|
||||||
|
|
||||||
|
if (is_string($path) && $path !== '') {
|
||||||
|
$recorder->linkSource($path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -153,11 +153,25 @@ final class Graph
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Watch-pattern lookup (non-PHP assets → test directories).
|
// 2. Watch-pattern lookup — fallback for files we don't have
|
||||||
|
// precise edges for. When a file is already in `$fileIds` step
|
||||||
|
// 1 resolved it surgically; broadcasting it again through the
|
||||||
|
// watch pattern would re-add every test the pattern maps to,
|
||||||
|
// defeating the point of recording the edge in the first place.
|
||||||
|
// Blade templates captured via Laravel's view composer are the
|
||||||
|
// motivating case — we want their specific tests, not every
|
||||||
|
// feature test.
|
||||||
|
$unknownToGraph = [];
|
||||||
|
foreach ($normalised as $rel) {
|
||||||
|
if (! isset($this->fileIds[$rel])) {
|
||||||
|
$unknownToGraph[] = $rel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @var WatchPatterns $watchPatterns */
|
/** @var WatchPatterns $watchPatterns */
|
||||||
$watchPatterns = Container::getInstance()->get(WatchPatterns::class);
|
$watchPatterns = Container::getInstance()->get(WatchPatterns::class);
|
||||||
|
|
||||||
$dirs = $watchPatterns->matchedDirectories($this->projectRoot, $normalised);
|
$dirs = $watchPatterns->matchedDirectories($this->projectRoot, $unknownToGraph);
|
||||||
$allTestFiles = array_keys($this->edges);
|
$allTestFiles = array_keys($this->edges);
|
||||||
|
|
||||||
foreach ($watchPatterns->testsUnderDirectories($dirs, $allTestFiles) as $testFile) {
|
foreach ($watchPatterns->testsUnderDirectories($dirs, $allTestFiles) as $testFile) {
|
||||||
|
|||||||
@ -144,6 +144,32 @@ final class Recorder
|
|||||||
$this->currentTestFile = null;
|
$this->currentTestFile = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records an extra source-file dependency for the currently-running
|
||||||
|
* test. Used by collaborators that capture edges the coverage driver
|
||||||
|
* cannot see — Blade templates rendered through Laravel's view
|
||||||
|
* factory are the motivating case (their `.blade.php` source never
|
||||||
|
* executes directly; a cached compiled PHP file does). No-op when
|
||||||
|
* the recorder is inactive or no test is in flight, so callers can
|
||||||
|
* fire it unconditionally from app-level hooks.
|
||||||
|
*/
|
||||||
|
public function linkSource(string $sourceFile): void
|
||||||
|
{
|
||||||
|
if (! $this->active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->currentTestFile === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sourceFile === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->perTestFiles[$this->currentTestFile][$sourceFile] = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, array<int, string>> absolute test file → list of absolute source files.
|
* @return array<string, array<int, string>> absolute test file → list of absolute source files.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user