optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View 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();
});