create(); $secondUser = User::factory()->create(); $service = app(CollectionService::class); Collection::factory()->for($firstUser)->create([ 'title' => 'Portal Vault', 'slug' => 'portal-vault', ]); expect($service->makeUniqueSlugForUser($firstUser, 'Portal Vault'))->toBe('portal-vault-2'); expect($service->makeUniqueSlugForUser($secondUser, 'Portal Vault'))->toBe('portal-vault'); }); it('falls back to the first attached artwork when an explicit cover is removed', function (): void { $user = User::factory()->create(); $collection = Collection::factory()->for($user)->create(); $firstArtwork = Artwork::factory()->for($user)->create(); $secondArtwork = Artwork::factory()->for($user)->create(); $service = app(CollectionService::class); $service->attachArtworks($collection, $user, [$firstArtwork->id, $secondArtwork->id]); $collection->refresh(); $service->updateCollection($collection->loadMissing('user'), [ 'title' => $collection->title, 'slug' => $collection->slug, 'description' => $collection->description, 'visibility' => $collection->visibility, 'sort_mode' => $collection->sort_mode, 'cover_artwork_id' => $secondArtwork->id, ]); $collection->refresh(); expect($collection->resolvedCoverArtwork()?->id)->toBe($secondArtwork->id); $service->removeArtwork($collection->loadMissing('user'), $secondArtwork); $collection->refresh(); expect($collection->cover_artwork_id)->toBeNull(); expect($collection->resolvedCoverArtwork()?->id)->toBe($firstArtwork->id); }); it('keeps artworks_count in sync while attaching and removing artworks', function (): void { $user = User::factory()->create(); $collection = Collection::factory()->for($user)->create(['artworks_count' => 0]); $artworkA = Artwork::factory()->for($user)->create(); $artworkB = Artwork::factory()->for($user)->create(); $service = app(CollectionService::class); $service->attachArtworks($collection, $user, [$artworkA->id, $artworkB->id]); $collection->refresh(); expect($collection->artworks_count)->toBe(2); $service->removeArtwork($collection->loadMissing('user'), $artworkA); $collection->refresh(); expect($collection->artworks_count)->toBe(1); expect($collection->artworks()->pluck('artworks.id')->all())->toBe([$artworkB->id]); }); it('sorts manual collection artworks by stats views for popular mode', function (): void { $user = User::factory()->create(); $collection = Collection::factory()->for($user)->create([ 'sort_mode' => Collection::SORT_POPULAR, ]); $lowViews = Artwork::factory()->for($user)->create(); $highViews = Artwork::factory()->for($user)->create(); $noStats = Artwork::factory()->for($user)->create(); $service = app(CollectionService::class); $service->attachArtworks($collection, $user, [$lowViews->id, $noStats->id, $highViews->id]); DB::table('artwork_stats')->insert([ [ 'artwork_id' => $lowViews->id, 'views' => 25, 'downloads' => 0, 'favorites' => 0, 'rating_avg' => 0, 'rating_count' => 0, ], [ 'artwork_id' => $highViews->id, 'views' => 250, 'downloads' => 0, 'favorites' => 0, 'rating_avg' => 0, 'rating_count' => 0, ], ]); $artworks = $service->getCollectionDetailArtworks($collection->fresh(), true, 24); expect($artworks->getCollection()->pluck('id')->all())->toBe([ $highViews->id, $lowViews->id, $noStats->id, ]); }); it('sorts smart collection artworks by stats views for popular mode', function (): void { $user = User::factory()->create(); $collection = Collection::factory()->for($user)->create([ 'mode' => Collection::MODE_SMART, 'smart_rules_json' => [ 'match' => 'all', 'sort' => Collection::SORT_POPULAR, 'rules' => [ [ 'field' => 'created_at', 'operator' => 'between', 'value' => [ 'from' => now()->subDay()->toDateString(), 'to' => now()->addDay()->toDateString(), ], ], ], ], ]); $lowViews = Artwork::factory()->for($user)->create(); $highViews = Artwork::factory()->for($user)->create(); $noStats = Artwork::factory()->for($user)->create(); $service = app(SmartCollectionService::class); DB::table('artwork_stats')->insert([ [ 'artwork_id' => $lowViews->id, 'views' => 12, 'downloads' => 0, 'favorites' => 0, 'rating_avg' => 0, 'rating_count' => 0, ], [ 'artwork_id' => $highViews->id, 'views' => 120, 'downloads' => 0, 'favorites' => 0, 'rating_avg' => 0, 'rating_count' => 0, ], ]); $artworks = $service->resolveArtworks($collection, true, 24); expect($artworks->getCollection()->pluck('id')->all())->toBe([ $highViews->id, $lowViews->id, $noStats->id, ]); }); it('builds a human readable smart summary for medium rules', function (): void { $service = app(SmartCollectionService::class); $summary = $service->smartSummary([ 'match' => 'all', 'sort' => 'newest', 'rules' => [ [ 'field' => 'medium', 'operator' => 'equals', 'value' => 'wallpapers', ], ], ]); expect($summary)->toBe('Includes artworks in medium wallpapers.'); }); it('builds a human readable smart summary for style and color rules', function (): void { $service = app(SmartCollectionService::class); $summary = $service->smartSummary([ 'match' => 'any', 'sort' => 'newest', 'rules' => [ [ 'field' => 'style', 'operator' => 'equals', 'value' => 'digital painting', ], [ 'field' => 'color', 'operator' => 'equals', 'value' => 'blue tones', ], ], ]); expect($summary)->toBe('Includes artworks matching style digital painting or using color palette blue tones.'); }); it('builds a human readable smart summary for mature rules', function (): void { $service = app(SmartCollectionService::class); $summary = $service->smartSummary([ 'match' => 'all', 'sort' => 'newest', 'rules' => [ [ 'field' => 'is_mature', 'operator' => 'equals', 'value' => true, ], ], ]); expect($summary)->toBe('Includes artworks marked as mature artworks.'); });