import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/pantallas/pantalla_favoritos.dart'; import 'package:pluriwave/pantallas/pantalla_inicio.dart'; import 'package:pluriwave/servicios/servicio_grabacion_radio.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes.dart'; void main() { setUp(() { SharedPreferences.setMockInitialValues({}); }); testWidgets( 'PantallaInicio muestra custom, reproducir usa EstadoRadio y favorito usa flujo existente', (tester) async { _setLargeSurfaceSize(tester); final audio = FakeServicioAudio(); final favoritos = FakeServicioFavoritos(); final radio = FakeServicioRadio(); final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno'); final archivo = await _crearArchivoCustom([custom]); final estado = EstadoRadio( audio: audio, favoritos: favoritos, radio: radio, servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: () async => archivo, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); await tester.runAsync(estado.inicializar); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, child: _testApp(const PantallaInicio()), ), ); await _pumpStableFrame(tester); await _scrollUntilText(tester, 'Custom Uno'); expect(find.text('Custom Uno'), findsOneWidget); await tester.ensureVisible(find.text('Custom Uno')); await _pumpStableFrame(tester); await tester.tap(find.text('Custom Uno')); await _pumpStableFrame(tester); expect( audio.emisorasReproducidas.map((e) => e.uuid), contains('custom-1'), ); expect(radio.ultimoUuidClick, 'custom-1'); final tarjetaCustom = find.ancestor( of: find.text('Custom Uno'), matching: find.byType(TarjetaEmisora), ); final botonFavorito = find .descendant(of: tarjetaCustom, matching: find.byType(InkWell)) .last; expect(botonFavorito, findsOneWidget); await tester.ensureVisible(botonFavorito); await _pumpStableFrame(tester); await tester.tap(botonFavorito); await _pumpStableFrame(tester); expect(favoritos.toggleCalls, 1); expect(await favoritos.esFavorito(custom.uuid), isTrue); }, ); testWidgets( 'PantallaInicio permite reintentar manualmente tras fallo inicial agotado', (tester) async { _setLargeSurfaceSize(tester); final radio = FakeServicioRadio( erroresPopularesPorLlamada: [Exception('sin red')], popularesPorLlamada: [ const [], [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')], ], tendenciasPorLlamada: [ const [], [emisoraDemo(uuid: 'trend-1', nombre: 'Trend Uno')], ], ); final estado = EstadoRadio( audio: FakeServicioAudio(), favoritos: FakeServicioFavoritos(), radio: radio, servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: _archivoCustomVacio, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); await tester.runAsync(estado.inicializar); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, child: _testApp(const PantallaInicio()), ), ); await _pumpStableFrame(tester); await _scrollUntilText(tester, 'Sin conexión a la API de radio'); expect(find.text('Sin conexión a la API de radio'), findsOneWidget); expect(find.text('Reintentar'), findsOneWidget); await tester.ensureVisible(find.text('Reintentar')); await _pumpStableFrame(tester); await tester.tap(find.text('Reintentar')); await _pumpStableFrame(tester); expect(radio.obtenerPopularesCalls, 2); expect(find.text('Sin conexión a la API de radio'), findsNothing); expect(find.text('API Uno'), findsOneWidget); }, ); testWidgets('PantallaFavoritos muestra custom favorito tras recarga', ( tester, ) async { _setLargeSurfaceSize(tester); final favoritos = FakeServicioFavoritos(); final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno'); final archivo = await _crearArchivoCustom([custom]); final estado = EstadoRadio( audio: FakeServicioAudio(), favoritos: favoritos, radio: FakeServicioRadio( populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')], ), servicioEcualizador: FakeServicioEcualizador(), servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: () async => archivo, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); await tester.runAsync(estado.inicializar); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, child: _testApp(const PantallaInicio()), ), ); await _pumpStableFrame(tester); await _scrollUntilText(tester, 'Custom Uno'); await _pumpStableFrame(tester); final tarjetaCustom = find.ancestor( of: find.text('Custom Uno'), matching: find.byType(TarjetaEmisora), ); final botonFavorito = find.descendant(of: tarjetaCustom, matching: find.byType(InkWell)).last; expect(botonFavorito, findsOneWidget); await tester.ensureVisible(botonFavorito); await _pumpStableFrame(tester); await tester.tap(botonFavorito); await _pumpStableFrame(tester); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, child: _testApp(const PantallaFavoritos()), ), ); await _pumpStableFrame(tester); expect(await favoritos.esFavorito(custom.uuid), isTrue); expect(find.text('Custom Uno'), findsOneWidget); }); } Widget _testApp(Widget body) { return MaterialApp( locale: const Locale('es'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: Scaffold(body: body), ); } class FakeServicioGrabacionRadio extends ServicioGrabacionRadio { final _controller = StreamController.broadcast(); @override EstadoGrabacionRadio get estado => const EstadoGrabacionRadio.inactiva(); @override Stream get estadoStream => _controller.stream; @override Future inicializar() async {} @override Future dispose() => _controller.close(); } Future _pumpStableFrame(WidgetTester tester) async { await tester.pump(); await tester.pumpAndSettle(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 _scrollUntilText(WidgetTester tester, String text) async { await tester.scrollUntilVisible( find.text(text), 300, scrollable: find.byType(Scrollable).first, ); await _pumpStableFrame(tester); } Future _crearArchivoCustom(List emisoras) async { final nombre = emisoras.isEmpty ? 'emisoras_custom_vacio.json' : 'emisoras_custom_uno.json'; return File('${Directory.current.path}/test/fixtures/$nombre'); } Future _archivoCustomVacio() async => _crearArchivoCustom(const []);