From 82d89632ebb392855e3a56dea920356edbaa96db Mon Sep 17 00:00:00 2001 From: Guajiro <276488307+Guajir0-code@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:53:57 -0300 Subject: [PATCH 1/2] fix(posts): return empty Eloquent collection for posts without category getRelatedPosts() declares Eloquent\Collection but returned collect(), which builds a Support\Collection. Support\Collection is not a subclass of Eloquent\Collection, so any post whose category lookup comes back empty raises a TypeError and the post page answers 500. Return an empty Eloquent\Collection instead. Covered by a regression test that reproduces the TypeError against the previous code. Co-Authored-By: Claude Opus 5 --- app/Services/WpPostService.php | 4 +- phpunit.xml | 3 +- tests/Feature/RelatedPostsTest.php | 103 +++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/RelatedPostsTest.php diff --git a/app/Services/WpPostService.php b/app/Services/WpPostService.php index 6e463f1..85ec801 100644 --- a/app/Services/WpPostService.php +++ b/app/Services/WpPostService.php @@ -120,7 +120,9 @@ public static function getRelatedPosts(WpPost $post, int $limit = 3): Collection ?->slug; if (! $categorySlug) { - return collect(); + // collect() builds a Support\Collection, which is not an + // Eloquent\Collection and fails this method's return type. + return new Collection; } return WpPost::query() diff --git a/phpunit.xml b/phpunit.xml index c09b5bc..61c031c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,8 @@ - + + diff --git a/tests/Feature/RelatedPostsTest.php b/tests/Feature/RelatedPostsTest.php new file mode 100644 index 0000000..8c48d27 --- /dev/null +++ b/tests/Feature/RelatedPostsTest.php @@ -0,0 +1,103 @@ +id('ID'); + $table->unsignedBigInteger('post_author')->default(0); + $table->string('post_title'); + $table->text('post_excerpt')->nullable(); + $table->text('post_content')->nullable(); + $table->string('post_name'); + $table->string('post_type')->default('post'); + $table->string('post_status')->default('publish'); + $table->dateTime('post_date'); + $table->string('guid')->default(''); + }); + + Schema::create('wp_postmeta', function (Blueprint $table) { + $table->id('meta_id'); + $table->unsignedBigInteger('post_id'); + $table->string('meta_key'); + $table->text('meta_value'); + }); + + Schema::create('wp_terms', function (Blueprint $table) { + $table->id('term_id'); + $table->string('name'); + $table->string('slug'); + }); + + Schema::create('wp_term_taxonomy', function (Blueprint $table) { + $table->id('term_taxonomy_id'); + $table->unsignedBigInteger('term_id'); + $table->string('taxonomy'); + }); + + Schema::create('wp_term_relationships', function (Blueprint $table) { + $table->unsignedBigInteger('object_id'); + $table->unsignedBigInteger('term_taxonomy_id'); + }); + + cache()->flush(); +}); + +function insertPost(int $id, string $slug): WpPost +{ + DB::table('wp_posts')->insert([ + 'ID' => $id, + 'post_author' => 1, + 'post_title' => "Post {$id}", + 'post_name' => $slug, + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_date' => '2026-07-01 10:00:00', + ]); + + return WpPost::query()->findOrFail($id); +} + +function attachCategory(int $postId, string $slug): void +{ + $termId = DB::table('wp_terms')->insertGetId(['name' => ucfirst($slug), 'slug' => $slug]); + $ttId = DB::table('wp_term_taxonomy')->insertGetId(['term_id' => $termId, 'taxonomy' => 'category']); + DB::table('wp_term_relationships')->insert(['object_id' => $postId, 'term_taxonomy_id' => $ttId]); +} + +it('returns an Eloquent collection when the post has no category', function () { + $post = insertPost(1, 'sem-categoria'); + + $related = WpPostService::getRelatedPosts($post); + + // The regression: collect() returns Support\Collection and blows up the + // declared Eloquent\Collection return type with a TypeError. + expect($related)->toBeInstanceOf(Collection::class) + ->and($related)->toBeEmpty(); +}); + +it('still returns posts from the same category', function () { + $post = insertPost(1, 'principal'); + attachCategory(1, 'games'); + + $sibling = insertPost(2, 'irmao'); + DB::table('wp_term_relationships')->insert([ + 'object_id' => 2, + 'term_taxonomy_id' => DB::table('wp_term_taxonomy')->value('term_taxonomy_id'), + ]); + + $related = WpPostService::getRelatedPosts($post); + + expect($related)->toBeInstanceOf(Collection::class) + ->and($related->pluck('ID')->all())->toBe([2]) + ->and($related->pluck('ID')->all())->not->toContain(1); + + unset($sibling); +}); From c4c90b85ec3220156505625db8955d6c9ee37625 Mon Sep 17 00:00:00 2001 From: Guajiro <276488307+Guajir0-code@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:55:24 -0300 Subject: [PATCH 2/2] fix(posts): guard category badge against posts without category Same data condition as the server-side fix: PostCard and PostContentCover indexed categories[0] unconditionally, so a post with an empty categories array threw at render time on the client. Co-Authored-By: Claude Opus 5 --- resources/js/components/PostCard.vue | 6 +++++- resources/js/components/PostContentCover.vue | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/js/components/PostCard.vue b/resources/js/components/PostCard.vue index 9a10475..c467495 100644 --- a/resources/js/components/PostCard.vue +++ b/resources/js/components/PostCard.vue @@ -39,7 +39,11 @@ const getFormattedDate = (post: Post) => { decoding="async" /> - + {{ post.categories[0].name }} diff --git a/resources/js/components/PostContentCover.vue b/resources/js/components/PostContentCover.vue index 47c0ba9..e882206 100644 --- a/resources/js/components/PostContentCover.vue +++ b/resources/js/components/PostContentCover.vue @@ -28,6 +28,7 @@ const { coverData } = defineProps<{