import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_alarmas.dart'; import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/l10n/formato_fechas.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/modelos/emisora.dart'; import 'package:pluriwave/pantallas/pantalla_alarmas.dart'; import 'package:pluriwave/servicios/servicio_alarmas.dart'; import 'package:pluriwave/tema/pluriwave_tokens.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; import '../helpers/fakes_alarmas.dart'; /// Tier 5 visual-fidelity closeout (items 7.1-7.4, audit id 2521) -- /// screens 4-14 batch. void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); Future montar( WidgetTester tester, { required EstadoAlarmas estadoAlarmas, EstadoRadio? radio, }) async { tester.view.physicalSize = const Size(1440, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final estadoRadio = radio ?? EstadoRadio( esPremium: () => true, audio: FakeServicioAudio(), favoritos: FakeServicioFavoritos(), radio: FakeServicioRadio(), servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadioInactiva(), iniciarAutomaticamente: false, ); if (radio == null) addTearDown(estadoRadio.dispose); await tester.pumpWidget( MultiProvider( providers: [ ChangeNotifierProvider.value(value: estadoRadio), ChangeNotifierProvider.value(value: estadoAlarmas), ], child: MaterialApp( locale: const Locale('en'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const Scaffold(body: PantallaAlarmas()), ), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); } EstadoAlarmas crearEstado() { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( esPremium: () => true, servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 11, 6, 0)), android: android, iniciarAutomaticamente: false, ); return estado; } group('visual fidelity (audit 7.1): "New" action', () { testWidgets('is a solid brand-teal pill with an add glyph, not the tonal ' 'auto_awesome button (t4:325)', (tester) async { final estado = crearEstado(); addTearDown(estado.dispose); await montar(tester, estadoAlarmas: estado); expect(find.byIcon(Icons.auto_awesome_rounded), findsNothing); expect(find.byIcon(Icons.add_rounded), findsOneWidget); final l10n = lookupAppLocalizations(const Locale('en')); final boton = tester.widget(find.byType(FilledButton)); final resuelto = boton.style?.backgroundColor?.resolve({}); expect(resuelto, PluriWaveTokens.brand); expect(find.text(l10n.createAlarmAction), findsOneWidget); }); }); group('visual fidelity (audit 7.2): next-alarm banner', () { testWidgets( 'is warmCoral-tinted and the Skip chip sits BESIDE the text, not ' 'below it (t4:327-331)', (tester) async { final estado = crearEstado(); addTearDown(estado.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'proxima', nombre: 'Despertar', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); await montar(tester, estadoAlarmas: estado); final l10n = lookupAppLocalizations(const Locale('en')); final banner = tester.widget( find.byKey(const ValueKey('next-alarm-banner')), ); final decoration = banner.decoration as BoxDecoration; expect( decoration.color, const Color(0xFFF4B860).withValues(alpha: 0.13), ); final skipY = tester.getCenter(find.text(l10n.alarmHeroSkipAction)).dy; final titleY = tester.getCenter(find.byIcon(Icons.alarm_on)).dy; expect( (skipY - titleY).abs() < 4, isTrue, reason: 'the Skip chip sits on the SAME row as the icon/text', ); }, ); }); group('visual fidelity (audit 7.3): vacation row date pill', () { testWidgets('shows the upcoming range as a "d-d MON" pill (t4:332)', ( tester, ) async { final estado = crearEstado(); addTearDown(estado.dispose); // The pill only renders for the ACTIVE or NEXT range // (`EstadoAlarmas.vacacionesProximas` keeps `inicioDia.isAfter(hoy)`), so // the range has to be in the future RELATIVE TO THE RUN. Hardcoded // calendar dates silently rot into the past and turn this into a // date-dependent failure; anchor on next month instead, days 4-18 so the // range never straddles a month boundary and the label stays "d–d MON". final ahora = DateTime.now(); final mesSiguiente = DateTime(ahora.year, ahora.month + 1); final inicio = DateTime(mesSiguiente.year, mesSiguiente.month, 4); final fin = DateTime(mesSiguiente.year, mesSiguiente.month, 18); await estado.crearRangoVacaciones( estado.servicio.crearRangoVacaciones( inicio: inicio, fin: fin, nombre: 'Summer', ), ); await montar(tester, estadoAlarmas: estado); // Assert against the same pure formatter the widget uses, so this stays // a check that the pill IS rendered in the right shape rather than a // duplicate of the formatter's own logic. expect(find.text(rangoFechasCorto('en', inicio, fin)), findsOneWidget); }); }); group('visual fidelity (audit 7.4): alarm card', () { testWidgets( 'shows a recurrence label next to the time and a station icon next ' 'to the station name (t4:337-345)', (tester) async { final estado = crearEstado(); addTearDown(estado.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'tarjeta', nombre: 'Despertar', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], emisora: Emisora( uuid: 'e1', nombre: 'Radio Uno', url: 'https://radio.example/stream', ), ), ); await montar(tester, estadoAlarmas: estado); final l10n = lookupAppLocalizations(const Locale('en')); expect( find.descendant( of: find.byKey(const ValueKey('tarjeta-alarma-tarjeta')), matching: find.text(l10n.dailyOption), ), findsOneWidget, ); expect( find.descendant( of: find.byKey(const ValueKey('tarjeta-alarma-tarjeta')), matching: find.byKey(const ValueKey('tarjeta-alarma-arte')), ), findsOneWidget, ); }, ); }); }