From cbd49c095c7fd79708a715625f271fcb491f20e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20=C5=A0imkevi=C4=8Dius?= Date: Thu, 30 Oct 2025 14:28:25 +0200 Subject: [PATCH 1/5] add tests for type, included merge and id in attributes --- .../Resources/JsonApi/JsonApiResourceTest.php | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php index 9f3fc8a94d6f..c78c7fe37e5d 100644 --- a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -3,8 +3,17 @@ namespace Illuminate\Tests\Http\Resources\JsonApi; use BadMethodCallException; +use Illuminate\Container\Container; +use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract; +use Illuminate\Contracts\View\Factory as ViewFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\JsonApi\JsonApiResource; +use Illuminate\Routing\Redirector; +use Illuminate\Routing\ResponseFactory; +use Mockery as m; use PHPUnit\Framework\TestCase; class JsonApiResourceTest extends TestCase @@ -13,6 +22,8 @@ protected function tearDown(): void { JsonResource::flushState(); JsonApiResource::flushState(); + Container::getInstance()->flush(); + Relation::morphMap([], false); } public function testResponseWrapperIsHardCodedToData() @@ -37,4 +48,62 @@ public function testUnableToUnsetWrapper() JsonApiResource::withoutWrapping(); } + + public function testResourceTypeIsPickedFromMorph() + { + Relation::morphMap([JsonApiModel::class => 'json-api-model']); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); + + $responseData = $this->modelToJsonApiData($model)['data']; + + $this->assertArrayHasKey('type', $responseData); + $this->assertSame('json-api-model', $responseData['type']); + } + + public function testIncludedResourceDoesNotContainPrimaryKey() + { + Relation::morphMap([JsonApiModel::class => 'json-api-model']); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); + $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); + $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com'])); + + $responseData = $this->modelToJsonApiData($model); + $this->assertArrayNotHasKey('id', $responseData['included'][0]['attributes']); + } + + public function testIncludedMatchingResourceAttributesAreMerged() + { + Relation::morphMap([JsonApiModel::class => 'json-api-model']); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); + $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); + $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com'])); + + $responseData = $this->modelToJsonApiData($model); + + $this->assertEquals([ + 'id' => '2', + 'type' => 'json-api-model', + 'attributes' => [ + 'name' => 'Bob', + 'email' => 'bob@example.com', + ], + ], $responseData['included'][0]); + } + + protected function modelToJsonApiData(Model $model): array + { + Container::getInstance()->instance(ResponseFactoryContract::class, new ResponseFactory( + m::mock(ViewFactory::class), + m::mock(Redirector::class) + )); + + return (new JsonApiResource($model)) + ->toResponse(new Request) + ->getData(true); + } +} + +class JsonApiModel extends Model +{ + protected $guarded = []; } From 8c927db58ce87776fcb89ac249c55e1dc26c4e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20=C5=A0imkevi=C4=8Dius?= Date: Thu, 30 Oct 2025 14:43:44 +0200 Subject: [PATCH 2/5] fix incorrect comparison --- tests/Http/Resources/JsonApi/JsonApiResourceTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php index c78c7fe37e5d..1e66b085bd26 100644 --- a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -84,8 +84,8 @@ public function testIncludedMatchingResourceAttributesAreMerged() 'id' => '2', 'type' => 'json-api-model', 'attributes' => [ - 'name' => 'Bob', - 'email' => 'bob@example.com', + 'name' => 'Manager', + 'email' => 'deputy@example.com', ], ], $responseData['included'][0]); } From f43f79a66003c981ea3cb4f6ef949abbeed3b3b3 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 31 Oct 2025 14:10:24 +0800 Subject: [PATCH 3/5] wip Signed-off-by: Mior Muhammad Zaki --- .../ResolvesJsonApiSpecifications.php | 10 +++++---- .../Resources/JsonApi/JsonApiResource.php | 4 ++-- .../Resources/JsonApi/JsonApiResourceTest.php | 21 +++++++++++-------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiSpecifications.php b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiSpecifications.php index 364b6d0a8555..329ad35bc73b 100644 --- a/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiSpecifications.php +++ b/src/Illuminate/Http/Resources/JsonApi/Concerns/ResolvesJsonApiSpecifications.php @@ -84,8 +84,8 @@ protected function resolveResourceRelationshipsIdentifiers(Request $request): ar public function resolveResourceData(Request $request): array { return [ - 'id' => $this->resolveResourceIdentifier($request), - 'type' => $this->resolveResourceType($request), + 'id' => $this->toId($request), + 'type' => $this->toType($request), ...(new Collection([ 'attributes' => $this->resolveResourceAttributes($request), 'relationships' => $this->resolveResourceRelationshipsIdentifiers($request), @@ -241,8 +241,10 @@ protected static function getResourceTypeFromEloquent(Model $model): string $modelClassName = $model::class; $morphMap = Relation::getMorphAlias($modelClassName); - $modelBaseName = $morphMap !== $modelClassName ? $morphMap : class_basename($modelClassName); + if ($morphMap !== $modelClassName) { + return Str::of($morphMap)->pluralStudly(); + } - return Str::of($modelBaseName)->snake()->pluralStudly(); + return Str::of($modelClassName)->classBasename()->snake()->pluralStudly(); } } diff --git a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php index cba19b2ce9f0..bffe1f3ec63f 100644 --- a/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php +++ b/src/Illuminate/Http/Resources/JsonApi/JsonApiResource.php @@ -109,7 +109,7 @@ public function meta(Request $request) * @param \Illuminate\Http\Request $request * @return string */ - public function id(Request $request) + public function toId(Request $request) { return $this->resolveResourceIdentifier($request); } @@ -120,7 +120,7 @@ public function id(Request $request) * @param \Illuminate\Http\Request $request * @return string */ - public function type(Request $request) + public function toType(Request $request) { return $this->resolveResourceType($request); } diff --git a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php index 1e66b085bd26..bc88c7245aac 100644 --- a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -51,38 +51,41 @@ public function testUnableToUnsetWrapper() public function testResourceTypeIsPickedFromMorph() { - Relation::morphMap([JsonApiModel::class => 'json-api-model']); + Relation::morphMap(['json-api-model' => JsonApiModel::class]); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); - $responseData = $this->modelToJsonApiData($model)['data']; + $responseData = $this->fakeJsonApiResponseForModel($model)['data']; $this->assertArrayHasKey('type', $responseData); - $this->assertSame('json-api-model', $responseData['type']); + $this->assertSame('json-api-models', $responseData['type']); } public function testIncludedResourceDoesNotContainPrimaryKey() { - Relation::morphMap([JsonApiModel::class => 'json-api-model']); + Relation::morphMap(['json-api-model' => JsonApiModel::class]); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com'])); - $responseData = $this->modelToJsonApiData($model); + $responseData = $this->fakeJsonApiResponseForModel($model); $this->assertArrayNotHasKey('id', $responseData['included'][0]['attributes']); } public function testIncludedMatchingResourceAttributesAreMerged() { - Relation::morphMap([JsonApiModel::class => 'json-api-model']); + Relation::morphMap(['json-api-model' => JsonApiModel::class]); + $model = new JsonApiModel(['id' => 1, 'name' => 'User']); $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com'])); - $responseData = $this->modelToJsonApiData($model); + $responseData = $this->fakeJsonApiResponseForModel($model); $this->assertEquals([ 'id' => '2', - 'type' => 'json-api-model', + 'type' => 'json-api-models', 'attributes' => [ 'name' => 'Manager', 'email' => 'deputy@example.com', @@ -90,7 +93,7 @@ public function testIncludedMatchingResourceAttributesAreMerged() ], $responseData['included'][0]); } - protected function modelToJsonApiData(Model $model): array + protected function fakeJsonApiResponseForModel(Model $model): array { Container::getInstance()->instance(ResponseFactoryContract::class, new ResponseFactory( m::mock(ViewFactory::class), From 32fdd037be49c248f06b422e8343cd7e3b70b411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20=C5=A0imkevi=C4=8Dius?= Date: Fri, 31 Oct 2025 09:34:49 +0200 Subject: [PATCH 4/5] change container use to be more inline with others, close mock --- .../Resources/JsonApi/JsonApiResourceTest.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php index bc88c7245aac..a05a9a05dab3 100644 --- a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -18,12 +18,23 @@ class JsonApiResourceTest extends TestCase { + protected function setUp(): void + { + $container = Container::setInstance(new Container); + $container->instance(ResponseFactoryContract::class, new ResponseFactory( + m::mock(ViewFactory::class), + m::mock(Redirector::class) + )); + } + protected function tearDown(): void { JsonResource::flushState(); JsonApiResource::flushState(); - Container::getInstance()->flush(); Relation::morphMap([], false); + + Container::setInstance(null); + m::close(); } public function testResponseWrapperIsHardCodedToData() @@ -95,11 +106,6 @@ public function testIncludedMatchingResourceAttributesAreMerged() protected function fakeJsonApiResponseForModel(Model $model): array { - Container::getInstance()->instance(ResponseFactoryContract::class, new ResponseFactory( - m::mock(ViewFactory::class), - m::mock(Redirector::class) - )); - return (new JsonApiResource($model)) ->toResponse(new Request) ->getData(true); From 89f5e2d98c4686bd0124f1a29f1ad76ac5c58e14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donatas=20=C5=A0imkevi=C4=8Dius?= Date: Fri, 31 Oct 2025 09:49:31 +0200 Subject: [PATCH 5/5] register morph type in setUp --- tests/Http/Resources/JsonApi/JsonApiResourceTest.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php index a05a9a05dab3..6beb29009508 100644 --- a/tests/Http/Resources/JsonApi/JsonApiResourceTest.php +++ b/tests/Http/Resources/JsonApi/JsonApiResourceTest.php @@ -20,6 +20,8 @@ class JsonApiResourceTest extends TestCase { protected function setUp(): void { + Relation::morphMap(['json-api-model' => JsonApiModel::class]); + $container = Container::setInstance(new Container); $container->instance(ResponseFactoryContract::class, new ResponseFactory( m::mock(ViewFactory::class), @@ -62,8 +64,6 @@ public function testUnableToUnsetWrapper() public function testResourceTypeIsPickedFromMorph() { - Relation::morphMap(['json-api-model' => JsonApiModel::class]); - $model = new JsonApiModel(['id' => 1, 'name' => 'User']); $responseData = $this->fakeJsonApiResponseForModel($model)['data']; @@ -74,8 +74,6 @@ public function testResourceTypeIsPickedFromMorph() public function testIncludedResourceDoesNotContainPrimaryKey() { - Relation::morphMap(['json-api-model' => JsonApiModel::class]); - $model = new JsonApiModel(['id' => 1, 'name' => 'User']); $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com'])); @@ -86,8 +84,6 @@ public function testIncludedResourceDoesNotContainPrimaryKey() public function testIncludedMatchingResourceAttributesAreMerged() { - Relation::morphMap(['json-api-model' => JsonApiModel::class]); - $model = new JsonApiModel(['id' => 1, 'name' => 'User']); $model->setRelation('manager', new JsonApiModel(['id' => 2, 'name' => 'Manager'])); $model->setRelation('deputy', new JsonApiModel(['id' => 2, 'email' => 'deputy@example.com']));