228 lines
7.3 KiB
PHP
228 lines
7.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\Artwork;
|
|
use App\Models\Collection;
|
|
use App\Models\User;
|
|
use App\Services\CollectionService;
|
|
use App\Services\SmartCollectionService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Tests\TestCase;
|
|
|
|
uses(TestCase::class, RefreshDatabase::class);
|
|
|
|
it('generates unique slugs per owner while allowing reuse across different owners', function (): void {
|
|
$firstUser = User::factory()->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.');
|
|
});
|