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..68b36d32 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,17 @@ public function shouldMerge(): bool { return $this->merge; } + + public function deepMerge(): static + { + + $this->deepMerge = true; + + return $this->merge(); + } + + public function shouldDeepMerge(): bool + { + return $this->deepMerge; + } } diff --git a/src/Response.php b/src/Response.php index 9c1ece36..29da27de 100644 --- a/src/Response.php +++ b/src/Response.php @@ -296,13 +296,31 @@ public function resolveMergeProps(Request $request): array }) ->filter(function ($prop) { return $prop->shouldMerge(); + }); + + $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->shouldDeepMerge(); }) ->filter(function ($prop, $key) use ($resetProps) { return ! $resetProps->contains($key); }) ->keys(); - return $mergeProps->isNotEmpty() ? ['mergeProps' => $mergeProps->toArray()] : []; + $props = []; + if ($mergeProps->isNotEmpty()) $props['mergeProps'] = $mergeProps->toArray(); + if ($deepMergeProps->isNotEmpty()) $props['deepMergeProps'] = $deepMergeProps->toArray(); + + return $props; } 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 */ 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');