From a720de13eacff55d1e53722ea61e824ad5ae3a16 Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Sat, 25 May 2024 16:54:44 -0400 Subject: [PATCH 01/93] Install and run Pint --- .php-cs-fixer.dist.php | 28 ------------------ CHANGELOG.md | 10 +++---- composer.json | 3 +- src/AlwaysProp.php | 2 +- src/Commands/CreateMiddleware.php | 2 +- src/Commands/StartSsr.php | 2 +- src/Directive.php | 4 +-- src/Response.php | 37 ++++++++++++------------ src/ResponseFactory.php | 25 ++++++++-------- src/ServiceProvider.php | 16 +++++----- src/Support/Header.php | 6 ++++ src/Testing/AssertableInertia.php | 6 ++-- src/Testing/Concerns/Debugging.php | 6 ++-- src/Testing/Concerns/Has.php | 7 ++--- src/Testing/Concerns/Interaction.php | 2 +- src/Testing/Concerns/Matching.php | 8 ++--- src/Testing/Concerns/PageObject.php | 4 +-- src/Testing/TestResponseMacros.php | 2 +- tests/AlwaysPropTest.php | 6 ++-- tests/ControllerTest.php | 4 +-- tests/DirectiveTest.php | 16 +++++----- tests/LazyPropTest.php | 2 +- tests/MiddlewareTest.php | 20 +++++++------ tests/ResponseFactoryTest.php | 19 ++++++------ tests/ResponseTest.php | 31 +++++++++++--------- tests/Stubs/ExampleMiddleware.php | 4 +-- tests/Stubs/FakeGateway.php | 2 +- tests/Stubs/FakeResource.php | 2 +- tests/TestCase.php | 8 ++--- tests/Testing/TestResponseMacrosTest.php | 2 +- 30 files changed, 136 insertions(+), 150 deletions(-) delete mode 100644 .php-cs-fixer.dist.php diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php deleted file mode 100644 index cc0b20ff..00000000 --- a/.php-cs-fixer.dist.php +++ /dev/null @@ -1,28 +0,0 @@ -setUsingCache(false) - ->setRiskyAllowed(true) - ->setRules([ - '@PHP70Migration' => true, - '@PHP71Migration' => true, - '@PSR2' => true, - '@Symfony' => true, - 'array_syntax' => ['syntax' => 'short'], - 'increment_style' => ['style' => 'post'], - 'multiline_whitespace_before_semicolons' => true, - 'array_indentation' => true, - 'not_operator_with_successor_space' => true, - 'ordered_imports' => ['sort_algorithm' => 'length'], - 'php_unit_method_casing' => ['case' => 'snake_case'], - 'semicolon_after_instruction' => false, - 'single_line_throw' => false, - 'yoda_style' => false, - 'strict_comparison' => true, - 'yoda_style' => false, - 'single_line_throw' => false, - 'php_unit_method_casing' => ['case' => 'snake_case'], - 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true], - ]); diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6fa08b..99824696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ ## [v1.2.0](https://github.com/inertiajs/inertia-laravel/compare/v1.1.0...v1.2.0) - 2024-05-17 -* [1.x] Make commands lazy by [@timacdonald](https://github.com/timacdonald) in https://github.com/inertiajs/inertia-laravel/pull/601 -* [1.x] Persistent properties by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/621 -* [1.x] Exclude properties from partial responses by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/622 +- Make commands lazy by [@timacdonald](https://github.com/timacdonald) in https://github.com/inertiajs/inertia-laravel/pull/601 +- Persistent properties by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/621 +- Exclude properties from partial responses by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/622 ## [v1.1.0](https://github.com/inertiajs/inertia-laravel/compare/v1.0.0...v1.1.0) - 2024-05-16 -* Support dot notation in partial requests by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/620 -* [1.x] Add `$request->inertia()` IDE helper by [@ycs77](https://github.com/ycs77) in https://github.com/inertiajs/inertia-laravel/pull/625 +- Support dot notation in partial requests by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/620 +- Add `$request->inertia()` IDE helper by [@ycs77](https://github.com/ycs77) in https://github.com/inertiajs/inertia-laravel/pull/625 ## [v1.0.0](https://github.com/inertiajs/inertia-laravel/compare/v0.6.11...v1.0.0) - 2024-03-08 diff --git a/composer.json b/composer.json index 832bdb76..859cb792 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,8 @@ "roave/security-advisories": "dev-master", "orchestra/testbench": "^6.4|^7.0|^8.0|^9.0", "mockery/mockery": "^1.3.3", - "phpunit/phpunit": "^8.0|^9.5.8|^10.4" + "phpunit/phpunit": "^8.0|^9.5.8|^10.4", + "laravel/pint": "^1.16" }, "suggest": { "ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command." diff --git a/src/AlwaysProp.php b/src/AlwaysProp.php index 5c9ee6ec..62066ebf 100644 --- a/src/AlwaysProp.php +++ b/src/AlwaysProp.php @@ -10,7 +10,7 @@ class AlwaysProp protected $value; /** - * @param mixed $value + * @param mixed $value */ public function __construct($value) { diff --git a/src/Commands/CreateMiddleware.php b/src/Commands/CreateMiddleware.php index 59bc2981..bd5c4a09 100644 --- a/src/Commands/CreateMiddleware.php +++ b/src/Commands/CreateMiddleware.php @@ -41,7 +41,7 @@ protected function getStub(): string /** * Get the default namespace for the class. * - * @param string $rootNamespace + * @param string $rootNamespace */ protected function getDefaultNamespace($rootNamespace): string { diff --git a/src/Commands/StartSsr.php b/src/Commands/StartSsr.php index c7f1215d..d2573ec2 100644 --- a/src/Commands/StartSsr.php +++ b/src/Commands/StartSsr.php @@ -2,9 +2,9 @@ namespace Inertia\Commands; -use Inertia\Ssr\SsrException; use Illuminate\Console\Command; use Inertia\Ssr\BundleDetector; +use Inertia\Ssr\SsrException; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Process\Process; diff --git a/src/Directive.php b/src/Directive.php index 71009b61..dca132f7 100644 --- a/src/Directive.php +++ b/src/Directive.php @@ -7,7 +7,7 @@ class Directive /** * Compiles the "@inertia" directive. * - * @param string $expression + * @param string $expression */ public static function compile($expression = ''): string { @@ -32,7 +32,7 @@ public static function compile($expression = ''): string /** * Compiles the "@inertiaHead" directive. * - * @param string $expression + * @param string $expression */ public static function compileHead($expression = ''): string { diff --git a/src/Response.php b/src/Response.php index fc7dcf04..ba2641fe 100644 --- a/src/Response.php +++ b/src/Response.php @@ -3,18 +3,18 @@ namespace Inertia; use Closure; -use Illuminate\Http\Request; -use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Str; use GuzzleHttp\Promise\PromiseInterface; -use Illuminate\Support\Traits\Macroable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Responsable; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceResponse; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Response as ResponseFactory; +use Illuminate\Support\Str; +use Illuminate\Support\Traits\Macroable; use Inertia\Support\Header; class Response implements Responsable @@ -22,13 +22,17 @@ class Response implements Responsable use Macroable; protected $component; + protected $props; + protected $rootView; + protected $version; + protected $viewData = []; /** - * @param array|Arrayable $props + * @param array|Arrayable $props */ public function __construct(string $component, array $props, string $rootView = 'app', string $version = '') { @@ -39,9 +43,8 @@ public function __construct(string $component, array $props, string $rootView = } /** - * @param string|array $key - * @param mixed $value - * + * @param string|array $key + * @param mixed $value * @return $this */ public function with($key, $value = null): self @@ -56,9 +59,8 @@ public function with($key, $value = null): self } /** - * @param string|array $key - * @param mixed $value - * + * @param string|array $key + * @param mixed $value * @return $this */ public function withViewData($key, $value = null): self @@ -82,8 +84,7 @@ public function rootView(string $rootView): self /** * Create an HTTP response that represents the object. * - * @param \Illuminate\Http\Request $request - * + * @param \Illuminate\Http\Request $request * @return \Symfony\Component\HttpFoundation\Response */ public function toResponse($request) @@ -111,7 +112,7 @@ public function resolveProperties(Request $request, array $props): array { $isPartial = $request->header(Header::PARTIAL_COMPONENT) === $this->component; - if(! $isPartial) { + if (! $isPartial) { $props = array_filter($this->props, static function ($prop) { return ! ($prop instanceof LazyProp); }); @@ -119,11 +120,11 @@ public function resolveProperties(Request $request, array $props): array $props = $this->resolveArrayableProperties($props, $request); - if($isPartial && $request->hasHeader(Header::PARTIAL_ONLY)) { + if ($isPartial && $request->hasHeader(Header::PARTIAL_ONLY)) { $props = $this->resolveOnly($request, $props); } - if($isPartial && $request->hasHeader(Header::PARTIAL_EXCEPT)) { + if ($isPartial && $request->hasHeader(Header::PARTIAL_EXCEPT)) { $props = $this->resolveExcept($request, $props); } @@ -168,7 +169,7 @@ public function resolveOnly(Request $request, array $props): array $value = []; - foreach($only as $key) { + foreach ($only as $key) { Arr::set($value, $key, data_get($props, $key)); } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 28144c7d..508e126e 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -3,16 +3,16 @@ namespace Inertia; use Closure; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Arr; use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Request; -use Illuminate\Support\Traits\Macroable; -use Illuminate\Contracts\Support\Arrayable; use Illuminate\Support\Facades\Redirect; +use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Response as BaseResponse; +use Illuminate\Support\Traits\Macroable; use Inertia\Support\Header; -use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirect; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class ResponseFactory { @@ -33,8 +33,8 @@ public function setRootView(string $name): void } /** - * @param string|array|Arrayable $key - * @param mixed $value + * @param string|array|Arrayable $key + * @param mixed $value */ public function share($key, $value = null): void { @@ -48,11 +48,10 @@ public function share($key, $value = null): void } /** - * @param mixed $default - * + * @param mixed $default * @return mixed */ - public function getShared(string $key = null, $default = null) + public function getShared(?string $key = null, $default = null) { if ($key) { return Arr::get($this->sharedProps, $key, $default); @@ -67,7 +66,7 @@ public function flushShared(): void } /** - * @param Closure|string|null $version + * @param Closure|string|null $version */ public function version($version): void { @@ -89,7 +88,7 @@ public function lazy(callable $callback): LazyProp } /** - * @param mixed $value + * @param mixed $value */ public function always($value): AlwaysProp { @@ -97,7 +96,7 @@ public function always($value): AlwaysProp } /** - * @param array|Arrayable $props + * @param array|Arrayable $props */ public function render(string $component, $props = []): Response { @@ -114,7 +113,7 @@ public function render(string $component, $props = []): Response } /** - * @param string|SymfonyRedirect $url + * @param string|SymfonyRedirect $url */ public function location($url): SymfonyResponse { diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 50868f16..1236cafa 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,18 +2,18 @@ namespace Inertia; -use LogicException; -use Inertia\Ssr\Gateway; -use ReflectionException; +use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Http\Request; -use Inertia\Ssr\HttpGateway; use Illuminate\Routing\Router; -use Illuminate\View\FileViewFinder; -use Illuminate\Testing\TestResponse; -use Inertia\Testing\TestResponseMacros; use Illuminate\Support\ServiceProvider as BaseServiceProvider; -use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; +use Illuminate\Testing\TestResponse; +use Illuminate\View\FileViewFinder; +use Inertia\Ssr\Gateway; +use Inertia\Ssr\HttpGateway; use Inertia\Support\Header; +use Inertia\Testing\TestResponseMacros; +use LogicException; +use ReflectionException; class ServiceProvider extends BaseServiceProvider { diff --git a/src/Support/Header.php b/src/Support/Header.php index 2b902d8a..5b706efa 100644 --- a/src/Support/Header.php +++ b/src/Support/Header.php @@ -5,10 +5,16 @@ class Header { public const INERTIA = 'X-Inertia'; + public const ERROR_BAG = 'X-Inertia-Error-Bag'; + public const LOCATION = 'X-Inertia-Location'; + public const VERSION = 'X-Inertia-Version'; + public const PARTIAL_COMPONENT = 'X-Inertia-Partial-Component'; + public const PARTIAL_ONLY = 'X-Inertia-Partial-Data'; + public const PARTIAL_EXCEPT = 'X-Inertia-Partial-Except'; } diff --git a/src/Testing/AssertableInertia.php b/src/Testing/AssertableInertia.php index 370e748d..d845ae55 100644 --- a/src/Testing/AssertableInertia.php +++ b/src/Testing/AssertableInertia.php @@ -2,11 +2,11 @@ namespace Inertia\Testing; -use InvalidArgumentException; +use Illuminate\Testing\Fluent\AssertableJson; use Illuminate\Testing\TestResponse; +use InvalidArgumentException; use PHPUnit\Framework\Assert as PHPUnit; use PHPUnit\Framework\AssertionFailedError; -use Illuminate\Testing\Fluent\AssertableJson; class AssertableInertia extends AssertableJson { @@ -42,7 +42,7 @@ public static function fromTestResponse(TestResponse $response): self return $instance; } - public function component(string $value = null, $shouldExist = null): self + public function component(?string $value = null, $shouldExist = null): self { PHPUnit::assertSame($value, $this->component, 'Unexpected Inertia page component.'); diff --git a/src/Testing/Concerns/Debugging.php b/src/Testing/Concerns/Debugging.php index 612374de..86a6269c 100644 --- a/src/Testing/Concerns/Debugging.php +++ b/src/Testing/Concerns/Debugging.php @@ -4,17 +4,17 @@ trait Debugging { - public function dump(string $prop = null): self + public function dump(?string $prop = null): self { dump($this->prop($prop)); return $this; } - public function dd(string $prop = null): void + public function dd(?string $prop = null): void { dd($this->prop($prop)); } - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); } diff --git a/src/Testing/Concerns/Has.php b/src/Testing/Concerns/Has.php index b48511c7..37866b60 100644 --- a/src/Testing/Concerns/Has.php +++ b/src/Testing/Concerns/Has.php @@ -36,11 +36,10 @@ public function hasAll($key): self } /** - * @param mixed $value - * + * @param mixed $value * @return $this */ - public function has(string $key, $value = null, Closure $scope = null): self + public function has(string $key, $value = null, ?Closure $scope = null): self { PHPUnit::assertTrue( Arr::has($this->prop(), $key), @@ -108,7 +107,7 @@ public function misses(string $key): self return $this->missing($key); } - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); abstract protected function dotPath(string $key): string; diff --git a/src/Testing/Concerns/Interaction.php b/src/Testing/Concerns/Interaction.php index 506d64d2..145d405e 100644 --- a/src/Testing/Concerns/Interaction.php +++ b/src/Testing/Concerns/Interaction.php @@ -37,5 +37,5 @@ public function etc(): self return $this; } - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); } diff --git a/src/Testing/Concerns/Matching.php b/src/Testing/Concerns/Matching.php index 6cb4404b..7ead91ba 100644 --- a/src/Testing/Concerns/Matching.php +++ b/src/Testing/Concerns/Matching.php @@ -3,11 +3,11 @@ namespace Inertia\Testing\Concerns; use Closure; -use Illuminate\Support\Collection; -use PHPUnit\Framework\Assert as PHPUnit; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceResponse; +use Illuminate\Support\Collection; +use PHPUnit\Framework\Assert as PHPUnit; trait Matching { @@ -68,7 +68,7 @@ protected function ensureSorted(&$value): void abstract protected function dotPath(string $key): string; - abstract protected function prop(string $key = null); + abstract protected function prop(?string $key = null); - abstract public function has(string $key, $value = null, Closure $scope = null); + abstract public function has(string $key, $value = null, ?Closure $scope = null); } diff --git a/src/Testing/Concerns/PageObject.php b/src/Testing/Concerns/PageObject.php index 1dc98389..467c48c3 100644 --- a/src/Testing/Concerns/PageObject.php +++ b/src/Testing/Concerns/PageObject.php @@ -8,7 +8,7 @@ trait PageObject { - public function component(string $value = null, $shouldExist = null): self + public function component(?string $value = null, $shouldExist = null): self { PHPUnit::assertSame($value, $this->component, 'Unexpected Inertia page component.'); @@ -23,7 +23,7 @@ public function component(string $value = null, $shouldExist = null): self return $this; } - protected function prop(string $key = null) + protected function prop(?string $key = null) { return Arr::get($this->props, $key); } diff --git a/src/Testing/TestResponseMacros.php b/src/Testing/TestResponseMacros.php index a4ca6802..428dc2a6 100644 --- a/src/Testing/TestResponseMacros.php +++ b/src/Testing/TestResponseMacros.php @@ -8,7 +8,7 @@ class TestResponseMacros { public function assertInertia() { - return function (Closure $callback = null) { + return function (?Closure $callback = null) { $assert = AssertableInertia::fromTestResponse($this); if (is_null($callback)) { diff --git a/tests/AlwaysPropTest.php b/tests/AlwaysPropTest.php index c1756e53..df1e41a8 100644 --- a/tests/AlwaysPropTest.php +++ b/tests/AlwaysPropTest.php @@ -25,8 +25,10 @@ public function test_can_accept_scalar_values(): void public function test_can_accept_callables(): void { - $callable = new class { - public function __invoke() { + $callable = new class + { + public function __invoke() + { return 'An always value'; } }; diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 5e90f3e1..5d099459 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -2,10 +2,10 @@ namespace Inertia\Tests; -use Inertia\Controller; +use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Facades\Route; +use Inertia\Controller; use Inertia\Tests\Stubs\ExampleMiddleware; -use Illuminate\Session\Middleware\StartSession; class ControllerTest extends TestCase { diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index 82e6b737..f1d7ef8a 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -2,18 +2,18 @@ namespace Inertia\Tests; -use Throwable; -use Mockery as m; -use Inertia\Directive; -use Inertia\Ssr\Gateway; -use Illuminate\View\View; -use Illuminate\View\Factory; -use Inertia\Tests\Stubs\FakeGateway; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Config; -use Illuminate\View\Engines\PhpEngine; use Illuminate\View\Compilers\BladeCompiler; +use Illuminate\View\Engines\PhpEngine; +use Illuminate\View\Factory; +use Illuminate\View\View; +use Inertia\Directive; +use Inertia\Ssr\Gateway; +use Inertia\Tests\Stubs\FakeGateway; +use Mockery as m; +use Throwable; class DirectiveTest extends TestCase { diff --git a/tests/LazyPropTest.php b/tests/LazyPropTest.php index d8ae0e50..f1127328 100644 --- a/tests/LazyPropTest.php +++ b/tests/LazyPropTest.php @@ -2,8 +2,8 @@ namespace Inertia\Tests; -use Inertia\LazyProp; use Illuminate\Http\Request; +use Inertia\LazyProp; class LazyPropTest extends TestCase { diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index 4fb9f5d4..2ab86cfd 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -2,17 +2,17 @@ namespace Inertia\Tests; -use LogicException; -use Inertia\Inertia; -use Inertia\Middleware; use Illuminate\Http\Request; -use Illuminate\Support\MessageBag; -use Illuminate\Support\ViewErrorBag; +use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Session; -use Inertia\Tests\Stubs\ExampleMiddleware; -use Illuminate\Session\Middleware\StartSession; +use Illuminate\Support\MessageBag; +use Illuminate\Support\ViewErrorBag; use Inertia\AlwaysProp; +use Inertia\Inertia; +use Inertia\Middleware; +use Inertia\Tests\Stubs\ExampleMiddleware; +use LogicException; class MiddlewareTest extends TestCase { @@ -216,7 +216,8 @@ public function test_validation_errors_are_scoped_to_error_bag_header(): void public function test_middleware_can_change_the_root_view_via_a_property(): void { - $this->prepareMockEndpoint(null, [], new class() extends Middleware { + $this->prepareMockEndpoint(null, [], new class() extends Middleware + { protected $rootView = 'welcome'; }); @@ -227,7 +228,8 @@ public function test_middleware_can_change_the_root_view_via_a_property(): void public function test_middleware_can_change_the_root_view_by_overriding_the_rootview_method(): void { - $this->prepareMockEndpoint(null, [], new class() extends Middleware { + $this->prepareMockEndpoint(null, [], new class() extends Middleware + { public function rootView(Request $request): string { return 'welcome'; diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index b86754b9..50d2c78b 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -2,20 +2,20 @@ namespace Inertia\Tests; -use Inertia\Inertia; -use Inertia\LazyProp; -use Inertia\ResponseFactory; -use Illuminate\Http\Response; -use Illuminate\Http\RedirectResponse; -use Illuminate\Support\Facades\Route; -use Illuminate\Support\Facades\Request; -use Inertia\Tests\Stubs\ExampleMiddleware; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request as HttpRequest; +use Illuminate\Http\Response; use Illuminate\Session\Middleware\StartSession; use Illuminate\Session\NullSessionHandler; use Illuminate\Session\Store; +use Illuminate\Support\Facades\Request; +use Illuminate\Support\Facades\Route; use Inertia\AlwaysProp; +use Inertia\Inertia; +use Inertia\LazyProp; +use Inertia\ResponseFactory; +use Inertia\Tests\Stubs\ExampleMiddleware; class ResponseFactoryTest extends TestCase { @@ -176,7 +176,8 @@ public function test_will_accept_arrayabe_props() Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { Inertia::share('foo', 'bar'); - return Inertia::render('User/Edit', new class() implements Arrayable { + return Inertia::render('User/Edit', new class() implements Arrayable + { public function toArray() { return [ diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index d36cdaae..2097fa35 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -2,20 +2,20 @@ namespace Inertia\Tests; -use Mockery; -use Inertia\LazyProp; -use Inertia\Response; -use Illuminate\View\View; -use Illuminate\Http\Request; -use Illuminate\Support\Fluent; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Collection; -use Inertia\Tests\Stubs\FakeResource; -use Illuminate\Http\Response as BaseResponse; -use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; +use Illuminate\Http\Response as BaseResponse; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Illuminate\Support\Fluent; +use Illuminate\View\View; use Inertia\AlwaysProp; +use Inertia\LazyProp; +use Inertia\Response; +use Inertia\Tests\Stubs\FakeResource; +use Mockery; class ResponseTest extends TestCase { @@ -98,7 +98,8 @@ public function test_lazy_resource_response(): void $callable = static function () use ($users) { $page = new LengthAwarePaginator($users->take(2), $users->count(), 2); - return new class($page, JsonResource::class) extends ResourceCollection { + return new class($page, JsonResource::class) extends ResourceCollection + { }; }; @@ -152,7 +153,9 @@ public function test_nested_lazy_resource_response(): void // nested array with ResourceCollection to resolve return [ - 'users' => new class($page, JsonResource::class) extends ResourceCollection {}, + 'users' => new class($page, JsonResource::class) extends ResourceCollection + { + }, ]; }; @@ -400,9 +403,9 @@ public function test_always_props_are_included_on_partial_reload(): void ], 'errors' => new AlwaysProp(function () { return [ - 'name' => 'The email field is required.' + 'name' => 'The email field is required.', ]; - }) + }), ]; $response = new Response('User/Edit', $props, 'app', '123'); diff --git a/tests/Stubs/ExampleMiddleware.php b/tests/Stubs/ExampleMiddleware.php index 16ef7885..bb5e5305 100644 --- a/tests/Stubs/ExampleMiddleware.php +++ b/tests/Stubs/ExampleMiddleware.php @@ -2,9 +2,9 @@ namespace Inertia\Tests\Stubs; -use LogicException; -use Inertia\Middleware; use Illuminate\Http\Request; +use Inertia\Middleware; +use LogicException; use Symfony\Component\HttpFoundation\Response; class ExampleMiddleware extends Middleware diff --git a/tests/Stubs/FakeGateway.php b/tests/Stubs/FakeGateway.php index 93190a25..804d230d 100644 --- a/tests/Stubs/FakeGateway.php +++ b/tests/Stubs/FakeGateway.php @@ -2,9 +2,9 @@ namespace Inertia\Tests\Stubs; +use Illuminate\Support\Facades\Config; use Inertia\Ssr\Gateway; use Inertia\Ssr\Response; -use Illuminate\Support\Facades\Config; class FakeGateway implements Gateway { diff --git a/tests/Stubs/FakeResource.php b/tests/Stubs/FakeResource.php index 4525fdd1..a80c216a 100644 --- a/tests/Stubs/FakeResource.php +++ b/tests/Stubs/FakeResource.php @@ -29,7 +29,7 @@ public function __construct(array $resource) /** * Transform the resource into an array. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request $request */ public function toArray($request): array { diff --git a/tests/TestCase.php b/tests/TestCase.php index a4df1131..c62786e4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,13 +2,13 @@ namespace Inertia\Tests; -use LogicException; -use Inertia\Inertia; -use Inertia\ServiceProvider; +use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Support\Facades\View; use Illuminate\Testing\TestResponse; +use Inertia\Inertia; +use Inertia\ServiceProvider; +use LogicException; use Orchestra\Testbench\TestCase as Orchestra; -use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; abstract class TestCase extends Orchestra { diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index c86fe0de..ae73dabc 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -2,9 +2,9 @@ namespace Inertia\Tests\Testing; +use Illuminate\Testing\Fluent\AssertableJson; use Inertia\Inertia; use Inertia\Tests\TestCase; -use Illuminate\Testing\Fluent\AssertableJson; class TestResponseMacrosTest extends TestCase { From c8f9b44d2f2a135bb5b11db13da1e86a4ec9fda8 Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Sat, 25 May 2024 16:57:26 -0400 Subject: [PATCH 02/93] Make changelog consistent with previous entries --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99824696..3b76a84d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,14 @@ ## [v1.2.0](https://github.com/inertiajs/inertia-laravel/compare/v1.1.0...v1.2.0) - 2024-05-17 -- Make commands lazy by [@timacdonald](https://github.com/timacdonald) in https://github.com/inertiajs/inertia-laravel/pull/601 -- Persistent properties by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/621 -- Exclude properties from partial responses by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/622 +- Make commands lazy ([#601](https://github.com/inertiajs/inertia-laravel/pull/601)) +- Add persistent properties ([#621](https://github.com/inertiajs/inertia-laravel/pull/621)) +- Exclude `except` props from partial reloads ([#622](https://github.com/inertiajs/inertia-laravel/pull/622)) ## [v1.1.0](https://github.com/inertiajs/inertia-laravel/compare/v1.0.0...v1.1.0) - 2024-05-16 -- Support dot notation in partial requests by [@lepikhinb](https://github.com/lepikhinb) in https://github.com/inertiajs/inertia-laravel/pull/620 -- Add `$request->inertia()` IDE helper by [@ycs77](https://github.com/ycs77) in https://github.com/inertiajs/inertia-laravel/pull/625 +- Support dot notation in partial requests ([#620](https://github.com/inertiajs/inertia-laravel/pull/620)) +- Add `$request->inertia()` IDE helper ([#625](https://github.com/inertiajs/inertia-laravel/pull/625)) ## [v1.0.0](https://github.com/inertiajs/inertia-laravel/compare/v0.6.11...v1.0.0) - 2024-03-08 From e68596e85e58c6f63168a36a9341f24b5a27f81c Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Mon, 27 May 2024 08:21:57 -0400 Subject: [PATCH 03/93] Drop support for Laravel 8 and 9 (#629) --- .github/workflows/tests.yml | 36 ++------------------ composer.json | 13 +++++--- tests/Testing/AssertableInertiaTest.php | 42 ++++++++---------------- tests/Testing/TestResponseMacrosTest.php | 9 ++--- 4 files changed, 27 insertions(+), 73 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7060e6e7..8cb4b6f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,44 +12,12 @@ jobs: strategy: fail-fast: true matrix: - php: [7.3, 7.4, "8.0", 8.1, 8.2, 8.3] - laravel: [8, 9, 10, 11] + php: [8.1, 8.2, 8.3] + laravel: [10, 11] stability: ["prefer-lowest", "prefer-stable"] exclude: - - php: 7.3 - laravel: 9 - - php: 7.3 - laravel: 10 - - php: 7.3 - laravel: 11 - - php: 7.4 - laravel: 9 - - php: 7.4 - laravel: 10 - - php: 7.4 - laravel: 11 - - php: '8.0' - laravel: 10 - - php: '8.0' - laravel: 11 - - php: 8.1 - laravel: 6 - - php: 8.1 - laravel: 7 - php: 8.1 laravel: 11 - - php: 8.2 - laravel: 6 - - php: 8.2 - laravel: 7 - - php: 8.2 - laravel: 8 - - php: 8.3 - laravel: 6 - - php: 8.3 - laravel: 7 - - php: 8.3 - laravel: 8 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} (w/ ${{ matrix.stability }}) steps: diff --git a/composer.json b/composer.json index 859cb792..1e7ca01b 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,10 @@ "name": "inertiajs/inertia-laravel", "type": "library", "description": "The Laravel adapter for Inertia.js.", - "keywords": ["laravel", "inertia"], + "keywords": [ + "laravel", + "inertia" + ], "license": "MIT", "authors": [ { @@ -25,16 +28,16 @@ } }, "require": { - "php": "^7.3|~8.0.0|~8.1.0|~8.2.0|~8.3.0", + "php": "^8.1.0", "ext-json": "*", - "laravel/framework": "^8.74|^9.0|^10.0|^11.0", + "laravel/framework": "^10.0|^11.0", "symfony/console": "^5.3|^6.0|^7.0" }, "require-dev": { "roave/security-advisories": "dev-master", - "orchestra/testbench": "^6.4|^7.0|^8.0|^9.0", + "orchestra/testbench": "^8.0|^9.0", "mockery/mockery": "^1.3.3", - "phpunit/phpunit": "^8.0|^9.5.8|^10.4", + "phpunit/phpunit": "^10.4|^11.0", "laravel/pint": "^1.16" }, "suggest": { diff --git a/tests/Testing/AssertableInertiaTest.php b/tests/Testing/AssertableInertiaTest.php index 613cccc2..913b7e1e 100644 --- a/tests/Testing/AssertableInertiaTest.php +++ b/tests/Testing/AssertableInertiaTest.php @@ -8,8 +8,7 @@ class AssertableInertiaTest extends TestCase { - /** @test */ - public function the_view_is_served_by_inertia(): void + public function test_the_view_is_served_by_inertia(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -18,8 +17,7 @@ public function the_view_is_served_by_inertia(): void $response->assertInertia(); } - /** @test */ - public function the_view_is_not_served_by_inertia(): void + public function test_the_view_is_not_served_by_inertia(): void { $response = $this->makeMockRequest(view('welcome')); $response->assertOk(); // Make sure we can render the built-in Orchestra 'welcome' view.. @@ -30,8 +28,7 @@ public function the_view_is_not_served_by_inertia(): void $response->assertInertia(); } - /** @test */ - public function the_component_matches(): void + public function test_the_component_matches(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -42,8 +39,7 @@ public function the_component_matches(): void }); } - /** @test */ - public function the_component_does_not_match(): void + public function test_the_component_does_not_match(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -57,8 +53,7 @@ public function the_component_does_not_match(): void }); } - /** @test */ - public function the_component_exists_on_the_filesystem(): void + public function test_the_component_exists_on_the_filesystem(): void { $response = $this->makeMockRequest( Inertia::render('Stubs/ExamplePage') @@ -70,8 +65,7 @@ public function the_component_exists_on_the_filesystem(): void }); } - /** @test */ - public function the_component_does_not_exist_on_the_filesystem(): void + public function test_the_component_does_not_exist_on_the_filesystem(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -86,8 +80,7 @@ public function the_component_does_not_exist_on_the_filesystem(): void }); } - /** @test */ - public function it_can_force_enable_the_component_file_existence(): void + public function test_it_can_force_enable_the_component_file_existence(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -102,8 +95,7 @@ public function it_can_force_enable_the_component_file_existence(): void }); } - /** @test */ - public function it_can_force_disable_the_component_file_existence_check(): void + public function test_it_can_force_disable_the_component_file_existence_check(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -116,8 +108,7 @@ public function it_can_force_disable_the_component_file_existence_check(): void }); } - /** @test */ - public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_exist_relative_to_any_of_the_given_paths(): void + public function test_the_component_does_not_exist_on_the_filesystem_when_it_does_not_exist_relative_to_any_of_the_given_paths(): void { $response = $this->makeMockRequest( Inertia::render('fixtures/ExamplePage') @@ -133,8 +124,7 @@ public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_ }); } - /** @test */ - public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_have_one_of_the_configured_extensions(): void + public function test_the_component_does_not_exist_on_the_filesystem_when_it_does_not_have_one_of_the_configured_extensions(): void { $response = $this->makeMockRequest( Inertia::render('fixtures/ExamplePage') @@ -150,8 +140,7 @@ public function the_component_does_not_exist_on_the_filesystem_when_it_does_not_ }); } - /** @test */ - public function the_page_url_matches(): void + public function test_the_page_url_matches(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -162,8 +151,7 @@ public function the_page_url_matches(): void }); } - /** @test */ - public function the_page_url_does_not_match(): void + public function test_the_page_url_does_not_match(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -177,8 +165,7 @@ public function the_page_url_does_not_match(): void }); } - /** @test */ - public function the_asset_version_matches(): void + public function test_the_asset_version_matches(): void { Inertia::version('example-version'); @@ -191,8 +178,7 @@ public function the_asset_version_matches(): void }); } - /** @test */ - public function the_asset_version_does_not_match(): void + public function test_the_asset_version_does_not_match(): void { Inertia::version('example-version'); diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index ae73dabc..14fe4fbd 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -8,8 +8,7 @@ class TestResponseMacrosTest extends TestCase { - /** @test */ - public function it_can_make_inertia_assertions(): void + public function test_it_can_make_inertia_assertions(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -24,8 +23,7 @@ public function it_can_make_inertia_assertions(): void $this->assertTrue($success); } - /** @test */ - public function it_preserves_the_ability_to_continue_chaining_laravel_test_response_calls(): void + public function test_it_preserves_the_ability_to_continue_chaining_laravel_test_response_calls(): void { $response = $this->makeMockRequest( Inertia::render('foo') @@ -37,8 +35,7 @@ public function it_preserves_the_ability_to_continue_chaining_laravel_test_respo ); } - /** @test */ - public function it_can_retrieve_the_inertia_page(): void + public function test_it_can_retrieve_the_inertia_page(): void { $response = $this->makeMockRequest( Inertia::render('foo', ['bar' => 'baz']) From 971d42d0de7290f4979500bfacd62074977aa2ee Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Mon, 27 May 2024 08:22:33 -0400 Subject: [PATCH 04/93] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b76a84d..5ea0fec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...1.x) +- Drop support for Laravel 8 and 9 ([#629](https://github.com/inertiajs/inertia-laravel/pull/629)) - Add "always" props using new `Inertia::always()` wrapper ([#627](https://github.com/inertiajs/inertia-laravel/pull/627)) ## [v1.2.0](https://github.com/inertiajs/inertia-laravel/compare/v1.1.0...v1.2.0) - 2024-05-17 From 2db5eb298fd73534829d90abe589f7eb8bd4938a Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 28 May 2024 14:05:05 +0200 Subject: [PATCH 05/93] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ea0fec7..aadf26ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Release Notes -## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...1.x) +## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...2.x) - Drop support for Laravel 8 and 9 ([#629](https://github.com/inertiajs/inertia-laravel/pull/629)) - Add "always" props using new `Inertia::always()` wrapper ([#627](https://github.com/inertiajs/inertia-laravel/pull/627)) From 7ad2ffba40e95cd974982c45f18aa8f7bbaf278d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Tue, 9 Jul 2024 12:49:30 -0400 Subject: [PATCH 06/93] deferred props --- src/DeferProp.php | 28 ++++++++++++++++++++++++++++ src/Inertia.php | 1 + src/Response.php | 17 ++++++++++++++++- src/ResponseFactory.php | 5 +++++ tests/DeferPropTest.php | 27 +++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/DeferProp.php create mode 100644 tests/DeferPropTest.php diff --git a/src/DeferProp.php b/src/DeferProp.php new file mode 100644 index 00000000..98277bc0 --- /dev/null +++ b/src/DeferProp.php @@ -0,0 +1,28 @@ +callback = $callback; + $this->group = $group; + } + + public function group() + { + return $this->group; + } + + public function __invoke() + { + return App::call($this->callback); + } +} diff --git a/src/Inertia.php b/src/Inertia.php index aee499ea..9e56c4e8 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -12,6 +12,7 @@ * @method static void version(\Closure|string|null $version) * @method static string getVersion() * @method static \Inertia\LazyProp lazy(callable $callback) + * @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default') * @method static \Inertia\AlwaysProp always(mixed $value) * @method static \Inertia\Response render(string $component, array|\Illuminate\Contracts\Support\Arrayable $props = []) * @method static \Symfony\Component\HttpFoundation\Response location(string|\Symfony\Component\HttpFoundation\RedirectResponse $url) diff --git a/src/Response.php b/src/Response.php index ba2641fe..b288b161 100644 --- a/src/Response.php +++ b/src/Response.php @@ -114,7 +114,7 @@ public function resolveProperties(Request $request, array $props): array if (! $isPartial) { $props = array_filter($this->props, static function ($prop) { - return ! ($prop instanceof LazyProp); + return ! ($prop instanceof LazyProp) && ! ($prop instanceof DeferProp); }); } @@ -132,6 +132,21 @@ public function resolveProperties(Request $request, array $props): array $props = $this->resolvePropertyInstances($props, $request); + if (! $isPartial) { + $deferredProps = collect($this->props)->filter(function ($prop) { + return $prop instanceof DeferProp; + })->map(function ($prop, $key) { + return [ + 'key' => $key, + 'group' => $prop->group(), + ]; + })->groupBy('group')->map->pluck('key'); + + if ($deferredProps->isNotEmpty()) { + $props['deferred'] = $deferredProps->toArray(); + } + } + return $props; } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 508e126e..a7f63483 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -87,6 +87,11 @@ public function lazy(callable $callback): LazyProp return new LazyProp($callback); } + public function defer(callable $callback, string $group = 'default'): DeferProp + { + return new DeferProp($callback, $group); + } + /** * @param mixed $value */ diff --git a/tests/DeferPropTest.php b/tests/DeferPropTest.php new file mode 100644 index 00000000..3bab4040 --- /dev/null +++ b/tests/DeferPropTest.php @@ -0,0 +1,27 @@ +assertSame('A lazy value', $deferProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $deferProp = new DeferProp(function (Request $request) { + return $request; + }); + + $this->assertInstanceOf(Request::class, $deferProp()); + } +} From 369faff0061b49031dfafdc9699d4d8a76f7e0b4 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Tue, 9 Jul 2024 12:58:02 -0400 Subject: [PATCH 07/93] lazy -> optional --- src/Inertia.php | 1 + src/OptionalProp.php | 20 ++++++++++++++++++++ src/Response.php | 4 ++-- src/ResponseFactory.php | 8 ++++++++ tests/OptionalPropTest.php | 27 +++++++++++++++++++++++++++ tests/ResponseFactoryTest.php | 22 ++++++++++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/OptionalProp.php create mode 100644 tests/OptionalPropTest.php diff --git a/src/Inertia.php b/src/Inertia.php index 9e56c4e8..b054f930 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -11,6 +11,7 @@ * @method static void flushShared() * @method static void version(\Closure|string|null $version) * @method static string getVersion() + * @method static \Inertia\OptionalProp optional(callable $callback) * @method static \Inertia\LazyProp lazy(callable $callback) * @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default') * @method static \Inertia\AlwaysProp always(mixed $value) diff --git a/src/OptionalProp.php b/src/OptionalProp.php new file mode 100644 index 00000000..f3539b38 --- /dev/null +++ b/src/OptionalProp.php @@ -0,0 +1,20 @@ +callback = $callback; + } + + public function __invoke() + { + return App::call($this->callback); + } +} diff --git a/src/Response.php b/src/Response.php index b288b161..cf5d4fdd 100644 --- a/src/Response.php +++ b/src/Response.php @@ -114,7 +114,7 @@ public function resolveProperties(Request $request, array $props): array if (! $isPartial) { $props = array_filter($this->props, static function ($prop) { - return ! ($prop instanceof LazyProp) && ! ($prop instanceof DeferProp); + return ! ($prop instanceof OptionalProp) && ! ($prop instanceof LazyProp) && ! ($prop instanceof DeferProp); }); } @@ -228,7 +228,7 @@ public function resolvePropertyInstances(array $props, Request $request): array $value = App::call($value); } - if ($value instanceof LazyProp) { + if ($value instanceof LazyProp || $value instanceof OptionalProp || $value instanceof DeferProp) { $value = App::call($value); } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index a7f63483..420644d0 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -82,11 +82,19 @@ public function getVersion(): string return (string) $version; } + /** + * @deprecated Use `optional` instead. + */ public function lazy(callable $callback): LazyProp { return new LazyProp($callback); } + public function optional(callable $callback): OptionalProp + { + return new OptionalProp($callback); + } + public function defer(callable $callback, string $group = 'default'): DeferProp { return new DeferProp($callback, $group); diff --git a/tests/OptionalPropTest.php b/tests/OptionalPropTest.php new file mode 100644 index 00000000..932c803b --- /dev/null +++ b/tests/OptionalPropTest.php @@ -0,0 +1,27 @@ +assertSame('A lazy value', $optionalProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $optionalProp = new OptionalProp(function (Request $request) { + return $request; + }); + + $this->assertInstanceOf(Request::class, $optionalProp()); + } +} diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 50d2c78b..52add836 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -12,8 +12,10 @@ use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Route; use Inertia\AlwaysProp; +use Inertia\DeferProp; use Inertia\Inertia; use Inertia\LazyProp; +use Inertia\OptionalProp; use Inertia\ResponseFactory; use Inertia\Tests\Stubs\ExampleMiddleware; @@ -161,6 +163,26 @@ public function test_can_create_lazy_prop(): void $this->assertInstanceOf(LazyProp::class, $lazyProp); } + public function test_can_create_deferred_prop(): void + { + $factory = new ResponseFactory(); + $deferredProp = $factory->defer(function () { + return 'A deferred value'; + }); + + $this->assertInstanceOf(DeferProp::class, $deferredProp); + } + + public function test_can_create_optional_prop(): void + { + $factory = new ResponseFactory(); + $optionalProp = $factory->optional(function () { + return 'An optional value'; + }); + + $this->assertInstanceOf(OptionalProp::class, $optionalProp); + } + public function test_can_create_always_prop(): void { $factory = new ResponseFactory(); From 8edbd0249aa444fabd82e785c74db4b94a3bb994 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 11 Jul 2024 22:51:39 -0400 Subject: [PATCH 08/93] new meta key, nested version -> assetVersion under meta key --- src/Response.php | 61 ++++++++++++++++++++++--------- src/Testing/AssertableInertia.php | 5 ++- tests/ControllerTest.php | 4 +- tests/ResponseTest.php | 20 +++++----- 4 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Response.php b/src/Response.php index cf5d4fdd..3d75fe71 100644 --- a/src/Response.php +++ b/src/Response.php @@ -95,7 +95,7 @@ public function toResponse($request) 'component' => $this->component, 'props' => $props, 'url' => Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'), - 'version' => $this->version, + 'meta' => $this->resolveMeta($request), ]; if ($request->header(Header::INERTIA)) { @@ -110,7 +110,7 @@ public function toResponse($request) */ public function resolveProperties(Request $request, array $props): array { - $isPartial = $request->header(Header::PARTIAL_COMPONENT) === $this->component; + $isPartial = $this->isPartial($request); if (! $isPartial) { $props = array_filter($this->props, static function ($prop) { @@ -132,21 +132,6 @@ public function resolveProperties(Request $request, array $props): array $props = $this->resolvePropertyInstances($props, $request); - if (! $isPartial) { - $deferredProps = collect($this->props)->filter(function ($prop) { - return $prop instanceof DeferProp; - })->map(function ($prop, $key) { - return [ - 'key' => $key, - 'group' => $prop->group(), - ]; - })->groupBy('group')->map->pluck('key'); - - if ($deferredProps->isNotEmpty()) { - $props['deferred'] = $deferredProps->toArray(); - } - } - return $props; } @@ -253,4 +238,46 @@ public function resolvePropertyInstances(array $props, Request $request): array return $props; } + + /** + * Resolve the meta data for the response. + */ + public function resolveMeta(Request $request): array + { + $meta = [ + 'assetVersion' => $this->version, + ]; + + if ($this->isPartial($request)) { + return $meta; + } + + $deferredProps = collect($this->props) + ->filter(function ($prop) { + return $prop instanceof DeferProp; + }) + ->map(function ($prop, $key) { + return [ + 'key' => $key, + 'group' => $prop->group(), + ]; + }) + ->groupBy('group') + ->map + ->pluck('key'); + + if ($deferredProps->isNotEmpty()) { + $meta['deferredProps'] = $deferredProps->toArray(); + } + + return $meta; + } + + /** + * Determine if the request is a partial request. + */ + public function isPartial(Request $request): bool + { + return $request->header(Header::PARTIAL_COMPONENT) === $this->component; + } } diff --git a/src/Testing/AssertableInertia.php b/src/Testing/AssertableInertia.php index d845ae55..fabdf50f 100644 --- a/src/Testing/AssertableInertia.php +++ b/src/Testing/AssertableInertia.php @@ -29,7 +29,8 @@ public static function fromTestResponse(TestResponse $response): self PHPUnit::assertArrayHasKey('component', $page); PHPUnit::assertArrayHasKey('props', $page); PHPUnit::assertArrayHasKey('url', $page); - PHPUnit::assertArrayHasKey('version', $page); + PHPUnit::assertArrayHasKey('meta', $page); + PHPUnit::assertArrayHasKey('assetVersion', $page['meta']); } catch (AssertionFailedError $e) { PHPUnit::fail('Not a valid Inertia response.'); } @@ -37,7 +38,7 @@ public static function fromTestResponse(TestResponse $response): self $instance = static::fromArray($page['props']); $instance->component = $page['component']; $instance->url = $page['url']; - $instance->version = $page['version']; + $instance->version = $page['meta']['assetVersion']; return $instance; } diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 5d099459..d92d7f3c 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -27,7 +27,9 @@ public function test_controller_returns_an_inertia_response(): void 'errors' => (object) [], ], 'url' => '/', - 'version' => '', + 'meta' => [ + 'assetVersion' => '', + ] ]); } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 2097fa35..f5264e58 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -45,8 +45,8 @@ public function test_server_response(): void $this->assertSame('User/Edit', $page['component']); $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); - $this->assertSame('123', $page['version']); - $this->assertSame('
', $view->render()); + $this->assertSame('123', $page['meta']['assetVersion']); + $this->assertSame('
', $view->render()); } public function test_xhr_response(): void @@ -63,7 +63,7 @@ public function test_xhr_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_resource_response(): void @@ -81,7 +81,7 @@ public function test_resource_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_lazy_resource_response(): void @@ -129,7 +129,7 @@ public function test_lazy_resource_response(): void $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('User/Index', $page->component); $this->assertSame('/users?page=1', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); tap($page->props->users, function ($users) use ($expected) { $this->assertSame(json_encode($expected['data']), json_encode($users->data)); $this->assertSame(json_encode($expected['links']), json_encode($users->links)); @@ -187,7 +187,7 @@ public function test_nested_lazy_resource_response(): void $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('User/Index', $page->component); $this->assertSame('/users?page=1', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); tap($page->props->something->users, function ($users) use ($expected) { $this->assertSame(json_encode($expected['users']['data']), json_encode($users->data)); $this->assertSame(json_encode($expected['users']['links']), json_encode($users->links)); @@ -210,7 +210,7 @@ public function test_arrayable_prop_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_promise_props_are_resolved(): void @@ -233,7 +233,7 @@ public function test_promise_props_are_resolved(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_xhr_partial_response(): void @@ -256,7 +256,7 @@ public function test_xhr_partial_response(): void $this->assertCount(1, $props); $this->assertSame('partial-data', $page->props->partial); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_exclude_props_from_partial_response(): void @@ -279,7 +279,7 @@ public function test_exclude_props_from_partial_response(): void $this->assertCount(1, $props); $this->assertSame('partial-data', $page->props->partial); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->version); + $this->assertSame('123', $page->meta->assetVersion); } public function test_nested_partial_props(): void From 4be6289902d240a3bb4b25b5bcf525c2ccc92bc9 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 18 Jul 2024 10:47:24 -0400 Subject: [PATCH 09/93] introduce the concept of ignore first load --- src/DeferProp.php | 2 +- src/IgnoreFirstLoad.php | 8 ++++++++ src/LazyProp.php | 2 +- src/OptionalProp.php | 2 +- src/Response.php | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 src/IgnoreFirstLoad.php diff --git a/src/DeferProp.php b/src/DeferProp.php index 98277bc0..183eb2af 100644 --- a/src/DeferProp.php +++ b/src/DeferProp.php @@ -4,7 +4,7 @@ use Illuminate\Support\Facades\App; -class DeferProp +class DeferProp implements IgnoreFirstLoad { protected $callback; diff --git a/src/IgnoreFirstLoad.php b/src/IgnoreFirstLoad.php new file mode 100644 index 00000000..0404fd5a --- /dev/null +++ b/src/IgnoreFirstLoad.php @@ -0,0 +1,8 @@ +props, static function ($prop) { - return ! ($prop instanceof OptionalProp) && ! ($prop instanceof LazyProp) && ! ($prop instanceof DeferProp); + return ! ($prop instanceof IgnoreFirstLoad); }); } From 88b00bab3a58d0dde4590808c73f6777ac56ac12 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 18 Jul 2024 10:47:43 -0400 Subject: [PATCH 10/93] simplify app call detection logic --- src/Response.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Response.php b/src/Response.php index 9c7f9a2d..9ac98c3a 100644 --- a/src/Response.php +++ b/src/Response.php @@ -209,15 +209,16 @@ public function resolveAlways(array $props): array public function resolvePropertyInstances(array $props, Request $request): array { foreach ($props as $key => $value) { - if ($value instanceof Closure) { - $value = App::call($value); - } - - if ($value instanceof LazyProp || $value instanceof OptionalProp || $value instanceof DeferProp) { - $value = App::call($value); - } - - if ($value instanceof AlwaysProp) { + $resolveViaApp = collect([ + Closure::class, + LazyProp::class, + OptionalProp::class, + DeferProp::class, + AlwaysProp::class, + WhenVisible::class, + ])->first(fn ($class) => $value instanceof $class); + + if ($resolveViaApp) { $value = App::call($value); } From 336809f56760bf09b9f7d7d0471720ff57b94815 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Tue, 23 Jul 2024 14:37:58 -0400 Subject: [PATCH 11/93] clear history from the server --- src/Inertia.php | 1 + src/Response.php | 6 +++++- src/ResponseFactory.php | 10 +++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Inertia.php b/src/Inertia.php index b054f930..ea109186 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -8,6 +8,7 @@ * @method static void setRootView(string $name) * @method static void share(string|array|\Illuminate\Contracts\Support\Arrayable $key, mixed $value = null) * @method static mixed getShared(string|null $key = null, mixed $default = null) + * @method static void clearHistory() * @method static void flushShared() * @method static void version(\Closure|string|null $version) * @method static string getVersion() diff --git a/src/Response.php b/src/Response.php index 9ac98c3a..b6e6a0c6 100644 --- a/src/Response.php +++ b/src/Response.php @@ -29,17 +29,20 @@ class Response implements Responsable protected $version; + protected $clearHistory; + protected $viewData = []; /** * @param array|Arrayable $props */ - public function __construct(string $component, array $props, string $rootView = 'app', string $version = '') + public function __construct(string $component, array $props, string $rootView = 'app', string $version = '', bool $clearHistory = false) { $this->component = $component; $this->props = $props instanceof Arrayable ? $props->toArray() : $props; $this->rootView = $rootView; $this->version = $version; + $this->clearHistory = $clearHistory; } /** @@ -247,6 +250,7 @@ public function resolveMeta(Request $request): array { $meta = [ 'assetVersion' => $this->version, + 'clearHistory' => $this->clearHistory, ]; if ($this->isPartial($request)) { diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 420644d0..1d32c99a 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -27,6 +27,8 @@ class ResponseFactory /** @var Closure|string|null */ protected $version; + protected $clearHistory = false; + public function setRootView(string $name): void { $this->rootView = $name; @@ -82,6 +84,11 @@ public function getVersion(): string return (string) $version; } + public function clearHistory(): void + { + $this->clearHistory = true; + } + /** * @deprecated Use `optional` instead. */ @@ -121,7 +128,8 @@ public function render(string $component, $props = []): Response $component, array_merge($this->sharedProps, $props), $this->rootView, - $this->getVersion() + $this->getVersion(), + $this->clearHistory ); } From 95434d63febc33c46a3721b7460e36dd15bae353 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Jul 2024 12:47:34 -0400 Subject: [PATCH 12/93] mergeable props --- src/DeferProp.php | 6 +++++- src/MergeProp.php | 27 +++++++++++++++++++++++++++ src/Mergeable.php | 10 ++++++++++ src/MergesProps.php | 21 +++++++++++++++++++++ src/Response.php | 38 ++++++++++++++++++++++++++++---------- src/ResponseFactory.php | 8 ++++++++ src/Support/Header.php | 2 ++ 7 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 src/MergeProp.php create mode 100644 src/Mergeable.php create mode 100644 src/MergesProps.php diff --git a/src/DeferProp.php b/src/DeferProp.php index 183eb2af..91857b62 100644 --- a/src/DeferProp.php +++ b/src/DeferProp.php @@ -4,12 +4,16 @@ use Illuminate\Support\Facades\App; -class DeferProp implements IgnoreFirstLoad +class DeferProp implements IgnoreFirstLoad, Mergeable { + use MergesProps; + protected $callback; protected $group; + protected $merge = false; + public function __construct(callable $callback, ?string $group = null) { $this->callback = $callback; diff --git a/src/MergeProp.php b/src/MergeProp.php new file mode 100644 index 00000000..499b43f4 --- /dev/null +++ b/src/MergeProp.php @@ -0,0 +1,27 @@ +value = $value; + $this->merge = true; + } + + public function __invoke() + { + return is_callable($this->value) ? App::call($this->value) : $this->value; + } +} diff --git a/src/Mergeable.php b/src/Mergeable.php new file mode 100644 index 00000000..64878a05 --- /dev/null +++ b/src/Mergeable.php @@ -0,0 +1,10 @@ +merge = true; + + return $this; + } + + public function shouldMerge() + { + return $this->merge; + } +} diff --git a/src/Response.php b/src/Response.php index b6e6a0c6..1b60340a 100644 --- a/src/Response.php +++ b/src/Response.php @@ -115,9 +115,9 @@ public function resolveProperties(Request $request, array $props): array { $isPartial = $this->isPartial($request); - if (! $isPartial) { + if (!$isPartial) { $props = array_filter($this->props, static function ($prop) { - return ! ($prop instanceof IgnoreFirstLoad); + return !($prop instanceof IgnoreFirstLoad); }); } @@ -218,6 +218,7 @@ public function resolvePropertyInstances(array $props, Request $request): array OptionalProp::class, DeferProp::class, AlwaysProp::class, + MergeProp::class, WhenVisible::class, ])->first(fn ($class) => $value instanceof $class); @@ -248,13 +249,34 @@ public function resolvePropertyInstances(array $props, Request $request): array */ public function resolveMeta(Request $request): array { - $meta = [ + return array_merge([ 'assetVersion' => $this->version, 'clearHistory' => $this->clearHistory, - ]; + ], $this->resolveMergeProps($request), $this->resolveDeferredProps($request)); + } + + public function resolveMergeProps(Request $request): array + { + $resetProps = collect(explode(',', $request->header(Header::RESET, ''))); + $mergeProps = collect($this->props) + ->filter(function ($prop) { + return $prop instanceof Mergeable; + }) + ->filter(function ($prop) { + return $prop->shouldMerge(); + }) + ->filter(function ($prop, $key) use ($resetProps) { + return !$resetProps->contains($key); + }) + ->keys(); + + return $mergeProps->isNotEmpty() ? ['mergeProps' => $mergeProps->toArray()] : []; + } + public function resolveDeferredProps(Request $request): array + { if ($this->isPartial($request)) { - return $meta; + return []; } $deferredProps = collect($this->props) @@ -271,11 +293,7 @@ public function resolveMeta(Request $request): array ->map ->pluck('key'); - if ($deferredProps->isNotEmpty()) { - $meta['deferredProps'] = $deferredProps->toArray(); - } - - return $meta; + return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : []; } /** diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 1d32c99a..8fa3d61e 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -107,6 +107,14 @@ public function defer(callable $callback, string $group = 'default'): DeferProp return new DeferProp($callback, $group); } + /** + * @param mixed $value + */ + public function merge($value): MergeProp + { + return new MergeProp($value); + } + /** * @param mixed $value */ diff --git a/src/Support/Header.php b/src/Support/Header.php index 5b706efa..5091b67f 100644 --- a/src/Support/Header.php +++ b/src/Support/Header.php @@ -17,4 +17,6 @@ class Header public const PARTIAL_ONLY = 'X-Inertia-Partial-Data'; public const PARTIAL_EXCEPT = 'X-Inertia-Partial-Except'; + + public const RESET = 'X-Inertia-Reset'; } From fa70b17ebf48751b37131bdf75a10255540707c6 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 1 Aug 2024 10:39:30 -0400 Subject: [PATCH 13/93] eager prefetch via config --- config/inertia.php | 8 + src/ServiceProvider.php | 8 + src/ViteEagerPrefetch.php | 176 +++++++++++++++++ tests/EagerPrefetchTest.php | 368 ++++++++++++++++++++++++++++++++++++ tests/build/mainfest.json | 283 +++++++++++++++++++++++++++ 5 files changed, 843 insertions(+) create mode 100644 src/ViteEagerPrefetch.php create mode 100644 tests/EagerPrefetchTest.php create mode 100644 tests/build/mainfest.json diff --git a/config/inertia.php b/config/inertia.php index 558e9858..d0eb1ccf 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -64,4 +64,12 @@ ], + 'eager_prefetch' => [ + + 'strategy' => null, + + 'chunks' => 3, + + ], + ]; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 1236cafa..d55426ce 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,6 +3,7 @@ namespace Inertia; use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; +use Illuminate\Foundation\Vite; use Illuminate\Http\Request; use Illuminate\Routing\Router; use Illuminate\Support\ServiceProvider as BaseServiceProvider; @@ -39,6 +40,13 @@ public function register(): void $app['config']->get('inertia.testing.page_extensions') ); }); + + if (config('inertia.eager_prefetch.strategy', false)) { + $this->app->singleton(Vite::class, fn () => (new ViteEagerPrefetch())->usePrefetchStrategy( + config('inertia.eager_prefetch.strategy'), + config('inertia.eager_prefetch.chunks') + )); + } } public function boot(): void diff --git a/src/ViteEagerPrefetch.php b/src/ViteEagerPrefetch.php new file mode 100644 index 00000000..efccb2a2 --- /dev/null +++ b/src/ViteEagerPrefetch.php @@ -0,0 +1,176 @@ +prefetchStrategy = $strategy; + + if ($strategy === 'waterfall') { + $this->prefetchChunks = $config[0] ?? 3; + } + + return $this; + } + + /** + * Generate Vite tags for an entrypoint. + * + * @param string|string[] $entrypoints + * @param string|null $buildDirectory + * @return \Illuminate\Support\HtmlString + */ + public function __invoke($entrypoints, $buildDirectory = null) + { + $manifest = $this->manifest($buildDirectory ??= $this->buildDirectory); + $base = parent::__invoke($entrypoints, $buildDirectory); + + if ($this->isRunningHot()) { + return $base; + } + + $discoveredImports = []; + + return collect($entrypoints) + ->flatMap(fn ($entrypoint) => collect($manifest[$entrypoint]['dynamicImports'] ?? []) + ->map(fn ($import) => $manifest[$import]) + ->filter(fn ($chunk) => str_ends_with($chunk['file'], '.js') || str_ends_with($chunk['file'], '.css')) + ->flatMap($f = function ($chunk) use (&$f, $manifest, &$discoveredImports) { + return collect([...$chunk['imports'] ?? [], ...$chunk['dynamicImports'] ?? []]) + ->reject(function ($import) use (&$discoveredImports) { + if (isset($discoveredImports[$import])) { + return true; + } + + return ! $discoveredImports[$import] = true; + }) + ->reduce( + fn ($chunks, $import) => $chunks->merge( + $f($manifest[$import]) + ), + collect([$chunk]) + ) + ->merge(collect($chunk['css'] ?? [])->map( + fn ($css) => collect($manifest)->first(fn ($chunk) => $chunk['file'] === $css) ?? [ + 'file' => $css, + ], + )); + }) + ->map(function ($chunk) use ($buildDirectory, $manifest) { + return collect([ + ...$this->resolvePreloadTagAttributes( + $chunk['src'] ?? null, + $url = $this->assetPath("{$buildDirectory}/{$chunk['file']}"), + $chunk, + $manifest, + ), + 'rel' => 'prefetch', + 'href' => $url, + ])->reject( + fn ($value) => in_array($value, [null, false], true) + )->mapWithKeys(fn ($value, $key) => [ + $key = (is_int($key) ? $value : $key) => $value === true ? $key : $value, + ])->all(); + }) + ->reject(fn ($attributes) => isset($this->preloadedAssets[$attributes['href']]))) + ->unique('href') + ->values() + ->pipe(fn ($assets) => with(Js::from($assets), fn ($assets) => match ($this->prefetchStrategy) { + 'waterfall' => new HtmlString($base.<< + window.addEventListener('load', () => window.setTimeout(() => { + const linkTemplate = document.createElement('link') + linkTemplate.rel = 'prefetch' + + const makeLink = (asset) => { + const link = linkTemplate.cloneNode() + + Object.keys(asset).forEach((attribute) => { + link.setAttribute(attribute, asset[attribute]) + }) + + return link + } + + const loadNext = (assets, count) => window.setTimeout(() => { + if (count > assets.length) { + count = assets.length + + if (count === 0) { + return + } + } + + const fragment = new DocumentFragment + + while (count > 0) { + const link = makeLink(assets.shift()) + fragment.append(link) + count-- + + if (assets.length) { + link.onload = () => loadNext(assets, 1) + link.error = () => loadNext(assets, 1) + } + } + + document.head.append(fragment) + }) + + loadNext({$assets}, {$this->prefetchChunks}) + })) + + HTML), + 'aggressive' => new HtmlString($base.<< + window.addEventListener('load', () => window.setTimeout(() => { + const linkTemplate = document.createElement('link') + linkTemplate.rel = 'prefetch' + + const makeLink = (asset) => { + const link = linkTemplate.cloneNode() + + Object.keys(asset).forEach((attribute) => { + link.setAttribute(attribute, asset[attribute]) + }) + + return link + } + + const fragment = new DocumentFragment + {$assets}.forEach((asset) => fragment.append(makeLink(asset))) + document.head.append(fragment) + })) + + HTML), + })); + } +} diff --git a/tests/EagerPrefetchTest.php b/tests/EagerPrefetchTest.php new file mode 100644 index 00000000..e5c7fa85 --- /dev/null +++ b/tests/EagerPrefetchTest.php @@ -0,0 +1,368 @@ +usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ]); + $this->assertSame(<< + + HTML, $html); + } + + public function test_it_uses_default_chunks_for_waterfall() + { + app()->usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('waterfall')->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ]); + $this->assertStringContainsString(<<usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/app.js', 'resources/js/Pages/Auth/Login.vue'])->usePrefetchStrategy('waterfall')->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ]); + $this->assertStringContainsString(<<usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('waterfall', 10)->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ]); + $this->assertStringContainsString(<<usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('aggressive')->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ]); + + $this->assertSame(<< + + HTML, $html); + } + + public function test_adds_attributes_to_prefetch_tags() + { + app()->usePublicPath(__DIR__); + + $html = (string) tap(Vite::withEntryPoints(['resources/js/app.js']))->useCspNonce('abc123')->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js', 'nonce' => 'abc123'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js', 'nonce' => 'abc123'], + ]); + $this->assertStringContainsString(<<usePublicPath(__DIR__); + + $html = (string) tap(Vite::withEntryPoints(['resources/js/app.js']))->usePreloadTagAttributes([ + 'key' => 'value', + 'key-only', + 'true-value' => true, + 'false-value' => false, + 'null-value' => null, + ])->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], + ]); + + $this->assertStringContainsString(<<usePublicPath(__DIR__); + + $html = (string) Vite::withEntryPoints(['resources/js/admin.js'])->toHtml(); + + $expectedAssets = Js::from([ + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/admin-runtime-import-CRvLQy6v.js'], + ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/admin-runtime-import-import-DKMIaPXC.js'], + ['rel' => 'prefetch', 'as' => 'style', 'href' => 'http://localhost/build/assets/admin-runtime-import-BlmN0T4U.css'], + ]); + $this->assertSame(<< + + HTML, $html); + } +} diff --git a/tests/build/mainfest.json b/tests/build/mainfest.json new file mode 100644 index 00000000..ab1b1cab --- /dev/null +++ b/tests/build/mainfest.json @@ -0,0 +1,283 @@ +{ + "_ApplicationLogo-BhIZH06z.js": { + "file": "assets/ApplicationLogo-BhIZH06z.js", + "name": "ApplicationLogo", + "imports": [ + "__plugin-vue_export-helper-DlAUqK2U.js", + "_index-BSdK3M0e.js" + ] + }, + "_AuthenticatedLayout-DfWF52N1.js": { + "file": "assets/AuthenticatedLayout-DfWF52N1.js", + "name": "AuthenticatedLayout", + "imports": [ + "_ApplicationLogo-BhIZH06z.js", + "_index-BSdK3M0e.js" + ] + }, + "_GuestLayout-BY3LC-73.js": { + "file": "assets/GuestLayout-BY3LC-73.js", + "name": "GuestLayout", + "imports": [ + "_ApplicationLogo-BhIZH06z.js", + "_index-BSdK3M0e.js" + ] + }, + "_PrimaryButton-DuXwr-9M.js": { + "file": "assets/PrimaryButton-DuXwr-9M.js", + "name": "PrimaryButton", + "imports": [ + "__plugin-vue_export-helper-DlAUqK2U.js", + "_index-BSdK3M0e.js" + ] + }, + "_TextInput-C8CCB_U_.js": { + "file": "assets/TextInput-C8CCB_U_.js", + "name": "TextInput", + "imports": [ + "_index-BSdK3M0e.js" + ] + }, + "__plugin-vue_export-helper-DlAUqK2U.js": { + "file": "assets/_plugin-vue_export-helper-DlAUqK2U.js", + "name": "_plugin-vue_export-helper" + }, + "_index-!~{00f}~.js": { + "file": "assets/index-B3s1tYeC.css", + "src": "_index-!~{00f}~.js" + }, + "_index-BSdK3M0e.js": { + "file": "assets/index-BSdK3M0e.js", + "name": "index", + "css": [ + "assets/index-B3s1tYeC.css" + ] + }, + "resources/js/Pages/Auth/ConfirmPassword.vue": { + "file": "assets/ConfirmPassword-CDwcgU8E.js", + "name": "ConfirmPassword", + "src": "resources/js/Pages/Auth/ConfirmPassword.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Auth/ForgotPassword.vue": { + "file": "assets/ForgotPassword-B0WWE0BO.js", + "name": "ForgotPassword", + "src": "resources/js/Pages/Auth/ForgotPassword.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Auth/Login.vue": { + "file": "assets/Login-DAFSdGSW.js", + "name": "Login", + "src": "resources/js/Pages/Auth/Login.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Auth/Register.vue": { + "file": "assets/Register-CfYQbTlA.js", + "name": "Register", + "src": "resources/js/Pages/Auth/Register.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Auth/ResetPassword.vue": { + "file": "assets/ResetPassword-BNl7a4X1.js", + "name": "ResetPassword", + "src": "resources/js/Pages/Auth/ResetPassword.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Auth/VerifyEmail.vue": { + "file": "assets/VerifyEmail-CyukB_SZ.js", + "name": "VerifyEmail", + "src": "resources/js/Pages/Auth/VerifyEmail.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_GuestLayout-BY3LC-73.js", + "_PrimaryButton-DuXwr-9M.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Dashboard.vue": { + "file": "assets/Dashboard-DM_LxQy2.js", + "name": "Dashboard", + "src": "resources/js/Pages/Dashboard.vue", + "isDynamicEntry": true, + "imports": [ + "_AuthenticatedLayout-DfWF52N1.js", + "_index-BSdK3M0e.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Profile/Edit.vue": { + "file": "assets/Edit-CYV2sXpe.js", + "name": "Edit", + "src": "resources/js/Pages/Profile/Edit.vue", + "isDynamicEntry": true, + "imports": [ + "_AuthenticatedLayout-DfWF52N1.js", + "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", + "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", + "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", + "_index-BSdK3M0e.js", + "_ApplicationLogo-BhIZH06z.js", + "__plugin-vue_export-helper-DlAUqK2U.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js" + ] + }, + "resources/js/Pages/Profile/Partials/DeleteUserForm.vue": { + "file": "assets/DeleteUserForm-B1oHFaVP.js", + "name": "DeleteUserForm", + "src": "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "__plugin-vue_export-helper-DlAUqK2U.js", + "_TextInput-C8CCB_U_.js" + ] + }, + "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue": { + "file": "assets/UpdatePasswordForm-CaeWqGla.js", + "name": "UpdatePasswordForm", + "src": "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue": { + "file": "assets/UpdateProfileInformationForm-CJwkYwQQ.js", + "name": "UpdateProfileInformationForm", + "src": "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js", + "_TextInput-C8CCB_U_.js", + "_PrimaryButton-DuXwr-9M.js", + "__plugin-vue_export-helper-DlAUqK2U.js" + ] + }, + "resources/js/Pages/Welcome.vue": { + "file": "assets/Welcome-D_7l79PQ.js", + "name": "Welcome", + "src": "resources/js/Pages/Welcome.vue", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js" + ] + }, + "resources/js/admin-runtime-import-import.js": { + "file": "assets/admin-runtime-import-import-DKMIaPXC.js", + "name": "admin-runtime-import-import", + "src": "resources/js/admin-runtime-import-import.js", + "isDynamicEntry": true + }, + "resources/js/admin-runtime-import.js": { + "file": "assets/admin-runtime-import-CRvLQy6v.js", + "name": "admin-runtime-import", + "src": "resources/js/admin-runtime-import.js", + "isDynamicEntry": true, + "imports": [ + "_index-BSdK3M0e.js" + ], + "dynamicImports": [ + "resources/js/admin-runtime-import-import.js" + ], + "css": [ + "assets/admin-runtime-import-BlmN0T4U.css" + ] + }, + "resources/js/admin.js": { + "file": "assets/admin-Sefg0Q45.js", + "name": "admin", + "src": "resources/js/admin.js", + "isEntry": true, + "imports": [ + "_index-BSdK3M0e.js" + ], + "dynamicImports": [ + "resources/js/Pages/Auth/ConfirmPassword.vue", + "resources/js/Pages/Auth/ForgotPassword.vue", + "resources/js/Pages/Auth/Login.vue", + "resources/js/Pages/Auth/Register.vue", + "resources/js/Pages/Auth/ResetPassword.vue", + "resources/js/Pages/Auth/VerifyEmail.vue", + "resources/js/Pages/Dashboard.vue", + "resources/js/Pages/Profile/Edit.vue", + "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", + "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", + "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", + "resources/js/Pages/Welcome.vue", + "resources/js/admin-runtime-import.js" + ], + "css": [ + "assets/admin-BctAalm_.css" + ] + }, + "resources/js/app.js": { + "file": "assets/app-lliD09ip.js", + "name": "app", + "src": "resources/js/app.js", + "isEntry": true, + "imports": [ + "_index-BSdK3M0e.js" + ], + "dynamicImports": [ + "resources/js/Pages/Auth/ConfirmPassword.vue", + "resources/js/Pages/Auth/ForgotPassword.vue", + "resources/js/Pages/Auth/Login.vue", + "resources/js/Pages/Auth/Register.vue", + "resources/js/Pages/Auth/ResetPassword.vue", + "resources/js/Pages/Auth/VerifyEmail.vue", + "resources/js/Pages/Dashboard.vue", + "resources/js/Pages/Profile/Edit.vue", + "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", + "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", + "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", + "resources/js/Pages/Welcome.vue" + ] + } +} From 0c99750a090a7bc3996621edab90f4323d08e437 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 8 Aug 2024 10:59:59 -0400 Subject: [PATCH 14/93] wip --- src/DeferProp.php | 2 -- src/MergesProps.php | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/DeferProp.php b/src/DeferProp.php index 91857b62..db722c75 100644 --- a/src/DeferProp.php +++ b/src/DeferProp.php @@ -12,8 +12,6 @@ class DeferProp implements IgnoreFirstLoad, Mergeable protected $group; - protected $merge = false; - public function __construct(callable $callback, ?string $group = null) { $this->callback = $callback; diff --git a/src/MergesProps.php b/src/MergesProps.php index 8e2cdcbe..3f30de77 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -5,16 +5,16 @@ trait MergesProps { - protected $merge = false; + protected bool $merge = false; - public function merge() + public function merge(): static { $this->merge = true; return $this; } - public function shouldMerge() + public function shouldMerge(): bool { return $this->merge; } From a60f761ab76e32da71ef5d429459cac1c293216e Mon Sep 17 00:00:00 2001 From: Robert Boes <2871897+RobertBoes@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:57:50 +0200 Subject: [PATCH 15/93] Replace md5 with xxhash --- src/Middleware.php | 6 +++--- tests/ResponseFactoryTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Middleware.php b/src/Middleware.php index 24caf9ad..ba4d9a34 100644 --- a/src/Middleware.php +++ b/src/Middleware.php @@ -29,15 +29,15 @@ class Middleware public function version(Request $request) { if (config('app.asset_url')) { - return md5(config('app.asset_url')); + return hash('xxh128', config('app.asset_url')); } if (file_exists($manifest = public_path('mix-manifest.json'))) { - return md5_file($manifest); + return hash_file('xxh128', $manifest); } if (file_exists($manifest = public_path('build/manifest.json'))) { - return md5_file($manifest); + return hash_file('xxh128', $manifest); } return null; diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 50d2c78b..465539da 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -109,7 +109,7 @@ public function test_the_version_can_be_a_closure(): void $this->assertSame('', Inertia::getVersion()); Inertia::version(function () { - return md5('Inertia'); + return hash('xxh128', 'Inertia'); }); return Inertia::render('User/Edit'); From 61b1c89f5ff9743ec1dbd49b9834b61d8d89633e Mon Sep 17 00:00:00 2001 From: Robert Boes <2871897+RobertBoes@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:35:54 +0200 Subject: [PATCH 16/93] Update the hash value for "Inertia" --- tests/ResponseFactoryTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 465539da..ba1bd91a 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -117,7 +117,7 @@ public function test_the_version_can_be_a_closure(): void $response = $this->withoutExceptionHandling()->get('/', [ 'X-Inertia' => 'true', - 'X-Inertia-Version' => 'b19a24ee5c287f42ee1d465dab77ab37', + 'X-Inertia-Version' => 'f445bd0a2c393a5af14fc677f59980a9', ]); $response->assertSuccessful(); From f6ad746994d2e0b64693af56c4fdfbed7b53ea0f Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 27 Sep 2024 16:23:47 -0400 Subject: [PATCH 17/93] encrypt history --- config/inertia.php | 6 ++++ src/EncryptHistoryMiddleware.php | 22 ++++++++++++ src/Inertia.php | 1 + src/Response.php | 57 ++++++++++++++++++++++++-------- src/ResponseFactory.php | 15 ++++++++- 5 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 src/EncryptHistoryMiddleware.php diff --git a/config/inertia.php b/config/inertia.php index d0eb1ccf..5634491f 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -72,4 +72,10 @@ ], + 'history' => [ + + 'encrypt' => false, + + ], + ]; diff --git a/src/EncryptHistoryMiddleware.php b/src/EncryptHistoryMiddleware.php new file mode 100644 index 00000000..e7eefa1a --- /dev/null +++ b/src/EncryptHistoryMiddleware.php @@ -0,0 +1,22 @@ +component = $component; $this->props = $props instanceof Arrayable ? $props->toArray() : $props; $this->rootView = $rootView; $this->version = $version; $this->clearHistory = $clearHistory; + $this->encryptHistory = $encryptHistory; } /** @@ -84,6 +90,13 @@ public function rootView(string $rootView): self return $this; } + public function cache(string|array $cacheFor): self + { + $this->cacheFor = is_array($cacheFor) ? $cacheFor : [$cacheFor]; + + return $this; + } + /** * Create an HTTP response that represents the object. * @@ -94,12 +107,19 @@ public function toResponse($request) { $props = $this->resolveProperties($request, $this->props); - $page = [ - 'component' => $this->component, - 'props' => $props, - 'url' => Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'), - 'meta' => $this->resolveMeta($request), - ]; + $page = array_merge( + [ + 'component' => $this->component, + 'props' => $props, + 'url' => Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'), + 'version' => $this->version, + 'clearHistory' => $this->clearHistory, + 'encryptHistory' => $this->encryptHistory, + ], + $this->resolveMergeProps($request), + $this->resolveDeferredProps($request), + $this->resolveCacheDirections($request), + ); if ($request->header(Header::INERTIA)) { return new JsonResponse($page, 200, [Header::INERTIA => 'true']); @@ -220,7 +240,7 @@ public function resolvePropertyInstances(array $props, Request $request): array AlwaysProp::class, MergeProp::class, WhenVisible::class, - ])->first(fn ($class) => $value instanceof $class); + ])->first(fn($class) => $value instanceof $class); if ($resolveViaApp) { $value = App::call($value); @@ -245,14 +265,23 @@ public function resolvePropertyInstances(array $props, Request $request): array } /** - * Resolve the meta data for the response. + * Resolve the cache directions for the response. */ - public function resolveMeta(Request $request): array + public function resolveCacheDirections(Request $request): array { - return array_merge([ - 'assetVersion' => $this->version, - 'clearHistory' => $this->clearHistory, - ], $this->resolveMergeProps($request), $this->resolveDeferredProps($request)); + if (count($this->cacheFor) === 0) { + return []; + } + + return [ + 'cache' => collect($this->cacheFor)->map(function ($value) { + if ($value instanceof CarbonInterval) { + return $value->totalSeconds; + } + + return intval($value); + }), + ]; } public function resolveMergeProps(Request $request): array diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 8fa3d61e..777b0243 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -29,6 +29,13 @@ class ResponseFactory protected $clearHistory = false; + protected $encryptHistory = false; + + public function __construct() + { + $this->encryptHistory = config('inertia.history.encrypt', false); + } + public function setRootView(string $name): void { $this->rootView = $name; @@ -89,6 +96,11 @@ public function clearHistory(): void $this->clearHistory = true; } + public function encryptHistory($encrypt = true): void + { + $this->encryptHistory = $encrypt; + } + /** * @deprecated Use `optional` instead. */ @@ -137,7 +149,8 @@ public function render(string $component, $props = []): Response array_merge($this->sharedProps, $props), $this->rootView, $this->getVersion(), - $this->clearHistory + $this->clearHistory, + $this->encryptHistory ); } From b198c78024e5d965d2b2a3e76d9b8510f313f370 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 10:36:14 -0400 Subject: [PATCH 18/93] fixing tests --- src/Testing/AssertableInertia.php | 7 ++++--- tests/ControllerTest.php | 6 +++--- tests/ResponseTest.php | 28 ++++++++++++---------------- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/Testing/AssertableInertia.php b/src/Testing/AssertableInertia.php index fabdf50f..f4ffd614 100644 --- a/src/Testing/AssertableInertia.php +++ b/src/Testing/AssertableInertia.php @@ -29,8 +29,9 @@ public static function fromTestResponse(TestResponse $response): self PHPUnit::assertArrayHasKey('component', $page); PHPUnit::assertArrayHasKey('props', $page); PHPUnit::assertArrayHasKey('url', $page); - PHPUnit::assertArrayHasKey('meta', $page); - PHPUnit::assertArrayHasKey('assetVersion', $page['meta']); + PHPUnit::assertArrayHasKey('version', $page); + PHPUnit::assertArrayHasKey('encryptHistory', $page); + PHPUnit::assertArrayHasKey('clearHistory', $page); } catch (AssertionFailedError $e) { PHPUnit::fail('Not a valid Inertia response.'); } @@ -38,7 +39,7 @@ public static function fromTestResponse(TestResponse $response): self $instance = static::fromArray($page['props']); $instance->component = $page['component']; $instance->url = $page['url']; - $instance->version = $page['meta']['assetVersion']; + $instance->version = $page['version']; return $instance; } diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index d92d7f3c..864fcfe9 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -27,9 +27,9 @@ public function test_controller_returns_an_inertia_response(): void 'errors' => (object) [], ], 'url' => '/', - 'meta' => [ - 'assetVersion' => '', - ] + 'version' => '', + 'clearHistory' => false, + 'encryptHistory' => false, ]); } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index f5264e58..b55275e1 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -45,8 +45,8 @@ public function test_server_response(): void $this->assertSame('User/Edit', $page['component']); $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); - $this->assertSame('123', $page['meta']['assetVersion']); - $this->assertSame('
', $view->render()); + $this->assertSame('123', $page['version']); + $this->assertSame('
', $view->render()); } public function test_xhr_response(): void @@ -63,7 +63,7 @@ public function test_xhr_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_resource_response(): void @@ -81,7 +81,7 @@ public function test_resource_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_lazy_resource_response(): void @@ -98,9 +98,7 @@ public function test_lazy_resource_response(): void $callable = static function () use ($users) { $page = new LengthAwarePaginator($users->take(2), $users->count(), 2); - return new class($page, JsonResource::class) extends ResourceCollection - { - }; + return new class($page, JsonResource::class) extends ResourceCollection {}; }; $response = new Response('User/Index', ['users' => $callable], 'app', '123'); @@ -129,7 +127,7 @@ public function test_lazy_resource_response(): void $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('User/Index', $page->component); $this->assertSame('/users?page=1', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); tap($page->props->users, function ($users) use ($expected) { $this->assertSame(json_encode($expected['data']), json_encode($users->data)); $this->assertSame(json_encode($expected['links']), json_encode($users->links)); @@ -153,9 +151,7 @@ public function test_nested_lazy_resource_response(): void // nested array with ResourceCollection to resolve return [ - 'users' => new class($page, JsonResource::class) extends ResourceCollection - { - }, + 'users' => new class($page, JsonResource::class) extends ResourceCollection {}, ]; }; @@ -187,7 +183,7 @@ public function test_nested_lazy_resource_response(): void $this->assertInstanceOf(JsonResponse::class, $response); $this->assertSame('User/Index', $page->component); $this->assertSame('/users?page=1', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); tap($page->props->something->users, function ($users) use ($expected) { $this->assertSame(json_encode($expected['users']['data']), json_encode($users->data)); $this->assertSame(json_encode($expected['users']['links']), json_encode($users->links)); @@ -210,7 +206,7 @@ public function test_arrayable_prop_response(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_promise_props_are_resolved(): void @@ -233,7 +229,7 @@ public function test_promise_props_are_resolved(): void $this->assertSame('User/Edit', $page->component); $this->assertSame('Jonathan', $page->props->user->name); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_xhr_partial_response(): void @@ -256,7 +252,7 @@ public function test_xhr_partial_response(): void $this->assertCount(1, $props); $this->assertSame('partial-data', $page->props->partial); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_exclude_props_from_partial_response(): void @@ -279,7 +275,7 @@ public function test_exclude_props_from_partial_response(): void $this->assertCount(1, $props); $this->assertSame('partial-data', $page->props->partial); $this->assertSame('/user/123', $page->url); - $this->assertSame('123', $page->meta->assetVersion); + $this->assertSame('123', $page->version); } public function test_nested_partial_props(): void From e00319c1b07023083920cc7f5e7e81c377b83ffe Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 10:43:58 -0400 Subject: [PATCH 19/93] fixed spelling error --- tests/build/{mainfest.json => manifest.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/build/{mainfest.json => manifest.json} (100%) diff --git a/tests/build/mainfest.json b/tests/build/manifest.json similarity index 100% rename from tests/build/mainfest.json rename to tests/build/manifest.json From 7b473f495b8ba331ba91da6e5e5e8f8da6ce8f11 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 10:56:47 -0400 Subject: [PATCH 20/93] removed eager loading as it landed in vite main --- config/inertia.php | 8 - src/ServiceProvider.php | 13 +- tests/EagerPrefetchTest.php | 368 ------------------------------------ tests/build/manifest.json | 283 --------------------------- 4 files changed, 3 insertions(+), 669 deletions(-) delete mode 100644 tests/EagerPrefetchTest.php delete mode 100644 tests/build/manifest.json diff --git a/config/inertia.php b/config/inertia.php index 5634491f..e35f2526 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -64,14 +64,6 @@ ], - 'eager_prefetch' => [ - - 'strategy' => null, - - 'chunks' => 3, - - ], - 'history' => [ 'encrypt' => false, diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index d55426ce..68553b90 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -24,7 +24,7 @@ public function register(): void $this->app->bind(Gateway::class, HttpGateway::class); $this->mergeConfigFrom( - __DIR__.'/../config/inertia.php', + __DIR__ . '/../config/inertia.php', 'inertia' ); @@ -40,13 +40,6 @@ public function register(): void $app['config']->get('inertia.testing.page_extensions') ); }); - - if (config('inertia.eager_prefetch.strategy', false)) { - $this->app->singleton(Vite::class, fn () => (new ViteEagerPrefetch())->usePrefetchStrategy( - config('inertia.eager_prefetch.strategy'), - config('inertia.eager_prefetch.chunks') - )); - } } public function boot(): void @@ -54,7 +47,7 @@ public function boot(): void $this->registerConsoleCommands(); $this->publishes([ - __DIR__.'/../config/inertia.php' => config_path('inertia.php'), + __DIR__ . '/../config/inertia.php' => config_path('inertia.php'), ]); } @@ -89,7 +82,7 @@ protected function registerRequestMacro(): void protected function registerRouterMacro(): void { Router::macro('inertia', function ($uri, $component, $props = []) { - return $this->match(['GET', 'HEAD'], $uri, '\\'.Controller::class) + return $this->match(['GET', 'HEAD'], $uri, '\\' . Controller::class) ->defaults('component', $component) ->defaults('props', $props); }); diff --git a/tests/EagerPrefetchTest.php b/tests/EagerPrefetchTest.php deleted file mode 100644 index e5c7fa85..00000000 --- a/tests/EagerPrefetchTest.php +++ /dev/null @@ -1,368 +0,0 @@ -usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ]); - $this->assertSame(<< - - HTML, $html); - } - - public function test_it_uses_default_chunks_for_waterfall() - { - app()->usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('waterfall')->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ]); - $this->assertStringContainsString(<<usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/app.js', 'resources/js/Pages/Auth/Login.vue'])->usePrefetchStrategy('waterfall')->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ]); - $this->assertStringContainsString(<<usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('waterfall', 10)->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ]); - $this->assertStringContainsString(<<usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/app.js'])->usePrefetchStrategy('aggressive')->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ]); - - $this->assertSame(<< - - HTML, $html); - } - - public function test_adds_attributes_to_prefetch_tags() - { - app()->usePublicPath(__DIR__); - - $html = (string) tap(Vite::withEntryPoints(['resources/js/app.js']))->useCspNonce('abc123')->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js', 'nonce' => 'abc123'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js', 'nonce' => 'abc123'], - ]); - $this->assertStringContainsString(<<usePublicPath(__DIR__); - - $html = (string) tap(Vite::withEntryPoints(['resources/js/app.js']))->usePreloadTagAttributes([ - 'key' => 'value', - 'key-only', - 'true-value' => true, - 'false-value' => false, - 'null-value' => null, - ])->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js', 'key' => 'value', 'key-only' => 'key-only', 'true-value' => 'true-value'], - ]); - - $this->assertStringContainsString(<<usePublicPath(__DIR__); - - $html = (string) Vite::withEntryPoints(['resources/js/admin.js'])->toHtml(); - - $expectedAssets = Js::from([ - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ConfirmPassword-CDwcgU8E.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/GuestLayout-BY3LC-73.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/TextInput-C8CCB_U_.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/PrimaryButton-DuXwr-9M.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ApplicationLogo-BhIZH06z.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/_plugin-vue_export-helper-DlAUqK2U.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ForgotPassword-B0WWE0BO.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Login-DAFSdGSW.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Register-CfYQbTlA.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/ResetPassword-BNl7a4X1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/VerifyEmail-CyukB_SZ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Dashboard-DM_LxQy2.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/AuthenticatedLayout-DfWF52N1.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Edit-CYV2sXpe.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/DeleteUserForm-B1oHFaVP.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdatePasswordForm-CaeWqGla.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/UpdateProfileInformationForm-CJwkYwQQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/Welcome-D_7l79PQ.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/admin-runtime-import-CRvLQy6v.js'], - ['rel' => 'prefetch', 'href' => 'http://localhost/build/assets/admin-runtime-import-import-DKMIaPXC.js'], - ['rel' => 'prefetch', 'as' => 'style', 'href' => 'http://localhost/build/assets/admin-runtime-import-BlmN0T4U.css'], - ]); - $this->assertSame(<< - - HTML, $html); - } -} diff --git a/tests/build/manifest.json b/tests/build/manifest.json deleted file mode 100644 index ab1b1cab..00000000 --- a/tests/build/manifest.json +++ /dev/null @@ -1,283 +0,0 @@ -{ - "_ApplicationLogo-BhIZH06z.js": { - "file": "assets/ApplicationLogo-BhIZH06z.js", - "name": "ApplicationLogo", - "imports": [ - "__plugin-vue_export-helper-DlAUqK2U.js", - "_index-BSdK3M0e.js" - ] - }, - "_AuthenticatedLayout-DfWF52N1.js": { - "file": "assets/AuthenticatedLayout-DfWF52N1.js", - "name": "AuthenticatedLayout", - "imports": [ - "_ApplicationLogo-BhIZH06z.js", - "_index-BSdK3M0e.js" - ] - }, - "_GuestLayout-BY3LC-73.js": { - "file": "assets/GuestLayout-BY3LC-73.js", - "name": "GuestLayout", - "imports": [ - "_ApplicationLogo-BhIZH06z.js", - "_index-BSdK3M0e.js" - ] - }, - "_PrimaryButton-DuXwr-9M.js": { - "file": "assets/PrimaryButton-DuXwr-9M.js", - "name": "PrimaryButton", - "imports": [ - "__plugin-vue_export-helper-DlAUqK2U.js", - "_index-BSdK3M0e.js" - ] - }, - "_TextInput-C8CCB_U_.js": { - "file": "assets/TextInput-C8CCB_U_.js", - "name": "TextInput", - "imports": [ - "_index-BSdK3M0e.js" - ] - }, - "__plugin-vue_export-helper-DlAUqK2U.js": { - "file": "assets/_plugin-vue_export-helper-DlAUqK2U.js", - "name": "_plugin-vue_export-helper" - }, - "_index-!~{00f}~.js": { - "file": "assets/index-B3s1tYeC.css", - "src": "_index-!~{00f}~.js" - }, - "_index-BSdK3M0e.js": { - "file": "assets/index-BSdK3M0e.js", - "name": "index", - "css": [ - "assets/index-B3s1tYeC.css" - ] - }, - "resources/js/Pages/Auth/ConfirmPassword.vue": { - "file": "assets/ConfirmPassword-CDwcgU8E.js", - "name": "ConfirmPassword", - "src": "resources/js/Pages/Auth/ConfirmPassword.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Auth/ForgotPassword.vue": { - "file": "assets/ForgotPassword-B0WWE0BO.js", - "name": "ForgotPassword", - "src": "resources/js/Pages/Auth/ForgotPassword.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Auth/Login.vue": { - "file": "assets/Login-DAFSdGSW.js", - "name": "Login", - "src": "resources/js/Pages/Auth/Login.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Auth/Register.vue": { - "file": "assets/Register-CfYQbTlA.js", - "name": "Register", - "src": "resources/js/Pages/Auth/Register.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Auth/ResetPassword.vue": { - "file": "assets/ResetPassword-BNl7a4X1.js", - "name": "ResetPassword", - "src": "resources/js/Pages/Auth/ResetPassword.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Auth/VerifyEmail.vue": { - "file": "assets/VerifyEmail-CyukB_SZ.js", - "name": "VerifyEmail", - "src": "resources/js/Pages/Auth/VerifyEmail.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_GuestLayout-BY3LC-73.js", - "_PrimaryButton-DuXwr-9M.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Dashboard.vue": { - "file": "assets/Dashboard-DM_LxQy2.js", - "name": "Dashboard", - "src": "resources/js/Pages/Dashboard.vue", - "isDynamicEntry": true, - "imports": [ - "_AuthenticatedLayout-DfWF52N1.js", - "_index-BSdK3M0e.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Profile/Edit.vue": { - "file": "assets/Edit-CYV2sXpe.js", - "name": "Edit", - "src": "resources/js/Pages/Profile/Edit.vue", - "isDynamicEntry": true, - "imports": [ - "_AuthenticatedLayout-DfWF52N1.js", - "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", - "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", - "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", - "_index-BSdK3M0e.js", - "_ApplicationLogo-BhIZH06z.js", - "__plugin-vue_export-helper-DlAUqK2U.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js" - ] - }, - "resources/js/Pages/Profile/Partials/DeleteUserForm.vue": { - "file": "assets/DeleteUserForm-B1oHFaVP.js", - "name": "DeleteUserForm", - "src": "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "__plugin-vue_export-helper-DlAUqK2U.js", - "_TextInput-C8CCB_U_.js" - ] - }, - "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue": { - "file": "assets/UpdatePasswordForm-CaeWqGla.js", - "name": "UpdatePasswordForm", - "src": "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue": { - "file": "assets/UpdateProfileInformationForm-CJwkYwQQ.js", - "name": "UpdateProfileInformationForm", - "src": "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js", - "_TextInput-C8CCB_U_.js", - "_PrimaryButton-DuXwr-9M.js", - "__plugin-vue_export-helper-DlAUqK2U.js" - ] - }, - "resources/js/Pages/Welcome.vue": { - "file": "assets/Welcome-D_7l79PQ.js", - "name": "Welcome", - "src": "resources/js/Pages/Welcome.vue", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js" - ] - }, - "resources/js/admin-runtime-import-import.js": { - "file": "assets/admin-runtime-import-import-DKMIaPXC.js", - "name": "admin-runtime-import-import", - "src": "resources/js/admin-runtime-import-import.js", - "isDynamicEntry": true - }, - "resources/js/admin-runtime-import.js": { - "file": "assets/admin-runtime-import-CRvLQy6v.js", - "name": "admin-runtime-import", - "src": "resources/js/admin-runtime-import.js", - "isDynamicEntry": true, - "imports": [ - "_index-BSdK3M0e.js" - ], - "dynamicImports": [ - "resources/js/admin-runtime-import-import.js" - ], - "css": [ - "assets/admin-runtime-import-BlmN0T4U.css" - ] - }, - "resources/js/admin.js": { - "file": "assets/admin-Sefg0Q45.js", - "name": "admin", - "src": "resources/js/admin.js", - "isEntry": true, - "imports": [ - "_index-BSdK3M0e.js" - ], - "dynamicImports": [ - "resources/js/Pages/Auth/ConfirmPassword.vue", - "resources/js/Pages/Auth/ForgotPassword.vue", - "resources/js/Pages/Auth/Login.vue", - "resources/js/Pages/Auth/Register.vue", - "resources/js/Pages/Auth/ResetPassword.vue", - "resources/js/Pages/Auth/VerifyEmail.vue", - "resources/js/Pages/Dashboard.vue", - "resources/js/Pages/Profile/Edit.vue", - "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", - "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", - "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", - "resources/js/Pages/Welcome.vue", - "resources/js/admin-runtime-import.js" - ], - "css": [ - "assets/admin-BctAalm_.css" - ] - }, - "resources/js/app.js": { - "file": "assets/app-lliD09ip.js", - "name": "app", - "src": "resources/js/app.js", - "isEntry": true, - "imports": [ - "_index-BSdK3M0e.js" - ], - "dynamicImports": [ - "resources/js/Pages/Auth/ConfirmPassword.vue", - "resources/js/Pages/Auth/ForgotPassword.vue", - "resources/js/Pages/Auth/Login.vue", - "resources/js/Pages/Auth/Register.vue", - "resources/js/Pages/Auth/ResetPassword.vue", - "resources/js/Pages/Auth/VerifyEmail.vue", - "resources/js/Pages/Dashboard.vue", - "resources/js/Pages/Profile/Edit.vue", - "resources/js/Pages/Profile/Partials/DeleteUserForm.vue", - "resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue", - "resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue", - "resources/js/Pages/Welcome.vue" - ] - } -} From da906d59263c7b316bd7ad3e57e356a400216343 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 11:03:55 -0400 Subject: [PATCH 21/93] added history related props to tests --- src/Testing/AssertableInertia.php | 10 ++++++++++ src/Testing/Concerns/PageObject.php | 2 ++ tests/ControllerTest.php | 2 +- tests/DirectiveTest.php | 8 ++++---- tests/ResponseTest.php | 2 ++ tests/Testing/TestResponseMacrosTest.php | 2 ++ 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Testing/AssertableInertia.php b/src/Testing/AssertableInertia.php index f4ffd614..d1ee90e7 100644 --- a/src/Testing/AssertableInertia.php +++ b/src/Testing/AssertableInertia.php @@ -19,6 +19,12 @@ class AssertableInertia extends AssertableJson /** @var string|null */ private $version; + /** @var bool */ + private $encryptHistory; + + /** @var bool */ + private $clearHistory; + public static function fromTestResponse(TestResponse $response): self { try { @@ -40,6 +46,8 @@ public static function fromTestResponse(TestResponse $response): self $instance->component = $page['component']; $instance->url = $page['url']; $instance->version = $page['version']; + $instance->encryptHistory = $page['encryptHistory']; + $instance->clearHistory = $page['clearHistory']; return $instance; } @@ -80,6 +88,8 @@ public function toArray() 'props' => $this->prop(), 'url' => $this->url, 'version' => $this->version, + 'encryptHistory' => $this->encryptHistory, + 'clearHistory' => $this->clearHistory, ]; } } diff --git a/src/Testing/Concerns/PageObject.php b/src/Testing/Concerns/PageObject.php index 467c48c3..109df780 100644 --- a/src/Testing/Concerns/PageObject.php +++ b/src/Testing/Concerns/PageObject.php @@ -49,6 +49,8 @@ public function toArray(): array 'props' => $this->props, 'url' => $this->url, 'version' => $this->version, + 'encryptHistory' => $this->encryptHistory, + 'clearHistory' => $this->clearHistory, ]; } } diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 864fcfe9..3648a0fc 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -28,8 +28,8 @@ public function test_controller_returns_an_inertia_response(): void ], 'url' => '/', 'version' => '', - 'clearHistory' => false, 'encryptHistory' => false, + 'clearHistory' => false, ]); } } diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index f1d7ef8a..a8303a56 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -30,7 +30,7 @@ class DirectiveTest extends TestCase /** * Example Page Objects. */ - protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '']; + protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; public function setUp(): void { @@ -39,7 +39,7 @@ public function setUp(): void $this->app->bind(Gateway::class, FakeGateway::class); $this->filesystem = m::mock(Filesystem::class); - $this->compiler = new BladeCompiler($this->filesystem, __DIR__.'/cache/views'); + $this->compiler = new BladeCompiler($this->filesystem, __DIR__ . '/cache/views'); $this->compiler->directive('inertia', [Directive::class, 'compile']); $this->compiler->directive('inertiaHead', [Directive::class, 'compileHead']); } @@ -94,7 +94,7 @@ public function test_inertia_directive_renders_the_root_element(): void { Config::set(['inertia.ssr.enabled' => false]); - $html = '
'; + $html = '
'; $this->assertSame($html, $this->renderView('@inertia', ['page' => self::EXAMPLE_PAGE_OBJECT])); $this->assertSame($html, $this->renderView('@inertia()', ['page' => self::EXAMPLE_PAGE_OBJECT])); @@ -116,7 +116,7 @@ public function test_inertia_directive_can_use_a_different_root_element_id(): vo { Config::set(['inertia.ssr.enabled' => false]); - $html = '
'; + $html = '
'; $this->assertSame($html, $this->renderView('@inertia(foo)', ['page' => self::EXAMPLE_PAGE_OBJECT])); $this->assertSame($html, $this->renderView("@inertia('foo')", ['page' => self::EXAMPLE_PAGE_OBJECT])); diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index b55275e1..d6467dad 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -46,6 +46,8 @@ public function test_server_response(): void $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); $this->assertSame('
', $view->render()); } diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index 14fe4fbd..312e8480 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -46,6 +46,8 @@ public function test_it_can_retrieve_the_inertia_page(): void $this->assertSame(['bar' => 'baz'], $page['props']); $this->assertSame('/example-url', $page['url']); $this->assertSame('', $page['version']); + $this->assertFalse($page['encryptHistory']); + $this->assertFalse($page['clearHistory']); }); } } From 141e693320ff6b432fda7889244df98f9e33b8bf Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 11:11:49 -0400 Subject: [PATCH 22/93] Update DeferPropTest.php --- tests/DeferPropTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/DeferPropTest.php b/tests/DeferPropTest.php index 3bab4040..00099d28 100644 --- a/tests/DeferPropTest.php +++ b/tests/DeferPropTest.php @@ -10,10 +10,10 @@ class DeferPropTest extends TestCase public function test_can_invoke(): void { $deferProp = new DeferProp(function () { - return 'A lazy value'; + return 'A deferred value'; }); - $this->assertSame('A lazy value', $deferProp()); + $this->assertSame('A deferred value', $deferProp()); } public function test_can_resolve_bindings_when_invoked(): void From 931130ed1ca550ebd182b82736169314c13e874a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 11:14:21 -0400 Subject: [PATCH 23/93] test for defer + merge --- tests/DeferPropTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/DeferPropTest.php b/tests/DeferPropTest.php index 00099d28..19d2ca09 100644 --- a/tests/DeferPropTest.php +++ b/tests/DeferPropTest.php @@ -16,6 +16,15 @@ public function test_can_invoke(): void $this->assertSame('A deferred value', $deferProp()); } + public function test_can_invoke_and_merge(): void + { + $deferProp = (new DeferProp(function () { + return 'A deferred value'; + }))->merge(); + + $this->assertSame('A deferred value', $deferProp()); + } + public function test_can_resolve_bindings_when_invoked(): void { $deferProp = new DeferProp(function (Request $request) { From 289222efe8a7230de1ff99b844be7c8ec7d907c4 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 11:51:10 -0400 Subject: [PATCH 24/93] encrypt/clear history --- src/ResponseFactory.php | 9 +--- tests/HistoryTest.php | 108 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 tests/HistoryTest.php diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 777b0243..190344a9 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -29,12 +29,7 @@ class ResponseFactory protected $clearHistory = false; - protected $encryptHistory = false; - - public function __construct() - { - $this->encryptHistory = config('inertia.history.encrypt', false); - } + protected $encryptHistory; public function setRootView(string $name): void { @@ -150,7 +145,7 @@ public function render(string $component, $props = []): Response $this->rootView, $this->getVersion(), $this->clearHistory, - $this->encryptHistory + $this->encryptHistory ?? config('inertia.history.encrypt', false), ); } diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php new file mode 100644 index 00000000..b325019d --- /dev/null +++ b/tests/HistoryTest.php @@ -0,0 +1,108 @@ +get('/', function () { + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => false, + 'clearHistory' => false, + ]); + } + + public function test_the_history_can_be_encrypted(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::encryptHistory(); + + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => true, + ]); + } + + public function test_the_history_can_be_encrypted_globally(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Config::set('inertia.history.encrypt', true); + + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => true, + ]); + } + + public function test_the_history_can_be_encrypted_globally_and_overridden(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Config::set('inertia.history.encrypt', true); + + Inertia::encryptHistory(false); + + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => false, + ]); + } + + public function test_the_history_can_be_cleared(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::clearHistory(); + + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'clearHistory' => true, + ]); + } +} From 735d20fad82bd24db10ea483e1da1bd7e1d1e61e Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 11:51:38 -0400 Subject: [PATCH 25/93] merge props test --- src/Inertia.php | 1 + tests/MergePropTest.php | 34 ++++++++++++++++++++++++++++++++++ tests/ResponseFactoryTest.php | 21 +++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/MergePropTest.php diff --git a/src/Inertia.php b/src/Inertia.php index 916217ca..0be8a2e1 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -17,6 +17,7 @@ * @method static \Inertia\LazyProp lazy(callable $callback) * @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default') * @method static \Inertia\AlwaysProp always(mixed $value) + * @method static \Inertia\MergeProp merge(mixed $value) * @method static \Inertia\Response render(string $component, array|\Illuminate\Contracts\Support\Arrayable $props = []) * @method static \Symfony\Component\HttpFoundation\Response location(string|\Symfony\Component\HttpFoundation\RedirectResponse $url) * @method static void macro(string $name, object|callable $macro) diff --git a/tests/MergePropTest.php b/tests/MergePropTest.php new file mode 100644 index 00000000..dfd21213 --- /dev/null +++ b/tests/MergePropTest.php @@ -0,0 +1,34 @@ +assertSame('A merge prop value', $mergeProp()); + } + + public function test_can_invoke_with_a_non_callback(): void + { + $mergeProp = new MergeProp(['key' => 'value']); + + $this->assertSame(['key' => 'value'], $mergeProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $mergeProp = new MergeProp(function (Request $request) { + return $request; + }); + + $this->assertInstanceOf(Request::class, $mergeProp()); + } +} diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 52add836..7df217a4 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -15,6 +15,7 @@ use Inertia\DeferProp; use Inertia\Inertia; use Inertia\LazyProp; +use Inertia\MergeProp; use Inertia\OptionalProp; use Inertia\ResponseFactory; use Inertia\Tests\Stubs\ExampleMiddleware; @@ -173,6 +174,26 @@ public function test_can_create_deferred_prop(): void $this->assertInstanceOf(DeferProp::class, $deferredProp); } + public function test_can_create_merged_prop(): void + { + $factory = new ResponseFactory(); + $mergedProp = $factory->merge(function () { + return 'A merged value'; + }); + + $this->assertInstanceOf(MergeProp::class, $mergedProp); + } + + public function test_can_create_deferred_and_merged_prop(): void + { + $factory = new ResponseFactory(); + $deferredProp = $factory->defer(function () { + return 'A deferred + merged value'; + })->merge(); + + $this->assertInstanceOf(DeferProp::class, $deferredProp); + } + public function test_can_create_optional_prop(): void { $factory = new ResponseFactory(); From 1a99ec8d09b49d85ac49fc23868104fa6334e2e3 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:09:37 -0400 Subject: [PATCH 26/93] history middleware tests --- src/ServiceProvider.php | 10 +++++++++- tests/HistoryTest.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 68553b90..580fac91 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -3,7 +3,6 @@ namespace Inertia; use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; -use Illuminate\Foundation\Vite; use Illuminate\Http\Request; use Illuminate\Routing\Router; use Illuminate\Support\ServiceProvider as BaseServiceProvider; @@ -32,6 +31,7 @@ public function register(): void $this->registerRequestMacro(); $this->registerRouterMacro(); $this->registerTestingMacros(); + $this->registerMiddleware(); $this->app->bind('inertia.testing.view-finder', function ($app) { return new FileViewFinder( @@ -108,4 +108,12 @@ protected function registerTestingMacros(): void throw new LogicException('Could not detect TestResponse class.'); } + + protected function registerMiddleware(): void + { + $this->app['router']->aliasMiddleware( + 'inertia.encrypt', + EncryptHistoryMiddleware::class + ); + } } diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php index b325019d..4f109b4d 100644 --- a/tests/HistoryTest.php +++ b/tests/HistoryTest.php @@ -5,6 +5,7 @@ use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Route; +use Inertia\EncryptHistoryMiddleware; use Inertia\Inertia; use Inertia\Tests\Stubs\ExampleMiddleware; @@ -47,6 +48,40 @@ public function test_the_history_can_be_encrypted(): void ]); } + public function test_the_history_can_be_encrypted_via_middleware(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class, EncryptHistoryMiddleware::class])->get('/', function () { + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => true, + ]); + } + + public function test_the_history_can_be_encrypted_via_middleware_alias(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class, 'inertia.encrypt'])->get('/', function () { + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'encryptHistory' => true, + ]); + } + public function test_the_history_can_be_encrypted_globally(): void { Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { From 935be8d385c5eff9604e1b99ed37baf9c757e0fa Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:29:36 -0400 Subject: [PATCH 27/93] Update Response.php --- src/Response.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index d4d98d37..17859d08 100644 --- a/src/Response.php +++ b/src/Response.php @@ -239,7 +239,6 @@ public function resolvePropertyInstances(array $props, Request $request): array DeferProp::class, AlwaysProp::class, MergeProp::class, - WhenVisible::class, ])->first(fn($class) => $value instanceof $class); if ($resolveViaApp) { From 49059071144ca51d2e23c05f1731fe15fed7ad9d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:29:42 -0400 Subject: [PATCH 28/93] Update DeferPropTest.php --- tests/DeferPropTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/DeferPropTest.php b/tests/DeferPropTest.php index 19d2ca09..83a27ae7 100644 --- a/tests/DeferPropTest.php +++ b/tests/DeferPropTest.php @@ -11,9 +11,10 @@ public function test_can_invoke(): void { $deferProp = new DeferProp(function () { return 'A deferred value'; - }); + }, 'default'); $this->assertSame('A deferred value', $deferProp()); + $this->assertSame('default', $deferProp->group()); } public function test_can_invoke_and_merge(): void From c6394450e2a6d04bcd0d29ba60971de2be355a0a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:29:49 -0400 Subject: [PATCH 29/93] Update ResponseFactoryTest.php --- tests/ResponseFactoryTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 7df217a4..5465fff0 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -172,6 +172,18 @@ public function test_can_create_deferred_prop(): void }); $this->assertInstanceOf(DeferProp::class, $deferredProp); + $this->assertSame($deferredProp->group(), 'default'); + } + + public function test_can_create_deferred_prop_with_custom_group(): void + { + $factory = new ResponseFactory(); + $deferredProp = $factory->defer(function () { + return 'A deferred value'; + }, 'foo'); + + $this->assertInstanceOf(DeferProp::class, $deferredProp); + $this->assertSame($deferredProp->group(), 'foo'); } public function test_can_create_merged_prop(): void From 22b22cd4e873221071dd5ef32ea125c69914905e Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:29:56 -0400 Subject: [PATCH 30/93] merge and defer prop tests --- tests/ResponseTest.php | 136 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index d6467dad..56744b9e 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -12,7 +12,9 @@ use Illuminate\Support\Fluent; use Illuminate\View\View; use Inertia\AlwaysProp; +use Inertia\DeferProp; use Inertia\LazyProp; +use Inertia\MergeProp; use Inertia\Response; use Inertia\Tests\Stubs\FakeResource; use Mockery; @@ -51,6 +53,140 @@ public function test_server_response(): void $this->assertSame('
', $view->render()); } + public function test_server_response_with_deferred_prop(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => new DeferProp(function () { + return 'bar'; + }, 'default'), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + + public function test_server_response_with_deferred_prop_and_multiple_groups(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => new DeferProp(function () { + return 'foo value'; + }, 'default'), + 'bar' => new DeferProp(function () { + return 'bar value'; + }, 'default'), + 'baz' => new DeferProp(function () { + return 'baz value'; + }, 'custom'), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + + public function test_server_response_with_merge_props(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => new MergeProp('foo value'), + 'bar' => new MergeProp('bar value'), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + + public function test_server_response_with_defer_and_merge_props(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => (new DeferProp(function () { + return 'foo value'; + }, 'default'))->merge(), + 'bar' => new MergeProp('bar value'), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + public function test_xhr_response(): void { $request = Request::create('/user/123', 'GET'); From 9b26d033f1257d605c991123068b30db46c010c7 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 3 Oct 2024 12:32:33 -0400 Subject: [PATCH 31/93] assertions for defer + merge tests --- tests/ResponseTest.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 56744b9e..fd6ab654 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -80,6 +80,9 @@ public function test_server_response_with_deferred_prop(): void $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); + $this->assertSame([ + 'default' => ['foo'], + ], $page['deferredProps']); $this->assertFalse($page['clearHistory']); $this->assertFalse($page['encryptHistory']); $this->assertSame('
', $view->render()); @@ -118,6 +121,10 @@ public function test_server_response_with_deferred_prop_and_multiple_groups(): v $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); + $this->assertSame([ + 'default' => ['foo', 'bar'], + 'custom' => ['baz'], + ], $page['deferredProps']); $this->assertFalse($page['clearHistory']); $this->assertFalse($page['encryptHistory']); $this->assertSame('
', $view->render()); @@ -149,6 +156,10 @@ public function test_server_response_with_merge_props(): void $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); + $this->assertSame([ + 'foo', + 'bar', + ], $page['mergeProps']); $this->assertFalse($page['clearHistory']); $this->assertFalse($page['encryptHistory']); $this->assertSame('
', $view->render()); @@ -182,6 +193,13 @@ public function test_server_response_with_defer_and_merge_props(): void $this->assertSame('Jonathan', $page['props']['user']['name']); $this->assertSame('/user/123', $page['url']); $this->assertSame('123', $page['version']); + $this->assertSame([ + 'default' => ['foo'], + ], $page['deferredProps']); + $this->assertSame([ + 'foo', + 'bar', + ], $page['mergeProps']); $this->assertFalse($page['clearHistory']); $this->assertFalse($page['encryptHistory']); $this->assertSame('
', $view->render()); From 7d702c63ba4afd6894e969c2259408b779b726df Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Wed, 9 Oct 2024 22:07:01 -0400 Subject: [PATCH 32/93] Formatting --- src/Commands/StartSsr.php | 2 +- src/MergesProps.php | 1 - src/Response.php | 8 ++++---- src/ServiceProvider.php | 10 +++++----- src/Ssr/HttpGateway.php | 2 +- src/Ssr/SsrException.php | 4 +--- tests/DirectiveTest.php | 6 +++--- tests/MiddlewareTest.php | 12 ++++++------ tests/ResponseFactoryTest.php | 30 +++++++++++++++--------------- 9 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/Commands/StartSsr.php b/src/Commands/StartSsr.php index d2573ec2..ae5d96f4 100644 --- a/src/Commands/StartSsr.php +++ b/src/Commands/StartSsr.php @@ -36,7 +36,7 @@ public function handle(): int return self::FAILURE; } - $bundle = (new BundleDetector())->detect(); + $bundle = (new BundleDetector)->detect(); $configuredBundle = config('inertia.ssr.bundle'); if ($bundle === null) { diff --git a/src/MergesProps.php b/src/MergesProps.php index 3f30de77..3a22fb18 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -2,7 +2,6 @@ namespace Inertia; - trait MergesProps { protected bool $merge = false; diff --git a/src/Response.php b/src/Response.php index 17859d08..bdeff879 100644 --- a/src/Response.php +++ b/src/Response.php @@ -135,9 +135,9 @@ public function resolveProperties(Request $request, array $props): array { $isPartial = $this->isPartial($request); - if (!$isPartial) { + if (! $isPartial) { $props = array_filter($this->props, static function ($prop) { - return !($prop instanceof IgnoreFirstLoad); + return ! ($prop instanceof IgnoreFirstLoad); }); } @@ -239,7 +239,7 @@ public function resolvePropertyInstances(array $props, Request $request): array DeferProp::class, AlwaysProp::class, MergeProp::class, - ])->first(fn($class) => $value instanceof $class); + ])->first(fn ($class) => $value instanceof $class); if ($resolveViaApp) { $value = App::call($value); @@ -294,7 +294,7 @@ public function resolveMergeProps(Request $request): array return $prop->shouldMerge(); }) ->filter(function ($prop, $key) use ($resetProps) { - return !$resetProps->contains($key); + return ! $resetProps->contains($key); }) ->keys(); diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 580fac91..e0142e23 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -23,7 +23,7 @@ public function register(): void $this->app->bind(Gateway::class, HttpGateway::class); $this->mergeConfigFrom( - __DIR__ . '/../config/inertia.php', + __DIR__.'/../config/inertia.php', 'inertia' ); @@ -47,7 +47,7 @@ public function boot(): void $this->registerConsoleCommands(); $this->publishes([ - __DIR__ . '/../config/inertia.php' => config_path('inertia.php'), + __DIR__.'/../config/inertia.php' => config_path('inertia.php'), ]); } @@ -82,7 +82,7 @@ protected function registerRequestMacro(): void protected function registerRouterMacro(): void { Router::macro('inertia', function ($uri, $component, $props = []) { - return $this->match(['GET', 'HEAD'], $uri, '\\' . Controller::class) + return $this->match(['GET', 'HEAD'], $uri, '\\'.Controller::class) ->defaults('component', $component) ->defaults('props', $props); }); @@ -94,14 +94,14 @@ protected function registerRouterMacro(): void protected function registerTestingMacros(): void { if (class_exists(TestResponse::class)) { - TestResponse::mixin(new TestResponseMacros()); + TestResponse::mixin(new TestResponseMacros); return; } // Laravel <= 6.0 if (class_exists(LegacyTestResponse::class)) { - LegacyTestResponse::mixin(new TestResponseMacros()); + LegacyTestResponse::mixin(new TestResponseMacros); return; } diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index 65a62b81..62b92717 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -12,7 +12,7 @@ class HttpGateway implements Gateway */ public function dispatch(array $page): ?Response { - if (! config('inertia.ssr.enabled', true) || ! (new BundleDetector())->detect()) { + if (! config('inertia.ssr.enabled', true) || ! (new BundleDetector)->detect()) { return null; } diff --git a/src/Ssr/SsrException.php b/src/Ssr/SsrException.php index 32379e1b..7cca6f42 100644 --- a/src/Ssr/SsrException.php +++ b/src/Ssr/SsrException.php @@ -4,6 +4,4 @@ use Exception; -class SsrException extends Exception -{ -} +class SsrException extends Exception {} diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index a8303a56..6fad7f99 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -39,7 +39,7 @@ public function setUp(): void $this->app->bind(Gateway::class, FakeGateway::class); $this->filesystem = m::mock(Filesystem::class); - $this->compiler = new BladeCompiler($this->filesystem, __DIR__ . '/cache/views'); + $this->compiler = new BladeCompiler($this->filesystem, __DIR__.'/cache/views'); $this->compiler->directive('inertia', [Directive::class, 'compile']); $this->compiler->directive('inertiaHead', [Directive::class, 'compileHead']); } @@ -65,7 +65,7 @@ protected function renderView($contents, $data = []) // Next, we'll 'render' out compiled view. $view = new View( m::mock(Factory::class), - new PhpEngine(new Filesystem()), + new PhpEngine(new Filesystem), 'fake-view', $path, $data @@ -143,7 +143,7 @@ public function test_inertia_head_directive_renders_server_side_rendered_head_el public function test_the_server_side_rendering_request_is_dispatched_only_once_per_request(): void { Config::set(['inertia.ssr.enabled' => true]); - $this->app->instance(Gateway::class, $gateway = new FakeGateway()); + $this->app->instance(Gateway::class, $gateway = new FakeGateway); $view = "\n\n\n@inertiaHead\n\n\n@inertia\n\n"; $expected = "\n\n\n\nExample SSR Title\n\n\n

This is some example SSR content

\n"; diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index 2ab86cfd..c00bf7b1 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -145,7 +145,7 @@ public function test_validation_errors_can_be_empty(): void public function test_validation_errors_are_returned_in_the_correct_format(): void { - Session::put('errors', (new ViewErrorBag())->put('default', new MessageBag([ + Session::put('errors', (new ViewErrorBag)->put('default', new MessageBag([ 'name' => 'The name field is required.', 'email' => 'Not a valid email address.', ]))); @@ -163,7 +163,7 @@ public function test_validation_errors_are_returned_in_the_correct_format(): voi public function test_validation_errors_with_named_error_bags_are_scoped(): void { - Session::put('errors', (new ViewErrorBag())->put('example', new MessageBag([ + Session::put('errors', (new ViewErrorBag)->put('example', new MessageBag([ 'name' => 'The name field is required.', 'email' => 'Not a valid email address.', ]))); @@ -181,7 +181,7 @@ public function test_validation_errors_with_named_error_bags_are_scoped(): void public function test_default_validation_errors_can_be_overwritten(): void { - Session::put('errors', (new ViewErrorBag())->put('example', new MessageBag([ + Session::put('errors', (new ViewErrorBag)->put('example', new MessageBag([ 'name' => 'The name field is required.', 'email' => 'Not a valid email address.', ]))); @@ -198,7 +198,7 @@ public function test_default_validation_errors_can_be_overwritten(): void public function test_validation_errors_are_scoped_to_error_bag_header(): void { - Session::put('errors', (new ViewErrorBag())->put('default', new MessageBag([ + Session::put('errors', (new ViewErrorBag)->put('default', new MessageBag([ 'name' => 'The name field is required.', 'email' => 'Not a valid email address.', ]))); @@ -216,7 +216,7 @@ public function test_validation_errors_are_scoped_to_error_bag_header(): void public function test_middleware_can_change_the_root_view_via_a_property(): void { - $this->prepareMockEndpoint(null, [], new class() extends Middleware + $this->prepareMockEndpoint(null, [], new class extends Middleware { protected $rootView = 'welcome'; }); @@ -228,7 +228,7 @@ public function test_middleware_can_change_the_root_view_via_a_property(): void public function test_middleware_can_change_the_root_view_by_overriding_the_rootview_method(): void { - $this->prepareMockEndpoint(null, [], new class() extends Middleware + $this->prepareMockEndpoint(null, [], new class extends Middleware { public function rootView(Request $request): string { diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 5465fff0..61f79c2c 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -24,7 +24,7 @@ class ResponseFactoryTest extends TestCase { public function test_can_macro(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $factory->macro('foo', function () { return 'bar'; }); @@ -38,7 +38,7 @@ public function test_location_response_for_inertia_requests(): void return true; }); - $response = (new ResponseFactory())->location('https://inertiajs.com'); + $response = (new ResponseFactory)->location('https://inertiajs.com'); $this->assertInstanceOf(Response::class, $response); $this->assertEquals(Response::HTTP_CONFLICT, $response->getStatusCode()); @@ -51,7 +51,7 @@ public function test_location_response_for_non_inertia_requests(): void return false; }); - $response = (new ResponseFactory())->location('https://inertiajs.com'); + $response = (new ResponseFactory)->location('https://inertiajs.com'); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals(Response::HTTP_FOUND, $response->getStatusCode()); @@ -65,7 +65,7 @@ public function test_location_response_for_inertia_requests_using_redirect_respo }); $redirect = new RedirectResponse('https://inertiajs.com'); - $response = (new ResponseFactory())->location($redirect); + $response = (new ResponseFactory)->location($redirect); $this->assertInstanceOf(Response::class, $response); $this->assertEquals(409, $response->getStatusCode()); @@ -75,7 +75,7 @@ public function test_location_response_for_inertia_requests_using_redirect_respo public function test_location_response_for_non_inertia_requests_using_redirect_response(): void { $redirect = new RedirectResponse('https://inertiajs.com'); - $response = (new ResponseFactory())->location($redirect); + $response = (new ResponseFactory)->location($redirect); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals(Response::HTTP_FOUND, $response->getStatusCode()); @@ -84,7 +84,7 @@ public function test_location_response_for_non_inertia_requests_using_redirect_r public function test_location_redirects_are_not_modified(): void { - $response = (new ResponseFactory())->location('/foo'); + $response = (new ResponseFactory)->location('/foo'); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals(Response::HTTP_FOUND, $response->getStatusCode()); @@ -96,7 +96,7 @@ public function test_location_response_for_non_inertia_requests_using_redirect_r $redirect = new RedirectResponse('https://inertiajs.com'); $redirect->setSession($session = new Store('test', new NullSessionHandler)); $redirect->setRequest($request = new HttpRequest); - $response = (new ResponseFactory())->location($redirect); + $response = (new ResponseFactory)->location($redirect); $this->assertInstanceOf(RedirectResponse::class, $response); $this->assertEquals(Response::HTTP_FOUND, $response->getStatusCode()); @@ -156,7 +156,7 @@ public function test_can_flush_shared_data(): void public function test_can_create_lazy_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $lazyProp = $factory->lazy(function () { return 'A lazy value'; }); @@ -166,7 +166,7 @@ public function test_can_create_lazy_prop(): void public function test_can_create_deferred_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $deferredProp = $factory->defer(function () { return 'A deferred value'; }); @@ -177,7 +177,7 @@ public function test_can_create_deferred_prop(): void public function test_can_create_deferred_prop_with_custom_group(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $deferredProp = $factory->defer(function () { return 'A deferred value'; }, 'foo'); @@ -188,7 +188,7 @@ public function test_can_create_deferred_prop_with_custom_group(): void public function test_can_create_merged_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $mergedProp = $factory->merge(function () { return 'A merged value'; }); @@ -198,7 +198,7 @@ public function test_can_create_merged_prop(): void public function test_can_create_deferred_and_merged_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $deferredProp = $factory->defer(function () { return 'A deferred + merged value'; })->merge(); @@ -208,7 +208,7 @@ public function test_can_create_deferred_and_merged_prop(): void public function test_can_create_optional_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $optionalProp = $factory->optional(function () { return 'An optional value'; }); @@ -218,7 +218,7 @@ public function test_can_create_optional_prop(): void public function test_can_create_always_prop(): void { - $factory = new ResponseFactory(); + $factory = new ResponseFactory; $alwaysProp = $factory->always(function () { return 'An always value'; }); @@ -231,7 +231,7 @@ public function test_will_accept_arrayabe_props() Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { Inertia::share('foo', 'bar'); - return Inertia::render('User/Edit', new class() implements Arrayable + return Inertia::render('User/Edit', new class implements Arrayable { public function toArray() { From 31fed76561dfcd3f8a0e2a50e0037651bf74b4bf Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Oct 2024 09:02:14 -0400 Subject: [PATCH 33/93] Delete ViteEagerPrefetch.php --- src/ViteEagerPrefetch.php | 176 -------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 src/ViteEagerPrefetch.php diff --git a/src/ViteEagerPrefetch.php b/src/ViteEagerPrefetch.php deleted file mode 100644 index efccb2a2..00000000 --- a/src/ViteEagerPrefetch.php +++ /dev/null @@ -1,176 +0,0 @@ -prefetchStrategy = $strategy; - - if ($strategy === 'waterfall') { - $this->prefetchChunks = $config[0] ?? 3; - } - - return $this; - } - - /** - * Generate Vite tags for an entrypoint. - * - * @param string|string[] $entrypoints - * @param string|null $buildDirectory - * @return \Illuminate\Support\HtmlString - */ - public function __invoke($entrypoints, $buildDirectory = null) - { - $manifest = $this->manifest($buildDirectory ??= $this->buildDirectory); - $base = parent::__invoke($entrypoints, $buildDirectory); - - if ($this->isRunningHot()) { - return $base; - } - - $discoveredImports = []; - - return collect($entrypoints) - ->flatMap(fn ($entrypoint) => collect($manifest[$entrypoint]['dynamicImports'] ?? []) - ->map(fn ($import) => $manifest[$import]) - ->filter(fn ($chunk) => str_ends_with($chunk['file'], '.js') || str_ends_with($chunk['file'], '.css')) - ->flatMap($f = function ($chunk) use (&$f, $manifest, &$discoveredImports) { - return collect([...$chunk['imports'] ?? [], ...$chunk['dynamicImports'] ?? []]) - ->reject(function ($import) use (&$discoveredImports) { - if (isset($discoveredImports[$import])) { - return true; - } - - return ! $discoveredImports[$import] = true; - }) - ->reduce( - fn ($chunks, $import) => $chunks->merge( - $f($manifest[$import]) - ), - collect([$chunk]) - ) - ->merge(collect($chunk['css'] ?? [])->map( - fn ($css) => collect($manifest)->first(fn ($chunk) => $chunk['file'] === $css) ?? [ - 'file' => $css, - ], - )); - }) - ->map(function ($chunk) use ($buildDirectory, $manifest) { - return collect([ - ...$this->resolvePreloadTagAttributes( - $chunk['src'] ?? null, - $url = $this->assetPath("{$buildDirectory}/{$chunk['file']}"), - $chunk, - $manifest, - ), - 'rel' => 'prefetch', - 'href' => $url, - ])->reject( - fn ($value) => in_array($value, [null, false], true) - )->mapWithKeys(fn ($value, $key) => [ - $key = (is_int($key) ? $value : $key) => $value === true ? $key : $value, - ])->all(); - }) - ->reject(fn ($attributes) => isset($this->preloadedAssets[$attributes['href']]))) - ->unique('href') - ->values() - ->pipe(fn ($assets) => with(Js::from($assets), fn ($assets) => match ($this->prefetchStrategy) { - 'waterfall' => new HtmlString($base.<< - window.addEventListener('load', () => window.setTimeout(() => { - const linkTemplate = document.createElement('link') - linkTemplate.rel = 'prefetch' - - const makeLink = (asset) => { - const link = linkTemplate.cloneNode() - - Object.keys(asset).forEach((attribute) => { - link.setAttribute(attribute, asset[attribute]) - }) - - return link - } - - const loadNext = (assets, count) => window.setTimeout(() => { - if (count > assets.length) { - count = assets.length - - if (count === 0) { - return - } - } - - const fragment = new DocumentFragment - - while (count > 0) { - const link = makeLink(assets.shift()) - fragment.append(link) - count-- - - if (assets.length) { - link.onload = () => loadNext(assets, 1) - link.error = () => loadNext(assets, 1) - } - } - - document.head.append(fragment) - }) - - loadNext({$assets}, {$this->prefetchChunks}) - })) - - HTML), - 'aggressive' => new HtmlString($base.<< - window.addEventListener('load', () => window.setTimeout(() => { - const linkTemplate = document.createElement('link') - linkTemplate.rel = 'prefetch' - - const makeLink = (asset) => { - const link = linkTemplate.cloneNode() - - Object.keys(asset).forEach((attribute) => { - link.setAttribute(attribute, asset[attribute]) - }) - - return link - } - - const fragment = new DocumentFragment - {$assets}.forEach((asset) => fragment.append(makeLink(asset))) - document.head.append(fragment) - })) - - HTML), - })); - } -} From 141256b2ed1833158852c9d239d00abec8ff4942 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Oct 2024 09:16:21 -0400 Subject: [PATCH 34/93] fixed dot config merging --- src/Response.php | 4 +++ tests/ResponseFactoryTest.php | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/Response.php b/src/Response.php index bdeff879..914831ec 100644 --- a/src/Response.php +++ b/src/Response.php @@ -172,6 +172,10 @@ public function resolveArrayableProperties(array $props, Request $request, bool $value = $this->resolveArrayableProperties($value, $request, false); } + if ($value instanceof Closure) { + $value = $value(); + } + if ($unpackDotProps && str_contains($key, '.')) { Arr::set($props, $key, $value); unset($props[$key]); diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 61f79c2c..68473d22 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -146,6 +146,66 @@ public function test_shared_data_can_be_shared_from_anywhere(): void ]); } + public function test_dot_props_are_merged_from_shared(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::share('auth.user', [ + 'name' => 'Jonathan', + ]); + + return Inertia::render('User/Edit', [ + 'auth.user.can.create_group' => false, + ]); + }); + + $response = $this->withoutExceptionHandling()->get('/', ['X-Inertia' => 'true']); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'props' => [ + 'auth' => [ + 'user' => [ + 'name' => 'Jonathan', + 'can' => [ + 'create_group' => false, + ], + ], + ], + ], + ]); + } + + public function test_dot_props_with_callbacks_are_merged_from_shared(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::share('auth.user', fn () => [ + 'name' => 'Jonathan', + ]); + + return Inertia::render('User/Edit', [ + 'auth.user.can.create_group' => false, + ]); + }); + + $response = $this->withoutExceptionHandling()->get('/', ['X-Inertia' => 'true']); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'props' => [ + 'auth' => [ + 'user' => [ + 'name' => 'Jonathan', + 'can' => [ + 'create_group' => false, + ], + ], + ], + ], + ]); + } + public function test_can_flush_shared_data(): void { Inertia::share('foo', 'bar'); From 2d451c4f5c0657a9fce4f6d403aa092555b13879 Mon Sep 17 00:00:00 2001 From: Lucas Yang Date: Sun, 13 Oct 2024 15:23:35 +0800 Subject: [PATCH 35/93] [2.x] Revert IDE helpers --- .gitattributes | 1 + _ide_helpers.php | 49 ------------------------------------------------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/.gitattributes b/.gitattributes index 7450a3ed..639989d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,6 +7,7 @@ /.gitignore export-ignore /.github export-ignore /.php-cs-fixer.dist.php export-ignore +/_ide_helpers.php export-ignore /phpunit.xml.dist export-ignore /tests export-ignore /CHANGELOG.md export-ignore diff --git a/_ide_helpers.php b/_ide_helpers.php index 72a525dd..c3b8f827 100644 --- a/_ide_helpers.php +++ b/_ide_helpers.php @@ -29,52 +29,3 @@ class TestResponse // } } - -namespace Illuminate\Support\Facades { - - /** - * @see \Inertia\ServiceProvider - * - * @method static bool inertia() - */ - class Request - { - // - } - - /** - * @see \Inertia\ServiceProvider - * - * @method static self inertia(string $uri, string $component, array $props = []) - */ - class Route - { - // - } -} - -namespace Illuminate\Http { - - /** - * @see \Inertia\ServiceProvider - * - * @method bool inertia() - */ - class Request - { - // - } -} - -namespace Illuminate\Routing { - - /** - * @see \Inertia\ServiceProvider - * - * @method self inertia(string $uri, string $component, array $props = []) - */ - class Router - { - // - } -} From 402a52efe7a5c8a4f776f756d6f8dd55fa9d8995 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Fri, 18 Oct 2024 13:50:34 -0400 Subject: [PATCH 36/93] store history clearing in session so it also works with redirects --- src/Response.php | 4 ++-- src/ResponseFactory.php | 3 +-- tests/HistoryTest.php | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Response.php b/src/Response.php index 914831ec..9c1ece36 100644 --- a/src/Response.php +++ b/src/Response.php @@ -41,13 +41,13 @@ class Response implements Responsable /** * @param array|Arrayable $props */ - public function __construct(string $component, array $props, string $rootView = 'app', string $version = '', bool $clearHistory = false, bool $encryptHistory = false) + public function __construct(string $component, array $props, string $rootView = 'app', string $version = '', bool $encryptHistory = false) { $this->component = $component; $this->props = $props instanceof Arrayable ? $props->toArray() : $props; $this->rootView = $rootView; $this->version = $version; - $this->clearHistory = $clearHistory; + $this->clearHistory = session()->pull('inertia.clear_history', false); $this->encryptHistory = $encryptHistory; } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 190344a9..735fa713 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -88,7 +88,7 @@ public function getVersion(): string public function clearHistory(): void { - $this->clearHistory = true; + session(['inertia.clear_history' => true]); } public function encryptHistory($encrypt = true): void @@ -144,7 +144,6 @@ public function render(string $component, $props = []): Response array_merge($this->sharedProps, $props), $this->rootView, $this->getVersion(), - $this->clearHistory, $this->encryptHistory ?? config('inertia.history.encrypt', false), ); } diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php index 4f109b4d..85e94c53 100644 --- a/tests/HistoryTest.php +++ b/tests/HistoryTest.php @@ -140,4 +140,26 @@ public function test_the_history_can_be_cleared(): void 'clearHistory' => true, ]); } + + public function test_the_history_can_be_cleared_when_redirecting(): void + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::clearHistory(); + + return redirect('/users'); + }); + + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/users', function () { + return Inertia::render('User/Edit'); + }); + + $this->followingRedirects(); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertContent('
'); + } } From 9d39da3b47c78e0c188ded8c064f9797c17089c5 Mon Sep 17 00:00:00 2001 From: HichemTab Date: Sat, 26 Oct 2024 21:24:04 +0100 Subject: [PATCH 37/93] Add deepMerge method for enhanced property merging Introduce a deepMerge method to handle nested property merges more effectively. This addition includes updates to the Response, MergesProps, and ResponseFactory classes, enabling nested properties to be merged and tracked separately. --- src/Inertia.php | 1 + src/MergesProps.php | 14 ++++++++++++++ src/Response.php | 16 +++++++++++++++- src/ResponseFactory.php | 8 ++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Inertia.php b/src/Inertia.php index 0be8a2e1..104f03ac 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -18,6 +18,7 @@ * @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default') * @method static \Inertia\AlwaysProp always(mixed $value) * @method static \Inertia\MergeProp merge(mixed $value) + * @method static \Inertia\MergeProp deepMerge(mixed $value) * @method static \Inertia\Response render(string $component, array|\Illuminate\Contracts\Support\Arrayable $props = []) * @method static \Symfony\Component\HttpFoundation\Response location(string|\Symfony\Component\HttpFoundation\RedirectResponse $url) * @method static void macro(string $name, object|callable $macro) diff --git a/src/MergesProps.php b/src/MergesProps.php index 3a22fb18..3bc5d787 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -6,6 +6,8 @@ trait MergesProps { protected bool $merge = false; + protected bool $deepMerge = false; + public function merge(): static { $this->merge = true; @@ -17,4 +19,16 @@ public function shouldMerge(): bool { return $this->merge; } + + public function deepMerge(): static + { + $this->deepMerge = true; + + return $this; + } + + public function shouldDeepMerge(): bool + { + return $this->deepMerge; + } } diff --git a/src/Response.php b/src/Response.php index 9c1ece36..93371e4b 100644 --- a/src/Response.php +++ b/src/Response.php @@ -293,7 +293,18 @@ public function resolveMergeProps(Request $request): array $mergeProps = collect($this->props) ->filter(function ($prop) { return $prop instanceof Mergeable; + }); + + $deepMergeProps = $mergeProps + ->filter(function ($prop) { + return $prop->shouldDeepMerge(); + }) + ->filter(function ($prop, $key) use ($resetProps) { + return ! $resetProps->contains($key); }) + ->keys(); + + $mergeProps = $mergeProps ->filter(function ($prop) { return $prop->shouldMerge(); }) @@ -302,7 +313,10 @@ public function resolveMergeProps(Request $request): array }) ->keys(); - return $mergeProps->isNotEmpty() ? ['mergeProps' => $mergeProps->toArray()] : []; + return $mergeProps->isNotEmpty() ? [ + 'mergeProps' => $mergeProps->toArray(), + 'deepMergeProps' => $deepMergeProps->toArray() + ] : []; } public function resolveDeferredProps(Request $request): array diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 735fa713..de42ad40 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -122,6 +122,14 @@ public function merge($value): MergeProp return new MergeProp($value); } + /** + * @param mixed $value + */ + public function deepMerge($value): MergeProp + { + return (new MergeProp($value))->deepMerge(); + } + /** * @param mixed $value */ From 5576c83f900feba95382e63784d522bc06c61dfb Mon Sep 17 00:00:00 2001 From: HichemTab Date: Sat, 26 Oct 2024 22:11:35 +0100 Subject: [PATCH 38/93] Fix merge logic in Response class filter Updated the merge logic to exclude properties that require deep merging, ensuring only shallow merges are considered. This fix prevents unintended deep merges, improving the accuracy of the merge process. --- src/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index 93371e4b..36850f59 100644 --- a/src/Response.php +++ b/src/Response.php @@ -306,7 +306,7 @@ public function resolveMergeProps(Request $request): array $mergeProps = $mergeProps ->filter(function ($prop) { - return $prop->shouldMerge(); + return $prop->shouldMerge() AND !$prop->shouldDeepMerge(); }) ->filter(function ($prop, $key) use ($resetProps) { return ! $resetProps->contains($key); From a74619c827d81ff9fb207b2174a66d03a154eec3 Mon Sep 17 00:00:00 2001 From: HichemTab Date: Sat, 26 Oct 2024 22:28:30 +0100 Subject: [PATCH 39/93] Add deep merge prop functionality and corresponding tests Implemented deep merging for response props and adjusted the existing merge logic to accommodate this feature. Added unit tests to ensure the deep merge functionality works correctly with various scenarios. --- src/MergesProps.php | 3 +- src/Response.php | 5 ++- tests/DeepMergePropTest.php | 37 +++++++++++++++++ tests/ResponseFactoryTest.php | 21 ++++++++++ tests/ResponseTest.php | 75 +++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 tests/DeepMergePropTest.php diff --git a/src/MergesProps.php b/src/MergesProps.php index 3bc5d787..68b36d32 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -22,9 +22,10 @@ public function shouldMerge(): bool public function deepMerge(): static { + $this->deepMerge = true; - return $this; + return $this->merge(); } public function shouldDeepMerge(): bool diff --git a/src/Response.php b/src/Response.php index 36850f59..bb32c2e7 100644 --- a/src/Response.php +++ b/src/Response.php @@ -293,6 +293,9 @@ public function resolveMergeProps(Request $request): array $mergeProps = collect($this->props) ->filter(function ($prop) { return $prop instanceof Mergeable; + }) + ->filter(function ($prop) { + return $prop->shouldMerge(); }); $deepMergeProps = $mergeProps @@ -306,7 +309,7 @@ public function resolveMergeProps(Request $request): array $mergeProps = $mergeProps ->filter(function ($prop) { - return $prop->shouldMerge() AND !$prop->shouldDeepMerge(); + return !$prop->shouldDeepMerge(); }) ->filter(function ($prop, $key) use ($resetProps) { return ! $resetProps->contains($key); diff --git a/tests/DeepMergePropTest.php b/tests/DeepMergePropTest.php new file mode 100644 index 00000000..d930b2ba --- /dev/null +++ b/tests/DeepMergePropTest.php @@ -0,0 +1,37 @@ +deepMerge(); + + $this->assertSame('A merge prop value', $mergeProp()); + } + + public function test_can_invoke_with_a_non_callback(): void + { + $mergeProp = new MergeProp(['key' => 'value']); + $mergeProp->deepMerge(); + + $this->assertSame(['key' => 'value'], $mergeProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $mergeProp = new MergeProp(function (Request $request) { + return $request; + }); + $mergeProp->deepMerge(); + + $this->assertInstanceOf(Request::class, $mergeProp()); + } +} diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 68473d22..4e0d8148 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -256,6 +256,17 @@ public function test_can_create_merged_prop(): void $this->assertInstanceOf(MergeProp::class, $mergedProp); } + public function test_can_create_deep_merged_prop(): void + { + $factory = new ResponseFactory; + $mergedProp = $factory->merge(function () { + return 'A merged value'; + }); + $mergedProp->deepMerge(); + + $this->assertInstanceOf(MergeProp::class, $mergedProp); + } + public function test_can_create_deferred_and_merged_prop(): void { $factory = new ResponseFactory; @@ -266,6 +277,16 @@ public function test_can_create_deferred_and_merged_prop(): void $this->assertInstanceOf(DeferProp::class, $deferredProp); } + public function test_can_create_deferred_and_deep_merged_prop(): void + { + $factory = new ResponseFactory; + $deferredProp = $factory->defer(function () { + return 'A deferred + merged value'; + })->deepMerge(); + + $this->assertInstanceOf(DeferProp::class, $deferredProp); + } + public function test_can_create_optional_prop(): void { $factory = new ResponseFactory; diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index fd6ab654..4d94ff34 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -165,6 +165,41 @@ public function test_server_response_with_merge_props(): void $this->assertSame('
', $view->render()); } + public function test_server_response_with_deep_merge_props(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => (new MergeProp('foo value'))->deepMerge(), + 'bar' => (new MergeProp('bar value'))->deepMerge(), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertSame([ + 'foo', + 'bar', + ], $page['deepMergeProps']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + public function test_server_response_with_defer_and_merge_props(): void { $request = Request::create('/user/123', 'GET'); @@ -205,6 +240,46 @@ public function test_server_response_with_defer_and_merge_props(): void $this->assertSame('
', $view->render()); } + public function test_server_response_with_defer_and_deep_merge_props(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => (new DeferProp(function () { + return 'foo value'; + }, 'default'))->deepMerge(), + 'bar' => (new MergeProp('bar value'))->deepMerge(), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertSame([ + 'default' => ['foo'], + ], $page['deferredProps']); + $this->assertSame([ + 'foo', + 'bar', + ], $page['deepMergeProps']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + public function test_xhr_response(): void { $request = Request::create('/user/123', 'GET'); From 830d9828a03be21000cbe013cbd445510f755f6f Mon Sep 17 00:00:00 2001 From: HichemTab Date: Sat, 26 Oct 2024 22:33:44 +0100 Subject: [PATCH 40/93] Refactor response property merging logic Simplify the merging logic by using a conditional check before assigning properties. This makes the code shorter and easier to understand, while maintaining the same functionality. --- src/Response.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Response.php b/src/Response.php index bb32c2e7..29da27de 100644 --- a/src/Response.php +++ b/src/Response.php @@ -316,10 +316,11 @@ public function resolveMergeProps(Request $request): array }) ->keys(); - return $mergeProps->isNotEmpty() ? [ - 'mergeProps' => $mergeProps->toArray(), - 'deepMergeProps' => $deepMergeProps->toArray() - ] : []; + $props = []; + if ($mergeProps->isNotEmpty()) $props['mergeProps'] = $mergeProps->toArray(); + if ($deepMergeProps->isNotEmpty()) $props['deepMergeProps'] = $deepMergeProps->toArray(); + + return $props; } public function resolveDeferredProps(Request $request): array From 4901e3576dcc847f7b49b8d3ed6962c572a7e303 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 20 Nov 2024 13:32:32 +0800 Subject: [PATCH 41/93] [2.x] Update dependencies --- composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 1e7ca01b..65010305 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "php": "^8.1.0", "ext-json": "*", "laravel/framework": "^10.0|^11.0", - "symfony/console": "^5.3|^6.0|^7.0" + "symfony/console": "^6.2|^7.0" }, "require-dev": { "roave/security-advisories": "dev-master", @@ -48,9 +48,6 @@ "providers": [ "Inertia\\ServiceProvider" ] - }, - "branch-alias": { - "dev-master": "1.x-dev" } }, "minimum-stability": "dev", From 1558bacba641bf494bf4c353b920cda4ed1ca1c6 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 20 Nov 2024 13:36:21 +0800 Subject: [PATCH 42/93] [2.x] Supports PHP 8.4 --- .github/workflows/tests.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8cb4b6f2..a021c3b8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,17 +12,19 @@ jobs: strategy: fail-fast: true matrix: - php: [8.1, 8.2, 8.3] + php: [8.1, 8.2, 8.3, 8.4] laravel: [10, 11] stability: ["prefer-lowest", "prefer-stable"] exclude: + - php: 8.4 + laravel: 10 - php: 8.1 laravel: 11 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} (w/ ${{ matrix.stability }}) steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -34,7 +36,7 @@ jobs: coverage: none - name: Set Minimum PHP 8.1 Versions - uses: nick-invision/retry@v1 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -44,7 +46,7 @@ jobs: if: matrix.php >= 8.1 && matrix.stability == 'prefer-lowest' - name: Set Minimum PHP 8.2 Versions - uses: nick-invision/retry@v1 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -53,7 +55,7 @@ jobs: if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' - name: Set Minimum PHP 8.2 Versions and Laravel > 11 - uses: nick-invision/retry@v1 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -62,14 +64,14 @@ jobs: if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 - name: Set Laravel version - uses: nick-invision/retry@v1 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer require "laravel/framework=^${{ matrix.laravel }}" --no-interaction --no-update - name: Install dependencies - uses: nick-invision/retry@v1 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 From 91935b033ec21b7a353a944758e1f9bb0c6e821f Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 20 Nov 2024 13:41:22 +0800 Subject: [PATCH 43/93] wip Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 65010305..8f02899a 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "require-dev": { "roave/security-advisories": "dev-master", - "orchestra/testbench": "^8.0|^9.0", + "orchestra/testbench": "^8.0|^9.2", "mockery/mockery": "^1.3.3", "phpunit/phpunit": "^10.4|^11.0", "laravel/pint": "^1.16" From 7ee78a9e933f651318c73b61f467a215636ede9a Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 20 Nov 2024 13:47:57 +0800 Subject: [PATCH 44/93] wip --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1e7ca01b..915d319d 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ }, "require-dev": { "roave/security-advisories": "dev-master", - "orchestra/testbench": "^8.0|^9.0", + "orchestra/testbench": "^8.0|^9.2", "mockery/mockery": "^1.3.3", "phpunit/phpunit": "^10.4|^11.0", "laravel/pint": "^1.16" From a07179417e0b266282fb4548e7966499bd1dc93a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:13:17 -0500 Subject: [PATCH 45/93] upgrade github action dependencies --- .github/workflows/facade.yml | 4 ++-- .github/workflows/tests.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/facade.yml b/.github/workflows/facade.yml index 115daf3d..b2917730 100644 --- a/.github/workflows/facade.yml +++ b/.github/workflows/facade.yml @@ -3,7 +3,7 @@ name: facades on: push: branches: - - 'master' + - "master" jobs: update: @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8cb4b6f2..e90e211f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} (w/ ${{ matrix.stability }}) steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -34,7 +34,7 @@ jobs: coverage: none - name: Set Minimum PHP 8.1 Versions - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -44,7 +44,7 @@ jobs: if: matrix.php >= 8.1 && matrix.stability == 'prefer-lowest' - name: Set Minimum PHP 8.2 Versions - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -53,7 +53,7 @@ jobs: if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' - name: Set Minimum PHP 8.2 Versions and Laravel > 11 - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -62,14 +62,14 @@ jobs: if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 - name: Set Laravel version - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer require "laravel/framework=^${{ matrix.laravel }}" --no-interaction --no-update - name: Install dependencies - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v3 with: timeout_minutes: 5 max_attempts: 5 From 4aa876b7c62883f9a8fa7b3d6965c479abdb536b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:35:46 -0500 Subject: [PATCH 46/93] Update tests.yml --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e90e211f..5033d82d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,6 +58,7 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: | + composer require orchestra/testbench:^9.0 --dev --${{ matrix.stability }} --no-update --no-interaction composer require phpunit/phpunit:^10.4 --dev --${{ matrix.stability }} --no-update --no-interaction if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 From 49f9305871a20aba8e42f83e773beb68bed28656 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:38:51 -0500 Subject: [PATCH 47/93] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5033d82d..81b0cda9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -58,7 +58,7 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: | - composer require orchestra/testbench:^9.0 --dev --${{ matrix.stability }} --no-update --no-interaction + composer require orchestra/testbench:^9.2 --dev --${{ matrix.stability }} --no-update --no-interaction composer require phpunit/phpunit:^10.4 --dev --${{ matrix.stability }} --no-update --no-interaction if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 From 1f84639c682ef0e7d907d8bb63922b4362b0b56b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:04:37 -0500 Subject: [PATCH 48/93] fixed situation where lazy functions were always being evaluated --- src/Response.php | 38 ++++++++++++++++++++++----------- tests/ResponseTest.php | 48 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/Response.php b/src/Response.php index 9c1ece36..5c06d7e8 100644 --- a/src/Response.php +++ b/src/Response.php @@ -133,27 +133,41 @@ public function toResponse($request) */ public function resolveProperties(Request $request, array $props): array { - $isPartial = $this->isPartial($request); + $props = $this->resolvePartialProperties($props, $request); + $props = $this->resolveArrayableProperties($props, $request); + $props = $this->resolveAlways($props); + $props = $this->resolvePropertyInstances($props, $request); + + return $props; + } - if (! $isPartial) { - $props = array_filter($this->props, static function ($prop) { + /** + * Resolve the `only` and `except` partial request props. + */ + public function resolvePartialProperties(array $props, Request $request): array + { + if (! $this->isPartial($request)) { + return array_filter($this->props, static function ($prop) { return ! ($prop instanceof IgnoreFirstLoad); }); } - $props = $this->resolveArrayableProperties($props, $request); + $only = array_filter(explode(',', $request->header(Header::PARTIAL_ONLY, ''))); + $except = array_filter(explode(',', $request->header(Header::PARTIAL_EXCEPT, ''))); - if ($isPartial && $request->hasHeader(Header::PARTIAL_ONLY)) { - $props = $this->resolveOnly($request, $props); - } + if (count($only)) { + $newProps = []; - if ($isPartial && $request->hasHeader(Header::PARTIAL_EXCEPT)) { - $props = $this->resolveExcept($request, $props); - } + foreach ($only as $key) { + Arr::set($newProps, $key, Arr::get($props, $key)); + } - $props = $this->resolveAlways($props); + $props = $newProps; + } - $props = $this->resolvePropertyInstances($props, $request); + if ($except) { + Arr::forget($props, $except); + } return $props; } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index fd6ab654..bfba526f 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -240,6 +240,54 @@ public function test_resource_response(): void $this->assertSame('123', $page->version); } + public function test_lazy_callable_resource_response(): void + { + $request = Request::create('/users', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('User/Index', [ + 'users' => fn () => [['name' => 'Jonathan']], + 'organizations' => fn () => [['name' => 'Inertia']], + ], 'app', '123'); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertSame('User/Index', $page->component); + $this->assertSame('/users', $page->url); + $this->assertSame('123', $page->version); + tap($page->props->users, function ($users) { + $this->assertSame(json_encode([['name' => 'Jonathan']]), json_encode($users)); + }); + tap($page->props->organizations, function ($organizations) { + $this->assertSame(json_encode([['name' => 'Inertia']]), json_encode($organizations)); + }); + } + + public function test_lazy_callable_resource_partial_response(): void + { + $request = Request::create('/users', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + $request->headers->add(['X-Inertia-Partial-Data' => 'users']); + $request->headers->add(['X-Inertia-Partial-Component' => 'User/Index']); + + $response = new Response('User/Index', [ + 'users' => fn () => [['name' => 'Jonathan']], + 'organizations' => fn () => [['name' => 'Inertia']], + ], 'app', '123'); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertSame('User/Index', $page->component); + $this->assertSame('/users', $page->url); + $this->assertSame('123', $page->version); + $this->assertFalse(property_exists($page->props, 'organizations')); + tap($page->props->users, function ($users) { + $this->assertSame(json_encode([['name' => 'Jonathan']]), json_encode($users)); + }); + } + public function test_lazy_resource_response(): void { $request = Request::create('/users', 'GET', ['page' => 1]); From 75410d7487b3ce9e11d3bddc81f0f7e7c398990e Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:50:54 -0500 Subject: [PATCH 49/93] resolve closures via app --- src/Response.php | 2 +- tests/ResponseFactoryTest.php | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index 5c06d7e8..b30ea096 100644 --- a/src/Response.php +++ b/src/Response.php @@ -187,7 +187,7 @@ public function resolveArrayableProperties(array $props, Request $request, bool } if ($value instanceof Closure) { - $value = $value(); + $value = App::call($value); } if ($unpackDotProps && str_contains($key, '.')) { diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 68473d22..c96b3c1d 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -176,6 +176,27 @@ public function test_dot_props_are_merged_from_shared(): void ]); } + public function test_shared_data_can_resolve_closure_arguments(): void + { + Inertia::share('query', fn (HttpRequest $request) => $request->query()); + + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + return Inertia::render('User/Edit'); + }); + + $response = $this->withoutExceptionHandling()->get('/?foo=bar', ['X-Inertia' => 'true']); + + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'props' => [ + 'query' => [ + 'foo' => 'bar', + ], + ], + ]); + } + public function test_dot_props_with_callbacks_are_merged_from_shared(): void { Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { From af3d0f330391b7a9e0601ba273b051a1ca95b17e Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 20 Nov 2024 13:51:52 -0500 Subject: [PATCH 50/93] public -> protected --- tests/DirectiveTest.php | 2 +- tests/TestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/DirectiveTest.php b/tests/DirectiveTest.php index 6fad7f99..f1afdd07 100644 --- a/tests/DirectiveTest.php +++ b/tests/DirectiveTest.php @@ -32,7 +32,7 @@ class DirectiveTest extends TestCase */ protected const EXAMPLE_PAGE_OBJECT = ['component' => 'Foo/Bar', 'props' => ['foo' => 'bar'], 'url' => '/test', 'version' => '', 'encryptHistory' => false, 'clearHistory' => false]; - public function setUp(): void + protected function setUp(): void { parent::setUp(); diff --git a/tests/TestCase.php b/tests/TestCase.php index c62786e4..8de5be0f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -19,7 +19,7 @@ protected function getPackageProviders($app): array ]; } - public function setUp(): void + protected function setUp(): void { parent::setUp(); From 0259e37f802bc39c814c42ba92c04ada17921f70 Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Thu, 12 Dec 2024 21:48:29 -0500 Subject: [PATCH 51/93] Update changelog --- CHANGELOG.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aadf26ee..6bf88552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,18 @@ # Release Notes -## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...2.x) +## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v2.0.0...2.x) +- Nothing! + +## [v2.0.0](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...2.0.0) + +- Add support for Inertia.js v2.0.0 +- Add `Inertia::defer()` to support deferred props +- Add `Inertia::merge()` to support merging props on client +- Add `Inertia::always()` for props that should always be included ([#627](https://github.com/inertiajs/inertia-laravel/pull/627)) +- Add `Inertia::clearHistory()` and `Inertia::encryptHistory()` methods, encryption config, and encryption middleware +- Deprecated `Inertia::lazy()` in favor of `Inertia::optional()` - Drop support for Laravel 8 and 9 ([#629](https://github.com/inertiajs/inertia-laravel/pull/629)) -- Add "always" props using new `Inertia::always()` wrapper ([#627](https://github.com/inertiajs/inertia-laravel/pull/627)) ## [v1.2.0](https://github.com/inertiajs/inertia-laravel/compare/v1.1.0...v1.2.0) - 2024-05-17 From 443c78ba9a4f1d679a0a3fc79d1351a01696b4e0 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Tue, 17 Dec 2024 20:40:52 +0100 Subject: [PATCH 52/93] Call `toArray()` on `Arrayable` props resolved from the Container --- src/Response.php | 4 ++++ tests/ResponseTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Response.php b/src/Response.php index b30ea096..45f652ba 100644 --- a/src/Response.php +++ b/src/Response.php @@ -263,6 +263,10 @@ public function resolvePropertyInstances(array $props, Request $request): array $value = App::call($value); } + if ($value instanceof Arrayable) { + $value = $value->toArray(); + } + if ($value instanceof PromiseInterface) { $value = $value->wait(); } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index bfba526f..63311655 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -2,6 +2,7 @@ namespace Inertia\Tests; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; @@ -584,6 +585,31 @@ public function test_lazy_props_are_included_in_partial_reload(): void $this->assertSame('A lazy value', $page->props->lazy); } + public function test_defer_arrayable_props_are_resolved_in_partial_reload(): void + { + $request = Request::create('/users', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + $request->headers->add(['X-Inertia-Partial-Component' => 'Users']); + $request->headers->add(['X-Inertia-Partial-Data' => 'defer']); + + $deferProp = new DeferProp(function () { + return new class implements Arrayable + { + public function toArray() + { + return ['foo' => 'bar']; + } + }; + }); + + $response = new Response('Users', ['users' => [], 'defer' => $deferProp], 'app', '123'); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertFalse(property_exists($page->props, 'users')); + $this->assertEquals((object) ['foo' => 'bar'], $page->props->defer); + } + public function test_always_props_are_included_on_partial_reload(): void { $request = Request::create('/user/123', 'GET'); From 0391cc7b4893af66af095d37221d98a74dd315ef Mon Sep 17 00:00:00 2001 From: Ashok Date: Tue, 24 Dec 2024 18:55:02 +0600 Subject: [PATCH 53/93] feat: add inertiaProps method for easier prop extraction in tests This commit introduces a new `inertiaProps` method in the `TestResponseMacros` class. The method simplifies the process of retrieving specific props from an Inertia.js response during testing. It uses Laravel's `Arr::get` utility to handle nested properties and returns a default value if the specified prop is not found. Example usage: $response->inertiaProps('user.name'); This enhancement improves test readability and usability for Inertia.js developers. --- src/Testing/TestResponseMacros.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Testing/TestResponseMacros.php b/src/Testing/TestResponseMacros.php index 428dc2a6..394cabd5 100644 --- a/src/Testing/TestResponseMacros.php +++ b/src/Testing/TestResponseMacros.php @@ -3,6 +3,7 @@ namespace Inertia\Testing; use Closure; +use Illuminate\Support\Arr; class TestResponseMacros { @@ -27,4 +28,12 @@ public function inertiaPage() return AssertableInertia::fromTestResponse($this)->toArray(); }; } + + public function inertiaProps(?string $propName = null): mixed + { + return Arr::get( + $this->inertiaPage()['props'] ?? [], + $propName + ); + } } From 2b1d63f64b385fdaa41fb4503998e63c5e452e0b Mon Sep 17 00:00:00 2001 From: Ashok Date: Tue, 24 Dec 2024 20:02:10 +0600 Subject: [PATCH 54/93] Add tests for retrieving Inertia prop values and nested properties with dot notation --- tests/Testing/TestResponseMacrosTest.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index 312e8480..034e908f 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -50,4 +50,23 @@ public function test_it_can_retrieve_the_inertia_page(): void $this->assertFalse($page['clearHistory']); }); } + + public function test_it_can_retrieve_the_inertia_props(): void + { + $props = ['bar' => 'baz']; + $response = $this->makeMockRequest( + Inertia::render('foo', $props) + ); + + tap($response->inertiaProps(), fn (array $pageProps) => $this->assertSame($props, $pageProps)); + } + + public function test_it_can_retrieve_nested_inertia_prop_values_with_dot_notation(): void + { + $response = $this->makeMockRequest( + Inertia::render('foo', ['bar' => ['baz' => 'qux']]) + ); + + tap($response->inertiaProps('bar.baz'), fn (mixed $value) => $this->assertSame('qux', $value)); + } } From 2c1e52ffe05ee5cde6cdbbbae5266459c10e5e18 Mon Sep 17 00:00:00 2001 From: Ashok Date: Tue, 24 Dec 2024 20:03:13 +0600 Subject: [PATCH 55/93] Fix inertiaProps method to correctly handle optional prop name for retrieving Inertia props --- src/Testing/TestResponseMacros.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Testing/TestResponseMacros.php b/src/Testing/TestResponseMacros.php index 394cabd5..ee9c258f 100644 --- a/src/Testing/TestResponseMacros.php +++ b/src/Testing/TestResponseMacros.php @@ -29,11 +29,13 @@ public function inertiaPage() }; } - public function inertiaProps(?string $propName = null): mixed + public function inertiaProps() { - return Arr::get( - $this->inertiaPage()['props'] ?? [], - $propName - ); + return function (?string $propName = null) { + return Arr::get( + $this->inertiaPage()['props'] ?? [], + $propName + ); + }; } } From 4f8105c42aebe909289456d1cc1a45855a8ced3f Mon Sep 17 00:00:00 2001 From: simon-tma Date: Mon, 13 Jan 2025 15:38:53 +1100 Subject: [PATCH 56/93] Handle SSR URLs with trailing slashes --- src/Ssr/HttpGateway.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index 62b92717..f6e0427d 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; class HttpGateway implements Gateway { @@ -16,7 +17,7 @@ public function dispatch(array $page): ?Response return null; } - $url = str_replace('/render', '', config('inertia.ssr.url', 'http://127.0.0.1:13714')).'/render'; + $url = str_replace('/render', '', Str::chopEnd(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; try { $response = Http::post($url, $page)->throw()->json(); From e078b4578cdf85090256a1014fd8935038ab9c69 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Wed, 15 Jan 2025 16:09:50 -0300 Subject: [PATCH 57/93] resolve closure first --- src/Response.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Response.php b/src/Response.php index b30ea096..31a5630d 100644 --- a/src/Response.php +++ b/src/Response.php @@ -178,6 +178,10 @@ public function resolvePartialProperties(array $props, Request $request): array public function resolveArrayableProperties(array $props, Request $request, bool $unpackDotProps = true): array { foreach ($props as $key => $value) { + if ($value instanceof Closure) { + $value = App::call($value); + } + if ($value instanceof Arrayable) { $value = $value->toArray(); } @@ -186,10 +190,6 @@ public function resolveArrayableProperties(array $props, Request $request, bool $value = $this->resolveArrayableProperties($value, $request, false); } - if ($value instanceof Closure) { - $value = App::call($value); - } - if ($unpackDotProps && str_contains($key, '.')) { Arr::set($props, $key, $value); unset($props[$key]); From 9042b6e73fa10c630312e98a228303f8cee063b8 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Sun, 26 Jan 2025 09:24:21 +0800 Subject: [PATCH 58/93] [2.x] Supports Laravel 12 Signed-off-by: Mior Muhammad Zaki --- .github/workflows/tests.yml | 10 ++++++---- composer.json | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf5e78d8..e3d2361b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,13 +13,15 @@ jobs: fail-fast: true matrix: php: [8.1, 8.2, 8.3, 8.4] - laravel: [10, 11] + laravel: [10, 11, 12] stability: ["prefer-lowest", "prefer-stable"] exclude: - php: 8.4 laravel: 10 - php: 8.1 laravel: 11 + - php: 8.1 + laravel: 12 name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} (w/ ${{ matrix.stability }}) steps: @@ -52,7 +54,7 @@ jobs: max_attempts: 5 command: | composer require nesbot/carbon:^2.62.1 --dev --${{ matrix.stability }} --no-update --no-interaction - if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' + if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel < 12 - name: Set Minimum PHP 8.2 Versions and Laravel > 11 uses: nick-fields/retry@v3 @@ -60,8 +62,8 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: | - composer require orchestra/testbench:^9.2 --dev --${{ matrix.stability }} --no-update --no-interaction - composer require phpunit/phpunit:^10.4 --dev --${{ matrix.stability }} --no-update --no-interaction + composer require "orchestra/testbench:^9.2|^10.0" --dev --${{ matrix.stability }} --no-update --no-interaction + composer require "phpunit/phpunit:^10.4|^11.5" --dev --${{ matrix.stability }} --no-update --no-interaction if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 - name: Set Laravel version diff --git a/composer.json b/composer.json index 8f02899a..6cf6f0c4 100644 --- a/composer.json +++ b/composer.json @@ -30,14 +30,14 @@ "require": { "php": "^8.1.0", "ext-json": "*", - "laravel/framework": "^10.0|^11.0", + "laravel/framework": "^10.0|^11.0|^12.0", "symfony/console": "^6.2|^7.0" }, "require-dev": { "roave/security-advisories": "dev-master", - "orchestra/testbench": "^8.0|^9.2", + "orchestra/testbench": "^8.0|^9.2|^10.0", "mockery/mockery": "^1.3.3", - "phpunit/phpunit": "^10.4|^11.0", + "phpunit/phpunit": "^10.4|^11.5", "laravel/pint": "^1.16" }, "suggest": { From 6a8228c2914fcf1610df164781fc49f797d399d3 Mon Sep 17 00:00:00 2001 From: Ostap Brehin Date: Sun, 26 Jan 2025 06:29:00 +0000 Subject: [PATCH 59/93] Replace `array_merge` with spread operator in `middleware.stub` --- stubs/middleware.stub | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stubs/middleware.stub b/stubs/middleware.stub index 256618b7..09f6b641 100644 --- a/stubs/middleware.stub +++ b/stubs/middleware.stub @@ -35,8 +35,9 @@ class {{ class }} extends Middleware */ public function share(Request $request): array { - return array_merge(parent::share($request), [ + return [ + ...parent::share($request), // - ]); + ]; } } From 46b9e9e0078d4ca0e96cf250804d2fc509153857 Mon Sep 17 00:00:00 2001 From: Bram Date: Tue, 11 Feb 2025 22:07:20 +0000 Subject: [PATCH 60/93] Update inertia.php --- config/inertia.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/inertia.php b/config/inertia.php index e35f2526..cfecb639 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -21,9 +21,9 @@ 'ssr' => [ - 'enabled' => true, + 'enabled' => (bool) env('INERTIA_SSR_ENABLED', true), - 'url' => 'http://127.0.0.1:13714', + 'url' => env('INERTIA_SSR_URL', 'http://127.0.0.1:13714'), // 'bundle' => base_path('bootstrap/ssr/ssr.mjs'), From d855ad18bcaae883e307619370d8c3dcfb5adbaa Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 18 Feb 2025 13:00:36 -0600 Subject: [PATCH 61/93] allow laravel 12 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8f02899a..ada6e347 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "require": { "php": "^8.1.0", "ext-json": "*", - "laravel/framework": "^10.0|^11.0", + "laravel/framework": "^10.0|^11.0|^12.0", "symfony/console": "^6.2|^7.0" }, "require-dev": { From 655a37dd255e74265652d9498edc64e3c54ae832 Mon Sep 17 00:00:00 2001 From: taylorotwell <463230+taylorotwell@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:01:56 +0000 Subject: [PATCH 62/93] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bf88552..f8ab406e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Release Notes -## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v2.0.0...2.x) +## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v2.0.1...2.x) - Nothing! +## [v2.0.1](https://github.com/inertiajs/inertia-laravel/compare/v2.0.0...v2.0.1) - 2025-02-18 + +- Allow Laravel 12.x. + +**Full Changelog**: https://github.com/inertiajs/inertia-laravel/compare/v2.0.0...v2.0.1 + ## [v2.0.0](https://github.com/inertiajs/inertia-laravel/compare/v1.2.0...2.0.0) - Add support for Inertia.js v2.0.0 From 3b401275cf86f6f006bcfa27fae7c100c97db204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rube=CC=81n=20Robles?= Date: Tue, 11 Mar 2025 17:47:58 +0100 Subject: [PATCH 63/93] fix props that extends Responsable after closures / lazy props --- src/Response.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Response.php b/src/Response.php index b30ea096..d47134d8 100644 --- a/src/Response.php +++ b/src/Response.php @@ -271,6 +271,10 @@ public function resolvePropertyInstances(array $props, Request $request): array $value = $value->toResponse($request)->getData(true); } + if ($value instanceof Responsable) { + $value = $value->toResponse($request)->getData(true); + } + if (is_array($value)) { $value = $this->resolvePropertyInstances($value, $request); } From ae5398b596899a37e3a254f8cdf1bf35133018fb Mon Sep 17 00:00:00 2001 From: mohammadrasoulasghari Date: Thu, 13 Mar 2025 04:15:52 +0330 Subject: [PATCH 64/93] Update PHPDoc annotations for ResponseFactory class methods - Added missing PHPDoc blocks for all methods - Improved existing documentation with better parameter and return - Added proper type hints in PHPDoc comments --- src/ResponseFactory.php | 42 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 735fa713..8026dca7 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -31,11 +31,16 @@ class ResponseFactory protected $encryptHistory; + /*** + * @param string $name The name of the root view + * @return void + */ public function setRootView(string $name): void { $this->rootView = $name; } + /** * @param string|array|Arrayable $key * @param mixed $value @@ -52,6 +57,7 @@ public function share($key, $value = null): void } /** + * @param string|null $key * @param mixed $default * @return mixed */ @@ -64,19 +70,27 @@ public function getShared(?string $key = null, $default = null) return $this->sharedProps; } - public function flushShared(): void + + /** + * @return void + */ + public function flushShared() { $this->sharedProps = []; } /** * @param Closure|string|null $version + * @return void */ public function version($version): void { $this->version = $version; } + /** + * @return string + */ public function getVersion(): string { $version = $this->version instanceof Closure @@ -86,11 +100,18 @@ public function getVersion(): string return (string) $version; } + /** + * @return void + */ public function clearHistory(): void { session(['inertia.clear_history' => true]); } + /** + * @param bool $encrypt + * @return void + */ public function encryptHistory($encrypt = true): void { $this->encryptHistory = $encrypt; @@ -104,11 +125,21 @@ public function lazy(callable $callback): LazyProp return new LazyProp($callback); } + /** + * @param callable $callback + * @return OptionalProp + */ public function optional(callable $callback): OptionalProp { return new OptionalProp($callback); } + /** + * + * @param callable $callback + * @param string $group + * @return DeferProp + */ public function defer(callable $callback, string $group = 'default'): DeferProp { return new DeferProp($callback, $group); @@ -116,6 +147,7 @@ public function defer(callable $callback, string $group = 'default'): DeferProp /** * @param mixed $value + * @return MergeProp */ public function merge($value): MergeProp { @@ -123,7 +155,8 @@ public function merge($value): MergeProp } /** - * @param mixed $value + * @param mixed $value + * @return AlwaysProp */ public function always($value): AlwaysProp { @@ -131,7 +164,9 @@ public function always($value): AlwaysProp } /** - * @param array|Arrayable $props + * @param string $component + * @param array|Arrayable $props + * @return Response */ public function render(string $component, $props = []): Response { @@ -150,6 +185,7 @@ public function render(string $component, $props = []): Response /** * @param string|SymfonyRedirect $url + * @return SymfonyResponse */ public function location($url): SymfonyResponse { From 12c4f7334a65efd6edbd17242a5e5ff910e3053b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 9 Apr 2025 13:13:12 -0400 Subject: [PATCH 65/93] Create coding-standards.yml --- .github/workflows/coding-standards.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/coding-standards.yml diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 00000000..24228e47 --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,12 @@ +name: fix code styling + +on: [push] + +permissions: + contents: write + +jobs: + lint: + uses: laravel/.github/.github/workflows/coding-standards.yml@main + with: + php: "8.3" From ea7c003e48d858fcc9f6f29f21b9b13bddb30054 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:14:55 +0000 Subject: [PATCH 66/93] Fix code styling --- src/Response.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Response.php b/src/Response.php index 3edaf991..f97f7716 100644 --- a/src/Response.php +++ b/src/Response.php @@ -323,7 +323,7 @@ public function resolveMergeProps(Request $request): array $mergeProps = $mergeProps ->filter(function ($prop) { - return !$prop->shouldDeepMerge(); + return ! $prop->shouldDeepMerge(); }) ->filter(function ($prop, $key) use ($resetProps) { return ! $resetProps->contains($key); @@ -331,8 +331,12 @@ public function resolveMergeProps(Request $request): array ->keys(); $props = []; - if ($mergeProps->isNotEmpty()) $props['mergeProps'] = $mergeProps->toArray(); - if ($deepMergeProps->isNotEmpty()) $props['deepMergeProps'] = $deepMergeProps->toArray(); + if ($mergeProps->isNotEmpty()) { + $props['mergeProps'] = $mergeProps->toArray(); + } + if ($deepMergeProps->isNotEmpty()) { + $props['deepMergeProps'] = $deepMergeProps->toArray(); + } return $props; } From 3672f1150041cbbcfcbc89208376dd36967960ca Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 9 Apr 2025 14:13:04 -0400 Subject: [PATCH 67/93] Update ServiceProviderTest.php --- tests/ServiceProviderTest.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index 28fe55fe..42a33d4b 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -30,10 +30,13 @@ public function test_route_macro_is_registered(): void $routes = Route::getRoutes(); $this->assertNotEmpty($routes->getRoutes()); - $this->assertEquals($route, $routes->getRoutes()[0]); - $this->assertEquals(['GET', 'HEAD'], $route->methods); - $this->assertEquals('/', $route->uri); - $this->assertEquals(['uses' => '\Inertia\Controller@__invoke', 'controller' => '\Inertia\Controller'], $route->action); - $this->assertEquals(['component' => 'User/Edit', 'props' => ['user' => ['name' => 'Jonathan']]], $route->defaults); + + $inertiaRoute = collect($routes->getRoutes())->first(fn($route) => $route->uri === '/'); + + $this->assertEquals($route, $inertiaRoute); + $this->assertEquals(['GET', 'HEAD'], $inertiaRoute->methods); + $this->assertEquals('/', $inertiaRoute->uri); + $this->assertEquals(['uses' => '\Inertia\Controller@__invoke', 'controller' => '\Inertia\Controller'], $inertiaRoute->action); + $this->assertEquals(['component' => 'User/Edit', 'props' => ['user' => ['name' => 'Jonathan']]], $inertiaRoute->defaults); } } From 3660cac14e2915a2a5e956f31116047b8ac6bcf6 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 9 Apr 2025 14:22:24 -0400 Subject: [PATCH 68/93] wip --- src/MergesProps.php | 11 +++++------ src/Response.php | 36 +++++++++-------------------------- tests/DeepMergePropTest.php | 13 +++---------- tests/ResponseFactoryTest.php | 3 +-- 4 files changed, 18 insertions(+), 45 deletions(-) diff --git a/src/MergesProps.php b/src/MergesProps.php index 68b36d32..4086a698 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -15,19 +15,18 @@ public function merge(): static return $this; } - public function shouldMerge(): bool - { - return $this->merge; - } - public function deepMerge(): static { - $this->deepMerge = true; return $this->merge(); } + public function shouldMerge(): bool + { + return $this->merge; + } + public function shouldDeepMerge(): bool { return $this->deepMerge; diff --git a/src/Response.php b/src/Response.php index f97f7716..5eb0b049 100644 --- a/src/Response.php +++ b/src/Response.php @@ -305,40 +305,22 @@ public function resolveMergeProps(Request $request): array { $resetProps = collect(explode(',', $request->header(Header::RESET, ''))); $mergeProps = collect($this->props) - ->filter(function ($prop) { - return $prop instanceof Mergeable; - }) - ->filter(function ($prop) { - return $prop->shouldMerge(); - }); + ->filter(fn ($prop) => $prop instanceof Mergeable) + ->filter(fn ($prop) => $prop->shouldMerge()) + ->filter(fn ($_, $key) => ! $resetProps->contains($key)); $deepMergeProps = $mergeProps - ->filter(function ($prop) { - return $prop->shouldDeepMerge(); - }) - ->filter(function ($prop, $key) use ($resetProps) { - return ! $resetProps->contains($key); - }) + ->filter(fn ($prop) => $prop->shouldDeepMerge()) ->keys(); $mergeProps = $mergeProps - ->filter(function ($prop) { - return ! $prop->shouldDeepMerge(); - }) - ->filter(function ($prop, $key) use ($resetProps) { - return ! $resetProps->contains($key); - }) + ->filter(fn ($prop) => ! $prop->shouldDeepMerge()) ->keys(); - $props = []; - if ($mergeProps->isNotEmpty()) { - $props['mergeProps'] = $mergeProps->toArray(); - } - if ($deepMergeProps->isNotEmpty()) { - $props['deepMergeProps'] = $deepMergeProps->toArray(); - } - - return $props; + return array_filter([ + 'mergeProps' => $mergeProps->toArray(), + 'deepMergeProps' => $deepMergeProps->toArray(), + ], fn ($prop) => count($prop) > 0); } public function resolveDeferredProps(Request $request): array diff --git a/tests/DeepMergePropTest.php b/tests/DeepMergePropTest.php index d930b2ba..b0fe8348 100644 --- a/tests/DeepMergePropTest.php +++ b/tests/DeepMergePropTest.php @@ -9,28 +9,21 @@ class DeepMergePropTest extends TestCase { public function test_can_invoke_with_a_callback(): void { - $mergeProp = new MergeProp(function () { - return 'A merge prop value'; - }); - $mergeProp->deepMerge(); + $mergeProp = (new MergeProp(fn () => 'A merge prop value'))->deepMerge(); $this->assertSame('A merge prop value', $mergeProp()); } public function test_can_invoke_with_a_non_callback(): void { - $mergeProp = new MergeProp(['key' => 'value']); - $mergeProp->deepMerge(); + $mergeProp = (new MergeProp(['key' => 'value']))->deepMerge(); $this->assertSame(['key' => 'value'], $mergeProp()); } public function test_can_resolve_bindings_when_invoked(): void { - $mergeProp = new MergeProp(function (Request $request) { - return $request; - }); - $mergeProp->deepMerge(); + $mergeProp = (new MergeProp(fn (Request $request) => $request))->deepMerge(); $this->assertInstanceOf(Request::class, $mergeProp()); } diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index beda75db..8137bb8b 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -280,10 +280,9 @@ public function test_can_create_merged_prop(): void public function test_can_create_deep_merged_prop(): void { $factory = new ResponseFactory; - $mergedProp = $factory->merge(function () { + $mergedProp = $factory->deepMerge(function () { return 'A merged value'; }); - $mergedProp->deepMerge(); $this->assertInstanceOf(MergeProp::class, $mergedProp); } From bb812e1ed73b95f92034c7ab03c5364291099540 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:22:49 +0000 Subject: [PATCH 69/93] Fix code styling --- tests/ServiceProviderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index 42a33d4b..efb270b4 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -31,7 +31,7 @@ public function test_route_macro_is_registered(): void $this->assertNotEmpty($routes->getRoutes()); - $inertiaRoute = collect($routes->getRoutes())->first(fn($route) => $route->uri === '/'); + $inertiaRoute = collect($routes->getRoutes())->first(fn ($route) => $route->uri === '/'); $this->assertEquals($route, $inertiaRoute); $this->assertEquals(['GET', 'HEAD'], $inertiaRoute->methods); From 2c0ad4adea16331a44b1a1ef60c7d99af28b2d3b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 9 Apr 2025 16:56:49 -0400 Subject: [PATCH 70/93] wip --- .github/CODE_OF_CONDUCT.md | 3 ++ .github/FUNDING.yml | 1 - .github/PULL_REQUEST_TEMPLATE.md | 9 ++++ .github/SECURITY.md | 92 ++++++++++++++++++++++++++++++++ .github/SUPPORT.md | 3 ++ 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/FUNDING.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/SECURITY.md create mode 100644 .github/SUPPORT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..92b5bf56 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +The Laravel Code of Conduct can be found in the [Laravel documentation](https://laravel.com/docs/contributions#code-of-conduct). diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 0944fe4d..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [reinink] diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..03786937 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ + diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 00000000..800b8aff --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,92 @@ +# Security Policy + +**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).** + +## Supported Versions + +Only the latest major version receives security fixes. + +## Reporting a Vulnerability + +If you discover a security vulnerability within Laravel, please send an email to Taylor Otwell at taylor@laravel.com. All security vulnerabilities will be promptly addressed. + +### Public PGP Key + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: OpenPGP v2.0.8 +Comment: Report Security Vulnerabilities to taylor@laravel.com + +xsFNBFugFSQBEACxEKhIY9IoJzcouVTIYKJfWFGvwFgbRjQWBiH3QdHId5vCrbWo +s2l+4Rv03gMG+yHLJ3rWElnNdRaNdQv59+lShrZF7Bvu7Zvc0mMNmFOM/mQ/K2Lt +OK/8bh6iwNNbEuyOhNQlarEy/w8hF8Yf55hBeu/rajGtcyURJDloQ/vNzcx4RWGK +G3CLr8ka7zPYIjIFUvHLt27mcYFF9F4/G7b4HKpn75ICKC4vPoQSaYNAHlHQBLFb +Jg/WPl93SySHLugU5F58sICs+fBZadXYQG5dWmbaF5OWB1K2XgRs45BQaBzf/8oS +qq0scN8wVhAdBeYlVFf0ImDOxGlZ2suLK1BKJboR6zCIkBAwufKss4NV1R9KSUMv +YGn3mq13PGme0QoIkvQkua5VjTwWfQx7wFDxZ3VQSsjIlbVyRL/Ac/hq71eAmiIR +t6ZMNMPFpuSwBfYimrXqqb4EOffrfsTzRenG1Cxm4jNZRzX/6P4an7F/euoqXeXZ +h37TiC7df+eHKcBI4mL+qOW4ibBqg8WwWPJ+jvuhANyQpRVzf3NNEOwJYCNbQPM/ +PbqYvMruAH+gg7uyu9u0jX3o/yLSxJMV7kF4x/SCDuBKIxSSUI4cgbjIlnxWLXZC +wl7KW4xAKkerO3wgIPnxNfxQoiYiEKA1c3PShWRA0wHIMt3rVRJxwGM4CwARAQAB +zRJ0YXlsb3JAbGFyYXZlbC5jb23CwXAEEwEKABoFAlugFSQCGy8DCwkHAxUKCAIe +AQIXgAIZAQAKCRDKAI7r/Ml7Zo0SD/9zwu9K87rbqXbvZ3TVu7TnN+z7mPvVBzl+ +SFEK360TYq8a4GosghZuGm4aNEyZ90CeUjPQwc5fHwa26tIwqgLRppsG21B/mZwu +0m8c5RaBFRFX/mCTEjlpvBkOwMJZ8f05nNdaktq6W98DbMN03neUwnpWlNSLeoNI +u4KYZmJopNFLEax5WGaaDpmqD1J+WDr/aPHx39MUAg2ZVuC3Gj/IjYZbD1nCh0xD +a09uDODje8a9uG33cKRBcKKPRLZjWEt5SWReLx0vsTuqJSWhCybHRBl9BQTc/JJR +gJu5V4X3f1IYMTNRm9GggxcXrlOAiDCjE2J8ZTUt0cSxedQFnNyGfKxe/l94oTFP +wwFHbdKhsSDZ1OyxPNIY5OHlMfMvvJaNbOw0xPPAEutPwr1aqX9sbgPeeiJwAdyw +mPw2x/wNQvKJITRv6atw56TtLxSevQIZGPHCYTSlsIoi9jqh9/6vfq2ruMDYItCq ++8uzei6TyH6w+fUpp/uFmcwZdrDwgNVqW+Ptu+pD2WmthqESF8UEQVoOv7OPgA5E +ofOMaeH2ND74r2UgcXjPxZuUp1RkhHE2jJeiuLtbvOgrWwv3KOaatyEbVl+zHA1e +1RHdJRJRPK+S7YThxxduqfOBX7E03arbbhHdS1HKhPwMc2e0hNnQDoNxQcv0GQp4 +2Y6UyCRaus7ATQRboBUkAQgA0h5j3EO2HNvp8YuT1t/VF00uUwbQaz2LIoZogqgC +14Eb77diuIPM9MnuG7bEOnNtPVMFXxI5UYBIlzhLMxf7pfbrsoR4lq7Ld+7KMzdm +eREqJRgUNfjZhtRZ9Z+jiFPr8AGpYxwmJk4v387uQGh1GC9JCc3CCLJoI62I9t/1 +K2b25KiOzW/FVZ/vYFj1WbISRd5GqS8SEFh4ifU79LUlJ/nEsFv4JxAXN9RqjU0e +H4S/m1Nb24UCtYAv1JKymcf5O0G7kOzvI0w06uKxk0hNwspjDcOebD8Vv9IdYtGl +0bn7PpBlVO1Az3s8s6Xoyyw+9Us+VLNtVka3fcrdaV/n0wARAQABwsKEBBgBCgAP +BQJboBUkBQkPCZwAAhsuASkJEMoAjuv8yXtmwF0gBBkBCgAGBQJboBUkAAoJEA1I +8aTLtYHmjpIH/A1ZKwTGetHFokJxsd2omvbqv+VtpAjnUbvZEi5g3yZXn+dHJV+K +UR/DNlfGxLWEcY6datJ3ziNzzD5u8zcPp2CqeTlCxOky8F74FjEL9tN/EqUbvvmR +td2LXsSFjHnLJRK5lYfZ3rnjKA5AjqC9MttILBovY2rI7lyVt67kbS3hMHi8AZl8 +EgihnHRJxGZjEUxyTxcB13nhfjAvxQq58LOj5754Rpe9ePSKbT8DNMjHbGpLrESz +cmyn0VzDMLfxg8AA9uQFMwdlKqve7yRZXzeqvy08AatUpJaL7DsS4LKOItwvBub6 +tHbCE3mqrUw5lSNyUahO3vOcMAHnF7fd4W++eA//WIQKnPX5t3CwCedKn8Qkb3Ow +oj8xUNl2T6kEtQJnO85lKBFXaMOUykopu6uB9EEXEr0ShdunOKX/UdDbkv46F2AB +7TtltDSLB6s/QeHExSb8Jo3qra86JkDUutWdJxV7DYFUttBga8I0GqdPu4yRRoc/ +0irVXsdDY9q7jz6l7fw8mSeJR96C0Puhk70t4M1Vg/tu/ONRarXQW7fJ8kl21PcD +UKNWWa242gji/+GLRI8AIpGMsBiX7pHhqmMMth3u7+ne5BZGGJz0uX+CzWboOHyq +kWgfY4a62t3hM0vwnUkl/D7VgSGy4LiKQrapd3LvU2uuEfFsMu3CDicZBRXPqoXj +PBjkkPKhwUTNlwEQrGF3QsZhNe0M9ptM2fC34qtxZtMIMB2NLvE4S621rmQ05oQv +sT0B9WgUL3GYRKdx700+ojHEuwZ79bcLgo1dezvkfPtu/++2CXtieFthDlWHy8x5 +XJJjI1pDfGO+BgX0rS3QrQEYlF/uPQynKwxe6cGI62eZ0ug0hNrPvKEcfMLVqBQv +w4VH6iGp9yNKMUOgAECLCs4YCxK+Eka9Prq/Gh4wuqjWiX8m66z8YvKf27sFL3fR +OwGaz3LsnRSxbk/8oSiZuOVLfn44XRcxsHebteZat23lwD93oq54rtKnlJgmZHJY +4vMgk1jpS4laGnvhZj7OwE0EW6AVJAEIAKJSrUvXRyK3XQnLp3Kfj82uj0St8Dt2 +h8BMeVbrAbg38wCN8XQZzVR9+bRZRR+aCzpKSqwhEQVtH7gdKgfdNdGNhG2DFAVk +SihMhQz190FKttUZgwY00enzD7uaaA5VwNAZzRIr8skwiASB7UoO+lIhrAYgcQCA +LpwCSMrUNB3gY1IVa2xi9FljEbS2uMABfOsTfl7z4L4T4DRv/ovDf+ihyZOXsXiH +RVoUTIpN8ZILCZiiKubE1sMj4fSQwCs06UyDy17HbOG5/dO9awR/LHW53O3nZCxE +JbCqr5iHa2MdHMC12+luxWJKD9DbVB01LiiPZCTkuKUDswCyi7otpVEAEQEAAcLC +hAQYAQoADwUCW6AVJAUJDwmcAAIbLgEpCRDKAI7r/Ml7ZsBdIAQZAQoABgUCW6AV +JAAKCRDxrCjKN7eORjt2B/9EnKVJ9lwB1JwXcQp6bZgJ21r6ghyXBssv24N9UF+v +5QDz/tuSkTsKK1UoBrBDEinF/xTP2z+xXIeyP4c3mthMHsYdMl7AaGpcCwVJiL62 +fZvd+AiYNX3C+Bepwnwoziyhx4uPaqoezSEMD8G2WQftt6Gqttmm0Di5RVysCECF +EyhkHwvCcbpXb5Qq+4XFzCUyaIZuGpe+oeO7U8B1CzOC16hEUu0Uhbk09Xt6dSbS +ZERoxFjrGU+6bk424MkZkKvNS8FdTN2s3kQuHoNmhbMY+fRzKX5JNrcQ4dQQufiB +zFcc2Ba0JVU0nYAMftTeT5ALakhwSqr3AcdD2avJZp3EYfYP/3smPGTeg1cDJV3E +WIlCtSlhbwviUjvWEWJUE+n9MjhoUNU0TJtHIliUYUajKMG/At5wJZTXJaKVUx32 +UCWr4ioKfSzlbp1ngBuFlvU7LgZRcKbBZWvKj/KRYpxpfvPyPElmegCjAk6oiZYV +LOV+jFfnMkk9PnR91ZZfTNx/bK+BwjOnO+g7oE8V2g2bA90vHdeSUHR52SnaVN/b +9ytt07R0f+YtyKojuPmlNsbyAaUYUtJ1o+XNCwdVxzarYEuUabhAfDiVTu9n8wTr +YVvnriSFOjNvOY9wdLAa56n7/qM8bzuGpoBS5SilXgJvITvQfWPvg7I9C3QhwK1S +F6B1uquQGbBSze2wlnMbKXmhyGLlv9XpOqpkkejQo3o58B+Sqj4B8DuYixSjoknr +pRbj8gqgqBKlcpf1wD5X9qCrl9vq19asVOHaKhiFZGxZIVbBpBOdvAKaMj4p/uln +yklN3YFIfgmGPYbL0elvXVn7XfvwSV1mCQV5LtMbLHsFf0VsA16UsG8A/tLWtwgt +0antzftRHXb+DI4qr+qEYKFkv9F3oCOXyH4QBhPA42EzKqhMXByEkEK9bu6skioL +mHhDQ7yHjTWcxstqQjkUQ0T/IF9ls+Sm5u7rVXEifpyI7MCb+76kSCDawesvInKt +WBGOG/qJGDlNiqBYYt2xNqzHCJoC +=zXOv +-----END PGP PUBLIC KEY BLOCK----- +``` diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 00000000..f0877fc2 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,3 @@ +# Support Questions + +The Laravel support guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions#support-questions). From 8e1b7e2ffccb7b049caf7fd4399a5cb0b8e192e5 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Wed, 9 Apr 2025 17:00:46 -0400 Subject: [PATCH 71/93] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 52d49ba8..747b29d9 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,19 @@

Visit [inertiajs.com](https://inertiajs.com/) to learn more. + +## Contributing + +Thank you for considering contributing to Inertia! You can read the contribution guide [here](.github/CONTRIBUTING.md). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +Please review [our security policy](https://github.com/inertiajs/inertia-laravel/security/policy) on how to report security vulnerabilities. + +## License + +Inertia is open-sourced software licensed under the [MIT license](LICENSE.md). From 47427a1ac757f02fd3ad6d20f04e4a996f316bc0 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Apr 2025 10:24:42 -0400 Subject: [PATCH 72/93] Update ResponseFactory.php --- src/ResponseFactory.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 706fc2b6..4f46052b 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -156,7 +156,7 @@ public function merge($value): MergeProp /** * @param mixed $value - * @return AlwaysProp + * @return MergeProp */ public function deepMerge($value): MergeProp { @@ -165,6 +165,7 @@ public function deepMerge($value): MergeProp /** * @param mixed $value + * @return AlwaysProp */ public function always($value): AlwaysProp { From 48fb7b9ddc6051380000d4777d72ea36d12102c4 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:25:05 +0000 Subject: [PATCH 73/93] Fix code styling --- src/ResponseFactory.php | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 4f46052b..74fd4e8b 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -40,7 +40,6 @@ public function setRootView(string $name): void $this->rootView = $name; } - /** * @param string|array|Arrayable $key * @param mixed $value @@ -57,7 +56,6 @@ public function share($key, $value = null): void } /** - * @param string|null $key * @param mixed $default * @return mixed */ @@ -70,7 +68,6 @@ public function getShared(?string $key = null, $default = null) return $this->sharedProps; } - /** * @return void */ @@ -81,16 +78,12 @@ public function flushShared() /** * @param Closure|string|null $version - * @return void */ public function version($version): void { $this->version = $version; } - /** - * @return string - */ public function getVersion(): string { $version = $this->version instanceof Closure @@ -100,17 +93,13 @@ public function getVersion(): string return (string) $version; } - /** - * @return void - */ public function clearHistory(): void { session(['inertia.clear_history' => true]); } /** - * @param bool $encrypt - * @return void + * @param bool $encrypt */ public function encryptHistory($encrypt = true): void { @@ -125,21 +114,11 @@ public function lazy(callable $callback): LazyProp return new LazyProp($callback); } - /** - * @param callable $callback - * @return OptionalProp - */ public function optional(callable $callback): OptionalProp { return new OptionalProp($callback); } - /** - * - * @param callable $callback - * @param string $group - * @return DeferProp - */ public function defer(callable $callback, string $group = 'default'): DeferProp { return new DeferProp($callback, $group); @@ -147,7 +126,6 @@ public function defer(callable $callback, string $group = 'default'): DeferProp /** * @param mixed $value - * @return MergeProp */ public function merge($value): MergeProp { @@ -155,8 +133,7 @@ public function merge($value): MergeProp } /** - * @param mixed $value - * @return MergeProp + * @param mixed $value */ public function deepMerge($value): MergeProp { @@ -165,7 +142,6 @@ public function deepMerge($value): MergeProp /** * @param mixed $value - * @return AlwaysProp */ public function always($value): AlwaysProp { @@ -173,9 +149,7 @@ public function always($value): AlwaysProp } /** - * @param string $component - * @param array|Arrayable $props - * @return Response + * @param array|Arrayable $props */ public function render(string $component, $props = []): Response { @@ -194,7 +168,6 @@ public function render(string $component, $props = []): Response /** * @param string|SymfonyRedirect $url - * @return SymfonyResponse */ public function location($url): SymfonyResponse { From d208951a61bef047c702355bc715c9c527ef6044 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Apr 2025 10:37:49 -0400 Subject: [PATCH 74/93] Update Response.php --- src/Response.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Response.php b/src/Response.php index 646864b1..fc9f16c8 100644 --- a/src/Response.php +++ b/src/Response.php @@ -257,7 +257,7 @@ public function resolvePropertyInstances(array $props, Request $request): array DeferProp::class, AlwaysProp::class, MergeProp::class, - ])->first(fn ($class) => $value instanceof $class); + ])->first(fn($class) => $value instanceof $class); if ($resolveViaApp) { $value = App::call($value); @@ -267,12 +267,12 @@ public function resolvePropertyInstances(array $props, Request $request): array $value = $value->wait(); } - if ($value instanceof ResourceResponse || $value instanceof JsonResource) { - $value = $value->toResponse($request)->getData(true); - } - if ($value instanceof Responsable) { - $value = $value->toResponse($request)->getData(true); + $_response = $value->toResponse($request); + + if (method_exists($_response, 'getData')) { + $value = $_response->getData(true); + } } if (is_array($value)) { @@ -309,22 +309,22 @@ public function resolveMergeProps(Request $request): array { $resetProps = collect(explode(',', $request->header(Header::RESET, ''))); $mergeProps = collect($this->props) - ->filter(fn ($prop) => $prop instanceof Mergeable) - ->filter(fn ($prop) => $prop->shouldMerge()) - ->filter(fn ($_, $key) => ! $resetProps->contains($key)); + ->filter(fn($prop) => $prop instanceof Mergeable) + ->filter(fn($prop) => $prop->shouldMerge()) + ->filter(fn($_, $key) => ! $resetProps->contains($key)); $deepMergeProps = $mergeProps - ->filter(fn ($prop) => $prop->shouldDeepMerge()) + ->filter(fn($prop) => $prop->shouldDeepMerge()) ->keys(); $mergeProps = $mergeProps - ->filter(fn ($prop) => ! $prop->shouldDeepMerge()) + ->filter(fn($prop) => ! $prop->shouldDeepMerge()) ->keys(); return array_filter([ 'mergeProps' => $mergeProps->toArray(), 'deepMergeProps' => $deepMergeProps->toArray(), - ], fn ($prop) => count($prop) > 0); + ], fn($prop) => count($prop) > 0); } public function resolveDeferredProps(Request $request): array From 9114fe8dd6289499540ea91bd3ab5c7b9f3a24b7 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:38:20 +0000 Subject: [PATCH 75/93] Fix code styling --- src/Response.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Response.php b/src/Response.php index fc9f16c8..eba94657 100644 --- a/src/Response.php +++ b/src/Response.php @@ -9,8 +9,6 @@ use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Http\Resources\Json\ResourceResponse; use Illuminate\Support\Arr; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Response as ResponseFactory; @@ -257,7 +255,7 @@ public function resolvePropertyInstances(array $props, Request $request): array DeferProp::class, AlwaysProp::class, MergeProp::class, - ])->first(fn($class) => $value instanceof $class); + ])->first(fn ($class) => $value instanceof $class); if ($resolveViaApp) { $value = App::call($value); @@ -309,22 +307,22 @@ public function resolveMergeProps(Request $request): array { $resetProps = collect(explode(',', $request->header(Header::RESET, ''))); $mergeProps = collect($this->props) - ->filter(fn($prop) => $prop instanceof Mergeable) - ->filter(fn($prop) => $prop->shouldMerge()) - ->filter(fn($_, $key) => ! $resetProps->contains($key)); + ->filter(fn ($prop) => $prop instanceof Mergeable) + ->filter(fn ($prop) => $prop->shouldMerge()) + ->filter(fn ($_, $key) => ! $resetProps->contains($key)); $deepMergeProps = $mergeProps - ->filter(fn($prop) => $prop->shouldDeepMerge()) + ->filter(fn ($prop) => $prop->shouldDeepMerge()) ->keys(); $mergeProps = $mergeProps - ->filter(fn($prop) => ! $prop->shouldDeepMerge()) + ->filter(fn ($prop) => ! $prop->shouldDeepMerge()) ->keys(); return array_filter([ 'mergeProps' => $mergeProps->toArray(), 'deepMergeProps' => $deepMergeProps->toArray(), - ], fn($prop) => count($prop) > 0); + ], fn ($prop) => count($prop) > 0); } public function resolveDeferredProps(Request $request): array From 2eb800f4e36b2eb1e7898c798d28c5473fee22c6 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Apr 2025 10:47:02 -0400 Subject: [PATCH 76/93] Update inertia.php --- config/inertia.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/inertia.php b/config/inertia.php index cfecb639..bab52f25 100644 --- a/config/inertia.php +++ b/config/inertia.php @@ -66,7 +66,7 @@ 'history' => [ - 'encrypt' => false, + 'encrypt' => (bool) env('INERTIA_ENCRYPT_HISTORY', false), ], From 0b2f8266f22d4f97ab599e7ccc4bbdf0698c55e0 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:52:03 +0000 Subject: [PATCH 77/93] Fix code styling --- src/Response.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Response.php b/src/Response.php index 8370f61c..751213a4 100644 --- a/src/Response.php +++ b/src/Response.php @@ -9,8 +9,6 @@ use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Http\Resources\Json\ResourceResponse; use Illuminate\Support\Arr; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Response as ResponseFactory; @@ -257,7 +255,7 @@ public function resolvePropertyInstances(array $props, Request $request): array DeferProp::class, AlwaysProp::class, MergeProp::class, - ])->first(fn($class) => $value instanceof $class); + ])->first(fn ($class) => $value instanceof $class); if ($resolveViaApp) { $value = App::call($value); @@ -309,22 +307,22 @@ public function resolveMergeProps(Request $request): array { $resetProps = collect(explode(',', $request->header(Header::RESET, ''))); $mergeProps = collect($this->props) - ->filter(fn($prop) => $prop instanceof Mergeable) - ->filter(fn($prop) => $prop->shouldMerge()) - ->filter(fn($_, $key) => ! $resetProps->contains($key)); + ->filter(fn ($prop) => $prop instanceof Mergeable) + ->filter(fn ($prop) => $prop->shouldMerge()) + ->filter(fn ($_, $key) => ! $resetProps->contains($key)); $deepMergeProps = $mergeProps - ->filter(fn($prop) => $prop->shouldDeepMerge()) + ->filter(fn ($prop) => $prop->shouldDeepMerge()) ->keys(); $mergeProps = $mergeProps - ->filter(fn($prop) => ! $prop->shouldDeepMerge()) + ->filter(fn ($prop) => ! $prop->shouldDeepMerge()) ->keys(); return array_filter([ 'mergeProps' => $mergeProps->toArray(), 'deepMergeProps' => $deepMergeProps->toArray(), - ], fn($prop) => count($prop) > 0); + ], fn ($prop) => count($prop) > 0); } public function resolveDeferredProps(Request $request): array From 91b6c53ade0041b8364a5053f2ac2bcdf824eda5 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Thu, 10 Apr 2025 10:55:35 -0400 Subject: [PATCH 78/93] Update HttpGateway.php --- src/Ssr/HttpGateway.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index f6e0427d..055f436a 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -4,7 +4,6 @@ use Exception; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Str; class HttpGateway implements Gateway { @@ -17,7 +16,7 @@ public function dispatch(array $page): ?Response return null; } - $url = str_replace('/render', '', Str::chopEnd(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; + $url = str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')) . '/render'; try { $response = Http::post($url, $page)->throw()->json(); From ff666e1d04f21759615c04390e986886368eb180 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Thu, 10 Apr 2025 14:57:08 +0000 Subject: [PATCH 79/93] Fix code styling --- src/Ssr/HttpGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ssr/HttpGateway.php b/src/Ssr/HttpGateway.php index 055f436a..9f777454 100644 --- a/src/Ssr/HttpGateway.php +++ b/src/Ssr/HttpGateway.php @@ -16,7 +16,7 @@ public function dispatch(array $page): ?Response return null; } - $url = str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')) . '/render'; + $url = str_replace('/render', '', rtrim(config('inertia.ssr.url', 'http://127.0.0.1:13714'), '/')).'/render'; try { $response = Http::post($url, $page)->throw()->json(); From f1321d4e01ba3cf767c383adaa421df66960ed18 Mon Sep 17 00:00:00 2001 From: joetannenbaum <2702148+joetannenbaum@users.noreply.github.com> Date: Thu, 10 Apr 2025 15:22:32 +0000 Subject: [PATCH 80/93] Update CHANGELOG --- CHANGELOG.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8ab406e..41da63bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,36 @@ # Release Notes -## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v2.0.1...2.x) +## [Unreleased](https://github.com/inertiajs/inertia-laravel/compare/v2.0.2...2.x) - Nothing! +## [v2.0.2](https://github.com/inertiajs/inertia-laravel/compare/v2.0.1...v2.0.2) - 2025-04-10 + +### What's Changed + +* [2.x] Supports Laravel 12 by [@crynobone](https://github.com/crynobone) in https://github.com/inertiajs/inertia-laravel/pull/709 +* Add Inertia::deepMerge Method for Handling Complex Data Merges in Responses by [@HichemTab-tech](https://github.com/HichemTab-tech) in https://github.com/inertiajs/inertia-laravel/pull/679 +* Improve PHPDoc annotations for ResponseFactory class by [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) in https://github.com/inertiajs/inertia-laravel/pull/723 +* fix props that extends Responsable after closures / lazy props by [@d8vjork](https://github.com/d8vjork) in https://github.com/inertiajs/inertia-laravel/pull/722 +* [2.x] Allow environment config for `ssr.enabled`, `ssr.url`, and `history.encrypt` by [@bram-pkg](https://github.com/bram-pkg) in https://github.com/inertiajs/inertia-laravel/pull/714 +* Replace `array_merge` with spread operator in `middleware.stub` by [@osbre](https://github.com/osbre) in https://github.com/inertiajs/inertia-laravel/pull/710 +* [2.x] Resolve Closure before checking if a prop implements the Arrayable contract by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/inertiajs/inertia-laravel/pull/706 +* Handle SSR URLs with trailing slashes by [@simon-tma](https://github.com/simon-tma) in https://github.com/inertiajs/inertia-laravel/pull/705 +* [2.x] Call `toArray()` on `Arrayable` props resolved from the Container by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/inertiajs/inertia-laravel/pull/696 +* [2.x] Replace md5 with xxhash by [@RobertBoes](https://github.com/RobertBoes) in https://github.com/inertiajs/inertia-laravel/pull/653 + +### New Contributors + +* [@HichemTab-tech](https://github.com/HichemTab-tech) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/679 +* [@mohammadrasoulasghari](https://github.com/mohammadrasoulasghari) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/723 +* [@d8vjork](https://github.com/d8vjork) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/722 +* [@bram-pkg](https://github.com/bram-pkg) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/714 +* [@osbre](https://github.com/osbre) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/710 +* [@simon-tma](https://github.com/simon-tma) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/705 +* [@pascalbaljet](https://github.com/pascalbaljet) made their first contribution in https://github.com/inertiajs/inertia-laravel/pull/696 + +**Full Changelog**: https://github.com/inertiajs/inertia-laravel/compare/v2.0.1...v2.0.2 + ## [v2.0.1](https://github.com/inertiajs/inertia-laravel/compare/v2.0.0...v2.0.1) - 2025-02-18 - Allow Laravel 12.x. From d9829d7dae966fccc823093df5608ab48fddfcbc Mon Sep 17 00:00:00 2001 From: ljbw <91081153+ljbw@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:58:28 -0600 Subject: [PATCH 81/93] Improve URL handling with support for trailing slashes --- src/Response.php | 19 +++++++++++- tests/ResponseTest.php | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index 633b6fd0..2ebd597a 100644 --- a/src/Response.php +++ b/src/Response.php @@ -109,7 +109,7 @@ public function toResponse($request) [ 'component' => $this->component, 'props' => $props, - 'url' => Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'), + 'url' => $this->getRequestUrl($request), 'version' => $this->version, 'clearHistory' => $this->clearHistory, 'encryptHistory' => $this->encryptHistory, @@ -359,4 +359,21 @@ public function isPartial(Request $request): bool { return $request->header(Header::PARTIAL_COMPONENT) === $this->component; } + + /** + * Get the request URL with proper handling of proxy prefixes and trailing slashes. + */ + protected function getRequestUrl(Request $request): string + { + $uri = $request->getRequestUri(); + + // Handle X-Forwarded-Prefix header for proxy setups + if ($prefix = $request->header('X_FORWARDED_PREFIX')) { + if (!str_starts_with($uri, $prefix)) { + $uri = $prefix . $uri; + } + } + + return $uri; + } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index dc71900e..ea7c420f 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -827,4 +827,72 @@ public function test_the_page_url_doesnt_double_up(): void $this->assertSame('/subpath/product/123', $page->url); } + + public function test_trailing_slashes_in_url_are_preserved(): void + { + // Test with trailing slash in the URL + $request = Request::create('/categories/', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/categories/', $page->url); + + // Test with trailing slash and query parameters + $request = Request::create('/categories/?page=1&sort=name', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/categories/?page=1&sort=name', $page->url); + + // Test with trailing slash and proxy prefix + $request = Request::create('/categories/', 'GET'); + $request->headers->set('X_FORWARDED_PREFIX', '/admin'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/admin/categories/', $page->url); + } + + public function test_non_trailing_slashes_in_url_work_correctly(): void + { + // Test with non-trailing slash in the URL + $request = Request::create('/categories', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/categories', $page->url); + + // Test with non-trailing slash and query parameters + $request = Request::create('/categories?page=1&sort=name', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/categories?page=1&sort=name', $page->url); + + // Test with non-trailing slash and proxy prefix + $request = Request::create('/categories', 'GET'); + $request->headers->set('X_FORWARDED_PREFIX', '/admin'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Category/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/admin/categories', $page->url); + } } From 07769257ce6fea2b3c49633be7abf58a0316866e Mon Sep 17 00:00:00 2001 From: ljbw <91081153+ljbw@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:32:01 -0600 Subject: [PATCH 82/93] Improve URL handling with support for trailing slashes and special characters This PR provides a more consistent approach to URL handling in Inertia responses by: - Adding support for preserving trailing slashes in URLs - Improving readability by decoding special characters in query parameters - Maintaining compatibility with proxy prefixes - Ensuring URL consistency for SEO and debugging Previously, characters like slashes (%2F) and ampersands (%26) were encoded in the URL, making debugging more difficult and causing inconsistencies between browser URLs and Inertia history state URLs. Comprehensive tests have been added for both trailing and non-trailing slash scenarios, as well as specific tests for slash and ampersand handling in query parameters. Resolves issues discussed in #663 --- src/Response.php | 17 ++++++++++---- tests/ResponseTest.php | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/Response.php b/src/Response.php index 2ebd597a..02d0b933 100644 --- a/src/Response.php +++ b/src/Response.php @@ -365,15 +365,24 @@ public function isPartial(Request $request): bool */ protected function getRequestUrl(Request $request): string { - $uri = $request->getRequestUri(); + // Get the original request URI which should already contain any base path + $originalUri = $request->getRequestUri(); // Handle X-Forwarded-Prefix header for proxy setups if ($prefix = $request->header('X_FORWARDED_PREFIX')) { - if (!str_starts_with($uri, $prefix)) { - $uri = $prefix . $uri; + // Only add the prefix if it's not already at the start of the URI + if (!str_starts_with($originalUri, $prefix)) { + $originalUri = $prefix . $originalUri; } } - return $uri; + // Decode the query string portion for better readability + if (str_contains($originalUri, '?')) { + list($path, $query) = explode('?', $originalUri, 2); + $query = urldecode($query); + return $path . '?' . $query; + } + + return $originalUri; } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index ea7c420f..d80172a4 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -32,6 +32,18 @@ public function test_can_macro(): void $this->assertEquals('bar', $response->foo()); } +public function test_the_page_url_is_not_encoded(): void + { + $request = Request::create('/product/123', 'GET', ['value' => 'te/st']); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Product/Show', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + $this->assertSame('/product/123?value=te/st', $page->url); + } + public function test_server_response(): void { $request = Request::create('/user/123', 'GET'); @@ -895,4 +907,44 @@ public function test_non_trailing_slashes_in_url_work_correctly(): void $this->assertSame('/admin/categories', $page->url); } + + public function test_url_with_slashes_in_query_params(): void + { + // Test with forward slashes in query parameters + $request = Request::create('/product/123?value=te/st', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Product/Show', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + // Forward slashes should be preserved, not encoded as %2F + $this->assertSame('/product/123?value=te/st', $page->url); + } + + public function test_url_with_ampersands_in_query_params(): void + { + // Test normal query parameters with & as separator + $request = Request::create('/families?search=jonathan&amy=test', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Family/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + // Check that the URL contains both parameters properly separated by & + $this->assertStringContainsString('search=jonathan', $page->url); + $this->assertStringContainsString('amy=test', $page->url); + + // Test encoded ampersand within a parameter value + $request = Request::create('/families?search=jonathan%26amy', 'GET'); + $request->headers->add(['X-Inertia' => 'true']); + + $response = new Response('Family/Index', []); + $response = $response->toResponse($request); + $page = $response->getData(); + + // The & should be decoded in the parameter value + $this->assertSame('/families?search=jonathan&amy', $page->url); + } } From 054bd74494357c76179256c88b2ab88abfd3098d Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Tue, 13 May 2025 12:04:13 +0200 Subject: [PATCH 83/93] Add mergeStrategies --- src/MergeProp.php | 4 +++- src/MergesProps.php | 7 +++++++ src/Response.php | 10 ++++++++++ src/ResponseFactory.php | 5 +++-- tests/DeepMergePropTest.php | 7 +++++++ tests/ResponseTest.php | 39 +++++++++++++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/MergeProp.php b/src/MergeProp.php index 499b43f4..4600b634 100644 --- a/src/MergeProp.php +++ b/src/MergeProp.php @@ -13,11 +13,13 @@ class MergeProp implements Mergeable /** * @param mixed $value + * @param string[] $mergeStrategies */ - public function __construct($value) + public function __construct($value, array $mergeStrategies = []) { $this->value = $value; $this->merge = true; + $this->mergeStrategies = $mergeStrategies; } public function __invoke() diff --git a/src/MergesProps.php b/src/MergesProps.php index 4086a698..0e442f58 100644 --- a/src/MergesProps.php +++ b/src/MergesProps.php @@ -8,6 +8,8 @@ trait MergesProps protected bool $deepMerge = false; + protected array $mergeStrategies = []; + public function merge(): static { $this->merge = true; @@ -31,4 +33,9 @@ public function shouldDeepMerge(): bool { return $this->deepMerge; } + + public function mergeStrategies(): array + { + return $this->mergeStrategies; + } } diff --git a/src/Response.php b/src/Response.php index 633b6fd0..0f029bf0 100644 --- a/src/Response.php +++ b/src/Response.php @@ -319,6 +319,15 @@ public function resolveMergeProps(Request $request): array ->filter(fn ($prop) => $prop->shouldDeepMerge()) ->keys(); + $mergeStrategies = $mergeProps + ->map(function ($prop, $key) { + return collect($prop->mergeStrategies()) + ->map(fn ($strategy) => $key.".".$strategy) + ->toArray(); + }) + ->flatten() + ->values(); + $mergeProps = $mergeProps ->filter(fn ($prop) => ! $prop->shouldDeepMerge()) ->keys(); @@ -326,6 +335,7 @@ public function resolveMergeProps(Request $request): array return array_filter([ 'mergeProps' => $mergeProps->toArray(), 'deepMergeProps' => $deepMergeProps->toArray(), + 'mergeStrategies' => $mergeStrategies->toArray(), ], fn ($prop) => count($prop) > 0); } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 74fd4e8b..3cc8f19d 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -134,10 +134,11 @@ public function merge($value): MergeProp /** * @param mixed $value + * @parram null|string|string[] $mergeStrategies */ - public function deepMerge($value): MergeProp + public function deepMerge($value, $mergeStrategies = null): MergeProp { - return (new MergeProp($value))->deepMerge(); + return (new MergeProp($value, Arr::wrap($mergeStrategies)))->deepMerge(); } /** diff --git a/tests/DeepMergePropTest.php b/tests/DeepMergePropTest.php index b0fe8348..4e282dec 100644 --- a/tests/DeepMergePropTest.php +++ b/tests/DeepMergePropTest.php @@ -27,4 +27,11 @@ public function test_can_resolve_bindings_when_invoked(): void $this->assertInstanceOf(Request::class, $mergeProp()); } + + public function test_can_use_single_string_as_merge_strategy(): void + { + $mergeProp = (new MergeProp(['key' => 'value'], ['key']))->deepMerge(); + + $this->assertEquals(['key'], $mergeProp->mergeStrategies()); + } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index dc71900e..81dcf9ab 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -201,6 +201,45 @@ public function test_server_response_with_deep_merge_props(): void $this->assertSame('
', $view->render()); } + public function test_server_response_with_merge_strategies(): void + { + $request = Request::create('/user/123', 'GET'); + + $user = ['name' => 'Jonathan']; + $response = new Response( + 'User/Edit', + [ + 'user' => $user, + 'foo' => (new MergeProp('foo value', ['foo-key']))->deepMerge(), + 'bar' => (new MergeProp('bar value', ['bar-key']))->deepMerge(), + ], + 'app', + '123' + ); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertInstanceOf(BaseResponse::class, $response); + $this->assertInstanceOf(View::class, $view); + + $this->assertSame('User/Edit', $page['component']); + $this->assertSame('Jonathan', $page['props']['user']['name']); + $this->assertSame('/user/123', $page['url']); + $this->assertSame('123', $page['version']); + $this->assertSame([ + 'foo', + 'bar', + ], $page['deepMergeProps']); + $this->assertSame([ + 'foo.foo-key', + 'bar.bar-key', + ], $page['mergeStrategies']); + $this->assertFalse($page['clearHistory']); + $this->assertFalse($page['encryptHistory']); + $this->assertSame('
', $view->render()); + } + public function test_server_response_with_defer_and_merge_props(): void { $request = Request::create('/user/123', 'GET'); From 9d4ae0658f57170e6192ff8c2185610304bfe3d5 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 4 Jun 2025 14:03:52 +0200 Subject: [PATCH 84/93] Refactor + remove `urldecode` from 0776925 --- src/Response.php | 43 +++++++------- tests/ResponseTest.php | 128 +++++++++-------------------------------- 2 files changed, 49 insertions(+), 122 deletions(-) diff --git a/src/Response.php b/src/Response.php index 02d0b933..578698af 100644 --- a/src/Response.php +++ b/src/Response.php @@ -109,7 +109,7 @@ public function toResponse($request) [ 'component' => $this->component, 'props' => $props, - 'url' => $this->getRequestUrl($request), + 'url' => $this->getUrl($request), 'version' => $this->version, 'clearHistory' => $this->clearHistory, 'encryptHistory' => $this->encryptHistory, @@ -361,28 +361,27 @@ public function isPartial(Request $request): bool } /** - * Get the request URL with proper handling of proxy prefixes and trailing slashes. + * Get the URL from the request (without the scheme and host) while preserving the trailing slash if it exists. */ - protected function getRequestUrl(Request $request): string + protected function getUrl(Request $request): string { - // Get the original request URI which should already contain any base path - $originalUri = $request->getRequestUri(); - - // Handle X-Forwarded-Prefix header for proxy setups - if ($prefix = $request->header('X_FORWARDED_PREFIX')) { - // Only add the prefix if it's not already at the start of the URI - if (!str_starts_with($originalUri, $prefix)) { - $originalUri = $prefix . $originalUri; - } - } - - // Decode the query string portion for better readability - if (str_contains($originalUri, '?')) { - list($path, $query) = explode('?', $originalUri, 2); - $query = urldecode($query); - return $path . '?' . $query; - } - - return $originalUri; + $url = Str::start(Str::after($request->fullUrl(), $request->getSchemeAndHttpHost()), '/'); + + $rawUri = Str::before($request->getRequestUri(), '?'); + + return Str::endsWith($rawUri, '/') ? $this->finishUrlWithTrailingSlash($url) : $url; + } + + /** + * Ensure the URL has a trailing slash before the query string (if it exists). + */ + protected function finishUrlWithTrailingSlash(string $url): string + { + // Make sure the relative URL ends with a trailing slash and re-append the query string if it exists. + $urlWithoutQueryWithTrailingSlash = Str::finish(Str::before($url, '?'), '/'); + + return str_contains($url, '?') + ? $urlWithoutQueryWithTrailingSlash.'?'.Str::after($url, '?') + : $urlWithoutQueryWithTrailingSlash; } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index d80172a4..976ee4da 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -32,18 +32,6 @@ public function test_can_macro(): void $this->assertEquals('bar', $response->foo()); } -public function test_the_page_url_is_not_encoded(): void - { - $request = Request::create('/product/123', 'GET', ['value' => 'te/st']); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Product/Show', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - $this->assertSame('/product/123?value=te/st', $page->url); - } - public function test_server_response(): void { $request = Request::create('/user/123', 'GET'); @@ -839,112 +827,52 @@ public function test_the_page_url_doesnt_double_up(): void $this->assertSame('/subpath/product/123', $page->url); } - - public function test_trailing_slashes_in_url_are_preserved(): void + + public function test_trailing_slashes_in_a_url_are_preserved(): void { - // Test with trailing slash in the URL - $request = Request::create('/categories/', 'GET'); + $request = Request::create('/users/', 'GET'); $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - $this->assertSame('/categories/', $page->url); - - // Test with trailing slash and query parameters - $request = Request::create('/categories/?page=1&sort=name', 'GET'); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - $this->assertSame('/categories/?page=1&sort=name', $page->url); - - // Test with trailing slash and proxy prefix - $request = Request::create('/categories/', 'GET'); - $request->headers->set('X_FORWARDED_PREFIX', '/admin'); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); + + $response = new Response('User/Index', []); $response = $response->toResponse($request); $page = $response->getData(); - - $this->assertSame('/admin/categories/', $page->url); + + $this->assertSame('/users/', $page->url); } - - public function test_non_trailing_slashes_in_url_work_correctly(): void + + public function test_trailing_slashes_in_a_url_with_query_parameters_are_preserved(): void { - // Test with non-trailing slash in the URL - $request = Request::create('/categories', 'GET'); + $request = Request::create('/users/?page=1&sort=name', 'GET'); $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - $this->assertSame('/categories', $page->url); - - // Test with non-trailing slash and query parameters - $request = Request::create('/categories?page=1&sort=name', 'GET'); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - $this->assertSame('/categories?page=1&sort=name', $page->url); - - // Test with non-trailing slash and proxy prefix - $request = Request::create('/categories', 'GET'); - $request->headers->set('X_FORWARDED_PREFIX', '/admin'); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Category/Index', []); + + $response = new Response('User/Index', []); $response = $response->toResponse($request); $page = $response->getData(); - - $this->assertSame('/admin/categories', $page->url); + + $this->assertSame('/users/?page=1&sort=name', $page->url); } - - public function test_url_with_slashes_in_query_params(): void + + public function test_a_url_without_trailing_slash_is_resolved_correctly(): void { - // Test with forward slashes in query parameters - $request = Request::create('/product/123?value=te/st', 'GET'); + $request = Request::create('/users', 'GET'); $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Product/Show', []); + + $response = new Response('User/Index', []); $response = $response->toResponse($request); $page = $response->getData(); - - // Forward slashes should be preserved, not encoded as %2F - $this->assertSame('/product/123?value=te/st', $page->url); + + $this->assertSame('/users', $page->url); } - - public function test_url_with_ampersands_in_query_params(): void + + public function test_a_url_without_trailing_slash_and_query_parameters_is_resolved_correctly(): void { - // Test normal query parameters with & as separator - $request = Request::create('/families?search=jonathan&amy=test', 'GET'); + $request = Request::create('/users?page=1&sort=name', 'GET'); $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Family/Index', []); - $response = $response->toResponse($request); - $page = $response->getData(); - - // Check that the URL contains both parameters properly separated by & - $this->assertStringContainsString('search=jonathan', $page->url); - $this->assertStringContainsString('amy=test', $page->url); - - // Test encoded ampersand within a parameter value - $request = Request::create('/families?search=jonathan%26amy', 'GET'); - $request->headers->add(['X-Inertia' => 'true']); - - $response = new Response('Family/Index', []); + + $response = new Response('User/Index', []); $response = $response->toResponse($request); $page = $response->getData(); - - // The & should be decoded in the parameter value - $this->assertSame('/families?search=jonathan&amy', $page->url); + + $this->assertSame('/users?page=1&sort=name', $page->url); } } From 3e7c1a79c5fc686b032d510fa1c1aa2e631357dc Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 4 Jun 2025 14:06:42 +0200 Subject: [PATCH 85/93] Run tests on Ubuntu 24.04 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3d2361b..1a7314a5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,7 +8,7 @@ on: jobs: tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: true matrix: From 248134b57d5a9d2bc899fdc9a71da9605a6e8cc4 Mon Sep 17 00:00:00 2001 From: nshiro <14008307+nshiro@users.noreply.github.com> Date: Thu, 5 Jun 2025 07:12:18 +0900 Subject: [PATCH 86/93] [2.x] Remove old LegacyTestResponse --- src/ServiceProvider.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index e0142e23..b0e0e0c8 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -2,7 +2,6 @@ namespace Inertia; -use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Http\Request; use Illuminate\Routing\Router; use Illuminate\Support\ServiceProvider as BaseServiceProvider; @@ -99,13 +98,6 @@ protected function registerTestingMacros(): void return; } - // Laravel <= 6.0 - if (class_exists(LegacyTestResponse::class)) { - LegacyTestResponse::mixin(new TestResponseMacros); - - return; - } - throw new LogicException('Could not detect TestResponse class.'); } From 33aeb801be988e920832295b831b35cd19b6480d Mon Sep 17 00:00:00 2001 From: nshiro <14008307+nshiro@users.noreply.github.com> Date: Fri, 2 Aug 2024 23:16:25 +0900 Subject: [PATCH 87/93] [1.x] Put Vite check before Mix --- src/Middleware.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Middleware.php b/src/Middleware.php index ba4d9a34..8c2d9e24 100644 --- a/src/Middleware.php +++ b/src/Middleware.php @@ -32,11 +32,11 @@ public function version(Request $request) return hash('xxh128', config('app.asset_url')); } - if (file_exists($manifest = public_path('mix-manifest.json'))) { + if (file_exists($manifest = public_path('build/manifest.json'))) { return hash_file('xxh128', $manifest); } - if (file_exists($manifest = public_path('build/manifest.json'))) { + if (file_exists($manifest = public_path('mix-manifest.json'))) { return hash_file('xxh128', $manifest); } From 512a989e0c0a4a9a1d2f77789f8c514e7cff9099 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 5 Jun 2025 09:50:21 +0200 Subject: [PATCH 88/93] Added tests for version hash --- tests/MiddlewareTest.php | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index c00bf7b1..002499fa 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -2,6 +2,7 @@ namespace Inertia\Tests; +use Illuminate\Filesystem\Filesystem; use Illuminate\Http\Request; use Illuminate\Session\Middleware\StartSession; use Illuminate\Support\Facades\Route; @@ -13,9 +14,16 @@ use Inertia\Middleware; use Inertia\Tests\Stubs\ExampleMiddleware; use LogicException; +use PHPUnit\Framework\Attributes\After; class MiddlewareTest extends TestCase { + #[After] + public function cleanupPublicFolder(): void + { + (new Filesystem)->cleanDirectory(public_path()); + } + public function test_no_response_value_by_default_means_automatically_redirecting_back_for_inertia_requests(): void { $fooCalled = false; @@ -241,6 +249,49 @@ public function rootView(Request $request): string $response->assertViewIs('welcome'); } + public function test_determine_the_version_by_a_hash_of_the_asset_url(): void + { + config(['app.asset_url' => $url = 'https://example.com/assets']); + + $this->prepareMockEndpoint(middleware: new Middleware); + + $response = $this->get('/'); + $response->assertOk(); + $response->assertViewHas('page.version', hash('xxh128', $url)); + } + + public function test_determine_the_version_by_a_hash_of_the_vite_manifest(): void + { + $filesystem = new Filesystem; + $filesystem->ensureDirectoryExists(public_path('build')); + $filesystem->put( + public_path('build/manifest.json'), + $contents = json_encode(['vite' => true]) + ); + + $this->prepareMockEndpoint(middleware: new Middleware); + + $response = $this->get('/'); + $response->assertOk(); + $response->assertViewHas('page.version', hash('xxh128', $contents)); + } + + public function test_determine_the_version_by_a_hash_of_the_mix_manifest(): void + { + $filesystem = new Filesystem; + $filesystem->ensureDirectoryExists(public_path()); + $filesystem->put( + public_path('mix-manifest.json'), + $contents = json_encode(['mix' => true]) + ); + + $this->prepareMockEndpoint(middleware: new Middleware); + + $response = $this->get('/'); + $response->assertOk(); + $response->assertViewHas('page.version', hash('xxh128', $contents)); + } + private function prepareMockEndpoint($version = null, $shared = [], $middleware = null): \Illuminate\Routing\Route { if (is_null($middleware)) { From 0acdad307b8a05e26b73f4170f248b605f8e8329 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 5 Jun 2025 09:59:23 +0200 Subject: [PATCH 89/93] Use PHPUnit 10 on PHP 8.1 --- .github/workflows/tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a7314a5..3591aea7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,7 +43,6 @@ jobs: timeout_minutes: 5 max_attempts: 5 command: | - composer require phpunit/phpunit:^9.5.8 --dev --${{ matrix.stability }} --no-update --no-interaction composer require vlucas/phpdotenv:^5.3.1 --${{ matrix.stability }} --no-update --no-interaction if: matrix.php >= 8.1 && matrix.stability == 'prefer-lowest' @@ -63,7 +62,6 @@ jobs: max_attempts: 5 command: | composer require "orchestra/testbench:^9.2|^10.0" --dev --${{ matrix.stability }} --no-update --no-interaction - composer require "phpunit/phpunit:^10.4|^11.5" --dev --${{ matrix.stability }} --no-update --no-interaction if: matrix.php >= 8.2 && matrix.stability == 'prefer-lowest' && matrix.laravel >= 11 - name: Set Laravel version From a43d67fb121dde08534804d3f3255ae276d4acf3 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 5 Jun 2025 16:22:17 +0200 Subject: [PATCH 90/93] Minor cleanup --- src/Testing/TestResponseMacros.php | 5 +---- tests/Testing/TestResponseMacrosTest.php | 13 ++++++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Testing/TestResponseMacros.php b/src/Testing/TestResponseMacros.php index ee9c258f..cf4e5e29 100644 --- a/src/Testing/TestResponseMacros.php +++ b/src/Testing/TestResponseMacros.php @@ -32,10 +32,7 @@ public function inertiaPage() public function inertiaProps() { return function (?string $propName = null) { - return Arr::get( - $this->inertiaPage()['props'] ?? [], - $propName - ); + return Arr::get($this->inertiaPage()['props'], $propName); }; } } diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index 034e908f..837177af 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -58,15 +58,22 @@ public function test_it_can_retrieve_the_inertia_props(): void Inertia::render('foo', $props) ); - tap($response->inertiaProps(), fn (array $pageProps) => $this->assertSame($props, $pageProps)); + $this->assertSame($props, $response->inertiaProps()); } public function test_it_can_retrieve_nested_inertia_prop_values_with_dot_notation(): void { $response = $this->makeMockRequest( - Inertia::render('foo', ['bar' => ['baz' => 'qux']]) + Inertia::render('foo', [ + 'bar' => ['baz' => 'qux'], + 'users' => [ + ['name' => 'John'], + ['name' => 'Jane'], + ], + ]) ); - tap($response->inertiaProps('bar.baz'), fn (mixed $value) => $this->assertSame('qux', $value)); + $this->assertSame('qux', $response->inertiaProps('bar.baz')); + $this->assertSame('John', $response->inertiaProps('users.0.name')); } } From a8cce303b5814d1a40e09569827179337148185d Mon Sep 17 00:00:00 2001 From: Lucas Yang Date: Fri, 6 Jun 2025 11:49:48 +0800 Subject: [PATCH 91/93] [2.x] Remove IDE helpers --- .gitattributes | 1 - _ide_helpers.php | 31 ------------------------------- 2 files changed, 32 deletions(-) delete mode 100644 _ide_helpers.php diff --git a/.gitattributes b/.gitattributes index 639989d1..7450a3ed 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,7 +7,6 @@ /.gitignore export-ignore /.github export-ignore /.php-cs-fixer.dist.php export-ignore -/_ide_helpers.php export-ignore /phpunit.xml.dist export-ignore /tests export-ignore /CHANGELOG.md export-ignore diff --git a/_ide_helpers.php b/_ide_helpers.php deleted file mode 100644 index c3b8f827..00000000 --- a/_ide_helpers.php +++ /dev/null @@ -1,31 +0,0 @@ - Date: Fri, 6 Jun 2025 10:04:15 +0200 Subject: [PATCH 92/93] Remove check for `TestResponse` from Laravel <= 6.0 --- tests/TestCase.php | 23 +---------------------- tests/Testing/TestResponseMacrosTest.php | 3 ++- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 8de5be0f..9e2a8833 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,12 +2,10 @@ namespace Inertia\Tests; -use Illuminate\Foundation\Testing\TestResponse as LegacyTestResponse; use Illuminate\Support\Facades\View; use Illuminate\Testing\TestResponse; use Inertia\Inertia; use Inertia\ServiceProvider; -use LogicException; use Orchestra\Testbench\TestCase as Orchestra; abstract class TestCase extends Orchestra @@ -30,26 +28,7 @@ protected function setUp(): void config()->set('inertia.testing.page_paths', [realpath(__DIR__)]); } - /** - * @throws LogicException - */ - protected function getTestResponseClass(): string - { - // Laravel >= 7.0 - if (class_exists(TestResponse::class)) { - return TestResponse::class; - } - - // Laravel <= 6.0 - if (class_exists(LegacyTestResponse::class)) { - return LegacyTestResponse::class; - } - - throw new LogicException('Could not detect TestResponse class.'); - } - - /** @returns TestResponse|LegacyTestResponse */ - protected function makeMockRequest($view) + protected function makeMockRequest($view): TestResponse { app('router')->get('/example-url', function () use ($view) { return $view; diff --git a/tests/Testing/TestResponseMacrosTest.php b/tests/Testing/TestResponseMacrosTest.php index 837177af..ed2e7764 100644 --- a/tests/Testing/TestResponseMacrosTest.php +++ b/tests/Testing/TestResponseMacrosTest.php @@ -3,6 +3,7 @@ namespace Inertia\Tests\Testing; use Illuminate\Testing\Fluent\AssertableJson; +use Illuminate\Testing\TestResponse; use Inertia\Inertia; use Inertia\Tests\TestCase; @@ -30,7 +31,7 @@ public function test_it_preserves_the_ability_to_continue_chaining_laravel_test_ ); $this->assertInstanceOf( - $this->getTestResponseClass(), + TestResponse::class, $response->assertInertia() ); } From a1f16269f38139eb6ccd8ecd1dd0fe8540b04bb2 Mon Sep 17 00:00:00 2001 From: pascalbaljet <8403149+pascalbaljet@users.noreply.github.com> Date: Fri, 13 Jun 2025 08:19:05 +0000 Subject: [PATCH 93/93] Fix code styling --- src/Response.php | 2 +- src/ResponseFactory.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index 91df689e..81c6813c 100644 --- a/src/Response.php +++ b/src/Response.php @@ -322,7 +322,7 @@ public function resolveMergeProps(Request $request): array $mergeStrategies = $mergeProps ->map(function ($prop, $key) { return collect($prop->mergeStrategies()) - ->map(fn ($strategy) => $key.".".$strategy) + ->map(fn ($strategy) => $key.'.'.$strategy) ->toArray(); }) ->flatten() diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 3cc8f19d..a0ba7947 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -134,6 +134,7 @@ public function merge($value): MergeProp /** * @param mixed $value + * * @parram null|string|string[] $mergeStrategies */ public function deepMerge($value, $mergeStrategies = null): MergeProp