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/gen/app_localizations.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/pantallas/pantalla_alarmas.dart'; import 'package:pluriwave/servicios/servicio_alarmas.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; import '../helpers/fakes_alarmas.dart'; /// Item 5: the alarm list must show which days a `diasSemana` alarm /// actually fires on (e.g. "Lun, Mié, Vie"), not the generic "Días" label, /// plus surface fade/volume/vacation-pause state when they are genuinely /// informative -- without cluttering the row. void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); Future<(EstadoRadio, EstadoAlarmas)> montar( WidgetTester tester, { required AlarmaMusical alarma, List vacaciones = const [], }) async { tester.view.physicalSize = const Size(1440, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final radio = EstadoRadio( audio: FakeServicioAudio(), favoritos: FakeServicioFavoritos(), radio: FakeServicioRadio(), servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadioInactiva(), iniciarAutomaticamente: false, ); addTearDown(radio.dispose); final android = FakePuertoAlarmasAndroid(); final estadoAlarmas = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 11, 6, 0)), android: android, iniciarAutomaticamente: false, ); addTearDown(estadoAlarmas.dispose); addTearDown(android.dispose); await estadoAlarmas.guardarAlarma(alarma); if (vacaciones.isNotEmpty) { await estadoAlarmas.guardarVacaciones(vacaciones); } await tester.pumpWidget( MultiProvider( providers: [ ChangeNotifierProvider.value(value: radio), ChangeNotifierProvider.value(value: estadoAlarmas), ], child: MaterialApp( locale: const Locale('es'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const Scaffold(body: PantallaAlarmas()), ), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); return (radio, estadoAlarmas); } testWidgets( 'diasSemana alarm shows the ACTUAL configured days (Lun, Mié, Vie), ' 'not the generic "Días" label', (tester) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-dias', nombre: 'Entre semana', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diasSemana, diasSemana: [DateTime.monday, DateTime.wednesday, DateTime.friday], ), ); expect(find.text('Lun, Mié, Vie'), findsOneWidget); expect(find.text('Días'), findsNothing); }, ); testWidgets('daily alarm still shows "Diaria" (unaffected)', ( tester, ) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-diaria', nombre: 'Todos los días', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(find.text('Diaria'), findsOneWidget); }); testWidgets('one-time alarm still shows "Una vez" (unaffected)', ( tester, ) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-unica', nombre: 'Una sola vez', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.unica, diasSemana: [], fechaUnica: null, ), ); expect(find.text('Una vez'), findsOneWidget); }); testWidgets( 'a diasSemana alarm with an (invalid/legacy) empty diasSemana falls ' 'back to the generic label instead of showing nothing', (tester) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-dias-vacio', nombre: 'Corrupta', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diasSemana, diasSemana: [], ), ); expect(find.text('Días'), findsOneWidget); }, ); testWidgets('a configured fade-in shows a compact "Fade-in Ns" detail', ( tester, ) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-fade', nombre: 'Con fade', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], fadeInSegundos: 8, ), ); expect(find.textContaining('Fade-in 8s'), findsOneWidget); }); testWidgets('no fade-in (0s, the default) shows no fade detail', ( tester, ) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-sin-fade', nombre: 'Sin fade', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], fadeInSegundos: 0, ), ); expect(find.textContaining('Fade-in'), findsNothing); }); testWidgets( 'a non-default volume shows a compact percentage detail', (tester) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-vol', nombre: 'Volumen bajo', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], volumen: 0.5, ), ); expect(find.textContaining('50%'), findsOneWidget); }, ); testWidgets('the default volume (85%) shows no volume detail', ( tester, ) async { await montar( tester, alarma: const AlarmaMusical( id: 'a-vol-default', nombre: 'Volumen default', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(find.textContaining('85%'), findsNothing); }); testWidgets( 'an alarm paused by a CURRENTLY active vacation range shows a ' 'vacation-paused detail', (tester) async { final l10n = lookupAppLocalizations(const Locale('es')); await montar( tester, alarma: const AlarmaMusical( id: 'a-vacaciones', nombre: 'Pausada', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: false, ), vacaciones: [ // Wide, real-wall-clock-safe range (rangoVacacionesActivo() // defaults to the REAL DateTime.now(), not this file's injected // `reloj`) -- deliberately spans many years so the test stays // valid regardless of exactly when it runs. RangoVacaciones( id: 'v1', nombre: 'Verano', inicio: DateTime(2020, 1, 1), fin: DateTime(2030, 12, 31), ), ], ); expect( find.textContaining(l10n.alarmCardVacationPausedBadge), findsOneWidget, ); }, ); testWidgets( 'an alarm that DOES sound during vacations shows NO vacation-paused ' 'detail even with an active range', (tester) async { final l10n = lookupAppLocalizations(const Locale('es')); await montar( tester, alarma: const AlarmaMusical( id: 'a-suena-vacaciones', nombre: 'Suena igual', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: true, ), vacaciones: [ // Wide, real-wall-clock-safe range (rangoVacacionesActivo() // defaults to the REAL DateTime.now(), not this file's injected // `reloj`) -- deliberately spans many years so the test stays // valid regardless of exactly when it runs. RangoVacaciones( id: 'v1', nombre: 'Verano', inicio: DateTime(2020, 1, 1), fin: DateTime(2030, 12, 31), ), ], ); expect( find.textContaining(l10n.alarmCardVacationPausedBadge), findsNothing, ); }, ); testWidgets( 'sonarEnVacaciones:false with NO currently-active vacation range shows ' 'no vacation-paused detail (nothing to be paused BY right now)', (tester) async { final l10n = lookupAppLocalizations(const Locale('es')); await montar( tester, alarma: const AlarmaMusical( id: 'a-sin-rango-activo', nombre: 'Sin vacaciones activas', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: false, ), ); expect( find.textContaining(l10n.alarmCardVacationPausedBadge), findsNothing, ); }, ); }