feat(fase-20): /settings language selector gains 'Euskera' + EU-01/02 e2e

Adds the third button to the language tray in /settings; the local state
union widens to 'en' | 'es' | 'eu' (matches the new LanguageCode). Each
button carries a stable `settings-language-{code}` testid for the e2e
hooks.

Playwright `tests/e2e/euskera.test.ts`:
  * EU-01 — Ana clicks the eu button; assertion lands on the selected-
    state class flip (synchronous proof the click reached
    switchLanguage) + a DB poll for `users.language='eu'`. We
    deliberately don't assert on the sidebar nav label re-rendering:
    ParaglideJS's {#key lang} re-render is wired to URL routing, not
    the runtime setLanguageTag call, so the visible labels only flip on
    the next navigation — a known cross-cutting gap outside Fase 20's
    scope.
  * EU-02 — Fresh browser context with locale + Accept-Language=eu;
    Eva logs in fresh and the Fase 10.8 bootstrap path UPDATEs
    users.language to 'eu'. /onboarding renders 'Ongi etorri…' — the
    Basque heading the seed produces. Cleanup restores Ana to seed 'es'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-19 04:00:35 +02:00
parent e9c29dcf38
commit 878402e579
2 changed files with 143 additions and 5 deletions

View File

@@ -36,7 +36,7 @@
// Profile state loaded from public.users
let displayNameInput = $state('');
let language = $state<'en' | 'es'>('en');
let language = $state<'en' | 'es' | 'eu'>('en');
let avatarType = $state<'initials' | 'emoji' | 'upload'>('initials');
let avatarEmoji = $state<string | null>(null);
let avatarUrl = $state<string | null>(null);
@@ -119,7 +119,7 @@
if (data) {
displayNameInput = data.display_name;
language = data.language as 'en' | 'es';
language = data.language as 'en' | 'es' | 'eu';
avatarType = data.avatar_type as 'initials' | 'emoji' | 'upload';
avatarEmoji = data.avatar_emoji;
avatarUrl = data.avatar_url;
@@ -246,7 +246,7 @@
}
}
async function switchLanguage(lang: 'en' | 'es') {
async function switchLanguage(lang: 'en' | 'es' | 'eu') {
language = lang;
setLanguageTag(lang);
await getSupabase()
@@ -438,9 +438,10 @@
{m.settings_language()}
</p>
<div class="flex gap-2">
{#each [['en', 'English'], ['es', 'Español']] as [code, label]}
{#each [['en', 'English'], ['es', 'Español'], ['eu', 'Euskera']] as [code, label]}
<button
onclick={() => switchLanguage(code as 'en' | 'es')}
data-testid="settings-language-{code}"
onclick={() => switchLanguage(code as 'en' | 'es' | 'eu')}
class="rounded-md px-4 py-2 text-sm font-medium transition-colors
{language === code
? 'bg-slate-900 text-white dark:bg-slate-50 dark:text-slate-900'