308 lines
11 KiB
PHP
308 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Feature tests for the new canonical route families introduced by the
|
|
* Routing Unification spec (§3.2 Explore, §4 Blog/Pages, §6.1 redirects).
|
|
*/
|
|
|
|
use App\Models\Category;
|
|
use App\Models\ContentType;
|
|
|
|
// ── /explore routes ──────────────────────────────────────────────────────────
|
|
|
|
it('GET /explore returns 200', function () {
|
|
$this->get('/explore')->assertOk();
|
|
});
|
|
|
|
it('GET /explore contains "Explore" heading', function () {
|
|
$this->get('/explore')->assertOk()->assertSee('Explore', false);
|
|
});
|
|
|
|
it('GET /explore/wallpapers redirects to the canonical /wallpapers route', function () {
|
|
$this->get('/explore/wallpapers')->assertRedirect('/wallpapers')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/skins redirects to the canonical /skins route', function () {
|
|
$this->get('/explore/skins')->assertRedirect('/skins')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/photography redirects to the canonical /photography route', function () {
|
|
$this->get('/explore/photography')->assertRedirect('/photography')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/artworks returns 200', function () {
|
|
$this->get('/explore/artworks')->assertOk();
|
|
});
|
|
|
|
it('GET /explore/other redirects to the canonical /other route', function () {
|
|
$this->get('/explore/other')->assertRedirect('/other')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/wallpapers/trending redirects to the canonical /wallpapers route', function () {
|
|
$this->get('/explore/wallpapers/trending')->assertRedirect('/wallpapers')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/wallpapers/latest redirects to the canonical sorted /wallpapers route', function () {
|
|
$this->get('/explore/wallpapers/latest')->assertRedirect('/wallpapers?sort=latest')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/wallpapers/best redirects to the canonical sorted /wallpapers route', function () {
|
|
$this->get('/explore/wallpapers/best')->assertRedirect('/wallpapers?sort=top-rated')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/wallpapers/new-hot redirects to the canonical sorted /wallpapers route', function () {
|
|
$this->get('/explore/wallpapers/new-hot')->assertRedirect('/wallpapers?sort=fresh')->assertStatus(301);
|
|
});
|
|
|
|
it('/explore pages include canonical link tag', function () {
|
|
$html = $this->get('/explore')->assertOk()->getContent();
|
|
expect($html)->toContain('rel="canonical"');
|
|
});
|
|
|
|
it('/explore pages set robots index,follow', function () {
|
|
$html = $this->get('/explore')->assertOk()->getContent();
|
|
expect($html)->toContain('index,follow');
|
|
});
|
|
|
|
it('/explore hub page includes breadcrumb JSON-LD', function () {
|
|
$html = $this->get('/explore')->assertOk()->getContent();
|
|
expect($html)->toContain('BreadcrumbList');
|
|
});
|
|
|
|
// ── 301 redirects from legacy routes ─────────────────────────────────────────
|
|
|
|
it('GET /browse redirects to /explore with 301', function () {
|
|
$this->get('/browse')->assertRedirect('/explore')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /discover redirects to /discover/trending with 301', function () {
|
|
$this->get('/discover')->assertRedirect('/discover/trending')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /sections returns the live sections page', function () {
|
|
$this->get('/sections')->assertOk()->assertSee('Browse Sections', false);
|
|
});
|
|
|
|
it('GET /browse-categories redirects to /categories with 301', function () {
|
|
$this->get('/browse-categories')->assertRedirect('/categories')->assertStatus(301);
|
|
});
|
|
|
|
it('legacy mixed-case category route redirects to the canonical category URL with 301', function () {
|
|
$contentType = ContentType::query()->create([
|
|
'name' => 'Skins',
|
|
'slug' => 'skins',
|
|
]);
|
|
|
|
$category = Category::query()->create([
|
|
'content_type_id' => $contentType->id,
|
|
'name' => 'BrowserBob',
|
|
'slug' => 'browserbob',
|
|
]);
|
|
|
|
$this->get('/Skins/BrowserBob/' . $category->id . '?ref=legacy')
|
|
->assertRedirect('/skins/browserbob?ref=legacy')
|
|
->assertStatus(301);
|
|
});
|
|
|
|
it('legacy nested category route redirects to the canonical nested category URL with 301', function () {
|
|
$contentType = ContentType::query()->create([
|
|
'name' => 'Skins',
|
|
'slug' => 'skins',
|
|
]);
|
|
|
|
$parent = Category::query()->create([
|
|
'content_type_id' => $contentType->id,
|
|
'name' => 'BrowserBob',
|
|
'slug' => 'browserbob',
|
|
]);
|
|
|
|
$child = Category::query()->create([
|
|
'content_type_id' => $contentType->id,
|
|
'parent_id' => $parent->id,
|
|
'name' => 'sdsdsdsd',
|
|
'slug' => 'sdsdsdsd',
|
|
]);
|
|
|
|
$this->get('/Skins/BrowserBob/sdsdsdsd/' . $child->id)
|
|
->assertRedirect('/skins/browserbob/sdsdsdsd')
|
|
->assertStatus(301);
|
|
});
|
|
|
|
it('legacy category route falls back to /categories and preserves query string when no category matches', function () {
|
|
$this->get('/Skins/does-not-exist/999999?source=old-site')
|
|
->assertRedirect('/categories?source=old-site')
|
|
->assertStatus(301);
|
|
});
|
|
|
|
it('legacy /category route redirects to the canonical category URL with 301', function () {
|
|
$contentType = ContentType::query()->create([
|
|
'name' => 'Skins',
|
|
'slug' => 'skins',
|
|
]);
|
|
|
|
$category = Category::query()->create([
|
|
'content_type_id' => $contentType->id,
|
|
'name' => 'BrowserBob',
|
|
'slug' => 'browserbob',
|
|
]);
|
|
|
|
$this->get('/category/skins/browserbob/' . $category->id . '?ref=legacy-category')
|
|
->assertRedirect('/skins/browserbob?ref=legacy-category')
|
|
->assertStatus(301);
|
|
});
|
|
|
|
it('legacy /category route falls back to /categories and preserves query string when no category matches', function () {
|
|
$this->get('/category/skins/does-not-exist/999999?source=legacy-category')
|
|
->assertRedirect('/categories?source=legacy-category')
|
|
->assertStatus(301);
|
|
});
|
|
|
|
it('GET /today-in-history redirects to /discover/on-this-day with 301', function () {
|
|
$this->get('/today-in-history')->assertRedirect('/discover/on-this-day')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /members redirects to /creators/top with 301', function () {
|
|
$this->get('/members')->assertRedirect('/creators/top')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /explore/members redirects to /creators/top with 301', function () {
|
|
$this->get('/explore/members')->assertRedirect('/creators/top')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /latest-artworks redirects to /discover/fresh with 301', function () {
|
|
$this->get('/latest-artworks')->assertRedirect('/discover/fresh')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /today-downloads redirects to /downloads/today with 301', function () {
|
|
$this->get('/today-downloads')->assertRedirect('/downloads/today')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /monthly-commentators redirects to /comments/monthly with 301', function () {
|
|
$this->get('/monthly-commentators')->assertRedirect('/comments/monthly')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /top-favourites redirects to /discover/top-rated with 301', function () {
|
|
$this->get('/top-favourites')->assertRedirect('/discover/top-rated')->assertStatus(301);
|
|
});
|
|
|
|
it('GET /downloads/today returns 200', function () {
|
|
$this->get('/downloads/today')->assertOk();
|
|
});
|
|
|
|
// ── /blog routes ─────────────────────────────────────────────────────────────
|
|
|
|
it('GET /blog returns 200', function () {
|
|
$this->get('/blog')->assertOk();
|
|
});
|
|
|
|
it('/blog page contains Blog heading', function () {
|
|
$html = $this->get('/blog')->assertOk()->getContent();
|
|
expect($html)->toContain('Blog');
|
|
});
|
|
|
|
it('/blog page includes canonical link', function () {
|
|
$html = $this->get('/blog')->assertOk()->getContent();
|
|
expect($html)->toContain('rel="canonical"');
|
|
});
|
|
|
|
it('/blog/:slug returns 404 for non-existent post', function () {
|
|
$this->get('/blog/this-post-does-not-exist-xyz')->assertNotFound();
|
|
});
|
|
|
|
it('published blog post is accessible at /blog/:slug', function () {
|
|
$post = \App\Models\BlogPost::factory()->create([
|
|
'slug' => 'hello-world',
|
|
'title' => 'Hello World Post',
|
|
'body' => '<p>Test content.</p>',
|
|
'is_published' => true,
|
|
'published_at' => now()->subDay(),
|
|
]);
|
|
|
|
$html = $this->get('/blog/hello-world')->assertOk()->getContent();
|
|
expect($html)->toContain('Hello World Post');
|
|
});
|
|
|
|
it('unpublished blog post returns 404', function () {
|
|
\App\Models\BlogPost::factory()->create([
|
|
'slug' => 'draft-post',
|
|
'title' => 'Draft Post',
|
|
'body' => '<p>Draft.</p>',
|
|
'is_published' => false,
|
|
]);
|
|
|
|
$this->get('/blog/draft-post')->assertNotFound();
|
|
});
|
|
|
|
it('/blog post includes Article JSON-LD', function () {
|
|
\App\Models\BlogPost::factory()->create([
|
|
'slug' => 'schema-post',
|
|
'title' => 'Schema Post',
|
|
'body' => '<p>Content.</p>',
|
|
'is_published' => true,
|
|
'published_at' => now()->subHour(),
|
|
]);
|
|
|
|
$html = $this->get('/blog/schema-post')->assertOk()->getContent();
|
|
expect($html)->toContain('"Article"');
|
|
});
|
|
|
|
// ── /pages routes ─────────────────────────────────────────────────────────────
|
|
|
|
it('GET /pages/:slug returns 404 for non-existent page', function () {
|
|
$this->get('/pages/does-not-exist-xyz')->assertNotFound();
|
|
});
|
|
|
|
it('published page is accessible at /pages/:slug', function () {
|
|
\App\Models\Page::factory()->create([
|
|
'slug' => 'test-page',
|
|
'title' => 'Test Page Title',
|
|
'body' => '<p>Page content.</p>',
|
|
'is_published' => true,
|
|
'published_at' => now()->subDay(),
|
|
]);
|
|
|
|
$html = $this->get('/pages/test-page')->assertOk()->getContent();
|
|
expect($html)->toContain('Test Page Title');
|
|
});
|
|
|
|
it('/about returns 200 when about page exists', function () {
|
|
\App\Models\Page::factory()->create([
|
|
'slug' => 'about',
|
|
'title' => 'About Skinbase',
|
|
'body' => '<p>About us.</p>',
|
|
'is_published' => true,
|
|
'published_at' => now()->subDay(),
|
|
]);
|
|
|
|
$this->get('/about')->assertOk();
|
|
});
|
|
|
|
it('/legal/terms returns 200 when legal-terms page exists', function () {
|
|
\App\Models\Page::factory()->create([
|
|
'slug' => 'legal-terms',
|
|
'title' => 'Terms of Service',
|
|
'body' => '<p>Terms.</p>',
|
|
'is_published' => true,
|
|
'published_at' => now()->subDay(),
|
|
]);
|
|
|
|
$this->get('/legal/terms')->assertOk();
|
|
});
|
|
|
|
// ── tags index layout ─────────────────────────────────────────────────────────
|
|
|
|
it('/tags page renders with ContentLayout breadcrumbs', function () {
|
|
$html = $this->get('/tags')->assertOk()->getContent();
|
|
// ContentLayout should inject breadcrumb nav
|
|
expect($html)->toContain('Tags');
|
|
});
|
|
|
|
it('/tags page includes canonical and robots', function () {
|
|
$html = $this->get('/tags')->assertOk()->getContent();
|
|
expect($html)
|
|
->toContain('rel="canonical"')
|
|
->toContain('index,follow');
|
|
});
|