feat(i18n): responsive locale-switcher with SVG flags (closes #75)
This commit is contained in:
128
tests/Controllers/LocaleSwitcherTest.php
Normal file
128
tests/Controllers/LocaleSwitcherTest.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Controllers;
|
||||
|
||||
use App\Controllers\LocaleSwitcher;
|
||||
use App\Core\Locale;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Renders the language switcher widget and checks that:
|
||||
* - 4 items, one per supported locale,
|
||||
* - the active locale is marked aria-current="true" and is a <span>,
|
||||
* - inactive locales are <a> links to /locale?set=...&return=...,
|
||||
* - every item contains a flag SVG,
|
||||
* - the rendered label is in the current locale's language.
|
||||
*/
|
||||
final class LocaleSwitcherTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function rendersFourItemsForAllSupportedLocales(): void
|
||||
{
|
||||
$html = (new LocaleSwitcher('en', '/'))->render();
|
||||
self::assertStringContainsString('<ul class="locale-switcher"', $html);
|
||||
|
||||
// The 3 inactive locales render as <a hreflang="..">. The active
|
||||
// locale renders as <span lang=".."> (no hreflang). Together all
|
||||
// 4 must be present in either form.
|
||||
foreach (Locale::SUPPORTED as $code) {
|
||||
self::assertTrue(
|
||||
str_contains($html, 'hreflang="' . $code . '"') || str_contains($html, 'lang="' . $code . '"'),
|
||||
"locale '$code' is missing from switcher",
|
||||
);
|
||||
}
|
||||
self::assertSame(4, substr_count($html, 'class="flag"'), 'expected 4 flag SVGs');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function marksCurrentLocaleWithAriaCurrentAndSpan(): void
|
||||
{
|
||||
$html = (new LocaleSwitcher('uk', '/'))->render();
|
||||
self::assertStringContainsString('is-current', $html);
|
||||
self::assertStringContainsString('aria-current="true"', $html);
|
||||
self::assertStringContainsString('lang="uk"', $html);
|
||||
|
||||
// active option must be a <span>, not an <a>
|
||||
self::assertMatchesRegularExpression(
|
||||
'/<span class="locale-switcher__option is-current"[^>]*aria-current="true"[^>]*lang="uk"/',
|
||||
$html,
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function inactiveLocalesAreLinksToLocaleController(): void
|
||||
{
|
||||
$html = (new LocaleSwitcher('de', '/foo/bar'))->render();
|
||||
self::assertStringContainsString('href="/locale?set=en&return=%2Ffoo%2Fbar"', $html);
|
||||
self::assertStringContainsString('href="/locale?set=uk&return=%2Ffoo%2Fbar"', $html);
|
||||
self::assertStringContainsString('href="/locale?set=ru&return=%2Ffoo%2Fbar"', $html);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function stripsQueryAndFragmentFromReturnPath(): void
|
||||
{
|
||||
$html = (new LocaleSwitcher('de', '/?lang=uk#kontakt'))->render();
|
||||
// sanitisePath keeps only the path part
|
||||
self::assertStringContainsString('return=%2F', $html);
|
||||
self::assertStringNotContainsString('return=%2F%3Flang', $html);
|
||||
self::assertStringNotContainsString('return=%2F%23kontakt', $html);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function rejectsPathsThatDoNotStartWithSlash(): void
|
||||
{
|
||||
$html = (new LocaleSwitcher('de', 'https://evil.example/'))->render();
|
||||
// sanitisePath falls back to '/'
|
||||
self::assertStringContainsString('return=%2F', $html);
|
||||
self::assertStringNotContainsString('evil.example', $html);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{string, string}>
|
||||
*/
|
||||
public static function flagDataProvider(): array
|
||||
{
|
||||
return [
|
||||
'DE Germany' => ['de', '#FFCC00'],
|
||||
'EN UnionJack' => ['en', '#C8102E'],
|
||||
'UK Ukraine' => ['uk', '#FFD500'],
|
||||
'RU Russia' => ['ru', '#D52B1E'],
|
||||
];
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function flagSvgReturnsValidSvgForEverySupportedLocale(): void
|
||||
{
|
||||
foreach (Locale::SUPPORTED as $code) {
|
||||
$svg = LocaleSwitcher::flagSvg($code);
|
||||
self::assertStringStartsWith('<svg', $svg);
|
||||
self::assertStringContainsString('viewBox="0 0 24 16"', $svg);
|
||||
self::assertStringContainsString('aria-hidden="true"', $svg);
|
||||
self::assertStringContainsString('focusable="false"', $svg);
|
||||
self::assertStringContainsString('class="flag"', $svg);
|
||||
self::assertStringEndsWith('</svg>', $svg);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function flagSvgHasFallbackForUnknownLocale(): void
|
||||
{
|
||||
$svg = LocaleSwitcher::flagSvg('xx');
|
||||
self::assertStringStartsWith('<svg', $svg);
|
||||
self::assertStringContainsString('class="flag"', $svg);
|
||||
self::assertStringEndsWith('</svg>', $svg);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function ariaLabelUsesCurrentLocaleName(): void
|
||||
{
|
||||
$htmlDe = (new LocaleSwitcher('de', '/'))->render();
|
||||
$htmlEn = (new LocaleSwitcher('en', '/'))->render();
|
||||
|
||||
self::assertStringContainsString('aria-label="Sprache wählen"', $htmlDe);
|
||||
self::assertStringContainsString('aria-label="Choose language"', $htmlEn);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user