Tint the mini player's reconnecting/error sub-states with the offlineAccent token (added in WU1, unused until now): the status label, the reconnect spinner, and the error retry icon now read as visually distinct "connectivity trouble" states instead of blending into the ordinary loading/paused look. Plain buffering keeps the default colour, confirmed by a dedicated regression test. Verify-first gate (task 16.1): ControladorReconexion.intentos exists, but ServicioAudio never surfaces it past a debug log line, and its estadoStream only carries the EstadoReproduccion enum. Adding an attempt-count label would require a getter/stream on servicio_audio.dart, one of the files this change must keep at an empty diff against main. Ship the restyle without the counter, per the risk register's own fallback. WU16.
214 lines
7.0 KiB
Dart
214 lines
7.0 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/tema/pluriwave_tokens.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);
|
|
});
|
|
|
|
// WU16: the offline/reconnect banner restyle. `offlineAccent` (added in
|
|
// WU1, previously unused by any screen) tints the connectivity-trouble
|
|
// sub-states so they read as visually distinct from ordinary loading.
|
|
testWidgets(
|
|
'reconnecting state tints the spinner and status label with the offline accent',
|
|
(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);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(
|
|
tester
|
|
.widget<CircularProgressIndicator>(
|
|
find.byType(CircularProgressIndicator),
|
|
)
|
|
.color,
|
|
PluriWaveTokens.dark.offlineAccent,
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('Reconectando...')).style?.color,
|
|
PluriWaveTokens.dark.offlineAccent,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'plain buffering keeps the default spinner colour, not the offline accent',
|
|
(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.cargando);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(
|
|
tester
|
|
.widget<CircularProgressIndicator>(
|
|
find.byType(CircularProgressIndicator),
|
|
)
|
|
.color,
|
|
isNull,
|
|
reason: 'cargando is not a connectivity problem, keep the default',
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'error state tints the retry icon and status label with the offline accent',
|
|
(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(
|
|
tester.widget<Icon>(find.byIcon(Icons.refresh_rounded)).color,
|
|
PluriWaveTokens.dark.offlineAccent,
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('Error de conexión')).style?.color,
|
|
PluriWaveTokens.dark.offlineAccent,
|
|
);
|
|
},
|
|
);
|
|
}
|