` (full HTML shape). Both carry * the component name in a structured payload we can parse cheaply. * * Same dep-free handshake as `BladeEdges` / `TableTracker`: string * class lookup + method-capability probes so Pest's `require` stays * Laravel-free. * * @internal */ final class InertiaEdges { private const string CONTAINER_CLASS = '\\Illuminate\\Container\\Container'; private const string REQUEST_HANDLED_EVENT = '\\Illuminate\\Foundation\\Http\\Events\\RequestHandled'; /** * App-scoped marker that makes `arm()` idempotent across per-test * `setUp()` calls. Laravel reuses the same app across tests in * most configurations — without this guard we'd stack one * listener per test. */ private const string MARKER = 'pest.tia.inertia-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('events')) { return; } $app->instance(self::MARKER, true); /** @var object $events */ $events = $app->make('events'); if (! method_exists($events, 'listen')) { return; } $events->listen(self::REQUEST_HANDLED_EVENT, static function (object $event) use ($recorder): void { if (! property_exists($event, 'response')) { return; } /** @var mixed $response */ $response = $event->response; if (! is_object($response)) { return; } $component = self::extractComponent($response); if ($component !== null) { $recorder->linkInertiaComponent($component); } }); } /** * Pulls the Inertia component name out of a Laravel response, * handling both XHR (`X-Inertia` + JSON body) and full HTML * (`