import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_navegacion.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/pantallas/pantalla_bienvenida.dart'; import 'package:provider/provider.dart'; /// WU17: `onboarding-welcome` spec — full-screen route (not a modal), /// content only (logo/headline/body/3 bullets/1 CTA), binding /// no-monetization guard, and CTA dismisses to Escuchar with the route /// popped. Widget _app({required EstadoNavegacionRaiz navegacion}) { return ChangeNotifierProvider.value( value: navegacion, child: const MaterialApp( locale: Locale('es'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: PantallaBienvenida(), ), ); } void main() { testWidgets( 'renders as a full-screen route with a logo, headline, body copy, ' 'exactly 3 feature bullets, and exactly one primary CTA', (tester) async { final navegacion = EstadoNavegacionRaiz(); addTearDown(navegacion.dispose); await tester.pumpWidget(_app(navegacion: navegacion)); await tester.pumpAndSettle(); // A full-screen route, never a modal dialog. expect(find.byType(Dialog), findsNothing); expect(find.byType(AlertDialog), findsNothing); expect(find.byType(Scaffold), findsOneWidget); // Audit 14.1 adds a second Image (the blurred backdrop), so "the // logo mark" is now asserted by key rather than "the only Image". expect( find.byKey(const ValueKey('welcome-logo')), findsOneWidget, reason: 'the logo mark', ); expect(find.text('Tu mundo, en directo'), findsOneWidget); expect( find.textContaining('53.412 emisoras de 238 países'), findsOneWidget, ); expect( find.byType(FilaCaracteristicaBienvenida), findsNWidgets(3), reason: 'exactly 3 feature bullets', ); expect( find.byType(FilledButton), findsOneWidget, reason: 'exactly 1 primary CTA', ); expect(find.text('Empezar a escuchar'), findsOneWidget); }, ); testWidgets('rendered content contains no monetization strings', ( tester, ) async { final navegacion = EstadoNavegacionRaiz(); addTearDown(navegacion.dispose); await tester.pumpWidget(_app(navegacion: navegacion)); await tester.pumpAndSettle(); final textoCompleto = tester .widgetList(find.byType(Text)) .map((widget) => widget.data ?? '') .join('\n'); expect(textoCompleto.contains('PRO'), isFalse); expect(textoCompleto.contains('€'), isFalse); expect(textoCompleto.contains(r'$'), isFalse); expect( RegExp(r'\d+\s*d[ií]as?\b', caseSensitive: false).hasMatch(textoCompleto), isFalse, reason: 'no day-count trial phrase (e.g. "14 días")', ); expect( find.textContaining('gratuita', findRichText: true), findsNothing, reason: 'no secondary "free version" link', ); expect( find.textContaining('gratis', findRichText: true), findsNothing, reason: 'no secondary "free version" link', ); // Only one tappable action on the whole screen: the primary CTA. expect(find.byType(TextButton), findsNothing); expect(find.byType(FilledButton), findsOneWidget); }); testWidgets( 'tapping the primary CTA switches to Escuchar and pops the welcome route', (tester) async { // Start on a DIFFERENT tab so a pass actually proves irA() ran, // rather than coasting on EstadoNavegacionRaiz's own escuchar default. final navegacion = EstadoNavegacionRaiz()..irA(RaizPluriWave.ajustes); addTearDown(navegacion.dispose); final navigatorKey = GlobalKey(); await tester.pumpWidget( ChangeNotifierProvider.value( value: navegacion, child: MaterialApp( navigatorKey: navigatorKey, locale: const Locale('es'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const Scaffold(body: Center(child: Text('home stand-in'))), ), ), ); unawaited( navigatorKey.currentState!.push( MaterialPageRoute(builder: (_) => const PantallaBienvenida()), ), ); await tester.pumpAndSettle(); expect(find.byType(PantallaBienvenida), findsOneWidget); await tester.tap(find.text('Empezar a escuchar')); await tester.pumpAndSettle(); expect(navegacion.actual, RaizPluriWave.escuchar); expect( find.byType(PantallaBienvenida), findsNothing, reason: 'the welcome route was removed from the back stack', ); expect(find.text('home stand-in'), findsOneWidget); }, ); testWidgets('visual fidelity (audit 14.1, proto t4 lines 687-689): a blurred ' 'banner backdrop is present behind the content', (tester) async { final navegacion = EstadoNavegacionRaiz(); addTearDown(navegacion.dispose); await tester.pumpWidget(_app(navegacion: navegacion)); await tester.pumpAndSettle(); expect( find.byKey(const ValueKey('welcome-background-art')), findsOneWidget, reason: 'the prototype draws a blurred, 40%-opacity banner image ' 'full-bleed behind the content — this screen used to be a ' 'plain Scaffold with no backdrop at all', ); }); testWidgets( 'visual fidelity (audit 14.2, proto t4 lines 693/706): the primary CTA ' 'is anchored to the bottom edge, not immediately after the bullets', (tester) async { final navegacion = EstadoNavegacionRaiz(); addTearDown(navegacion.dispose); tester.view.physicalSize = const Size(1440, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await tester.pumpWidget(_app(navegacion: navegacion)); await tester.pumpAndSettle(); final alturaPantalla = tester.view.physicalSize.height; final ctaAbajo = tester.getBottomLeft(find.byType(FilledButton)).dy; expect( ctaAbajo, greaterThan(alturaPantalla - 100), reason: 'the prototype anchors content to the bottom with two ' 'flexible spacers (lines 693, 706) so the CTA sits at the ' 'screen edge — it used to render immediately after the top ' 'padding instead', ); }, ); }