Files
landingpage-haus-schleusingen/tests/Controllers/LocaleSwitcherTest.php
Hermes 391985cd42 fix(flags): replace hand-coded inline SVGs with official flag-icons assets
The previous inline flag SVGs were visually broken — most notably the
'en' Union Jack, which was reduced to a single X plus a cross and did
not resemble the real flag at all. The 'de' and 'ru' stripes also had
slight off-by-pixel rounding errors.

Switched to lipis/flag-icons (CC-BY 4.0) shipped as static files under
public/img/flags/. These are the canonical, professionally-designed
flag icons with correct proportions and all the details of the real
flags. Loaded via plain <img> tags (no JS, no external CDN at
runtime, no FOUC, no extra request after the page is cached).

Locale code mapping: en -> gb (per ADR-002, en = en-GB). Unknown
locales fall back to a 1x1 transparent gif so the layout stays
intact.
2026-06-04 19:43:23 +00:00

154 lines
6.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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:
* - exactly one <details class="locale-switcher"> dropdown,
* - 4 menu 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=...,
* - the trigger and every menu item contain a flag SVG,
* - the rendered label is in the current locale's language.
*/
final class LocaleSwitcherTest extends TestCase
{
#[Test]
public function rendersSingleDropdownForAllSupportedLocales(): void
{
$html = (new LocaleSwitcher('en', '/'))->render();
// exactly one <details class="locale-switcher"> (no -mobile suffix, no desktop <ul>)
self::assertStringContainsString('<details class="locale-switcher">', $html);
self::assertStringNotContainsString('locale-switcher-mobile', $html);
self::assertStringNotContainsString('<ul class="locale-switcher"', $html);
self::assertStringNotContainsString('locale-switcher__item', $html);
// the menu lists all 4 supported locales
self::assertSame(4, substr_count($html, 'class="locale-switcher__option'), 'expected 4 menu options');
// 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",
);
}
// 1 flag in trigger + 4 flags in menu = 5 total
self::assertSame(5, substr_count($html, 'class="flag"'), 'expected 5 flag SVGs (1 trigger + 4 menu)');
}
#[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&amp;return=%2Ffoo%2Fbar"', $html);
self::assertStringContainsString('href="/locale?set=uk&amp;return=%2Ffoo%2Fbar"', $html);
self::assertStringContainsString('href="/locale?set=ru&amp;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', 'de.svg'],
'EN en-GB' => ['en', 'gb.svg'],
'UK Ukraine' => ['uk', 'ua.svg'],
'RU Russia' => ['ru', 'ru.svg'],
];
}
#[Test]
public function flagImgReturnsValidImgForEverySupportedLocale(): void
{
foreach (Locale::SUPPORTED as $code) {
$img = LocaleSwitcher::flagImg($code);
self::assertStringStartsWith('<img', $img);
self::assertStringContainsString('class="flag"', $img);
self::assertStringContainsString('width="24" height="18"', $img);
self::assertStringContainsString('alt=""', $img);
self::assertStringEndsWith('>', $img);
}
}
#[Test]
public function flagImgHasFallbackForUnknownLocale(): void
{
$img = LocaleSwitcher::flagImg('xx');
self::assertStringStartsWith('<img', $img);
self::assertStringContainsString('class="flag"', $img);
// 1×1 transparent gif keeps the layout stable even when the
// locale code is not one of our four.
self::assertStringContainsString('data:image/gif', $img);
}
#[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);
}
#[Test]
public function triggerContainsCurrentLocaleFlag(): void
{
// The closed dropdown shows the current locale's flag in the trigger
$html = (new LocaleSwitcher('de', '/'))->render();
// The first <img class="flag"> in the document is the trigger and it
// must point at the German flag asset under /img/flags/.
$deFlag = LocaleSwitcher::flagImg('de');
$pos = strpos($html, $deFlag);
self::assertNotFalse($pos, 'expected German flag <img> in the trigger (first <img class="flag"> in document)');
self::assertStringContainsString('src="/img/flags/de.svg"', $deFlag);
}
}