690 lines
25 KiB
PHP
690 lines
25 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\Artwork;
|
|
use App\Models\User;
|
|
use App\Models\World;
|
|
use App\Models\WorldRewardGrant;
|
|
use App\Models\WorldRelation;
|
|
use App\Models\WorldSubmission;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Inertia\Testing\AssertableInertia;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
function worldSubmissionCategoryId(): int
|
|
{
|
|
$contentTypeId = DB::table('content_types')->insertGetId([
|
|
'name' => 'World Submission Type',
|
|
'slug' => 'world-submission-type-' . Str::lower(Str::random(6)),
|
|
'description' => null,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
return DB::table('categories')->insertGetId([
|
|
'content_type_id' => $contentTypeId,
|
|
'parent_id' => null,
|
|
'name' => 'World Submission Category',
|
|
'slug' => 'world-submission-category-' . Str::lower(Str::random(6)),
|
|
'description' => null,
|
|
'image' => null,
|
|
'is_active' => true,
|
|
'sort_order' => 0,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
}
|
|
|
|
function acceptingWorld(?User $creator = null, array $attributes = []): World
|
|
{
|
|
$creator ??= User::factory()->create([
|
|
'role' => 'moderator',
|
|
'username' => 'worldmoderator-' . Str::lower(Str::random(6)),
|
|
'name' => 'World Moderator',
|
|
]);
|
|
|
|
return World::factory()->create(array_merge([
|
|
'created_by_user_id' => $creator->id,
|
|
'status' => World::STATUS_PUBLISHED,
|
|
'published_at' => now()->subDay(),
|
|
'accepts_submissions' => true,
|
|
'participation_mode' => World::PARTICIPATION_MODE_MANUAL_APPROVAL,
|
|
'submission_note_enabled' => true,
|
|
'community_section_enabled' => true,
|
|
'allow_readd_after_removal' => true,
|
|
'submission_starts_at' => now()->subDay(),
|
|
'submission_ends_at' => now()->addDays(7),
|
|
], $attributes));
|
|
}
|
|
|
|
it('creates pending world submissions when publishing an artwork draft', function (): void {
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld();
|
|
$categoryId = worldSubmissionCategoryId();
|
|
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Draft Upload',
|
|
'slug' => 'draft-upload',
|
|
'is_public' => false,
|
|
'visibility' => Artwork::VISIBILITY_PRIVATE,
|
|
'is_approved' => false,
|
|
'published_at' => null,
|
|
'artwork_status' => 'draft',
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->postJson("/api/uploads/{$artwork->id}/publish", [
|
|
'title' => 'World Upload',
|
|
'category' => $categoryId,
|
|
'tags' => ['world', 'submission'],
|
|
'world_submissions' => [
|
|
['world_id' => $world->id, 'note' => 'Fits the active theme.', 'source_surface' => 'upload_flow'],
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('status', 'published');
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_PENDING,
|
|
'is_featured' => false,
|
|
'note' => 'Fits the active theme.',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('world_analytics_events', [
|
|
'world_id' => $world->id,
|
|
'event_type' => 'world_submission_created',
|
|
'source_surface' => 'upload_flow',
|
|
'entity_type' => 'artwork',
|
|
'entity_id' => $artwork->id,
|
|
'entity_title' => 'World Upload',
|
|
]);
|
|
});
|
|
|
|
it('creates live world participation immediately for auto-add worlds', function (): void {
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld(attributes: [
|
|
'participation_mode' => World::PARTICIPATION_MODE_AUTO_ADD,
|
|
]);
|
|
$categoryId = worldSubmissionCategoryId();
|
|
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Auto Add Upload',
|
|
'slug' => 'auto-add-upload',
|
|
'is_public' => false,
|
|
'visibility' => Artwork::VISIBILITY_PRIVATE,
|
|
'is_approved' => false,
|
|
'published_at' => null,
|
|
'artwork_status' => 'draft',
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->postJson("/api/uploads/{$artwork->id}/publish", [
|
|
'title' => 'Auto Add Upload',
|
|
'category' => $categoryId,
|
|
'tags' => ['world', 'auto-add'],
|
|
'world_submissions' => [
|
|
['world_id' => $world->id, 'note' => 'Ship it.'],
|
|
],
|
|
])
|
|
->assertOk();
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_LIVE,
|
|
'is_featured' => false,
|
|
]);
|
|
|
|
$this->assertDatabaseHas('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'reward_type' => 'participant',
|
|
'grant_source' => 'automatic',
|
|
]);
|
|
});
|
|
|
|
it('syncs world submissions from the studio artwork editor update flow', function (): void {
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld();
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Studio Draft',
|
|
'slug' => 'studio-draft',
|
|
'is_public' => false,
|
|
'visibility' => Artwork::VISIBILITY_PRIVATE,
|
|
'published_at' => null,
|
|
'artwork_status' => 'draft',
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->putJson(route('api.studio.artworks.update', ['id' => $artwork->id]), [
|
|
'world_submissions' => [
|
|
['world_id' => $world->id, 'note' => 'Added after upload.'],
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('world_submission_options.0.id', $world->id)
|
|
->assertJsonPath('world_submission_options.0.selected', true)
|
|
->assertJsonPath('world_submission_options.0.status', WorldSubmission::STATUS_PENDING);
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'status' => WorldSubmission::STATUS_PENDING,
|
|
'note' => 'Added after upload.',
|
|
]);
|
|
});
|
|
|
|
it('allows removed submissions to be re-added when the world permits it', function (): void {
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld();
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Re Add Artwork',
|
|
'slug' => 're-add-artwork',
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
'published_at' => now()->subDay(),
|
|
'artwork_status' => 'published',
|
|
]);
|
|
|
|
WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_REMOVED,
|
|
'moderation_reason' => 'Needs tighter fit.',
|
|
'removed_at' => now()->subHour(),
|
|
'reviewed_at' => now()->subHour(),
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->putJson(route('api.studio.artworks.update', ['id' => $artwork->id]), [
|
|
'world_submissions' => [
|
|
['world_id' => $world->id, 'note' => 'Updated to fit the brief.'],
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('world_submission_options.0.can_resubmit', false)
|
|
->assertJsonPath('world_submission_options.0.status', WorldSubmission::STATUS_PENDING);
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'status' => WorldSubmission::STATUS_PENDING,
|
|
'note' => 'Updated to fit the brief.',
|
|
'moderation_reason' => null,
|
|
]);
|
|
});
|
|
|
|
it('keeps existing live submissions live when the artwork is updated in a manual approval world', function (): void {
|
|
$creator = User::factory()->create();
|
|
$moderator = User::factory()->create([
|
|
'role' => 'moderator',
|
|
'username' => 'livereviewmod-' . Str::lower(Str::random(6)),
|
|
'name' => 'Live Review Moderator',
|
|
]);
|
|
$world = acceptingWorld($moderator);
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Live World Artwork',
|
|
'slug' => 'live-world-artwork',
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
'published_at' => now()->subDay(),
|
|
'artwork_status' => 'published',
|
|
]);
|
|
|
|
$submission = WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_LIVE,
|
|
'is_featured' => true,
|
|
'note' => 'Original approved note.',
|
|
'reviewed_by_user_id' => $moderator->id,
|
|
'reviewed_at' => now()->subHour(),
|
|
'featured_at' => now()->subHour(),
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->putJson(route('api.studio.artworks.update', ['id' => $artwork->id]), [
|
|
'world_submissions' => [
|
|
['world_id' => $world->id, 'note' => 'Updated creator note after going live.'],
|
|
],
|
|
])
|
|
->assertOk()
|
|
->assertJsonPath('world_submission_options.0.status', WorldSubmission::STATUS_LIVE)
|
|
->assertJsonPath('world_submission_options.0.selected', true)
|
|
->assertJsonPath('world_submission_options.0.note', 'Updated creator note after going live.');
|
|
|
|
$submission->refresh();
|
|
|
|
expect($submission->status)->toBe(WorldSubmission::STATUS_LIVE)
|
|
->and($submission->note)->toBe('Updated creator note after going live.')
|
|
->and($submission->is_featured)->toBeTrue()
|
|
->and((int) $submission->reviewed_by_user_id)->toBe($moderator->id)
|
|
->and($submission->reviewed_at)->not->toBeNull()
|
|
->and($submission->removed_at)->toBeNull()
|
|
->and($submission->blocked_at)->toBeNull();
|
|
});
|
|
|
|
it('does not expose closed worlds in creator submission options', function (): void {
|
|
$creator = User::factory()->create();
|
|
$openWorld = acceptingWorld(attributes: ['title' => 'Open World']);
|
|
$closedWorld = acceptingWorld(attributes: [
|
|
'title' => 'Closed World',
|
|
'accepts_submissions' => false,
|
|
'participation_mode' => World::PARTICIPATION_MODE_CLOSED,
|
|
]);
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Selector Artwork',
|
|
'slug' => 'selector-artwork',
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
'published_at' => now()->subDay(),
|
|
'artwork_status' => 'published',
|
|
]);
|
|
|
|
$this->actingAs($creator)
|
|
->putJson(route('api.studio.artworks.update', ['id' => $artwork->id]), [])
|
|
->assertOk()
|
|
->assertJsonCount(1, 'world_submission_options')
|
|
->assertJsonPath('world_submission_options.0.id', $openWorld->id);
|
|
|
|
expect($closedWorld->id)->not->toBe($openWorld->id);
|
|
});
|
|
|
|
it('shows and reviews world participation in the studio world editor', function (): void {
|
|
$moderator = User::factory()->create([
|
|
'role' => 'moderator',
|
|
'username' => 'reviewmod',
|
|
'name' => 'Review Moderator',
|
|
]);
|
|
$creator = User::factory()->create([
|
|
'username' => 'queueartist',
|
|
'name' => 'Queue Artist',
|
|
]);
|
|
$world = acceptingWorld($moderator);
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Queue Artwork',
|
|
'slug' => 'queue-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
|
|
$submission = WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_PENDING,
|
|
'note' => 'Please review this for the world.',
|
|
]);
|
|
|
|
$this->actingAs($moderator)
|
|
->get(route('studio.worlds.edit', ['world' => $world->id]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('Studio/StudioWorldEditor')
|
|
->where('world.participation_mode', World::PARTICIPATION_MODE_MANUAL_APPROVAL)
|
|
->where('world.submission_review_queue.counts.pending', 1)
|
|
->where('world.submission_review_queue.items.0.artwork.title', 'Queue Artwork')
|
|
->where('world.submission_review_queue.items.0.can_grant_manual_rewards', false));
|
|
|
|
$this->actingAs($moderator)
|
|
->post(route('studio.worlds.submissions.approve', ['world' => $world->id, 'submission' => $submission->id]))
|
|
->assertRedirect();
|
|
|
|
$this->actingAs($moderator)
|
|
->post(route('studio.worlds.submissions.feature', ['world' => $world->id, 'submission' => $submission->id]))
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'id' => $submission->id,
|
|
'status' => WorldSubmission::STATUS_LIVE,
|
|
'is_featured' => true,
|
|
'reviewed_by_user_id' => $moderator->id,
|
|
]);
|
|
|
|
$this->assertDatabaseHas('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'participant',
|
|
'grant_source' => 'automatic',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'featured',
|
|
'grant_source' => 'automatic',
|
|
]);
|
|
|
|
$this->actingAs($moderator)
|
|
->post(route('studio.worlds.submissions.block', ['world' => $world->id, 'submission' => $submission->id]), [
|
|
'review_note' => 'Off brief for this world.',
|
|
])
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseHas('world_submissions', [
|
|
'id' => $submission->id,
|
|
'status' => WorldSubmission::STATUS_BLOCKED,
|
|
'moderation_reason' => 'Off brief for this world.',
|
|
'is_featured' => false,
|
|
]);
|
|
|
|
$this->assertDatabaseMissing('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'featured',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'participant',
|
|
'grant_source' => 'automatic',
|
|
]);
|
|
});
|
|
|
|
it('allows moderators to grant and revoke manual world rewards', function (): void {
|
|
$moderator = User::factory()->create([
|
|
'role' => 'moderator',
|
|
'username' => 'worldrewardmod-' . Str::lower(Str::random(6)),
|
|
]);
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld($moderator);
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Reward Artwork',
|
|
'slug' => 'reward-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
|
|
$submission = WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_LIVE,
|
|
'reviewed_by_user_id' => $moderator->id,
|
|
'reviewed_at' => now()->subHour(),
|
|
]);
|
|
|
|
$this->actingAs($moderator)
|
|
->post(route('studio.worlds.submissions.rewards.grant', ['world' => $world->id, 'submission' => $submission->id, 'rewardType' => 'winner']), [
|
|
'review_note' => 'Editorial pick for the final showcase.',
|
|
])
|
|
->assertRedirect();
|
|
|
|
$grant = WorldRewardGrant::query()->where('user_id', $creator->id)->where('world_id', $world->id)->where('reward_type', 'winner')->first();
|
|
|
|
expect($grant)->not->toBeNull();
|
|
|
|
$this->assertDatabaseHas('notifications', [
|
|
'type' => 'world_reward_granted',
|
|
]);
|
|
|
|
$this->assertDatabaseHas('user_activities', [
|
|
'user_id' => $creator->id,
|
|
'type' => 'world_reward',
|
|
'entity_type' => 'world_reward',
|
|
'entity_id' => $grant->id,
|
|
]);
|
|
|
|
$this->actingAs($moderator)
|
|
->post(route('studio.worlds.submissions.rewards.revoke', ['world' => $world->id, 'submission' => $submission->id, 'rewardType' => 'winner']))
|
|
->assertRedirect();
|
|
|
|
$this->assertDatabaseMissing('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'winner',
|
|
]);
|
|
});
|
|
|
|
it('rejects manual world rewards for non-live submissions', function (): void {
|
|
$moderator = User::factory()->create([
|
|
'role' => 'moderator',
|
|
'username' => 'worldrewardpending-' . Str::lower(Str::random(6)),
|
|
]);
|
|
$creator = User::factory()->create();
|
|
$world = acceptingWorld($moderator);
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Pending Reward Artwork',
|
|
'slug' => 'pending-reward-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
|
|
$submission = WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_PENDING,
|
|
]);
|
|
|
|
$this->actingAs($moderator)
|
|
->from(route('studio.worlds.edit', ['world' => $world->id]))
|
|
->post(route('studio.worlds.submissions.rewards.grant', ['world' => $world->id, 'submission' => $submission->id, 'rewardType' => 'winner']), [
|
|
'review_note' => 'Tried to award too early.',
|
|
])
|
|
->assertRedirect(route('studio.worlds.edit', ['world' => $world->id]))
|
|
->assertSessionHasErrors(['submission']);
|
|
|
|
$this->assertDatabaseMissing('world_reward_grants', [
|
|
'user_id' => $creator->id,
|
|
'world_id' => $world->id,
|
|
'reward_type' => 'winner',
|
|
]);
|
|
});
|
|
|
|
it('renders only live community submissions on public world pages and hides pending or blocked ones', function (): void {
|
|
$world = acceptingWorld(attributes: [
|
|
'title' => 'Public World',
|
|
'slug' => 'public-world',
|
|
]);
|
|
$creator = User::factory()->create();
|
|
|
|
$featuredArtwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Featured Community Artwork',
|
|
'slug' => 'featured-community-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
$approvedArtwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Approved Community Artwork',
|
|
'slug' => 'approved-community-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
$pendingArtwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Pending Community Artwork',
|
|
'slug' => 'pending-community-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
$matureArtwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Mature Community Artwork',
|
|
'slug' => 'mature-community-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
'is_mature' => true,
|
|
]);
|
|
|
|
foreach ([
|
|
[$featuredArtwork, WorldSubmission::STATUS_LIVE, true],
|
|
[$approvedArtwork, WorldSubmission::STATUS_LIVE, false],
|
|
[$pendingArtwork, WorldSubmission::STATUS_PENDING, false],
|
|
[$matureArtwork, WorldSubmission::STATUS_LIVE, false],
|
|
] as [$artwork, $status, $isFeatured]) {
|
|
WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => $status,
|
|
'is_featured' => $isFeatured,
|
|
'reviewed_at' => $status === WorldSubmission::STATUS_PENDING ? null : Carbon::now(),
|
|
'featured_at' => $isFeatured ? Carbon::now() : null,
|
|
]);
|
|
}
|
|
|
|
WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => Artwork::factory()->for($creator)->create([
|
|
'title' => 'Blocked Community Artwork',
|
|
'slug' => 'blocked-community-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
])->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_BLOCKED,
|
|
'reviewed_at' => Carbon::now(),
|
|
'blocked_at' => Carbon::now(),
|
|
]);
|
|
|
|
$this->get(route('worlds.show', ['world' => $world->slug]))
|
|
->assertOk()
|
|
->assertInertia(fn (AssertableInertia $page) => $page
|
|
->component('World/WorldShow')
|
|
->where('communitySubmissions.items.0.title', 'Featured Community Artwork')
|
|
->where('communitySubmissions.items.0.status', WorldSubmission::STATUS_LIVE)
|
|
->where('communitySubmissions.items.0.status_label', 'Featured')
|
|
->has('communitySubmissions.items', 2)
|
|
->where('communitySubmissions.items.1.title', 'Approved Community Artwork'));
|
|
});
|
|
|
|
it('exposes world participation badges on the artwork page for curated and live world placements', function (): void {
|
|
$world = acceptingWorld(attributes: [
|
|
'title' => 'Retro Month',
|
|
'slug' => 'retro-month',
|
|
]);
|
|
$creator = User::factory()->create();
|
|
$artwork = Artwork::factory()->for($creator)->create([
|
|
'title' => 'Badge Artwork',
|
|
'slug' => 'badge-artwork',
|
|
'artwork_status' => 'published',
|
|
'published_at' => now()->subDay(),
|
|
'is_public' => true,
|
|
'visibility' => Artwork::VISIBILITY_PUBLIC,
|
|
]);
|
|
|
|
WorldRelation::query()->create([
|
|
'world_id' => $world->id,
|
|
'related_type' => WorldRelation::TYPE_ARTWORK,
|
|
'related_id' => $artwork->id,
|
|
'section_key' => 'featured_artworks',
|
|
'context_label' => 'Curated spotlight',
|
|
'sort_order' => 1,
|
|
'is_featured' => true,
|
|
]);
|
|
|
|
WorldSubmission::query()->create([
|
|
'world_id' => $world->id,
|
|
'artwork_id' => $artwork->id,
|
|
'submitted_by_user_id' => $creator->id,
|
|
'status' => WorldSubmission::STATUS_LIVE,
|
|
'is_featured' => true,
|
|
'reviewed_at' => Carbon::now(),
|
|
'featured_at' => Carbon::now(),
|
|
]);
|
|
|
|
$this->get(route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug]))
|
|
->assertOk()
|
|
->assertViewHas('artworkData', function (array $artworkData): bool {
|
|
$items = collect($artworkData['world_participation'] ?? []);
|
|
|
|
return $items->count() === 1
|
|
&& $items->contains(fn (array $item): bool => ($item['badge_label'] ?? null) === 'Featured in Retro Month');
|
|
});
|
|
});
|
|
|
|
it('prioritizes active campaign worlds in creator submission options', function (): void {
|
|
$creator = User::factory()->create();
|
|
|
|
$liveCampaign = acceptingWorld(attributes: [
|
|
'title' => 'Spring Vibes',
|
|
'slug' => 'spring-vibes',
|
|
'is_active_campaign' => true,
|
|
'is_homepage_featured' => true,
|
|
'campaign_priority' => 500,
|
|
'campaign_label' => 'Live now',
|
|
'teaser_title' => 'Now live: Spring Vibes',
|
|
'teaser_summary' => 'Fresh spring palettes and active submissions.',
|
|
'promotion_starts_at' => now()->subHour(),
|
|
'promotion_ends_at' => now()->addDays(5),
|
|
]);
|
|
|
|
$regularWorld = acceptingWorld(attributes: [
|
|
'title' => 'Open Worlds Lab',
|
|
'slug' => 'open-worlds-lab',
|
|
'is_active_campaign' => false,
|
|
'is_homepage_featured' => false,
|
|
'campaign_priority' => null,
|
|
]);
|
|
|
|
$options = app(\App\Services\Worlds\WorldSubmissionService::class)->eligibleWorldOptions($creator);
|
|
|
|
expect($options)->toHaveCount(2)
|
|
->and($options[0]['id'])->toBe($liveCampaign->id)
|
|
->and($options[0]['teaser_title'])->toBe('Now live: Spring Vibes')
|
|
->and(collect($options[0]['status_badges'])->pluck('label')->all())->toContain('Live now', 'Featured')
|
|
->and($options[1]['id'])->toBe($regularWorld->id);
|
|
});
|
|
|
|
it('only exposes the canonical current edition for recurring submission options', function (): void {
|
|
$creator = User::factory()->create();
|
|
|
|
acceptingWorld(attributes: [
|
|
'title' => 'Pixel Week 2025',
|
|
'slug' => 'pixel-week-2025',
|
|
'is_recurring' => true,
|
|
'recurrence_key' => 'pixel-week',
|
|
'edition_year' => 2025,
|
|
'is_active_campaign' => false,
|
|
'is_homepage_featured' => false,
|
|
'campaign_priority' => 50,
|
|
'starts_at' => now()->subDays(30),
|
|
'ends_at' => now()->addDays(2),
|
|
]);
|
|
|
|
$currentEdition = acceptingWorld(attributes: [
|
|
'title' => 'Pixel Week 2026',
|
|
'slug' => 'pixel-week-2026',
|
|
'is_recurring' => true,
|
|
'recurrence_key' => 'pixel-week',
|
|
'edition_year' => 2026,
|
|
'is_active_campaign' => true,
|
|
'is_homepage_featured' => true,
|
|
'campaign_priority' => 500,
|
|
'teaser_title' => 'Now live: Pixel Week 2026',
|
|
]);
|
|
|
|
$options = app(\App\Services\Worlds\WorldSubmissionService::class)->eligibleWorldOptions($creator);
|
|
|
|
expect($options)->toHaveCount(1)
|
|
->and($options[0]['id'])->toBe($currentEdition->id)
|
|
->and($options[0]['title'])->toBe('Pixel Week 2026');
|
|
}); |