import 'dart:async'; 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_alarma_sonando.dart'; import 'package:pluriwave/servicios/servicio_alarmas.dart'; import 'package:pluriwave/servicios/servicio_audio.dart'; import 'package:pluriwave/tema/pluriwave_theme.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; import '../helpers/fakes_alarmas.dart'; /// Closing batch (items 9.4, 9.10, audit id 2521) -- the ringing screen. /// Mirrors `pantalla_alarma_sonando_tier4_test.dart`'s own harness (kept as /// a local copy, not a shared import, matching this file's established /// precedent of small helper duplication over cross-test-file coupling). Future _montarPantalla(WidgetTester tester) async { tester.view.physicalSize = const Size(1440, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final audio = FakeServicioAudio(); audio.emitirEstado(EstadoReproduccion.reproduciendo); final radio = EstadoRadio( audio: audio, 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, 7, 0)), android: android, iniciarAutomaticamente: false, ); addTearDown(estadoAlarmas.dispose); addTearDown(android.dispose); await estadoAlarmas.guardarAlarma( const AlarmaMusical( id: 'tier5-1', nombre: 'Despertar', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], snoozeMinutos: 10, emisora: Emisora( uuid: 'e1', nombre: 'Radio Uno', url: 'https://radio.example/stream', ), ), ); 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, theme: PluriWaveTheme.dark(), home: const SizedBox.shrink(), ), ), ); final navigator = tester.state(find.byType(Navigator)); unawaited( navigator.push( MaterialPageRoute( builder: (_) => PantallaAlarmaSonando(alarma: estadoAlarmas.alarmas.single), fullscreenDialog: true, ), ), ); await tester.pump(); await tester.pump(const Duration(milliseconds: 50)); } void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); testWidgets('visual fidelity (audit 9.4): the date line renders between the ' 'schedule pill and the hero time (t4:419)', (tester) async { await _montarPantalla(tester); final localeTag = Localizations.localeOf( tester.element(find.byType(PantallaAlarmaSonando)), ).toString(); final esperado = fechaLargaConDiaSemana(localeTag, DateTime.now()); expect(find.text(esperado), findsOneWidget); // Order: pill above the date line, date line above the hero time. final pillY = tester .getBottomLeft(find.byKey(const ValueKey('ringing-schedule-pill'))) .dy; final dateY = tester.getTopLeft(find.text(esperado)).dy; final timeY = tester.getTopLeft(find.byKey(const ValueKey('ringing-hero-time'))).dy; expect(pillY <= dateY, isTrue); expect(dateY <= timeY, isTrue); // Regression guard: pumpAndSettle must still complete (purely // additive static text, no new animation). await tester.pumpAndSettle(); }); testWidgets( 'visual fidelity (audit 9.10): the highlighted snooze tile carries a ' '"usual" qualifier ADDITIVELY -- the original flat label the dismiss ' 'guard finds by text stays fully intact', (tester) async { await _montarPantalla(tester); final l10n = AppLocalizations.of( tester.element(find.byType(PantallaAlarmaSonando)), ); // The ORIGINAL flat label must still resolve to exactly one Text -- // this IS the dismiss-guard's own finder contract. expect(find.text(l10n.alarmSnoozeOptionLabel(10)), findsOneWidget); // The additive qualifier renders too, but only on the destacado tile. expect(find.text(l10n.alarmSnoozeUsualLabel), findsOneWidget); expect(find.text(l10n.alarmSnoozeOptionLabel(3)), findsOneWidget); expect(find.text(l10n.alarmSnoozeOptionLabel(5)), findsOneWidget); // Regression guard: the exact hazard 9.9/9.10 share -- pumpAndSettle // must still complete, and the flat label must remain the SAME // widget the dismiss guard taps (still inside a FilledButton). expect( find.ancestor( of: find.text(l10n.alarmSnoozeOptionLabel(10)), matching: find.byType(FilledButton), ), findsOneWidget, ); await tester.pumpAndSettle(); }, ); }