Implements KTO-01 criteria 1-4, 10 and 11 (client part). Refs #1 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
126 lines
4.3 KiB
Dart
126 lines
4.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_riverpod/misc.dart' show Override;
|
|
import 'package:flutter_riverpod/misc.dart' show ProviderListenable;
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:mocktail/mocktail.dart';
|
|
import 'package:shuffle/core/l10n/app_locale.dart';
|
|
import 'package:shuffle/core/platform/platform_info.dart';
|
|
import 'package:shuffle/core/platform/url_opener.dart';
|
|
import 'package:shuffle/core/result/result.dart';
|
|
import 'package:shuffle/features/auth/domain/auth_failure.dart';
|
|
import 'package:shuffle/features/auth/domain/auth_repository.dart';
|
|
import 'package:shuffle/features/auth/domain/email_link_type.dart';
|
|
import 'package:shuffle/features/auth/domain/registration_input.dart';
|
|
import 'package:shuffle/features/auth/domain/validation/common_passwords.dart';
|
|
import 'package:shuffle/features/auth/presentation/auth_routes.dart';
|
|
import 'package:shuffle/features/auth/presentation/providers/auth_providers.dart';
|
|
|
|
import '../../../helpers/fakes.dart';
|
|
import '../../../helpers/pump_app.dart';
|
|
|
|
class MockAuthRepository extends Mock implements AuthRepository {}
|
|
|
|
class FakeRegistrationInput extends Fake implements RegistrationInput {}
|
|
|
|
/// [AppLocale] with a fixed language (tests do not depend on the device).
|
|
class FixedAppLocale extends AppLocale {
|
|
FixedAppLocale(this.locale);
|
|
|
|
final Locale locale;
|
|
|
|
@override
|
|
Locale build() => locale;
|
|
}
|
|
|
|
const commonTestPasswords = SetCommonPasswordList({'passwort1234'});
|
|
|
|
void registerAuthFallbacks() {
|
|
registerFallbackValue(FakeRegistrationInput());
|
|
registerFallbackValue(EmailLinkType.confirmation);
|
|
}
|
|
|
|
Result<void, AuthFailure> ok() => const Result.success(null);
|
|
|
|
Result<void, AuthFailure> failed(AuthFailure value) => Result.failure(value);
|
|
|
|
/// Overrides for auth UI tests: repository mock, fixed language, common
|
|
/// password list, URL opener and platform.
|
|
List<Override> authUiOverrides(
|
|
MockAuthRepository repository, {
|
|
Locale locale = const Locale('de'),
|
|
UrlOpener? urlOpener,
|
|
bool mobileWeb = false,
|
|
Future<CommonPasswordList> Function()? commonPasswords,
|
|
List<Override> extra = const [],
|
|
}) => testOverrides(
|
|
overrides: [
|
|
authRepositoryProvider.overrideWithValue(repository),
|
|
appLocaleProvider.overrideWith(() => FixedAppLocale(locale)),
|
|
commonPasswordListProvider.overrideWith(
|
|
(ref) => commonPasswords?.call() ?? Future.value(commonTestPasswords),
|
|
),
|
|
urlOpenerProvider.overrideWithValue(urlOpener ?? (_) async => true),
|
|
isMobileWebProvider.overrideWithValue(mobileWeb),
|
|
...extra,
|
|
],
|
|
);
|
|
|
|
/// The text field inside an `AppTextField`/`AppPasswordField` with [key].
|
|
Finder fieldOf(String key) =>
|
|
find.descendant(of: find.byKey(Key(key)), matching: find.byType(TextField));
|
|
|
|
/// Keeps an auto-dispose provider alive during a controller test.
|
|
void keepAlive(ProviderContainer container, ProviderListenable<Object?> p) {
|
|
container.listen(p, (_, _) {});
|
|
}
|
|
|
|
typedef AuthUiHandle = ({ProviderContainer container, GoRouter router});
|
|
|
|
/// Pumps the real auth routes (register, check-email, confirm; the others are
|
|
/// placeholders) at [location] on a phone-sized screen.
|
|
Future<AuthUiHandle> pumpAuthRoute(
|
|
WidgetTester tester,
|
|
MockAuthRepository repository, {
|
|
String location = '/register',
|
|
Object? extra,
|
|
Locale locale = const Locale('de'),
|
|
ThemeMode themeMode = ThemeMode.light,
|
|
double? textScale,
|
|
Size size = const Size(400, 800),
|
|
UrlOpener? urlOpener,
|
|
bool mobileWeb = false,
|
|
bool settle = true,
|
|
}) async {
|
|
tester.view.physicalSize = size;
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final router = GoRouter(
|
|
initialLocation: location,
|
|
initialExtra: extra,
|
|
routes: authRoutes,
|
|
);
|
|
addTearDown(router.dispose);
|
|
final container = await pumpApp(
|
|
tester,
|
|
router: router,
|
|
locale: locale,
|
|
themeMode: themeMode,
|
|
textScale: textScale,
|
|
settle: settle,
|
|
overrides: authUiOverrides(
|
|
repository,
|
|
locale: locale,
|
|
urlOpener: urlOpener,
|
|
mobileWeb: mobileWeb,
|
|
),
|
|
);
|
|
return (container: container, router: router);
|
|
}
|
|
|
|
/// All texts currently visible (for comparing two screens exactly).
|
|
List<String> visibleTexts(WidgetTester tester) => [
|
|
for (final text in tester.widgetList<Text>(find.byType(Text)))
|
|
if (text.data != null) text.data!,
|
|
];
|