Files
pluriwave/test/widgets/reconnect_ui_test.dart
T
FreeTLab d81fabbe27 refactor(iap): make esPremium a required constructor parameter
EstadoAlarmas, EstadoGrabacion and EstadoRadio defaulted `esPremium` to
`() => true`, so any construction site that forgot to wire entitlement
compiled fine and silently ran ungated — failing OPEN to premium and
disabling the paywall with no test able to catch it.

The parameter is now required with no default. Production wiring in
app.dart was already correct and is unchanged; the 184 pre-existing test
call sites now pass `() => true` explicitly, which is exactly the old
implicit default, so every assertion is untouched.

EstadoRadio has no gate of its own but constructs EstadoGrabacion, so it
inherits the same contract.

The one test that existed to pin the old default is renamed to describe
what it still covers (the premium path through iniciar() with no
duracion); its assertions are unchanged.
2026-08-10 22:06:36 +02:00

215 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(
esPremium: () => true,
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,
);
},
);
}