test(auth): address review findings on sign-in prompt (PR A)
All checks were successful
All checks were successful
Correct the focus comment (refs #61), test Space key and focus state, prove scrollability at 200 percent text, drop needless minWidth, English doc comment, allow OutlinedButton as secondary action in standalone cards, English prompt text says plays instead of games. Refs #49 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f078c8a094
commit
7eaed46dc2
7 changed files with 52 additions and 14 deletions
|
|
@ -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/providers/sign_in_prompt_provider.dart';
|
||||||
import 'package:shuffle/features/auth/presentation/widgets/sign_in_prompt.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
|
/// Reads only [showSignInPromptProvider]; the decision whether the sign-in
|
||||||
/// Anmelde-Hinweis erscheint, liegt dort (Spec #49 § 5.1).
|
/// prompt appears lives there (Spec #49 § 5.1).
|
||||||
class HomeScreen extends ConsumerWidget {
|
class HomeScreen extends ConsumerWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
|
|
@ -26,7 +26,6 @@ class HomeScreen extends ConsumerWidget {
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minHeight: constraints.maxHeight - AppSpacing.lg * 2,
|
minHeight: constraints.maxHeight - AppSpacing.lg * 2,
|
||||||
minWidth: constraints.maxWidth,
|
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
"authLoginForgotPassword": "Forgot password?",
|
"authLoginForgotPassword": "Forgot password?",
|
||||||
"authLoginCreateAccount": "Create account",
|
"authLoginCreateAccount": "Create account",
|
||||||
"signInPromptTitle": "Your 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",
|
"signInPromptSignIn": "Sign in",
|
||||||
"signInPromptRegister": "Create account",
|
"signInPromptRegister": "Create account",
|
||||||
"authCheckEmailTitle": "Check your inbox",
|
"authCheckEmailTitle": "Check your inbox",
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @signInPromptBody.
|
/// No description provided for @signInPromptBody.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// 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;
|
String get signInPromptBody;
|
||||||
|
|
||||||
/// No description provided for @signInPromptSignIn.
|
/// No description provided for @signInPromptSignIn.
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get signInPromptTitle => 'Your account';
|
String get signInPromptTitle => 'Your account';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get signInPromptBody => 'Sign in to manage your collection and games.';
|
String get signInPromptBody => 'Sign in to manage your collection and plays.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get signInPromptSignIn => 'Sign in';
|
String get signInPromptSignIn => 'Sign in';
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,15 @@ void main() {
|
||||||
locale: locale,
|
locale: locale,
|
||||||
);
|
);
|
||||||
expect(tester.takeException(), isNull);
|
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.ensureVisible(registerButton);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -233,11 +241,31 @@ void main() {
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect(focusIn(registerButton), isTrue);
|
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.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(const Key('register_screen')), findsOneWidget);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Schlüssel sind `lowerCamelCase` mit Präfix `auth`.
|
||||||
- Titel: `headlineSmall`, darunter optional ein Einleitungssatz in `bodyMedium`. Titel als Semantics-Überschrift
|
- Titel: `headlineSmall`, darunter optional ein Einleitungssatz in `bodyMedium`. Titel als Semantics-Überschrift
|
||||||
(`Semantics(header: true)`).
|
(`Semantics(header: true)`).
|
||||||
- Banner (`AppBanner`) stehen **über** den Feldern, direkt unter dem Titel. Fehler am Feld stehen unter dem Feld.
|
- 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
|
- Eingaben bleiben bei jedem Fehler erhalten. Während `submitting` sind Knopf und Felder gesperrt, der Knopf
|
||||||
zeigt den Spinner und den gesprochenen Text `authSubmitting`.
|
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
|
- Nach einem Fehler setzt der Screen den Fokus auf das erste fehlerhafte Feld; bei Banner-Fehlern bleibt der
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const gotrue = new GoTrueClient();
|
||||||
/** Texte (app_en.arb). */
|
/** Texte (app_en.arb). */
|
||||||
const prompt = {
|
const prompt = {
|
||||||
title: 'Your account',
|
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', () => {
|
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.every((p) => p >= 0)).toBe(true);
|
||||||
expect([...pos].sort((x, y) => x - y)).toEqual(pos);
|
expect([...pos].sort((x, y) => x - y)).toEqual(pos);
|
||||||
|
|
||||||
// Flutter-Web behandelt Tab im eigenen Fokussystem; `document.activeElement` bleibt auf dem
|
// Reihenfolge ueber das Verhalten: erster Tab = „Sign in“, zweiter Tab = „Create account“,
|
||||||
// Semantics-Platzhalter. Deshalb wird die Reihenfolge ueber das Verhalten geprueft: erster Tab
|
// Enter loest den fokussierten Knopf aus. DOM-Fokus (toBeFocused) liegt nur auf „Create account“;
|
||||||
// = „Sign in“, zweiter Tab = „Create account“, Enter loest den fokussierten Knopf aus.
|
// „Sign in“ bekommt nie DOM-Fokus (kein tabindex, AppPrimaryButton verwirft focusable/focused
|
||||||
// Der Fokusring selbst wird im Canvas gezeichnet (Widget-Test, Theme).
|
// des FilledButton), siehe Issue #61. Der Fokusring selbst wird im Canvas gezeichnet.
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await expect(page).toHaveURL(/\/login$/);
|
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 expect(page.getByRole('button', { name: 'Create account', exact: true })).toBeVisible();
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
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 page.keyboard.press('Enter');
|
||||||
await expect(page).toHaveURL(/\/register$/);
|
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 }) => {
|
test('#49 Kriterium 1: angemeldet erscheint der Hinweis nicht', async ({ page }) => {
|
||||||
const email = randomEmail();
|
const email = randomEmail();
|
||||||
const password = randomPassword();
|
const password = randomPassword();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue