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/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<{
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); +});