diff --git a/test/fixtures/emisoras_custom_uno.json b/test/fixtures/emisoras_custom_uno.json new file mode 100644 index 0000000..a691001 --- /dev/null +++ b/test/fixtures/emisoras_custom_uno.json @@ -0,0 +1,18 @@ +[ + { + "uuid": "custom-1", + "nombre": "Custom Uno", + "url": "https://stream.demo/radio", + "favicon": null, + "pais": null, + "codigo_pais": null, + "idioma": null, + "tags": null, + "codec": null, + "bitrate": null, + "votes": 0, + "clickcount": 0, + "orden": 0, + "grupo_id": "sin_asignar" + } +] diff --git a/test/fixtures/emisoras_custom_vacio.json b/test/fixtures/emisoras_custom_vacio.json new file mode 100644 index 0000000..b76e44e --- /dev/null +++ b/test/fixtures/emisoras_custom_vacio.json @@ -0,0 +1 @@ +[] diff --git a/test/pantallas/pantalla_inicio_test.dart b/test/pantallas/pantalla_inicio_test.dart index 564b52a..573b207 100644 --- a/test/pantallas/pantalla_inicio_test.dart +++ b/test/pantallas/pantalla_inicio_test.dart @@ -1,115 +1,135 @@ -import 'dart:convert'; +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() { - testWidgets( - 'PantallaInicio muestra custom, reproducir usa EstadoRadio y favorito usa flujo existente', - (tester) async { - 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(), - resolverArchivoCustom: () async => archivo, - iniciarAutomaticamente: false, - ); - await estado.inicializar(); - - await tester.pumpWidget( - ChangeNotifierProvider.value( - value: estado, - child: const MaterialApp( - home: Scaffold(body: PantallaInicio()), - ), - ), - ); - await tester.pumpAndSettle(); - - expect(find.text('Custom Uno'), findsOneWidget); - - await tester.tap(find.text('Custom Uno')); - await tester.pumpAndSettle(); - 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(Card), - ); - final botonFavorito = find.descendant( - of: tarjetaCustom.first, - matching: find.byIcon(Icons.favorite_outline_rounded), - ); - expect(botonFavorito, findsOneWidget); - - await tester.tap(botonFavorito); - await tester.pumpAndSettle(); - - expect(favoritos.toggleCalls, 1); - expect(await favoritos.esFavorito(custom.uuid), isTrue); + setUp(() { + SharedPreferences.setMockInitialValues({}); }); testWidgets( - 'PantallaInicio permite reintentar manualmente tras fallo inicial agotado', - (tester) async { - 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(), - resolverArchivoCustom: _archivoCustomVacio, - iniciarAutomaticamente: false, - ); - await estado.inicializar(); + 'PantallaInicio muestra custom, reproducir usa EstadoRadio y favorito usa flujo existente', + (tester) async { + 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: const MaterialApp( - home: Scaffold(body: PantallaInicio()), + await tester.pumpWidget( + ChangeNotifierProvider.value( + value: estado, + child: _testApp(const PantallaInicio()), ), - ), - ); - await tester.pumpAndSettle(); + ); + await _pumpStableFrame(tester); - expect(find.text('Sin conexión a la API de radio'), findsOneWidget); - expect(find.text('Reintentar'), findsOneWidget); + expect(find.text('Custom Uno'), findsOneWidget); - await tester.tap(find.text('Reintentar')); - await tester.pumpAndSettle(); + 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'); - expect(radio.obtenerPopularesCalls, 2); - expect(find.text('Sin conexión a la API de radio'), findsNothing); - expect(find.text('API Uno'), findsOneWidget); - }); + 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); - testWidgets('PantallaFavoritos muestra custom favorito tras recarga', - (tester) async { + 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 { + 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); + + 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 { final favoritos = FakeServicioFavoritos(); final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno'); final archivo = await _crearArchivoCustom([custom]); @@ -120,54 +140,84 @@ void main() { populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')], ), servicioEcualizador: FakeServicioEcualizador(), + servicioGrabacion: FakeServicioGrabacionRadio(), resolverArchivoCustom: () async => archivo, iniciarAutomaticamente: false, ); - await estado.inicializar(); + addTearDown(estado.dispose); + await tester.runAsync(estado.inicializar); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, - child: const MaterialApp( - home: Scaffold(body: PantallaInicio()), - ), + child: _testApp(const PantallaInicio()), ), ); - await tester.pumpAndSettle(); + await _pumpStableFrame(tester); + await tester.ensureVisible(find.text('Custom Uno')); + await _pumpStableFrame(tester); final tarjetaCustom = find.ancestor( of: find.text('Custom Uno'), - matching: find.byType(Card), - ); - final botonFavorito = find.descendant( - of: tarjetaCustom.first, - matching: find.byIcon(Icons.favorite_outline_rounded), + 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 tester.pumpAndSettle(); + await _pumpStableFrame(tester); await tester.pumpWidget( ChangeNotifierProvider.value( value: estado, - child: const MaterialApp( - home: Scaffold(body: PantallaFavoritos()), - ), + child: _testApp(const PantallaFavoritos()), ), ); - await tester.pumpAndSettle(); + await _pumpStableFrame(tester); expect(await favoritos.esFavorito(custom.uuid), isTrue); expect(find.text('Custom Uno'), findsOneWidget); }); } +Widget _testApp(Widget body) { + return MaterialApp( + 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.pump(const Duration(milliseconds: 100)); +} + Future _crearArchivoCustom(List emisoras) async { - final dir = await Directory.systemTemp.createTemp('pluriwave-test-'); - final archivo = File('${dir.path}/emisoras_custom.json'); - await archivo.writeAsString(jsonEncode(emisoras.map((e) => e.toMap()).toList())); - return archivo; + 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 []); diff --git a/test/servicios/servicio_radio_test.dart b/test/servicios/servicio_radio_test.dart index 14d6a0e..c755a0b 100644 --- a/test/servicios/servicio_radio_test.dart +++ b/test/servicios/servicio_radio_test.dart @@ -63,8 +63,8 @@ void main() { retryDelay: Duration.zero, ); - expect( - () => servicio.obtenerPopulares(limit: 1), + await expectLater( + servicio.obtenerPopulares(limit: 1), throwsA(isA()), ); expect(intentos, 2); diff --git a/test/widgets/pluriwave_foundations_test.dart b/test/widgets/pluriwave_foundations_test.dart index 796eb1d..4b05fbf 100644 --- a/test/widgets/pluriwave_foundations_test.dart +++ b/test/widgets/pluriwave_foundations_test.dart @@ -8,8 +8,8 @@ import 'package:pluriwave/widgets/pluri_wave_scaffold.dart'; void main() { test('PluriWaveTokens.dark mantiene valores base esperados', () { - expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF24123D)); - expect(PluriWaveTokens.dark.radiusMd, 16); + expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF07121A)); + expect(PluriWaveTokens.dark.radiusMd, 22); expect(PluriWaveTokens.dark.spacingMd, 16); }); @@ -30,16 +30,14 @@ void main() { expect(find.bySemanticsLabel('Buscar emisoras'), findsOneWidget); }); - testWidgets('PluriGlassSurface y PluriWaveScaffold se instancian', (tester) async { + testWidgets('PluriGlassSurface y PluriWaveScaffold se instancian', ( + tester, + ) async { await tester.pumpWidget( MaterialApp( theme: PluriWaveTheme.dark(), home: const PluriWaveScaffold( - body: Center( - child: PluriGlassSurface( - child: Text('ok'), - ), - ), + body: Center(child: PluriGlassSurface(child: Text('ok'))), ), ), );