Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/Services/WpPostService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_DATABASE" value="testing"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/>
Expand Down
6 changes: 5 additions & 1 deletion resources/js/components/PostCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ const getFormattedDate = (post: Post) => {
decoding="async"
/>
</Link>
<Link :href="'/category/' + post.categories[0].slug" class="absolute bottom-0 left-[5px] bg-[#e6c619] px-2 py-1 text-black">
<Link
v-if="post.categories?.length"
:href="'/category/' + post.categories[0].slug"
class="absolute bottom-0 left-[5px] bg-[#e6c619] px-2 py-1 text-black"
>
{{ post.categories[0].name }}
</Link>
</div>
Expand Down
1 change: 1 addition & 0 deletions resources/js/components/PostContentCover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const { coverData } = defineProps<{
<div class="relative mx-auto w-full border-b-3 border-black xl:w-[970px]">
<img class="w-full xl:h-[530px]" :src="coverData.thumbnail" :alt="'Capa do post - ' + coverData.title" />
<Link
v-if="coverData.categories?.length"
:href="'/category/' + coverData.categories[0].slug"
class="absolute bottom-0 left-[20px] bg-[#e6c619] px-2 py-1 font-bold text-black"
>
Expand Down
103 changes: 103 additions & 0 deletions tests/Feature/RelatedPostsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

use App\Models\WpPost;
use App\Services\WpPostService;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

beforeEach(function () {
// Minimal slice of the WordPress schema getRelatedPosts() touches.
// A reusable fixture set comes with the test infrastructure PR.
Schema::create('wp_posts', function (Blueprint $table) {
$table->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);
});