import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_busqueda.dart'; import 'package:pluriwave/estado/estado_ecualizador.dart'; import 'package:pluriwave/estado/estado_grabacion.dart'; import 'package:pluriwave/estado/estado_navegacion.dart'; import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/pantallas/pantalla_inicio.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); // WU6 correction: the 3 scenarios that used to live here ("muestra custom // ... favorito usa flujo existente", "permite reintentar manualmente", // "PantallaFavoritos muestra custom favorito tras recarga") all depended // on `_gridEmisoras`/`_errorBanner`, which task 6.5 relocates to // `PantallaBuscar` and DELETES from here (WU5's task 5.9 deferred this // cleanup explicitly). Their coverage moved, adapted, to // `pantalla_buscar_test.dart` — PantallaInicio no longer renders any // station list of its own (only the hero + the "Tus emisoras" favorites // preview), so there is nothing left on this screen for those scenarios // to exercise. testWidgets( 'WU5 ADR-7 anti-cache: the Escuchar hero reflects a station changed ' 'from OUTSIDE the widget tree (e.g. Android Auto / a notification ' 'action), proving it caches nothing of its own', (tester) async { _setLargeSurfaceSize(tester); final audio = FakeServicioAudio(); final estado = EstadoRadio( audio: audio, favoritos: FakeServicioFavoritos(), radio: FakeServicioRadio(), servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: _archivoCustomVacio, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); await tester.runAsync(estado.inicializar); final estacionA = emisoraDemo(uuid: 'a', nombre: 'Estacion A'); final estacionB = emisoraDemo(uuid: 'b', nombre: 'Estacion B'); await estado.reproducir(estacionA); await tester.pumpWidget( _conProviders(estado, _testApp(const PantallaInicio())), ); // Bounded pump, not _pumpStableFrame/pumpAndSettle: a "reproduciendo" // station makes VisualizadorAudio start an indeterminately-repeating // AnimationController (visualizador_audio.dart:77, `_controller.repeat()`) // for its animated-fallback waveform — the same class of hazard as an // indeterminate spinner, just via animation. pumpAndSettle() would // never return while it keeps scheduling frames. await _pumpBounded(tester); expect(find.text('Estacion A'), findsOneWidget); // Mutates the underlying ServicioAudio DIRECTLY, bypassing // EstadoRadio.reproducir() entirely — this is exactly the shape of // navegacion_auto.dart's out-of-band mutation (Android Auto's // playFromMediaId). EstadoRadio's own audio.estadoStream listener // (not this test) is what is expected to pick this up and update // emisoraActual. await audio.reproducir(estacionB); await _pumpBounded(tester); expect(find.text('Estacion B'), findsOneWidget); expect(find.text('Estacion A'), findsNothing); }, ); testWidgets('WU5: "Ver todas" switches to the Favoritos root via ' 'EstadoNavegacionRaiz.irA, without pushing a route', (tester) async { _setLargeSurfaceSize(tester); final favoritos = FakeServicioFavoritos(); final estado = EstadoRadio( audio: FakeServicioAudio(), favoritos: favoritos, radio: FakeServicioRadio(), servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: _archivoCustomVacio, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); await tester.runAsync(estado.inicializar); await favoritos.agregar(emisoraDemo(uuid: 'f1', nombre: 'Favorita Uno')); await estado.cargarFavoritos(); final navegacion = EstadoNavegacionRaiz(); final observer = _RecordingNavigatorObserver(); await tester.pumpWidget( _conProviders( estado, _testApp(const PantallaInicio(), observers: [observer]), navegacion: navegacion, ), ); await _pumpStableFrame(tester); final pushesAntesDeTocar = observer.pushCount; await tester.ensureVisible(find.text('Ver todas')); await _pumpStableFrame(tester); await tester.tap(find.text('Ver todas')); await _pumpStableFrame(tester); expect(navegacion.actual, RaizPluriWave.favoritos); expect( observer.pushCount, pushesAntesDeTocar, reason: 'switches tabs — must NOT push a new route', ); }); } /// Mirrors the app.dart wiring: EstadoRadio owns the domain notifiers and /// the providers only expose the instances (no dispose callbacks). /// [navegacion] defaults to a fresh [EstadoNavegacionRaiz] — harmless to /// include for every test, only exercised by the "Ver todas" scenarios. Widget _conProviders( EstadoRadio estado, Widget child, { EstadoNavegacionRaiz? navegacion, }) { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: estado), ListenableProvider.value(value: estado.ecualizador), ListenableProvider.value(value: estado.grabacion), ListenableProvider.value(value: estado.busqueda), ChangeNotifierProvider.value( value: navegacion ?? EstadoNavegacionRaiz(), ), ], child: child, ); } Widget _testApp(Widget body, {List observers = const []}) { return MaterialApp( locale: const Locale('es'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, navigatorObservers: observers, home: Scaffold(body: body), ); } /// Counts route pushes so a test can assert "switched tabs, did not push". class _RecordingNavigatorObserver extends NavigatorObserver { int pushCount = 0; @override void didPush(Route route, Route? previousRoute) { pushCount++; } } Future _pumpStableFrame(WidgetTester tester) async { await tester.pump(); await tester.pumpAndSettle(const Duration(milliseconds: 100)); } /// WU5: bounded pump, safe when a "reproduciendo" station is rendered — /// `VisualizadorAudio` starts a repeating `AnimationController` for its /// animated-fallback waveform in that case, which `pumpAndSettle` (used by /// `_pumpStableFrame`) would wait on forever. Future _pumpBounded(WidgetTester tester) async { await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); } void _setLargeSurfaceSize(WidgetTester tester) { tester.view.physicalSize = const Size(1440, 3200); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); } Future _archivoCustomVacio() async => File('${Directory.current.path}/test/fixtures/emisoras_custom_vacio.json');