0380bbb1e7
- Construct the audio player with an enlarged live-stream buffer (15-50s forward cushion, 2.5s to start, 5s after rebuffer) so short network drops play through silently - Add reconnect-on-stall state machine with bounded exponential backoff (1/2/4/8/16s, ~90s total window, 5 attempts) that re-prepares to the live edge; backoff/decision logic extracted to controlador_reconexion.dart as pure testable code - Surface a new reconnecting playback state in the mini player and full player (localized in all 13 locales) instead of error dialogs during the retry window; a single friendly error appears only after exhaustion - Guard interplay: user pause/stop cancels retries, audio interruptions cancel reconnect, alarm wake-up path keeps precedence, recording fails cleanly during drops - Reset retry budget on station change; route stream timeouts through the network-error class - 10 new tests (99 total green), flutter analyze clean
97 lines
3.2 KiB
Dart
97 lines
3.2 KiB
Dart
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/servicios/servicio_audio.dart';
|
|
import 'package:pluriwave/widgets/mini_reproductor.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
import '../helpers/fakes_alarmas.dart';
|
|
|
|
EstadoRadio _estadoRadio(FakeServicioAudio audio) {
|
|
return EstadoRadio(
|
|
audio: audio,
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
}
|
|
|
|
/// S7-R3-A: while the handler is reconnecting, the UI shows a loading
|
|
/// indicator — never an error dialog or snackbar per retry attempt.
|
|
void main() {
|
|
testWidgets(
|
|
'estado reconectando muestra indicador de carga, sin dialogo ni snackbar',
|
|
(tester) async {
|
|
final audio = FakeServicioAudio();
|
|
final estado = _estadoRadio(audio);
|
|
addTearDown(estado.dispose);
|
|
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
|
|
|
|
await tester.pumpWidget(
|
|
ChangeNotifierProvider<EstadoRadio>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const Scaffold(body: MiniReproductor()),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
audio.emitirEstado(EstadoReproduccion.reconectando);
|
|
// Two pumps: one delivers the stream event, one rebuilds the frame.
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
expect(
|
|
find.byType(CircularProgressIndicator),
|
|
findsOneWidget,
|
|
reason: 'reconectando se presenta como carga, no como error',
|
|
);
|
|
expect(
|
|
find.byIcon(Icons.refresh_rounded),
|
|
findsNothing,
|
|
reason: 'el boton de reintento manual es solo para el estado error',
|
|
);
|
|
expect(find.text('Reconectando...'), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('el estado error si muestra el boton de reintento manual', (
|
|
tester,
|
|
) async {
|
|
final audio = FakeServicioAudio();
|
|
final estado = _estadoRadio(audio);
|
|
addTearDown(estado.dispose);
|
|
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
|
|
|
|
await tester.pumpWidget(
|
|
ChangeNotifierProvider<EstadoRadio>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const Scaffold(body: MiniReproductor()),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
audio.emitirEstado(EstadoReproduccion.error);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(find.byType(AlertDialog), findsNothing);
|
|
expect(find.byIcon(Icons.refresh_rounded), findsOneWidget);
|
|
});
|
|
}
|