optimizations
This commit is contained in:
621
tests/Feature/NovaCards/NovaCardPublicPagesTest.php
Normal file
621
tests/Feature/NovaCards/NovaCardPublicPagesTest.php
Normal file
@@ -0,0 +1,621 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Events\NovaCards\NovaCardDownloaded;
|
||||
use App\Events\NovaCards\NovaCardShared;
|
||||
use App\Events\NovaCards\NovaCardViewed;
|
||||
use App\Models\NovaCard;
|
||||
use App\Models\NovaCardCategory;
|
||||
use App\Models\NovaCardChallenge;
|
||||
use App\Models\NovaCardChallengeEntry;
|
||||
use App\Models\NovaCardComment;
|
||||
use App\Models\NovaCardCollection;
|
||||
use App\Models\NovaCardCollectionItem;
|
||||
use App\Models\NovaCardCreatorPreset;
|
||||
use App\Models\NovaCardTag;
|
||||
use App\Models\NovaCardTemplate;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
function novaCardCategory(array $attributes = []): NovaCardCategory
|
||||
{
|
||||
return NovaCardCategory::query()->create(array_merge([
|
||||
'slug' => 'category-' . Str::lower(Str::random(8)),
|
||||
'name' => 'Category ' . Str::upper(Str::random(4)),
|
||||
'description' => 'Category description',
|
||||
'active' => true,
|
||||
'order_num' => 0,
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function novaCardTemplate(array $attributes = []): NovaCardTemplate
|
||||
{
|
||||
return NovaCardTemplate::query()->create(array_merge([
|
||||
'slug' => 'template-' . Str::lower(Str::random(8)),
|
||||
'name' => 'Template ' . Str::upper(Str::random(4)),
|
||||
'description' => 'Template description',
|
||||
'config_json' => [
|
||||
'font_preset' => 'modern-sans',
|
||||
'gradient_preset' => 'midnight-nova',
|
||||
'layout' => 'quote_heavy',
|
||||
'text_align' => 'center',
|
||||
'text_color' => '#ffffff',
|
||||
'overlay_style' => 'dark-soft',
|
||||
],
|
||||
'supported_formats' => ['square', 'portrait'],
|
||||
'active' => true,
|
||||
'official' => true,
|
||||
'order_num' => 0,
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function novaCardTag(array $attributes = []): NovaCardTag
|
||||
{
|
||||
return NovaCardTag::query()->create(array_merge([
|
||||
'slug' => 'tag-' . Str::lower(Str::random(8)),
|
||||
'name' => 'Tag ' . Str::upper(Str::random(4)),
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
function publishedNovaCard(User $user, array $attributes = []): NovaCard
|
||||
{
|
||||
$category = $attributes['category'] ?? novaCardCategory();
|
||||
$template = $attributes['template'] ?? novaCardTemplate();
|
||||
|
||||
$card = NovaCard::query()->create(array_merge([
|
||||
'user_id' => $user->id,
|
||||
'category_id' => $category->id,
|
||||
'template_id' => $template->id,
|
||||
'title' => 'Skybound Thought',
|
||||
'slug' => 'skybound-thought',
|
||||
'quote_text' => 'A bright sentence for public display.',
|
||||
'quote_author' => 'Nova Author',
|
||||
'quote_source' => 'Test Source',
|
||||
'description' => 'A public card used in tests.',
|
||||
'format' => NovaCard::FORMAT_SQUARE,
|
||||
'project_json' => [
|
||||
'content' => [
|
||||
'title' => 'Skybound Thought',
|
||||
'quote_text' => 'A bright sentence for public display.',
|
||||
'quote_author' => 'Nova Author',
|
||||
'quote_source' => 'Test Source',
|
||||
],
|
||||
'layout' => [
|
||||
'layout' => 'quote_heavy',
|
||||
'position' => 'center',
|
||||
'alignment' => 'center',
|
||||
'padding' => 'comfortable',
|
||||
'max_width' => 'balanced',
|
||||
],
|
||||
'typography' => [
|
||||
'font_preset' => 'modern-sans',
|
||||
'text_color' => '#ffffff',
|
||||
'accent_color' => '#e0f2fe',
|
||||
'quote_size' => 72,
|
||||
'author_size' => 28,
|
||||
'letter_spacing' => 0,
|
||||
'line_height' => 1.2,
|
||||
'shadow_preset' => 'soft',
|
||||
],
|
||||
'background' => [
|
||||
'type' => 'gradient',
|
||||
'gradient_preset' => 'midnight-nova',
|
||||
'gradient_colors' => ['#0f172a', '#1d4ed8'],
|
||||
'overlay_style' => 'dark-soft',
|
||||
'focal_position' => 'center',
|
||||
'blur_level' => 0,
|
||||
'opacity' => 50,
|
||||
],
|
||||
'decorations' => [],
|
||||
],
|
||||
'render_version' => 1,
|
||||
'background_type' => 'gradient',
|
||||
'visibility' => NovaCard::VISIBILITY_PUBLIC,
|
||||
'status' => NovaCard::STATUS_PUBLISHED,
|
||||
'moderation_status' => NovaCard::MOD_APPROVED,
|
||||
'featured' => false,
|
||||
'allow_download' => true,
|
||||
'views_count' => 12,
|
||||
'shares_count' => 3,
|
||||
'downloads_count' => 1,
|
||||
'published_at' => now()->subHour(),
|
||||
], Arr::except($attributes, ['category', 'template', 'tags'])));
|
||||
|
||||
foreach (($attributes['tags'] ?? []) as $tag) {
|
||||
$card->tags()->attach($tag->id);
|
||||
}
|
||||
|
||||
return $card->fresh(['user.profile', 'category', 'template', 'tags']);
|
||||
}
|
||||
|
||||
it('renders the public cards index with featured content', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'novacreator']);
|
||||
$featured = publishedNovaCard($creator, ['featured' => true, 'title' => 'Featured Nova']);
|
||||
$latest = publishedNovaCard($creator, ['slug' => 'latest-nova', 'title' => 'Latest Nova']);
|
||||
|
||||
$response = $this->get(route('cards.index'));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
expect($response->getContent())
|
||||
->toContain('Nova Cards')
|
||||
->toContain('Featured Nova')
|
||||
->toContain('Latest Nova')
|
||||
->toContain(route('cards.show', ['slug' => $featured->slug, 'id' => $featured->id]))
|
||||
->toContain('application/ld+json')
|
||||
->toContain('CollectionPage')
|
||||
->toContain('index,follow');
|
||||
});
|
||||
|
||||
it('renders category, tag, style, palette, and creator pages with their matching card', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'tagcreator']);
|
||||
$category = novaCardCategory(['slug' => 'mindset', 'name' => 'Mindset']);
|
||||
$tag = novaCardTag(['slug' => 'clarity', 'name' => 'Clarity']);
|
||||
$moodTag = novaCardTag(['slug' => 'calm', 'name' => 'Calm']);
|
||||
$template = novaCardTemplate(['slug' => 'editorial-starter', 'name' => 'Editorial Starter']);
|
||||
$card = publishedNovaCard($creator, [
|
||||
'category' => $category,
|
||||
'template' => $template,
|
||||
'slug' => 'clarity-card',
|
||||
'title' => 'Clarity Card',
|
||||
'featured' => true,
|
||||
'featured_score' => 95.0,
|
||||
'style_family' => 'editorial',
|
||||
'palette_family' => 'cool-tones',
|
||||
'editor_mode_last_used' => 'full',
|
||||
'views_count' => 120,
|
||||
'likes_count' => 24,
|
||||
'saves_count' => 18,
|
||||
'remixes_count' => 5,
|
||||
'tags' => [$tag, $moodTag],
|
||||
]);
|
||||
$second = publishedNovaCard($creator, [
|
||||
'category' => $category,
|
||||
'template' => $template,
|
||||
'slug' => 'clarity-card-two',
|
||||
'title' => 'Clarity Card Two',
|
||||
'style_family' => 'editorial',
|
||||
'palette_family' => 'cool-tones',
|
||||
'editor_mode_last_used' => 'full',
|
||||
'views_count' => 80,
|
||||
'likes_count' => 14,
|
||||
'saves_count' => 7,
|
||||
'remixes_count' => 2,
|
||||
'tags' => [$tag, $moodTag],
|
||||
]);
|
||||
$remix = publishedNovaCard($creator, [
|
||||
'category' => $category,
|
||||
'slug' => 'clarity-card-remix',
|
||||
'title' => 'Clarity Card Remix',
|
||||
'template' => $template,
|
||||
'original_card_id' => $card->id,
|
||||
'root_card_id' => $card->id,
|
||||
'editor_mode_last_used' => 'quick',
|
||||
'views_count' => 50,
|
||||
'likes_count' => 9,
|
||||
'saves_count' => 4,
|
||||
'remixes_count' => 1,
|
||||
'tags' => [$tag, $moodTag],
|
||||
]);
|
||||
|
||||
NovaCardCreatorPreset::query()->create([
|
||||
'user_id' => $creator->id,
|
||||
'name' => 'Clarity Style',
|
||||
'preset_type' => NovaCardCreatorPreset::TYPE_STYLE,
|
||||
'config_json' => ['typography' => ['font_preset' => 'modern-sans']],
|
||||
'is_default' => true,
|
||||
]);
|
||||
|
||||
NovaCardCreatorPreset::query()->create([
|
||||
'user_id' => $creator->id,
|
||||
'name' => 'Editorial Starter',
|
||||
'preset_type' => NovaCardCreatorPreset::TYPE_STARTER,
|
||||
'config_json' => ['template' => ['slug' => 'editorial-starter']],
|
||||
'is_default' => false,
|
||||
]);
|
||||
|
||||
$collection = NovaCardCollection::query()->create([
|
||||
'user_id' => $creator->id,
|
||||
'slug' => 'clarity-picks',
|
||||
'name' => 'Clarity Picks',
|
||||
'description' => 'A featured public collection from this creator.',
|
||||
'visibility' => NovaCardCollection::VISIBILITY_PUBLIC,
|
||||
'official' => false,
|
||||
'featured' => true,
|
||||
'cards_count' => 2,
|
||||
]);
|
||||
|
||||
NovaCardCollectionItem::query()->create([
|
||||
'collection_id' => $collection->id,
|
||||
'card_id' => $card->id,
|
||||
'sort_order' => 1,
|
||||
]);
|
||||
|
||||
NovaCardCollectionItem::query()->create([
|
||||
'collection_id' => $collection->id,
|
||||
'card_id' => $second->id,
|
||||
'sort_order' => 2,
|
||||
]);
|
||||
|
||||
$challenge = NovaCardChallenge::query()->create([
|
||||
'slug' => 'clarity-sprint',
|
||||
'title' => 'Clarity Sprint',
|
||||
'description' => 'A creator challenge history entry.',
|
||||
'status' => NovaCardChallenge::STATUS_COMPLETED,
|
||||
'official' => true,
|
||||
'featured' => true,
|
||||
'entries_count' => 2,
|
||||
]);
|
||||
|
||||
NovaCardChallengeEntry::query()->create([
|
||||
'challenge_id' => $challenge->id,
|
||||
'card_id' => $card->id,
|
||||
'user_id' => $creator->id,
|
||||
'status' => NovaCardChallengeEntry::STATUS_WINNER,
|
||||
]);
|
||||
|
||||
$this->get(route('cards.category', ['categorySlug' => $category->slug]))
|
||||
->assertOk();
|
||||
expect($this->get(route('cards.category', ['categorySlug' => $category->slug]))->getContent())
|
||||
->toContain('Mindset')
|
||||
->toContain('Clarity Card');
|
||||
|
||||
expect($this->get(route('cards.tag', ['tagSlug' => $tag->slug]))->getContent())
|
||||
->toContain('#Clarity')
|
||||
->toContain('Clarity Card');
|
||||
|
||||
expect($this->get(route('cards.style', ['styleSlug' => 'editorial']))->getContent())
|
||||
->toContain('Editorial')
|
||||
->toContain('Clarity Card')
|
||||
->toContain('Style families');
|
||||
|
||||
expect($this->get(route('cards.palette', ['paletteSlug' => 'cool-tones']))->getContent())
|
||||
->toContain('Cool Tones')
|
||||
->toContain('Clarity Card')
|
||||
->toContain('Palette families');
|
||||
|
||||
expect($this->get(route('cards.creator', ['username' => $creator->username]))->getContent())
|
||||
->toContain('@' . $creator->username)
|
||||
->toContain('Clarity Card')
|
||||
->toContain('Creator profile')
|
||||
->toContain('Featured works')
|
||||
->toContain('Featured collections')
|
||||
->toContain('Clarity Picks')
|
||||
->toContain('Signature themes')
|
||||
->toContain('Cool Tones')
|
||||
->toContain('Soft Morning')
|
||||
->toContain('Most remixed works')
|
||||
->toContain('Most liked works')
|
||||
->toContain('Remix branches')
|
||||
->toContain('Community branches')
|
||||
->toContain('Published remixes')
|
||||
->toContain('Published remix')
|
||||
->toContain('Community branch')
|
||||
->toContain('Clarity Card Remix')
|
||||
->toContain('Source:')
|
||||
->toContain('View lineage')
|
||||
->toContain('Remix graph')
|
||||
->toContain('Peak branch card')
|
||||
->toContain('Creator identity')
|
||||
->toContain('Preference signals')
|
||||
->toContain('Editorial Starter')
|
||||
->toContain('Preferred editor mode')
|
||||
->toContain('Full')
|
||||
->toContain('Saved presets')
|
||||
->toContain('Style')
|
||||
->toContain('Starter')
|
||||
->toContain('Recent timeline')
|
||||
->toContain('Featured release')
|
||||
->toContain('Audience favorite')
|
||||
->toContain('Remix traction')
|
||||
->toContain('Challenge track record')
|
||||
->toContain('Clarity Sprint')
|
||||
->toContain('Winner entry')
|
||||
->toContain('Creator highlights')
|
||||
->toContain('All published works')
|
||||
->toContain('Editorial')
|
||||
->toContain('#Clarity')
|
||||
->toContain('Mindset')
|
||||
->toContain('200')
|
||||
->toContain('Clarity Card Two');
|
||||
|
||||
expect($this->get(route('cards.creator.portfolio', ['username' => $creator->username]))->getContent())
|
||||
->toContain('@' . $creator->username)
|
||||
->toContain('Portfolio')
|
||||
->toContain('Portfolio works')
|
||||
->toContain('Profile overview')
|
||||
->toContain('Portfolio page')
|
||||
->toContain('Most liked works')
|
||||
->toContain('Remix branches')
|
||||
->toContain('Recent timeline')
|
||||
->toContain('Clarity Card Remix');
|
||||
});
|
||||
|
||||
it('renders the public card detail page and increments views', function (): void {
|
||||
Event::fake([NovaCardViewed::class]);
|
||||
|
||||
$viewer = User::factory()->create(['username' => 'reportviewer']);
|
||||
$creator = User::factory()->create(['username' => 'detailcreator']);
|
||||
$category = novaCardCategory(['slug' => 'quotes', 'name' => 'Quotes']);
|
||||
$tag = novaCardTag(['slug' => 'focus', 'name' => 'Focus']);
|
||||
$card = publishedNovaCard($creator, [
|
||||
'category' => $category,
|
||||
'slug' => 'detail-card',
|
||||
'title' => 'Detail Card',
|
||||
'quote_text' => 'Precision matters when pages are crawlable.',
|
||||
'views_count' => 7,
|
||||
'tags' => [$tag],
|
||||
]);
|
||||
|
||||
$response = $this->actingAs($viewer)->get(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
expect($response->getContent())
|
||||
->toContain('Detail Card')
|
||||
->toContain('Precision matters when pages are crawlable.')
|
||||
->toContain('#Focus')
|
||||
->toContain('CreativeWork')
|
||||
->toContain('Copy link')
|
||||
->toContain('data-card-report');
|
||||
|
||||
expect($card->fresh()->views_count)->toBe(8);
|
||||
Event::assertDispatched(NovaCardViewed::class);
|
||||
});
|
||||
|
||||
it('tracks share and download engagement for a public card', function (): void {
|
||||
Event::fake([NovaCardShared::class, NovaCardDownloaded::class]);
|
||||
|
||||
$creator = User::factory()->create(['username' => 'engagementcreator']);
|
||||
$card = publishedNovaCard($creator, [
|
||||
'slug' => 'engagement-card',
|
||||
'title' => 'Engagement Card',
|
||||
'preview_path' => 'cards/previews/example.webp',
|
||||
]);
|
||||
|
||||
$this->postJson(route('api.cards.share', ['id' => $card->id]))
|
||||
->assertOk()
|
||||
->assertJsonPath('shares_count', 4);
|
||||
|
||||
$this->postJson(route('api.cards.download', ['id' => $card->id]))
|
||||
->assertOk()
|
||||
->assertJsonPath('downloads_count', 2)
|
||||
->assertJsonPath('download_url', $card->fresh()->previewUrl());
|
||||
|
||||
Event::assertDispatched(NovaCardShared::class);
|
||||
Event::assertDispatched(NovaCardDownloaded::class);
|
||||
});
|
||||
|
||||
it('redirects creator and show routes to canonical casing and slug', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'CanonicalUser']);
|
||||
$card = publishedNovaCard($creator, ['slug' => 'canonical-card', 'title' => 'Canonical Card']);
|
||||
|
||||
$this->get('/cards/creator/CANONICALUSER')
|
||||
->assertRedirect(route('cards.creator', ['username' => 'canonicaluser']));
|
||||
|
||||
$this->get('/cards/creator/CANONICALUSER/portfolio')
|
||||
->assertRedirect(route('cards.creator.portfolio', ['username' => 'canonicaluser']));
|
||||
|
||||
$this->get(route('cards.show', ['slug' => 'wrong-slug', 'id' => $card->id]))
|
||||
->assertRedirect(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]));
|
||||
});
|
||||
|
||||
it('renders a public collection detail page with curated cards', function (): void {
|
||||
$owner = User::factory()->create(['username' => 'collectionowner']);
|
||||
$first = publishedNovaCard($owner, ['slug' => 'collection-card-one', 'title' => 'Collection Card One']);
|
||||
$second = publishedNovaCard($owner, ['slug' => 'collection-card-two', 'title' => 'Collection Card Two']);
|
||||
|
||||
$collection = NovaCardCollection::query()->create([
|
||||
'user_id' => $owner->id,
|
||||
'slug' => 'launch-picks',
|
||||
'name' => 'Launch Picks',
|
||||
'description' => 'Curated launch cards.',
|
||||
'visibility' => NovaCardCollection::VISIBILITY_PUBLIC,
|
||||
'official' => true,
|
||||
'featured' => true,
|
||||
'cards_count' => 2,
|
||||
]);
|
||||
|
||||
NovaCardCollectionItem::query()->create(['collection_id' => $collection->id, 'card_id' => $first->id, 'sort_order' => 1, 'note' => 'Anchor card']);
|
||||
NovaCardCollectionItem::query()->create(['collection_id' => $collection->id, 'card_id' => $second->id, 'sort_order' => 2]);
|
||||
|
||||
$response = $this->get(route('cards.collections.show', ['slug' => $collection->slug, 'id' => $collection->id]));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
expect($response->getContent())
|
||||
->toContain('Launch Picks')
|
||||
->toContain('Collection Card One')
|
||||
->toContain('Collection Card Two')
|
||||
->toContain('Anchor card')
|
||||
->toContain('CollectionPage');
|
||||
});
|
||||
|
||||
it('hides hidden challenge entries from the public card page', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'challengeowner']);
|
||||
$card = publishedNovaCard($creator, ['slug' => 'challenge-visibility-card', 'title' => 'Challenge Visibility Card']);
|
||||
$challenge = NovaCardChallenge::query()->create([
|
||||
'slug' => 'hidden-entry-check',
|
||||
'title' => 'Hidden Entry Check',
|
||||
'status' => NovaCardChallenge::STATUS_ACTIVE,
|
||||
'official' => true,
|
||||
]);
|
||||
|
||||
NovaCardChallengeEntry::query()->create([
|
||||
'challenge_id' => $challenge->id,
|
||||
'card_id' => $card->id,
|
||||
'user_id' => $creator->id,
|
||||
'status' => NovaCardChallengeEntry::STATUS_HIDDEN,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]));
|
||||
|
||||
$response->assertOk();
|
||||
expect($response->getContent())->not->toContain('Hidden Entry Check');
|
||||
});
|
||||
|
||||
it('renders a lineage page for remixed cards', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'lineagecreator']);
|
||||
$root = publishedNovaCard($creator, ['slug' => 'lineage-root', 'title' => 'Lineage Root']);
|
||||
$remix = publishedNovaCard($creator, [
|
||||
'slug' => 'lineage-remix',
|
||||
'title' => 'Lineage Remix',
|
||||
'original_card_id' => $root->id,
|
||||
'root_card_id' => $root->id,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('cards.lineage', ['slug' => $remix->slug, 'id' => $remix->id]));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
expect($response->getContent())
|
||||
->toContain('Lineage Remix')
|
||||
->toContain('Lineage Root')
|
||||
->toContain('Cards in this remix branch');
|
||||
});
|
||||
|
||||
it('renders a best remixes page ranked by remix traction', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'remixhighlightcreator']);
|
||||
$root = publishedNovaCard($creator, ['slug' => 'highlight-root', 'title' => 'Highlight Root']);
|
||||
$best = publishedNovaCard($creator, [
|
||||
'slug' => 'best-remix-card',
|
||||
'title' => 'Best Remix Card',
|
||||
'original_card_id' => $root->id,
|
||||
'root_card_id' => $root->id,
|
||||
'remixes_count' => 12,
|
||||
'saves_count' => 30,
|
||||
'likes_count' => 20,
|
||||
]);
|
||||
$other = publishedNovaCard($creator, [
|
||||
'slug' => 'other-remix-card',
|
||||
'title' => 'Other Remix Card',
|
||||
'original_card_id' => $root->id,
|
||||
'root_card_id' => $root->id,
|
||||
'remixes_count' => 3,
|
||||
'saves_count' => 4,
|
||||
'likes_count' => 2,
|
||||
]);
|
||||
|
||||
$response = $this->get(route('cards.remix-highlights'));
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
expect($response->getContent())
|
||||
->toContain('Best remixes')
|
||||
->toContain('Best Remix Card')
|
||||
->toContain('Other Remix Card')
|
||||
->toContain('Remix discovery')
|
||||
->toContain(route('cards.show', ['slug' => $best->slug, 'id' => $best->id]))
|
||||
->toContain('View lineage');
|
||||
});
|
||||
|
||||
it('renders mood, editorial, and seasonal discovery pages', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'discoverycreator', 'nova_featured_creator' => true]);
|
||||
$calm = novaCardTag(['slug' => 'calm', 'name' => 'Calm']);
|
||||
$winter = novaCardTag(['slug' => 'winter', 'name' => 'Winter']);
|
||||
|
||||
$editorialCard = publishedNovaCard($creator, [
|
||||
'slug' => 'editorial-spotlight-card',
|
||||
'title' => 'Editorial Spotlight Card',
|
||||
'featured' => true,
|
||||
'featured_score' => 88.5,
|
||||
]);
|
||||
|
||||
$moodCard = publishedNovaCard($creator, [
|
||||
'slug' => 'calm-mood-card',
|
||||
'title' => 'Calm Mood Card',
|
||||
'tags' => [$calm],
|
||||
]);
|
||||
|
||||
$seasonalCard = publishedNovaCard($creator, [
|
||||
'slug' => 'winter-card',
|
||||
'title' => 'Winter Card',
|
||||
'tags' => [$winter],
|
||||
]);
|
||||
|
||||
$collection = NovaCardCollection::query()->create([
|
||||
'user_id' => $creator->id,
|
||||
'slug' => 'editorial-picks-collection',
|
||||
'name' => 'Editorial Picks Collection',
|
||||
'description' => 'A featured collection for editorial discovery.',
|
||||
'visibility' => NovaCardCollection::VISIBILITY_PUBLIC,
|
||||
'official' => true,
|
||||
'featured' => true,
|
||||
'cards_count' => 1,
|
||||
]);
|
||||
|
||||
NovaCardCollectionItem::query()->create([
|
||||
'collection_id' => $collection->id,
|
||||
'card_id' => $editorialCard->id,
|
||||
'sort_order' => 1,
|
||||
]);
|
||||
|
||||
$challenge = NovaCardChallenge::query()->create([
|
||||
'slug' => 'editorial-highlight-challenge',
|
||||
'title' => 'Editorial Highlight Challenge',
|
||||
'description' => 'A featured challenge for discovery.',
|
||||
'status' => NovaCardChallenge::STATUS_ACTIVE,
|
||||
'official' => true,
|
||||
'featured' => true,
|
||||
'entries_count' => 3,
|
||||
]);
|
||||
|
||||
expect($this->get(route('cards.mood', ['moodSlug' => 'soft-morning']))->getContent())
|
||||
->toContain('Soft Morning')
|
||||
->toContain('Calm Mood Card')
|
||||
->toContain('Mood families');
|
||||
|
||||
$editorialResponse = $this->get(route('cards.editorial'));
|
||||
$editorialResponse->assertOk()->assertViewHas('featuredCreators', function (array $creators): bool {
|
||||
return count($creators) === 1
|
||||
&& ($creators[0]['username'] ?? null) === 'discoverycreator'
|
||||
&& ($creators[0]['featured_cards_count'] ?? null) === 1;
|
||||
});
|
||||
|
||||
expect($editorialResponse->getContent())
|
||||
->toContain('Editorial picks')
|
||||
->toContain('Editorial Spotlight Card')
|
||||
->toContain('Featured creators')
|
||||
->toContain('Editorial Picks Collection')
|
||||
->toContain('Editorial Highlight Challenge');
|
||||
|
||||
expect($this->get(route('cards.seasonal'))->getContent())
|
||||
->toContain('Seasonal cards')
|
||||
->toContain('Winter Card')
|
||||
->toContain('Seasonal hubs');
|
||||
});
|
||||
|
||||
it('allows authenticated viewers to comment on public cards and delete their own comment', function (): void {
|
||||
$creator = User::factory()->create(['username' => 'commentcardcreator']);
|
||||
$viewer = User::factory()->create(['username' => 'commentcardviewer']);
|
||||
$card = publishedNovaCard($creator, ['slug' => 'commentable-card', 'title' => 'Commentable Card']);
|
||||
|
||||
$this->actingAs($viewer)
|
||||
->post(route('cards.comments.store', ['card' => $card->id]), [
|
||||
'body' => 'This layout has a strong editorial feel.',
|
||||
])
|
||||
->assertRedirect(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]) . '#comments');
|
||||
|
||||
$comment = NovaCardComment::query()->latest('id')->first();
|
||||
|
||||
expect($comment)->not->toBeNull()
|
||||
->and($comment->card_id)->toBe($card->id)
|
||||
->and($comment->body)->toBe('This layout has a strong editorial feel.');
|
||||
|
||||
$show = $this->actingAs($viewer)
|
||||
->get(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]))
|
||||
->assertOk();
|
||||
|
||||
expect($show->getContent())
|
||||
->toContain('Comments')
|
||||
->toContain('This layout has a strong editorial feel.');
|
||||
|
||||
$this->actingAs($viewer)
|
||||
->delete(route('cards.comments.destroy', ['card' => $card->id, 'comment' => $comment->id]))
|
||||
->assertRedirect(route('cards.show', ['slug' => $card->slug, 'id' => $card->id]) . '#comments');
|
||||
|
||||
expect($comment->fresh()->deleted_at)->not->toBeNull();
|
||||
});
|
||||
Reference in New Issue
Block a user