create(['name' => 'Schema Author']); $contentType = ContentType::create([ 'name' => 'Photography', 'slug' => 'photography', 'description' => 'Photography content', ]); $category = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Abstract', 'slug' => 'abstract-' . Str::lower(Str::random(5)), 'description' => 'Abstract works', 'is_active' => true, 'sort_order' => 0, ]); $artwork = Artwork::factory()->create([ 'user_id' => $user->id, 'title' => 'Schema Ready Artwork', 'slug' => 'schema-ready-artwork', 'description' => 'Artwork description for schema test.', 'mime_type' => 'image/jpeg', 'published_at' => now()->subMinute(), 'is_public' => true, 'is_approved' => true, ]); $artwork->categories()->attach($category->id); $tagA = Tag::create(['name' => 'neon', 'slug' => 'neon', 'usage_count' => 0, 'is_active' => true]); $tagB = Tag::create(['name' => 'city', 'slug' => 'city', 'usage_count' => 0, 'is_active' => true]); $artwork->tags()->attach([ $tagA->id => ['source' => 'user', 'confidence' => 0.9], $tagB->id => ['source' => 'user', 'confidence' => 0.8], ]); $artwork->load(['user', 'tags', 'categories.contentType']); expect(json_encode(app(SeoFactory::class)->artwork( $artwork, [ 'md' => ['url' => 'https://files.skinbase.org/md/schema-ready.webp', 'width' => 600, 'height' => 400], 'lg' => ['url' => 'https://files.skinbase.org/lg/schema-ready.webp', 'width' => 1200, 'height' => 800], 'xl' => ['url' => 'https://files.skinbase.org/xl/schema-ready.webp', 'width' => 2400, 'height' => 1600], ], route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug]), )->toArray()['json_ld'], JSON_UNESCAPED_SLASHES)) ->toContain('"@type":"ImageObject"') ->toContain('"name":"Schema Ready Artwork"') ->toContain('"keywords":["neon","city"]'); }); it('builds artwork seo data with breadcrumb and image-license metadata', function () { $user = User::factory()->create(['name' => 'Schema Breadcrumb Author']); $contentType = ContentType::create([ 'name' => 'Photography', 'slug' => 'photography', 'description' => 'Photography content', ]); $parentCategory = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Nature', 'slug' => 'nature', 'description' => 'Nature works', 'is_active' => true, 'sort_order' => 0, ]); $category = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => $parentCategory->id, 'name' => 'Forest', 'slug' => 'forest', 'description' => 'Forest works', 'is_active' => true, 'sort_order' => 1, ]); $artwork = Artwork::factory()->create([ 'user_id' => $user->id, 'title' => 'Licensed Artwork', 'slug' => 'licensed-artwork', 'description' => 'Artwork description for breadcrumb schema test.', 'published_at' => now()->subMinute(), 'is_public' => true, 'is_approved' => true, ]); $artwork->categories()->attach($category->id); $artwork->load(['user', 'tags', 'categories.contentType', 'categories.parent.contentType']); $artwork->setAttribute('license_url', 'https://skinbase.org/licenses/custom-license'); $seo = app(SeoFactory::class)->artwork( $artwork, [ 'md' => ['url' => 'https://files.skinbase.org/md/licensed.webp', 'width' => 600, 'height' => 400], 'lg' => ['url' => 'https://files.skinbase.org/lg/licensed.webp', 'width' => 1200, 'height' => 800], 'xl' => ['url' => 'https://files.skinbase.org/xl/licensed.webp', 'width' => 2400, 'height' => 1600], ], route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug]), [ ['name' => 'Photography', 'url' => url('/photography')], ['name' => 'Nature', 'url' => url('/photography/nature')], ['name' => 'Forest', 'url' => url('/photography/nature/forest')], ['name' => 'Licensed Artwork', 'url' => route('art.show', ['id' => $artwork->id, 'slug' => $artwork->slug])], ], )->toArray(); expect(json_encode($seo['json_ld'], JSON_UNESCAPED_SLASHES)) ->toContain('"@type":"ImageObject"') ->toContain('"creditText":"Schema Breadcrumb Author"') ->toContain('"license":"https://skinbase.org/licenses/custom-license"') ->toContain('"@type":"BreadcrumbList"') ->toContain('"name":"Photography"') ->toContain('"name":"Forest"'); }); it('renders JSON-LD via routed artwork show endpoint', function () { $user = User::factory()->create(['name' => 'Schema Route Author']); $contentType = ContentType::create([ 'name' => 'Photography', 'slug' => 'photography', 'description' => 'Photography content', ]); $category = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Abstract', 'slug' => 'abstract-route', 'description' => 'Abstract works', 'is_active' => true, 'sort_order' => 0, ]); $artwork = Artwork::factory()->create([ 'user_id' => $user->id, 'title' => 'Schema Route Artwork', 'slug' => 'schema-route-artwork', 'description' => 'Artwork description for routed schema test.', 'mime_type' => 'image/png', 'published_at' => now()->subMinute(), 'is_public' => true, 'is_approved' => true, ]); $artwork->categories()->attach($category->id); $tag = Tag::create(['name' => 'route-tag', 'slug' => 'route-tag', 'usage_count' => 0, 'is_active' => true]); $artwork->tags()->attach([ $tag->id => ['source' => 'user', 'confidence' => 0.95], ]); $url = route('artworks.show', [ 'contentTypeSlug' => $contentType->slug, 'categoryPath' => $category->slug, 'artwork' => $artwork->slug, ]); expect($url)->toContain('/photography/abstract-route/schema-route-artwork'); $matchedRoute = app('router')->getRoutes()->match(Request::create($url, 'GET')); expect($matchedRoute->getName())->toBe('artworks.show'); $response = $this->get($url); $response->assertOk() ->assertInertia(fn (AssertableInertia $page) => $page ->component('ArtworkPage') ->where('seo.title', fn (string $title): bool => str_contains($title, 'Schema Route Artwork')) ->where('seo.json_ld', function ($schemas): bool { $json = json_encode($schemas, JSON_UNESCAPED_SLASHES); return str_contains($json, '"@type":"ImageObject"') && str_contains($json, '"@type":"BreadcrumbList"') && str_contains($json, '"name":"Schema Route Artwork"') && str_contains($json, '"keywords":["route-tag"]'); })); });