diff --git a/app/lib/features/home/presentation/home_screen.dart b/app/lib/features/home/presentation/home_screen.dart index 9f317f4..6044ac3 100644 --- a/app/lib/features/home/presentation/home_screen.dart +++ b/app/lib/features/home/presentation/home_screen.dart @@ -5,10 +5,10 @@ import 'package:shuffle/core/theme/app_spacing.dart'; import 'package:shuffle/features/auth/presentation/providers/sign_in_prompt_provider.dart'; import 'package:shuffle/features/auth/presentation/widgets/sign_in_prompt.dart'; -/// Platzhalter-Startseite. Wird durch die Katalogsuche bzw. QUE-09 ersetzt. +/// Placeholder home screen. To be replaced by the catalog search / QUE-09. /// -/// Liest nur [showSignInPromptProvider]; die Entscheidung, ob der -/// Anmelde-Hinweis erscheint, liegt dort (Spec #49 § 5.1). +/// Reads only [showSignInPromptProvider]; the decision whether the sign-in +/// prompt appears lives there (Spec #49 § 5.1). class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @@ -26,7 +26,6 @@ class HomeScreen extends ConsumerWidget { child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight - AppSpacing.lg * 2, - minWidth: constraints.maxWidth, ), child: Center( child: Column( diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb index ba96571..d466f62 100644 --- a/app/lib/l10n/app_en.arb +++ b/app/lib/l10n/app_en.arb @@ -91,7 +91,7 @@ "authLoginForgotPassword": "Forgot password?", "authLoginCreateAccount": "Create account", "signInPromptTitle": "Your account", - "signInPromptBody": "Sign in to manage your collection and games.", + "signInPromptBody": "Sign in to manage your collection and plays.", "signInPromptSignIn": "Sign in", "signInPromptRegister": "Create account", "authCheckEmailTitle": "Check your inbox", diff --git a/app/lib/l10n/app_localizations.dart b/app/lib/l10n/app_localizations.dart index 078f9d7..1f4ab53 100644 --- a/app/lib/l10n/app_localizations.dart +++ b/app/lib/l10n/app_localizations.dart @@ -413,7 +413,7 @@ abstract class AppLocalizations { /// No description provided for @signInPromptBody. /// /// In en, this message translates to: - /// **'Sign in to manage your collection and games.'** + /// **'Sign in to manage your collection and plays.'** String get signInPromptBody; /// No description provided for @signInPromptSignIn. diff --git a/app/lib/l10n/app_localizations_en.dart b/app/lib/l10n/app_localizations_en.dart index cc7e7c5..bba36f6 100644 --- a/app/lib/l10n/app_localizations_en.dart +++ b/app/lib/l10n/app_localizations_en.dart @@ -192,7 +192,7 @@ class AppLocalizationsEn extends AppLocalizations { String get signInPromptTitle => 'Your account'; @override - String get signInPromptBody => 'Sign in to manage your collection and games.'; + String get signInPromptBody => 'Sign in to manage your collection and plays.'; @override String get signInPromptSignIn => 'Sign in'; diff --git a/app/test/features/home/presentation/home_screen_test.dart b/app/test/features/home/presentation/home_screen_test.dart index e8c891d..146ddb0 100644 --- a/app/test/features/home/presentation/home_screen_test.dart +++ b/app/test/features/home/presentation/home_screen_test.dart @@ -188,7 +188,15 @@ void main() { locale: locale, ); expect(tester.takeException(), isNull); - // The page scrolls: the buttons stay reachable and tappable. + // The page scrolls: dragging moves the content, the buttons stay + // reachable and tappable. + final before = tester.getTopLeft(registerButton).dy; + await tester.drag( + find.byType(SingleChildScrollView), + const Offset(0, -200), + ); + await tester.pump(); + expect(tester.getTopLeft(registerButton).dy, lessThan(before)); await tester.ensureVisible(registerButton); await tester.pump(); expect( @@ -233,11 +241,31 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.tab); await tester.pump(); expect(focusIn(registerButton), isTrue); + // Visible focus: keyboard navigation switches to traditional highlight + // mode and the button holds the primary focus (the focus ring comes + // from the focused state of the theme). + expect( + FocusManager.instance.highlightMode, + FocusHighlightMode.traditional, + ); + expect(FocusManager.instance.primaryFocus?.hasPrimaryFocus, isTrue); await tester.sendKeyEvent(LogicalKeyboardKey.enter); await tester.pumpAndSettle(); expect(find.byKey(const Key('register_screen')), findsOneWidget); }, ); + + testWidgets('QUE-06 Krit. 4: Leertaste löst „Konto erstellen“ aus', ( + tester, + ) async { + await open(tester); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pumpAndSettle(); + expect(find.byKey(const Key('register_screen')), findsOneWidget); + }); }); } diff --git a/docs/design/auth-screens.md b/docs/design/auth-screens.md index d04f41c..cba224a 100644 --- a/docs/design/auth-screens.md +++ b/docs/design/auth-screens.md @@ -15,7 +15,7 @@ Schlüssel sind `lowerCamelCase` mit Präfix `auth`. - Titel: `headlineSmall`, darunter optional ein Einleitungssatz in `bodyMedium`. Titel als Semantics-Überschrift (`Semantics(header: true)`). - Banner (`AppBanner`) stehen **über** den Feldern, direkt unter dem Titel. Fehler am Feld stehen unter dem Feld. -- Genau ein Primärknopf (`AppPrimaryButton`, volle Breite). Sekundäraktionen sind `TextButton`s (48 dp). +- Genau ein Primärknopf (`AppPrimaryButton`, volle Breite). Sekundäraktionen sind `TextButton`s (48 dp). Für eigenständige Karten (z. B. `SignInPrompt`) ist ein `OutlinedButton` als Sekundäraktion zulässig. - Eingaben bleiben bei jedem Fehler erhalten. Während `submitting` sind Knopf und Felder gesperrt, der Knopf zeigt den Spinner und den gesprochenen Text `authSubmitting`. - Nach einem Fehler setzt der Screen den Fokus auf das erste fehlerhafte Feld; bei Banner-Fehlern bleibt der diff --git a/e2e/tests/web/krit49-anmeldung-einstieg.spec.ts b/e2e/tests/web/krit49-anmeldung-einstieg.spec.ts index b900655..37a353a 100644 --- a/e2e/tests/web/krit49-anmeldung-einstieg.spec.ts +++ b/e2e/tests/web/krit49-anmeldung-einstieg.spec.ts @@ -7,7 +7,7 @@ const gotrue = new GoTrueClient(); /** Texte (app_en.arb). */ const prompt = { title: 'Your account', - body: 'Sign in to manage your collection and games.', + body: 'Sign in to manage your collection and plays.', }; test.describe('#49 Kriterium 1 / QUE-09 Kriterium 13 (web): Anmelde-Hinweis auf der Startseite', () => { @@ -48,10 +48,10 @@ test.describe('#49 Kriterium 1 / QUE-09 Kriterium 13 (web): Anmelde-Hinweis auf expect(pos.every((p) => p >= 0)).toBe(true); expect([...pos].sort((x, y) => x - y)).toEqual(pos); - // Flutter-Web behandelt Tab im eigenen Fokussystem; `document.activeElement` bleibt auf dem - // Semantics-Platzhalter. Deshalb wird die Reihenfolge ueber das Verhalten geprueft: erster Tab - // = „Sign in“, zweiter Tab = „Create account“, Enter loest den fokussierten Knopf aus. - // Der Fokusring selbst wird im Canvas gezeichnet (Widget-Test, Theme). + // Reihenfolge ueber das Verhalten: erster Tab = „Sign in“, zweiter Tab = „Create account“, + // Enter loest den fokussierten Knopf aus. DOM-Fokus (toBeFocused) liegt nur auf „Create account“; + // „Sign in“ bekommt nie DOM-Fokus (kein tabindex, AppPrimaryButton verwirft focusable/focused + // des FilledButton), siehe Issue #61. Der Fokusring selbst wird im Canvas gezeichnet. await page.keyboard.press('Tab'); await page.keyboard.press('Enter'); await expect(page).toHaveURL(/\/login$/); @@ -62,10 +62,21 @@ test.describe('#49 Kriterium 1 / QUE-09 Kriterium 13 (web): Anmelde-Hinweis auf await expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeVisible(); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeFocused(); await page.keyboard.press('Enter'); await expect(page).toHaveURL(/\/register$/); }); + test('QUE-06 Kriterium 4: Leertaste loest „Create account“ aus', async ({ page }) => { + await open(page, '/'); + await expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeVisible(); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeFocused(); + await page.keyboard.press('Space'); + await expect(page).toHaveURL(/\/register$/); + }); + test('#49 Kriterium 1: angemeldet erscheint der Hinweis nicht', async ({ page }) => { const email = randomEmail(); const password = randomPassword();