Files
pluriwave/test/widgets/reconnect_ui_test.dart
FreeTLab 862197ab48 feat(connectivity): restyle offline and reconnect banners
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.
2026-07-29 14:43:34 +02:00

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,
);
},
);
}