Adds a public "درباره ما" page at /درباره-ما reusing the landing theme
(same webpack entry, header, footer, registration modal). Section ids
match the header anchors so the nav works on this page too.
The explicit route takes precedence over LandingController's catch-all
/{slug}, which is registered with priority -10.
Also links it from the header nav and footer quick links, and lists it
in sitemap.xml.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
161 lines
6.4 KiB
PHP
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(2 + 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 صفحهٔ اصلی را بارگذاری کند');
|
|
}
|
|
}
|