Files
libra/tests/Feature/Design/RedesignIntegrationTest.php
T

122 lines
5.4 KiB
PHP

<?php
use App\Models\Page;
use App\Models\Post;
use App\Models\User;
use App\Models\WorkingHour;
beforeEach(function () {
foreach (range(0, 6) as $dow) {
WorkingHour::factory()->create(['day_of_week' => $dow, 'is_active' => true, 'start_time' => '09:00', 'end_time' => '13:00']);
}
Post::factory()->published()->count(3)->create();
Page::factory()->terms()->create();
Page::factory()->create(['slug' => 'privacy', 'title_en' => 'Privacy Policy', 'title_ar' => 'سياسة الخصوصية']);
});
function publicUrls(): array
{
return ['/', '/about', '/posts', '/posts/'.Post::first()->id, '/booking', '/booking/success', '/page/terms', '/page/privacy'];
}
test('public pages: one h1, alt text, no raw keys, no mockup text, in both languages', function () {
foreach (['ar', 'en'] as $locale) {
foreach (publicUrls() as $url) {
$html = $this->withSession(['locale' => $locale])->get($url)->assertOk()->getContent();
expect(substr_count($html, '<h1'))->toBe(1, "$locale $url h1");
preg_match_all('/<img\b[^>]*>/', $html, $imgs);
foreach ($imgs[0] as $img) {
expect($img)->toMatch('/\balt="[^"]+"/', "$locale $url img alt");
}
$text = strip_tags(preg_replace('~<(script|style)\b.*?</\1>~s', '', $html));
expect(preg_match('/(?<![\w.@\/-])(site|home|about|booking|posts|footer|pages|common|navigation|accessibility)\.[a-z][a-z0-9_]+(?![\w.-])/', $text, $m))
->toBe(0, "$locale $url raw key: ".($m[0] ?? ''));
expect($html)->not->toContain('Unsplash')->not->toContain('mockup');
expect($html)->toContain('site-progress')->toContain('id="site-nav"');
}
}
});
test('auth pages carry the auth layer in both languages', function () {
foreach (['ar', 'en'] as $locale) {
foreach (['/login', '/forgot-password'] as $url) {
$html = $this->withSession(['locale' => $locale])->get($url)->assertOk()->getContent();
expect($html)->toContain('site-auth')->not->toContain('Unsplash')->not->toContain('mockup');
}
}
});
test('unchanged areas load none of the public assets', function () {
$admin = User::factory()->admin()->create();
foreach (['/admin/dashboard', '/admin/bookings/pending', '/admin/posts'] as $url) {
$html = $this->actingAs($admin)->get($url)->assertOk()->getContent();
expect($html)->not->toContain('public.css')->not->toContain('public.js')->not->toContain('class="site')->not->toContain('site-auth');
}
auth()->logout();
$client = User::factory()->individual()->create();
foreach (['/client/dashboard', '/client/consultations/book'] as $url) {
$html = $this->actingAs($client)->get($url)->assertOk()->getContent();
expect($html)->not->toContain('public.css')->not->toContain('public.js')->not->toContain('class="site')->not->toContain('site-auth');
}
$nablus = $this->get('/forms/nablus-workshop')->assertOk()->getContent();
expect($nablus)->not->toContain('public.css')->not->toContain('class="site');
});
test('the public interactive controls carry no tabindex trap', function () {
$html = $this->get('/')->assertOk()->getContent();
expect($html)->not->toMatch('/tabindex="[1-9]/');
expect($html)->toContain('aria-label="'.__('site.nav_menu').'"');
});
test('measured contrast of the redesign colour pairs meets WCAG AA', function () {
$lum = function (string $hex) {
[$r, $g, $b] = array_map(fn ($i) => hexdec(substr(ltrim($hex, '#'), $i, 2)) / 255, [0, 2, 4]);
$f = fn ($c) => $c <= 0.03928 ? $c / 12.92 : (($c + 0.055) / 1.055) ** 2.4;
return 0.2126 * $f($r) + 0.7152 * $f($g) + 0.0722 * $f($b);
};
$ratio = function (string $a, string $b) use ($lum) {
[$hi, $lo] = $lum($a) >= $lum($b) ? [$lum($a), $lum($b)] : [$lum($b), $lum($a)];
return ($hi + 0.05) / ($lo + 0.05);
};
expect($ratio('#1E2219', '#F4F1EA'))->toBeGreaterThanOrEqual(4.5); // body text on cream
expect($ratio('#C4A882', '#2D3624'))->toBeGreaterThanOrEqual(4.5); // gold-lt small text on forest
expect($ratio('#C4A882', '#232A1C'))->toBeGreaterThanOrEqual(4.5); // gold-lt on footer
expect($ratio('#1E2219', '#A68966'))->toBeGreaterThanOrEqual(4.5); // text on the gold button
expect($ratio('#7A6648', '#F4F1EA'))->toBeGreaterThanOrEqual(4.5); // gold-ink eyebrow on cream
expect($ratio('#7A6648', '#FFFFFF'))->toBeGreaterThanOrEqual(4.5); // gold-ink post meta on white
expect($ratio('#5B6152', '#F4F1EA'))->toBeGreaterThanOrEqual(4.5); // muted text on cream
});
function nearestLivewireRoot(string $html, string $xpathTarget): ?string
{
$dom = new DOMDocument;
@$dom->loadHTML('<?xml encoding="utf-8"?>'.$html);
$xp = new DOMXPath($dom);
$node = $xp->query($xpathTarget)->item(0);
while ($node instanceof DOMElement) {
if ($node->hasAttribute('wire:id')) {
return $node->getAttribute('wire:snapshot');
}
$node = $node->parentNode;
}
return null;
}
test('the booking form on the home page and /booking sits inside its own Livewire component', function () {
$home = $this->get('/')->assertOk()->getContent();
expect(nearestLivewireRoot($home, '//form[contains(@class,"book-r")]'))->toContain('guestName');
$page = $this->get('/booking')->assertOk()->getContent();
expect(nearestLivewireRoot($page, '//*[contains(@class,"book-r")]'))->toContain('guestName');
});