Build the first-run welcome surface from mockup screen 14, stripped of its entire monetization block: no PRO pill, no "14 dias PRO gratis" trial line, no pricing card, no secondary "free version" link. Ships only logo, headline, body copy, exactly 3 feature bullets, and the single "Empezar a escuchar" CTA, as a full-screen route (not a modal). Spanish copy is re-cast from the mockup's "tu" form to the app's established voseo register (matching ~750 existing app_es.arb lines), using "auto" instead of "coche" per the one existing precedent. The 3 bullet icons reuse existing tokens (electricMagenta/liveGreen/ warmCoral) that already match the mockup's own hex values for them. CTA switches to the Escuchar tab and pops the route. Wiring this screen into the real first-launch flow (main.dart/app.dart) is left for a follow-up unit, same shape as WU15/WU15b - this WU only covers the isolated, tested screen per its own task list and verify command. WU17.
145 lines
4.8 KiB
Dart
145 lines
4.8 KiB
Dart
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<EstadoNavegacionRaiz>.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);
|
|
|
|
expect(find.byType(Image), 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<Text>(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<NavigatorState>();
|
|
|
|
await tester.pumpWidget(
|
|
ChangeNotifierProvider<EstadoNavegacionRaiz>.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<void>(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);
|
|
},
|
|
);
|
|
}
|