Item 24 / audit 5.1-5.3, 5.5 (t4:255-269): Paises' "Tus idiomas" was a Wrap of non-interactive Chips, and the full list was a plain ListTile with no ISO column and no onTap. Both now share one tappable row (ISO code, name, station count, chevron); the first "Tus idiomas" row gets the prototype's teal-tinted highlight. The one production call site wires the tap to filter Buscar by that country's code and pop back -- EstadoBusqueda.buscar(pais: ...) already accepts any ISO alpha-2 code, not just the ~10 presets in the filter sheet. Item 25 / audit 13.2 (t4:643-646): a reconnect card (rotating ring, station name, "Reconectando...", a stop affordance) replaces the complete absence of any reconnect signal outside a word in the mini player. Ships WITHOUT the prototype's attempt counter: the only live ControladorReconexion instance is a private field of PluriWaveAudioHandler inside servicio_audio.dart, a file this task requires stay byte-identical to main, and nothing else re-exposes it. Reconstructing a count from estadoStream's reconectando emissions would not be faithful (the stream can emit it many times per actual backoff attempt), so that was deliberately not attempted.
231 lines
7.8 KiB
Dart
231 lines
7.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_busqueda.dart';
|
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
|
import 'package:pluriwave/modelos/pais_radio.dart';
|
|
import 'package:pluriwave/pantallas/pantalla_paises.dart';
|
|
import 'package:pluriwave/widgets/pluri_push_scaffold.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
/// WU7, `station-discovery-browse` spec — "Países Browser Over the Verified
|
|
/// Countries Contract": "Tus idiomas" shortlist + the full alphabetical
|
|
/// list, each entry showing its station count. `stationcount`'s "arrives as
|
|
/// a JSON string" edge case is covered at its own layer boundary in
|
|
/// `test/modelos/pais_radio_test.dart` — `FakeServicioRadio.obtenerPaises()`
|
|
/// returns already-parsed `PaisRadio` instances here, so this file tests
|
|
/// rendering only, not JSON parsing.
|
|
void main() {
|
|
Widget buildScreen(EstadoBusqueda estado) {
|
|
return ChangeNotifierProvider<EstadoBusqueda>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const PantallaPaises(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> pumpEstable(WidgetTester tester) async {
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
testWidgets(
|
|
'renders inside a PluriPushScaffold; muestra "Tus idiomas" y la lista '
|
|
'alfabética completa con el conteo de cada país',
|
|
(tester) async {
|
|
final estado = EstadoBusqueda(
|
|
radio: FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(nombre: 'Spain', codigoIso: 'ES', numeroEmisoras: 482),
|
|
PaisRadio(
|
|
nombre: 'Argentina',
|
|
codigoIso: 'AR',
|
|
numeroEmisoras: 120,
|
|
),
|
|
PaisRadio(nombre: 'France', codigoIso: 'FR', numeroEmisoras: 75),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(buildScreen(estado));
|
|
await pumpEstable(tester);
|
|
|
|
expect(find.byType(PluriPushScaffold), findsOneWidget);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaPaises)),
|
|
);
|
|
expect(find.text(l10n.countriesYourLanguagesTitle), findsOneWidget);
|
|
expect(find.text(l10n.countriesAllTitle), findsOneWidget);
|
|
|
|
// Full alphabetical list: all 3 fetched countries render.
|
|
expect(find.text('Argentina'), findsOneWidget);
|
|
// 'France' and 'Spain' each render twice: once in "Tus idiomas" (FR
|
|
// and ES both back a supported locale) and once in the full list
|
|
// below. Item 24's row now shows the name as its OWN Text (not
|
|
// concatenated with the count into one Chip label), so both are
|
|
// independently findable in each section.
|
|
expect(find.text('France'), findsWidgets);
|
|
expect(find.text('Spain'), findsWidgets);
|
|
|
|
// Each entry's parsed `stationcount` renders via the existing
|
|
// `stationsCount` string — 482 came from the API as a STRING
|
|
// (`PaisRadio.fromApi`'s job, verified separately) and must display
|
|
// as a plain number here, never throwing a cast error.
|
|
expect(find.text(l10n.stationsCount(482)), findsWidgets);
|
|
expect(find.text(l10n.stationsCount(120)), findsOneWidget);
|
|
// Also doubled, for the same reason as 'France' above.
|
|
expect(find.text(l10n.stationsCount(75)), findsWidgets);
|
|
},
|
|
);
|
|
|
|
// Item 24 / audit 5.1-5.3, 5.5 (t4:256-269): tappable ISO rows, not
|
|
// non-interactive chips/plain ListTiles.
|
|
group('Item 24 -- tappable ISO rows', () {
|
|
testWidgets('rows show the ISO code and no longer use Chip or ListTile', (
|
|
tester,
|
|
) async {
|
|
final estado = EstadoBusqueda(
|
|
radio: FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(nombre: 'Spain', codigoIso: 'ES', numeroEmisoras: 482),
|
|
PaisRadio(
|
|
nombre: 'Argentina',
|
|
codigoIso: 'AR',
|
|
numeroEmisoras: 120,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(buildScreen(estado));
|
|
await pumpEstable(tester);
|
|
|
|
expect(
|
|
find.byType(Chip),
|
|
findsNothing,
|
|
reason: 'audit 5.1 replaces the Wrap-of-Chip with tappable rows',
|
|
);
|
|
expect(
|
|
find.byType(ListTile),
|
|
findsNothing,
|
|
reason:
|
|
'audit 5.3 rows need an ISO-code column a plain ListTile '
|
|
'has no slot for',
|
|
);
|
|
expect(find.text('AR'), findsOneWidget);
|
|
// 'ES' renders in BOTH the "Tus idiomas" and "Todos" rows.
|
|
expect(find.text('ES'), findsWidgets);
|
|
expect(find.byIcon(Icons.chevron_right_rounded), findsWidgets);
|
|
});
|
|
|
|
testWidgets('tapping a row invokes onPaisSeleccionado with that country', (
|
|
tester,
|
|
) async {
|
|
final estado = EstadoBusqueda(
|
|
radio: FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(
|
|
nombre: 'Argentina',
|
|
codigoIso: 'AR',
|
|
numeroEmisoras: 120,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
PaisRadio? seleccionado;
|
|
|
|
await tester.pumpWidget(
|
|
ChangeNotifierProvider<EstadoBusqueda>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: PantallaPaises(
|
|
onPaisSeleccionado: (pais) => seleccionado = pais,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await pumpEstable(tester);
|
|
|
|
await tester.tap(find.text('Argentina'));
|
|
await pumpEstable(tester);
|
|
|
|
expect(seleccionado?.codigoIso, 'AR');
|
|
});
|
|
|
|
testWidgets(
|
|
'the first "Tus idiomas" row is highlighted: teal-tinted background, '
|
|
'bold name',
|
|
(tester) async {
|
|
final estado = EstadoBusqueda(
|
|
radio: FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(nombre: 'Spain', codigoIso: 'ES', numeroEmisoras: 482),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(buildScreen(estado));
|
|
await pumpEstable(tester);
|
|
|
|
final nombre = tester.widget<Text>(find.text('Spain').first);
|
|
expect(nombre.style?.fontWeight, FontWeight.w800);
|
|
|
|
final decorado = tester.widget<DecoratedBox>(
|
|
find
|
|
.ancestor(
|
|
of: find.text('Spain').first,
|
|
matching: find.byType(DecoratedBox),
|
|
)
|
|
.first,
|
|
);
|
|
final decoration = decorado.decoration as BoxDecoration;
|
|
expect(
|
|
decoration.color,
|
|
isNot(Colors.transparent),
|
|
reason: 't4:256 the first row is teal-tinted, not transparent',
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'cargarPaises no se re-dispara si ya hay datos en caché al reconstruir',
|
|
(tester) async {
|
|
final radio = FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(nombre: 'Italy', codigoIso: 'IT', numeroEmisoras: 30),
|
|
],
|
|
);
|
|
final estado = EstadoBusqueda(radio: radio);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(buildScreen(estado));
|
|
await pumpEstable(tester);
|
|
expect(radio.obtenerPaisesCalls, 1);
|
|
|
|
// Re-entering the screen (a fresh State, same EstadoBusqueda instance)
|
|
// must not refetch — the in-memory cache guard lives on EstadoBusqueda,
|
|
// not on the widget.
|
|
await tester.pumpWidget(const SizedBox.shrink());
|
|
await pumpEstable(tester);
|
|
await tester.pumpWidget(buildScreen(estado));
|
|
await pumpEstable(tester);
|
|
|
|
expect(radio.obtenerPaisesCalls, 1);
|
|
},
|
|
);
|
|
}
|