test(ci): stabilize hidden failures
Build & Deploy PluriWave / Análisis de código (push) Failing after 13m34s
Build & Deploy PluriWave / Build APK + AAB release (push) Has been skipped

This commit is contained in:
2026-05-28 23:37:51 +02:00
parent e47c0a88e0
commit cf994757a4
5 changed files with 187 additions and 120 deletions
+18
View File
@@ -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"
}
]
+1
View File
@@ -0,0 +1 @@
[]
+160 -110
View File
@@ -1,115 +1,135 @@
import 'dart:convert'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_radio.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_favoritos.dart';
import 'package:pluriwave/pantallas/pantalla_inicio.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:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart'; import '../helpers/fakes.dart';
void main() { void main() {
testWidgets( setUp(() {
'PantallaInicio muestra custom, reproducir usa EstadoRadio y favorito usa flujo existente', SharedPreferences.setMockInitialValues({});
(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<EstadoRadio>.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);
}); });
testWidgets( testWidgets(
'PantallaInicio permite reintentar manualmente tras fallo inicial agotado', 'PantallaInicio muestra custom, reproducir usa EstadoRadio y favorito usa flujo existente',
(tester) async { (tester) async {
final radio = FakeServicioRadio( final audio = FakeServicioAudio();
erroresPopularesPorLlamada: [Exception('sin red')], final favoritos = FakeServicioFavoritos();
popularesPorLlamada: [ final radio = FakeServicioRadio();
const [], final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno');
[emisoraDemo(uuid: 'api-1', nombre: 'API Uno')], final archivo = await _crearArchivoCustom([custom]);
], final estado = EstadoRadio(
tendenciasPorLlamada: [ audio: audio,
const [], favoritos: favoritos,
[emisoraDemo(uuid: 'trend-1', nombre: 'Trend Uno')], radio: radio,
], servicioEcualizador: FakeServicioEcualizador(),
); servicioGrabacion: FakeServicioGrabacionRadio(),
final estado = EstadoRadio( resolverArchivoCustom: () async => archivo,
audio: FakeServicioAudio(), iniciarAutomaticamente: false,
favoritos: FakeServicioFavoritos(), );
radio: radio, addTearDown(estado.dispose);
servicioEcualizador: FakeServicioEcualizador(), await tester.runAsync(estado.inicializar);
resolverArchivoCustom: _archivoCustomVacio,
iniciarAutomaticamente: false,
);
await estado.inicializar();
await tester.pumpWidget( await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value( ChangeNotifierProvider<EstadoRadio>.value(
value: estado, value: estado,
child: const MaterialApp( child: _testApp(const PantallaInicio()),
home: Scaffold(body: PantallaInicio()),
), ),
), );
); await _pumpStableFrame(tester);
await tester.pumpAndSettle();
expect(find.text('Sin conexión a la API de radio'), findsOneWidget); expect(find.text('Custom Uno'), findsOneWidget);
expect(find.text('Reintentar'), findsOneWidget);
await tester.tap(find.text('Reintentar')); await tester.ensureVisible(find.text('Custom Uno'));
await tester.pumpAndSettle(); 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); final tarjetaCustom = find.ancestor(
expect(find.text('Sin conexión a la API de radio'), findsNothing); of: find.text('Custom Uno'),
expect(find.text('API Uno'), findsOneWidget); 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', await tester.ensureVisible(botonFavorito);
(tester) async { 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<EstadoRadio>.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 favoritos = FakeServicioFavoritos();
final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno'); final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno');
final archivo = await _crearArchivoCustom([custom]); final archivo = await _crearArchivoCustom([custom]);
@@ -120,54 +140,84 @@ void main() {
populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')], populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')],
), ),
servicioEcualizador: FakeServicioEcualizador(), servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadio(),
resolverArchivoCustom: () async => archivo, resolverArchivoCustom: () async => archivo,
iniciarAutomaticamente: false, iniciarAutomaticamente: false,
); );
await estado.inicializar(); addTearDown(estado.dispose);
await tester.runAsync(estado.inicializar);
await tester.pumpWidget( await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value( ChangeNotifierProvider<EstadoRadio>.value(
value: estado, value: estado,
child: const MaterialApp( child: _testApp(const PantallaInicio()),
home: Scaffold(body: PantallaInicio()),
),
), ),
); );
await tester.pumpAndSettle(); await _pumpStableFrame(tester);
await tester.ensureVisible(find.text('Custom Uno'));
await _pumpStableFrame(tester);
final tarjetaCustom = find.ancestor( final tarjetaCustom = find.ancestor(
of: find.text('Custom Uno'), of: find.text('Custom Uno'),
matching: find.byType(Card), matching: find.byType(TarjetaEmisora),
);
final botonFavorito = find.descendant(
of: tarjetaCustom.first,
matching: find.byIcon(Icons.favorite_outline_rounded),
); );
final botonFavorito =
find.descendant(of: tarjetaCustom, matching: find.byType(InkWell)).last;
expect(botonFavorito, findsOneWidget); expect(botonFavorito, findsOneWidget);
await tester.ensureVisible(botonFavorito);
await _pumpStableFrame(tester);
await tester.tap(botonFavorito); await tester.tap(botonFavorito);
await tester.pumpAndSettle(); await _pumpStableFrame(tester);
await tester.pumpWidget( await tester.pumpWidget(
ChangeNotifierProvider<EstadoRadio>.value( ChangeNotifierProvider<EstadoRadio>.value(
value: estado, value: estado,
child: const MaterialApp( child: _testApp(const PantallaFavoritos()),
home: Scaffold(body: PantallaFavoritos()),
),
), ),
); );
await tester.pumpAndSettle(); await _pumpStableFrame(tester);
expect(await favoritos.esFavorito(custom.uuid), isTrue); expect(await favoritos.esFavorito(custom.uuid), isTrue);
expect(find.text('Custom Uno'), findsOneWidget); 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<EstadoGrabacionRadio>.broadcast();
@override
EstadoGrabacionRadio get estado => const EstadoGrabacionRadio.inactiva();
@override
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
@override
Future<void> inicializar() async {}
@override
Future<void> dispose() => _controller.close();
}
Future<void> _pumpStableFrame(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
}
Future<File> _crearArchivoCustom(List<dynamic> emisoras) async { Future<File> _crearArchivoCustom(List<dynamic> emisoras) async {
final dir = await Directory.systemTemp.createTemp('pluriwave-test-'); final nombre =
final archivo = File('${dir.path}/emisoras_custom.json'); emisoras.isEmpty
await archivo.writeAsString(jsonEncode(emisoras.map((e) => e.toMap()).toList())); ? 'emisoras_custom_vacio.json'
return archivo; : 'emisoras_custom_uno.json';
return File('${Directory.current.path}/test/fixtures/$nombre');
} }
Future<File> _archivoCustomVacio() async => _crearArchivoCustom(const []); Future<File> _archivoCustomVacio() async => _crearArchivoCustom(const []);
+2 -2
View File
@@ -63,8 +63,8 @@ void main() {
retryDelay: Duration.zero, retryDelay: Duration.zero,
); );
expect( await expectLater(
() => servicio.obtenerPopulares(limit: 1), servicio.obtenerPopulares(limit: 1),
throwsA(isA<Exception>()), throwsA(isA<Exception>()),
); );
expect(intentos, 2); expect(intentos, 2);
+6 -8
View File
@@ -8,8 +8,8 @@ import 'package:pluriwave/widgets/pluri_wave_scaffold.dart';
void main() { void main() {
test('PluriWaveTokens.dark mantiene valores base esperados', () { test('PluriWaveTokens.dark mantiene valores base esperados', () {
expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF24123D)); expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF07121A));
expect(PluriWaveTokens.dark.radiusMd, 16); expect(PluriWaveTokens.dark.radiusMd, 22);
expect(PluriWaveTokens.dark.spacingMd, 16); expect(PluriWaveTokens.dark.spacingMd, 16);
}); });
@@ -30,16 +30,14 @@ void main() {
expect(find.bySemanticsLabel('Buscar emisoras'), findsOneWidget); 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( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: PluriWaveTheme.dark(), theme: PluriWaveTheme.dark(),
home: const PluriWaveScaffold( home: const PluriWaveScaffold(
body: Center( body: Center(child: PluriGlassSurface(child: Text('ok'))),
child: PluriGlassSurface(
child: Text('ok'),
),
),
), ),
), ),
); );