Files
clinicpro/tests/Shared/LandingPageTest.php
T
hamedandClaude Opus 5 40b1da64be fix(seo): shorten the footer and center the related-solutions cards
Every landing page was listed in the footer, so adding keyword clusters made
that one column taller than the rest of the footer put together. It now lists
six — the head terms and the two biggest specialties — and the rest stay
reachable through the "other solutions" block each page already renders.

That trade only holds if nothing falls out of the link graph, so the old test
asserting "the footer links to every page" is replaced by one that walks the
related-page graph from the footer links and fails when any page is not
reachable. Physiotherapy was the one page that would have been orphaned; the
polyclinic page now links to it.

The related-solutions heading said "for your trade", which reads oddly for the
clinics it is addressing, and its cards were left-aligned in a centered
section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 18:26:59 +03:30

161 lines
6.4 KiB
PHP

<?php
namespace App\Tests\Shared;
use App\Shared\Landing\LandingRegistry;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* صفحات فرود واقعاً رندر می‌شوند و متادیتای هرکدام مالِ خودش است.
*
* روت `/{slug}` هر مسیر تک‌بخشی را می‌گیرد، پس اینجا هم بررسی می‌شود که صفحهٔ اصلی و
* مسیرهای موجود را نخورده باشد.
*/
class LandingPageTest extends WebTestCase
{
private KernelBrowser $client;
private LandingRegistry $registry;
protected function setUp(): void
{
$this->client = static::createClient();
$this->registry = new LandingRegistry();
}
private function html(string $path): string
{
$this->client->request('GET', $path);
return $this->client->getResponse()->getContent();
}
public function testEveryLandingRenders(): void
{
foreach ($this->registry->all() as $slug => $page) {
$html = $this->html('/' . $slug);
self::assertSame(200, $this->client->getResponse()->getStatusCode(), "صفحهٔ «{$slug}» باز نشد");
self::assertStringContainsString('<h1 class="reveal">' . $page->h1 . '</h1>', $html);
self::assertStringContainsString('<title>' . $page->metaTitle . '</title>', $html);
self::assertStringContainsString('content="' . $page->metaDescription . '"', $html);
}
}
public function testCanonicalPointsAtTheSamePage(): void
{
foreach (array_keys($this->registry->all()) as $slug) {
$html = $this->html('/' . $slug);
self::assertMatchesRegularExpression(
'~<link rel="canonical" href="[^"]+/' . preg_quote($slug, '~') . '"/>~u',
$html,
"canonical صفحهٔ «{$slug}» به خودش اشاره نمی‌کند",
);
}
}
public function testUnknownSlugIsNotFound(): void
{
$this->client->request('GET', '/یک-اسلاگ-که-وجود-ندارد');
self::assertSame(404, $this->client->getResponse()->getStatusCode());
}
/** روت catch-all نباید صفحهٔ اصلی را بخورد. */
public function testHomePageStillWorks(): void
{
$html = $this->html('/');
self::assertSame(200, $this->client->getResponse()->getStatusCode());
self::assertStringContainsString('هوشمند، سریع و', $html);
}
public function testExistingRoutesAreNotShadowed(): void
{
foreach (['/sitemap.xml', '/health'] as $path) {
$this->client->request('GET', $path);
self::assertSame(200, $this->client->getResponse()->getStatusCode(), "{$path} گرفته شد");
}
}
public function testSitemapListsHomeAndEveryLanding(): void
{
$xml = $this->html('/sitemap.xml');
$doc = new \SimpleXMLElement($xml);
// خانه + هر صفحهٔ فرود. عدد از خودِ رجیستری می‌آید تا افزودن خوشهٔ کلیدواژهٔ
// تازه این تست را نشکند، ولی جاافتادن یک صفحه از sitemap هنوز قرمز شود.
self::assertCount(1 + count($this->registry->all()), $doc->url);
foreach (array_keys($this->registry->all()) as $slug) {
self::assertStringContainsString(rawurlencode($slug), $xml, {$slug}» در sitemap نیست");
}
}
/** بدون لینک داخلی، صفحات فقط از sitemap دیده می‌شوند. */
public function testFooterLinksToTheFeaturedLandingsOnEveryPage(): void
{
foreach (['/', '/' . LandingRegistry::CRM] as $path) {
$html = $this->html($path);
foreach ($this->registry->featured() as $page) {
self::assertStringContainsString('href="/' . $page->slug . '"', $html, "لینک «{$page->slug}» در {$path} نیست");
}
}
}
/**
* فوتر دیگر همهٔ صفحه‌ها را فهرست نمی‌کند، پس باید ثابت بماند که از همان چند
* لینکِ فوتر، با دنبال‌کردن «راهکارهای دیگر»، به هر صفحه می‌رسیم. صفحه‌ای که فقط
* در sitemap باشد عملاً بدون لینک داخلی است و رتبه نمی‌گیرد.
*/
public function testEveryLandingIsReachableFromTheFooterLinks(): void
{
$pages = $this->registry->all();
$queue = array_map(static fn ($page): string => $page->slug, $this->registry->featured());
$reached = array_flip($queue);
while ($queue !== []) {
$slug = array_shift($queue);
foreach ($pages[$slug]->related as $next) {
if (!isset($reached[$next])) {
$reached[$next] = true;
$queue[] = $next;
}
}
}
foreach (array_keys($pages) as $slug) {
self::assertArrayHasKey($slug, $reached, {$slug}» از هیچ صفحه‌ای لینک نمی‌گیرد");
}
}
public function testStructuredDataIsValidJson(): void
{
foreach (array_keys($this->registry->all()) as $slug) {
$html = $this->html('/' . $slug);
preg_match('~<script type="application/ld\+json">(.*?)</script>~s', $html, $m);
self::assertNotEmpty($m, "صفحهٔ «{$slug}» بلاک JSON-LD ندارد");
$data = json_decode(html_entity_decode($m[1]), true, flags: JSON_THROW_ON_ERROR);
$types = array_column($data['@graph'], '@type');
self::assertContains('SoftwareApplication', $types);
self::assertContains('BreadcrumbList', $types);
self::assertContains('FAQPage', $types);
}
}
/** تم مشترک است، پس همان CSS صفحهٔ اصلی باید بارگذاری شود. */
public function testLandingUsesTheHomepageTheme(): void
{
preg_match_all('~href="(/build/[^"]+\.css)"~', $this->html('/'), $home);
preg_match_all('~href="(/build/[^"]+\.css)"~', $this->html('/' . LandingRegistry::BEAUTY), $landing);
self::assertNotEmpty($home[1]);
self::assertSame($home[1], $landing[1], 'لندینگ باید همان CSS صفحهٔ اصلی را بارگذاری کند');
}
}