Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
154 lines
4.7 KiB
PHP
154 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Media;
|
|
|
|
use App\Filament\Resources\Projects\ProjectResource;
|
|
use App\Models\ImageAttachment;
|
|
use App\Models\Media;
|
|
use App\Models\Project;
|
|
use Database\Seeders\RolePermissionSeeder;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\FakesPireon;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* More than one image on a project: the screenshots and the before-and-after
|
|
* that make a write-up worth reading.
|
|
*/
|
|
class ProjectGalleryTest extends TestCase
|
|
{
|
|
use FakesPireon, RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->withHeaders(['Accept-Language' => 'es']);
|
|
Queue::fake();
|
|
}
|
|
|
|
protected function projectWithGallery(): Project
|
|
{
|
|
$project = Project::factory()->published()->create([
|
|
'title' => ['es' => 'Mi proyecto'],
|
|
'slug' => ['es' => 'mi-proyecto'],
|
|
]);
|
|
|
|
$project->images()->createMany([
|
|
[
|
|
'path' => 'projects/gallery/after.jpg',
|
|
'alt' => ['es' => 'El panel nuevo'],
|
|
'caption' => ['es' => 'Después'],
|
|
'position' => 2,
|
|
],
|
|
[
|
|
'path' => 'projects/gallery/before.jpg',
|
|
'alt' => ['es' => 'La hoja de cálculo que usaban'],
|
|
'caption' => ['es' => 'Antes'],
|
|
'position' => 1,
|
|
],
|
|
]);
|
|
|
|
return $project;
|
|
}
|
|
|
|
#[Test]
|
|
public function the_gallery_comes_back_in_the_order_it_was_arranged(): void
|
|
{
|
|
$this->projectWithGallery();
|
|
|
|
$gallery = $this->getJson('/api/public/v1/projects/mi-proyecto')
|
|
->assertOk()
|
|
->json('data.gallery');
|
|
|
|
// An unordered gallery is a different gallery every time it is read,
|
|
// and "before" after "after" tells the wrong story.
|
|
$this->assertSame(['Antes', 'Después'], array_column($gallery, 'caption'));
|
|
}
|
|
|
|
#[Test]
|
|
public function each_image_carries_everything_needed_to_render_it(): void
|
|
{
|
|
$this->projectWithGallery();
|
|
|
|
Media::query()->where('path', 'projects/gallery/before.jpg')->sole()->forceFill([
|
|
'width' => 1200,
|
|
'height' => 800,
|
|
'variants' => [
|
|
['format' => 'webp', 'width' => 400, 'path' => 'projects/gallery/before-400.webp', 'size' => 8000],
|
|
],
|
|
'processed_at' => now(),
|
|
])->save();
|
|
|
|
$first = $this->getJson('/api/public/v1/projects/mi-proyecto')
|
|
->assertOk()
|
|
->json('data.gallery.0');
|
|
|
|
$this->assertStringStartsWith('http', $first['url']);
|
|
$this->assertSame(1200, $first['width']);
|
|
$this->assertSame('La hoja de cálculo que usaban', $first['alt']);
|
|
$this->assertStringContainsString('400w', $first['srcset']['webp']);
|
|
|
|
// A caption is not a description: one is shown to everyone, the other
|
|
// is read by whoever cannot see the image.
|
|
$this->assertSame('Antes', $first['caption']);
|
|
}
|
|
|
|
#[Test]
|
|
public function a_listing_does_not_carry_every_screenshot_of_every_project(): void
|
|
{
|
|
$this->projectWithGallery();
|
|
|
|
$project = $this->getJson('/api/public/v1/projects')
|
|
->assertOk()
|
|
->json('data.0');
|
|
|
|
$this->assertArrayNotHasKey('gallery', $project);
|
|
}
|
|
|
|
#[Test]
|
|
public function adding_a_gallery_image_registers_it_like_any_other_upload(): void
|
|
{
|
|
$project = Project::factory()->create();
|
|
|
|
$project->images()->create(['path' => 'projects/gallery/new.jpg']);
|
|
|
|
// Nothing special about a gallery image: same media row, same derived
|
|
// versions, same queue.
|
|
$this->assertSame(1, Media::query()->where('path', 'projects/gallery/new.jpg')->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function erasing_a_project_for_good_takes_its_gallery_with_it(): void
|
|
{
|
|
$project = $this->projectWithGallery();
|
|
|
|
$project->delete();
|
|
|
|
// A soft delete keeps it — that is the point of a soft delete.
|
|
$this->assertSame(2, ImageAttachment::query()->count());
|
|
|
|
$project->forceDelete();
|
|
|
|
// A morph relation has no foreign key to cascade for it, so this is
|
|
// handled explicitly and worth pinning down.
|
|
$this->assertSame(0, ImageAttachment::query()->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function the_gallery_editor_renders(): void
|
|
{
|
|
$this->seed(RolePermissionSeeder::class);
|
|
$project = $this->projectWithGallery();
|
|
|
|
$this->actingAs($this->userWithRoles('admin'));
|
|
|
|
$this->get(ProjectResource::getUrl('edit', ['record' => $project]))
|
|
->assertSuccessful();
|
|
}
|
|
}
|