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.
1129 lines
40 KiB
Dart
1129 lines
40 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_busqueda.dart';
|
|
import 'package:pluriwave/estado/estado_ecualizador.dart';
|
|
import 'package:pluriwave/estado/estado_grabacion.dart';
|
|
import 'package:pluriwave/estado/estado_radio.dart';
|
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
|
import 'package:pluriwave/modelos/pais_radio.dart';
|
|
import 'package:pluriwave/pantallas/pantalla_buscar.dart';
|
|
import 'package:pluriwave/pantallas/pantalla_favoritos.dart';
|
|
import 'package:pluriwave/pantallas/pantalla_paises.dart';
|
|
import 'package:pluriwave/servicios/servicio_audio.dart';
|
|
import 'package:pluriwave/tema/pluriwave_theme.dart';
|
|
import 'package:pluriwave/tema/pluriwave_tokens.dart';
|
|
import 'package:pluriwave/widgets/fila_emisora_plana.dart';
|
|
import 'package:pluriwave/widgets/pluri_layout.dart';
|
|
import 'package:pluriwave/widgets/tarjeta_emisora.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
/// WU6, `station-discovery-browse` spec: Buscar's landing state (relocated
|
|
/// discovery content from PantallaInicio, task 6.5), active-filter pills +
|
|
/// results counter + clear-all-filters (task 6.6), and the client-side
|
|
/// "Ordenar" control (task 6.7).
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
group('Buscar — landing state (empty query shows discovery content)', () {
|
|
testWidgets('consulta vacia muestra cerca de vos, generos y tendencias; '
|
|
'el grid de resultados de busqueda no aparece', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
populares: [emisoraDemo(uuid: 'pop-1', nombre: 'Populares Uno')],
|
|
tendencias: [emisoraDemo(uuid: 'tr-1', nombre: 'Tendencia Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.nearYou), findsOneWidget);
|
|
// Audit 3.2 (t4 lines 164-170): the "Explorar por" 2x2 grid replaced
|
|
// the old always-visible Tendencias chip strip and Géneros chip Wrap
|
|
// (both now live behind this grid's own cells, task 18's scope).
|
|
expect(find.text(l10n.exploreByTitle), findsOneWidget);
|
|
expect(find.text(l10n.countriesScreenTitle), findsOneWidget);
|
|
expect(find.text(l10n.genresTitle), findsOneWidget);
|
|
expect(find.text(l10n.exploreTrendingTitle), findsOneWidget);
|
|
expect(find.text(l10n.exploreNewTitle), findsOneWidget);
|
|
expect(find.text('Populares Uno'), findsOneWidget);
|
|
// The search-results empty state must NOT show — this is the
|
|
// landing state, not "you searched and got nothing".
|
|
expect(find.text(l10n.searchEmptyTitle), findsNothing);
|
|
});
|
|
|
|
testWidgets(
|
|
'escribir una consulta reemplaza el contenido de descubrimiento por '
|
|
'la vista de resultados',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
populares: [emisoraDemo(uuid: 'pop-1', nombre: 'Populares Uno')],
|
|
busqueda: [emisoraDemo(uuid: 'res-1', nombre: 'Resultado Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.nearYou), findsOneWidget);
|
|
|
|
await tester.enterText(find.byType(SearchBar), 'resultado');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text(l10n.nearYou), findsNothing);
|
|
expect(find.text('Resultado Uno'), findsOneWidget);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('Buscar — active-filter pills y contador de resultados', () {
|
|
testWidgets(
|
|
'aplicar un filtro de pais muestra un pill removible y el contador '
|
|
'refleja el conteo filtrado',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'es-1', nombre: 'Radio Espana')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await _abrirYSeleccionarPais(tester, l10n.countrySpain);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text(l10n.countrySpain), findsOneWidget);
|
|
expect(find.byIcon(Icons.close), findsWidgets);
|
|
expect(find.text(l10n.searchResultsCount(1)), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('quitar un pill vuelve a buscar sin ese filtro', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'es-1', nombre: 'Radio Espana')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await _abrirYSeleccionarPais(tester, l10n.countrySpain);
|
|
await _pumpStableFrame(tester);
|
|
expect(find.text(l10n.countrySpain), findsOneWidget);
|
|
|
|
final pillPais = find.ancestor(
|
|
of: find.text(l10n.countrySpain),
|
|
matching: find.byType(Chip),
|
|
);
|
|
await tester.tap(
|
|
find.descendant(of: pillPais, matching: find.byIcon(Icons.close)),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text(l10n.countrySpain), findsNothing);
|
|
});
|
|
|
|
testWidgets(
|
|
'dos filtros activos y cero resultados ofrecen quitar ambos de una '
|
|
'sola vez',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(radio: FakeServicioRadio(busqueda: []));
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await _abrirYSeleccionarPais(tester, l10n.countrySpain);
|
|
await _pumpStableFrame(tester);
|
|
await _abrirYSeleccionarCalidad(tester, '320 kbps');
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text(l10n.countrySpain), findsOneWidget);
|
|
final accionQuitar = find.text(l10n.searchClearFiltersAction(2));
|
|
expect(accionQuitar, findsOneWidget);
|
|
|
|
await tester.tap(accionQuitar);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text(l10n.countrySpain), findsNothing);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('visual fidelity (audit 6.2/6.3/6.4)', () {
|
|
testWidgets(
|
|
'6.2: an active filter pill is brand-teal tinted with an inline '
|
|
'close glyph (t4:292-293)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'es-1', nombre: 'Radio Espana')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await _abrirYSeleccionarPais(tester, l10n.countrySpain);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final chip = tester.widget<Chip>(
|
|
find.ancestor(
|
|
of: find.text(l10n.countrySpain),
|
|
matching: find.byType(Chip),
|
|
),
|
|
);
|
|
expect(
|
|
chip.backgroundColor,
|
|
const Color(0xFF21D4D9).withValues(alpha: 0.2),
|
|
);
|
|
expect(
|
|
(chip.shape as RoundedRectangleBorder?)?.side.color,
|
|
const Color(0xFF21D4D9).withValues(alpha: 0.45),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'6.3: an "Idioma" entry chip is always reachable once a search is '
|
|
'active, opening the filters sheet (t4:294-295)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.searchLanguageFilterLabel), findsOneWidget);
|
|
|
|
await tester.tap(find.text(l10n.searchLanguageFilterLabel));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text(l10n.searchCountryFilterLabel), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'6.4: the results-count eyebrow uses eyebrowLabel styling (t4:299)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
final texto = tester.widget<Text>(
|
|
find.text(l10n.searchResultsCount(1)),
|
|
);
|
|
expect(texto.style?.fontSize, 11);
|
|
expect(texto.style?.fontWeight, FontWeight.w800);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('Buscar — Ordenar (client-side, WU6)', () {
|
|
testWidgets(
|
|
'cada opcion renderizada de Ordenar corresponde a un caso real de '
|
|
'OrdenEmisoras (regresion: nunca una opcion decorativa)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byIcon(Icons.swap_vert_rounded));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(
|
|
find.byType(PopupMenuItem<OrdenEmisoras>),
|
|
findsNWidgets(OrdenEmisoras.values.length),
|
|
);
|
|
expect(find.text(l10n.stationOrderByName), findsOneWidget);
|
|
expect(find.text(l10n.stationOrderByQuality), findsOneWidget);
|
|
expect(find.text(l10n.stationOrderByPopularity), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('elegir un criterio reordena la pagina actual sin una nueva '
|
|
'solicitud de red con parametro order', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final radio = FakeServicioRadio(
|
|
busqueda: [
|
|
emisoraDemo(
|
|
uuid: 'alta',
|
|
nombre: 'Estacion Alta',
|
|
).copyWith(bitrate: 320, votes: 1),
|
|
emisoraDemo(
|
|
uuid: 'popular',
|
|
nombre: 'Estacion Popular',
|
|
).copyWith(bitrate: 64, votes: 900),
|
|
],
|
|
);
|
|
final estado = _crearEstado(radio: radio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'estacion');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
// Default global ordering is OrdenEmisoras.calidad (bitrate desc):
|
|
// "Estacion Alta" (320 kbps) must render above "Estacion Popular".
|
|
final yAltaAntes = tester.getTopLeft(find.text('Estacion Alta')).dy;
|
|
final yPopularAntes = tester.getTopLeft(find.text('Estacion Popular')).dy;
|
|
expect(yAltaAntes, lessThan(yPopularAntes));
|
|
|
|
final llamadasAntes = radio.buscarCalls;
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await tester.tap(find.byIcon(Icons.swap_vert_rounded));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text(l10n.stationOrderByPopularity));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
radio.buscarCalls,
|
|
llamadasAntes,
|
|
reason: 'sorting must not trigger a new /json request',
|
|
);
|
|
|
|
final yAltaDespues = tester.getTopLeft(find.text('Estacion Alta')).dy;
|
|
final yPopularDespues =
|
|
tester.getTopLeft(find.text('Estacion Popular')).dy;
|
|
expect(yPopularDespues, lessThan(yAltaDespues));
|
|
});
|
|
});
|
|
|
|
group('Buscar — contenido relocalizado desde PantallaInicio (WU6 6.5)', () {
|
|
testWidgets('el grid de descubrimiento muestra custom + populares; tocar '
|
|
'reproduce via EstadoRadio y el boton de favorito usa el flujo '
|
|
'existente', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final audio = FakeServicioAudio();
|
|
final favoritos = FakeServicioFavoritos();
|
|
final radio = FakeServicioRadio();
|
|
final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno');
|
|
final archivo = await _crearArchivoCustom([custom]);
|
|
final estado = _crearEstado(
|
|
audio: audio,
|
|
favoritos: favoritos,
|
|
radio: radio,
|
|
resolverArchivoCustom: () async => archivo,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text('Custom Uno'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Custom Uno'));
|
|
await _pumpStableFrame(tester);
|
|
expect(
|
|
audio.emisorasReproducidas.map((e) => e.uuid),
|
|
contains('custom-1'),
|
|
);
|
|
expect(radio.ultimoUuidClick, 'custom-1');
|
|
|
|
final tarjetaCustom = find.ancestor(
|
|
of: find.text('Custom Uno'),
|
|
matching: find.byType(TarjetaEmisora),
|
|
);
|
|
final botonFavorito =
|
|
find
|
|
.descendant(of: tarjetaCustom, matching: find.byType(InkWell))
|
|
.last;
|
|
expect(botonFavorito, findsOneWidget);
|
|
|
|
await tester.ensureVisible(botonFavorito);
|
|
await _pumpStableFrame(tester);
|
|
await tester.tap(botonFavorito);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(favoritos.toggleCalls, 1);
|
|
expect(await favoritos.esFavorito(custom.uuid), isTrue);
|
|
});
|
|
|
|
testWidgets('permite reintentar manualmente tras fallo inicial agotado', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final radio = FakeServicioRadio(
|
|
erroresPopularesPorLlamada: [Exception('sin red')],
|
|
popularesPorLlamada: [
|
|
const [],
|
|
[emisoraDemo(uuid: 'api-1', nombre: 'API Uno')],
|
|
],
|
|
tendenciasPorLlamada: [
|
|
const [],
|
|
[emisoraDemo(uuid: 'trend-1', nombre: 'Trend Uno')],
|
|
],
|
|
);
|
|
final estado = _crearEstado(radio: radio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.text('Sin conexión a la API de radio'), findsOneWidget);
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.retryAction), findsOneWidget);
|
|
|
|
await tester.tap(find.text(l10n.retryAction));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(radio.obtenerPopularesCalls, 2);
|
|
expect(find.text('Sin conexión a la API de radio'), findsNothing);
|
|
expect(find.text('API Uno'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 13.1, proto t4 line 641): banner sin conexion '
|
|
'usa offlineAccent, no colorScheme.error',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final radio = FakeServicioRadio(
|
|
erroresPopularesPorLlamada: [Exception('sin red')],
|
|
);
|
|
final estado = _crearEstado(radio: radio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final banner = find.byKey(const ValueKey('offline-banner'));
|
|
expect(banner, findsOneWidget);
|
|
final contexto = tester.element(banner);
|
|
final tokens = contexto.pluriTokens;
|
|
final errorColor = Theme.of(contexto).colorScheme.error;
|
|
|
|
final caja = tester.widget<DecoratedBox>(banner);
|
|
final decoracion = caja.decoration as BoxDecoration;
|
|
expect(
|
|
decoracion.color,
|
|
tokens.offlineAccent.withValues(alpha: 0.14),
|
|
reason:
|
|
'prototype t4 line 641: rgba(207,102,121,.14) — this app '
|
|
'consolidates on the offlineAccent token (#E8879A)',
|
|
);
|
|
expect(decoracion.color, isNot(errorColor));
|
|
expect(
|
|
decoracion.borderRadius,
|
|
BorderRadius.circular(16),
|
|
reason: 'prototype t4 line 641: border-radius:16px',
|
|
);
|
|
|
|
final icono = tester.widget<Icon>(
|
|
find.descendant(of: banner, matching: find.byIcon(Icons.wifi_off)),
|
|
);
|
|
expect(icono.color, tokens.offlineAccent);
|
|
expect(icono.color, isNot(errorColor));
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.offlineBannerTitle), findsOneWidget);
|
|
expect(find.text(l10n.retryAction), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'PantallaFavoritos muestra el custom marcado como favorito desde '
|
|
'Buscar tras recargar',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final favoritos = FakeServicioFavoritos();
|
|
final custom = emisoraDemo(uuid: 'custom-1', nombre: 'Custom Uno');
|
|
final archivo = await _crearArchivoCustom([custom]);
|
|
final estado = _crearEstado(
|
|
favoritos: favoritos,
|
|
radio: FakeServicioRadio(
|
|
populares: [emisoraDemo(uuid: 'api-1', nombre: 'API Uno')],
|
|
),
|
|
resolverArchivoCustom: () async => archivo,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
final tarjetaCustom = find.ancestor(
|
|
of: find.text('Custom Uno'),
|
|
matching: find.byType(TarjetaEmisora),
|
|
);
|
|
final botonFavorito =
|
|
find
|
|
.descendant(of: tarjetaCustom, matching: find.byType(InkWell))
|
|
.last;
|
|
await tester.ensureVisible(botonFavorito);
|
|
await _pumpStableFrame(tester);
|
|
await tester.tap(botonFavorito);
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(child: const PantallaFavoritos())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(await favoritos.esFavorito(custom.uuid), isTrue);
|
|
expect(find.text('Custom Uno'), findsOneWidget);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('Buscar — "Explorar por" 2x2 grid (audit 3.2)', () {
|
|
testWidgets('renders exactly 4 cells arranged 2 per row', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byKey(const Key('explore-cell-paises')), findsOneWidget);
|
|
expect(find.byKey(const Key('explore-cell-generos')), findsOneWidget);
|
|
expect(find.byKey(const Key('explore-cell-tendencias')), findsOneWidget);
|
|
expect(find.byKey(const Key('explore-cell-novedades')), findsOneWidget);
|
|
|
|
// Geometry, not GridView internals: prototype t4 line 166
|
|
// (grid-template-columns:1fr 1fr) means Países/Géneros share a row
|
|
// and Tendencias/Novedades share the NEXT row, 2 columns each.
|
|
final paises = tester.getTopLeft(
|
|
find.byKey(const Key('explore-cell-paises')),
|
|
);
|
|
final generos = tester.getTopLeft(
|
|
find.byKey(const Key('explore-cell-generos')),
|
|
);
|
|
final tendencias = tester.getTopLeft(
|
|
find.byKey(const Key('explore-cell-tendencias')),
|
|
);
|
|
final novedades = tester.getTopLeft(
|
|
find.byKey(const Key('explore-cell-novedades')),
|
|
);
|
|
expect(paises.dy, generos.dy, reason: 'Países/Géneros share a row');
|
|
expect(
|
|
tendencias.dy,
|
|
novedades.dy,
|
|
reason: 'Tendencias/Novedades share a row',
|
|
);
|
|
expect(
|
|
paises.dy,
|
|
lessThan(tendencias.dy),
|
|
reason: 'the first row sits above the second',
|
|
);
|
|
expect(paises.dx, lessThan(generos.dx), reason: 'Países is column 1');
|
|
expect(
|
|
tendencias.dx,
|
|
lessThan(novedades.dx),
|
|
reason: 'Tendencias is column 1',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'each cell icon uses the prototype accent colour (t4 lines 167-170)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
Color colorDe(String key) {
|
|
final icono = tester.widget<Icon>(
|
|
find.descendant(
|
|
of: find.byKey(Key(key)),
|
|
matching: find.byType(Icon),
|
|
),
|
|
);
|
|
return icono.color!;
|
|
}
|
|
|
|
final contexto = tester.element(
|
|
find.byKey(const Key('explore-cell-generos')),
|
|
);
|
|
expect(colorDe('explore-cell-paises'), PluriWaveTokens.brand);
|
|
expect(colorDe('explore-cell-generos'), contexto.pluriTokens.liveGreen);
|
|
expect(
|
|
colorDe('explore-cell-tendencias'),
|
|
contexto.pluriTokens.warmCoral,
|
|
);
|
|
expect(colorDe('explore-cell-novedades'), PluriWaveTokens.skyBlue);
|
|
},
|
|
);
|
|
|
|
testWidgets('tapping Paises pushes PantallaPaises', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('explore-cell-paises')));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byType(PantallaPaises), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'Item 24: tapping a country row in Paises pops back and filters '
|
|
'Buscar by that ISO code',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
paises: const [
|
|
PaisRadio(
|
|
nombre: 'Kazakhstan',
|
|
codigoIso: 'KZ',
|
|
numeroEmisoras: 12,
|
|
),
|
|
],
|
|
busqueda: [emisoraDemo(uuid: 'kz-1', nombre: 'Radio Kazajstan')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('explore-cell-paises')));
|
|
await _pumpStableFrame(tester);
|
|
expect(find.byType(PantallaPaises), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Kazakhstan'));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
find.byType(PantallaPaises),
|
|
findsNothing,
|
|
reason: 'selecting a country pops back to Buscar',
|
|
);
|
|
expect(find.text('Radio Kazajstan'), findsOneWidget);
|
|
expect(estado.busqueda.resultados.map((e) => e.uuid), contains('kz-1'));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'tapping Generos opens a picker sheet; selecting a genre closes it '
|
|
'and filters the discovery grid (same capability, relocated)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'rock-1', nombre: 'Rock Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('explore-cell-generos')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = _l10nDe(tester);
|
|
await tester.tap(find.text(l10n.genreRock));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Tap-once picker (matches this file's country/language/quality
|
|
// filter sheets) — the sheet closes and the grid below now shows
|
|
// the genre-filtered results.
|
|
expect(find.byType(BottomSheet), findsNothing);
|
|
expect(find.text('Rock Uno'), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('tapping Tendencias opens a sheet listing the trending strip; '
|
|
'tapping a station plays it via the existing flow', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final audio = FakeServicioAudio();
|
|
final estado = _crearEstado(
|
|
audio: audio,
|
|
radio: FakeServicioRadio(
|
|
tendencias: [emisoraDemo(uuid: 'trend-1', nombre: 'Trend Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('explore-cell-tendencias')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Trend Uno'));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
audio.emisorasReproducidas.map((e) => e.uuid),
|
|
contains('trend-1'),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'tapping Novedades re-triggers the discovery refresh (no distinct '
|
|
'"new stations" feed exists in the domain)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final radio = FakeServicioRadio();
|
|
final estado = _crearEstado(radio: radio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
final llamadasAntes = radio.obtenerPopularesCalls;
|
|
|
|
await tester.tap(find.byKey(const Key('explore-cell-novedades')));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(radio.obtenerPopularesCalls, greaterThan(llamadasAntes));
|
|
},
|
|
);
|
|
});
|
|
|
|
// Item 23 / audit 6.5 + 6.6 (t4:302-306): flat, background-less search-
|
|
// result rows with a square thumbnail, a favourite toggle, and a circular
|
|
// play affordance -- replacing the full glass TarjetaEmisora card. Rows
|
|
// sit back-to-back (no 10px separator), matching t4:299's bare column.
|
|
group('Item 23 -- flat search-result rows (audit 6.5, 6.6)', () {
|
|
testWidgets(
|
|
'each result is a flat FilaEmisoraPlana with a "genre - country - '
|
|
'kbps" meta line, not the full glass card',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [
|
|
emisoraDemo(
|
|
uuid: 'r-1',
|
|
nombre: 'Radio Uno',
|
|
).copyWith(tags: 'Rock', pais: 'Spain', bitrate: 128),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byType(FilaEmisoraPlana), findsOneWidget);
|
|
expect(
|
|
find.byType(TarjetaEmisora),
|
|
findsNothing,
|
|
reason: 'audit 6.5 replaces the full glass card with a flat row',
|
|
);
|
|
expect(find.text('Rock · Spain · 128 kbps'), findsOneWidget);
|
|
expect(find.byType(BotonFavoritoEmisora), findsOneWidget);
|
|
expect(find.byType(BotonReproducirCircular), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('rows sit back-to-back, with no 10px separator (t4:299)', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [
|
|
emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno'),
|
|
emisoraDemo(uuid: 'r-2', nombre: 'Radio Dos'),
|
|
],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final filas = find.byType(FilaEmisoraPlana);
|
|
expect(filas, findsNWidgets(2));
|
|
final primeraAbajo = tester.getBottomLeft(filas.at(0)).dy;
|
|
final segundaArriba = tester.getTopLeft(filas.at(1)).dy;
|
|
expect(
|
|
segundaArriba - primeraAbajo,
|
|
closeTo(0, 0.5),
|
|
reason:
|
|
't4:299 rows have no separator; each keeps only its own '
|
|
'8px padding',
|
|
);
|
|
});
|
|
|
|
testWidgets('issue 3 (feedback-pruebas): the results list uses row-tier '
|
|
'horizontal padding (12), not the card-tier constant this "flat, '
|
|
'background-less row" was documented as needing but never got', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final fila = find.byType(FilaEmisoraPlana);
|
|
expect(
|
|
tester.getTopLeft(fila).dx,
|
|
PluriLayout.rowHorizontal,
|
|
reason:
|
|
'issue 3: background-less rows are row tier (12), not card '
|
|
'tier (16)',
|
|
);
|
|
});
|
|
|
|
testWidgets('issue 3 (feedback-pruebas): the results list is topped by the '
|
|
'standard section gap, not the horizontal-inset constant reused for '
|
|
'a vertical axis', (tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
// Reads the structural padding directly, rather than measuring a
|
|
// gap between two rendered widgets — the count row's own height is
|
|
// dictated by its taller PopupMenuButton (48dp touch target), so a
|
|
// position-based gap measurement against the count TEXT specifically
|
|
// would be thrown off by that unrelated vertical centring.
|
|
//
|
|
// Scoped to `shrinkWrap: true` — the OUTER page ListView is ALSO an
|
|
// ancestor of every `FilaEmisoraPlana`, but only `_resultados`'s OWN
|
|
// inner `ListView.builder` sets `shrinkWrap`.
|
|
final listaResultados = tester.widget<ListView>(
|
|
find.byWidgetPredicate((w) => w is ListView && w.shrinkWrap),
|
|
);
|
|
final padding = listaResultados.padding as EdgeInsets;
|
|
|
|
expect(
|
|
padding.top,
|
|
PluriLayout.sectionGap,
|
|
reason:
|
|
'issue 3: the results list must use the dedicated vertical '
|
|
'section gap above its first row, not the horizontal (16) '
|
|
'constant reused for a vertical axis',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'tapping the favourite toggle on a search result adds it to favorites',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(
|
|
radio: FakeServicioRadio(
|
|
busqueda: [emisoraDemo(uuid: 'r-1', nombre: 'Radio Uno')],
|
|
),
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'radio');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byIcon(Icons.favorite_outline_rounded), findsOneWidget);
|
|
await tester.tap(find.byIcon(Icons.favorite_outline_rounded));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(await estado.esFavorito('r-1'), isTrue);
|
|
},
|
|
);
|
|
});
|
|
|
|
// Item 25 / audit 13.2 (t4:643-646): a reconnect card with a rotating
|
|
// ring, the station name, and a stop affordance -- previously the ONLY
|
|
// signal of a reconnect was a word in the mini player.
|
|
//
|
|
// Hazard: `pumpAndSettle()` never terminates while this card's rotating
|
|
// ring animates -- every assertion below uses a bounded `pump()` once
|
|
// `reconectando` is emitted, never `_pumpStableFrame`'s `pumpAndSettle`.
|
|
group('visual fidelity (audit 13.5/13.6)', () {
|
|
testWidgets(
|
|
'13.5/13.6: the no-results card quotes the query, and the "clear '
|
|
'filters" pill sits INSIDE the same card (t4:657-663)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = _crearEstado(radio: FakeServicioRadio(busqueda: []));
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
await _abrirYSeleccionarPais(tester, _l10nDe(tester).countrySpain);
|
|
await _pumpStableFrame(tester);
|
|
await tester.enterText(find.byType(SearchBar), 'jazzz');
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final l10n = _l10nDe(tester);
|
|
expect(
|
|
find.text(l10n.searchNoResultsForQueryTitle('jazzz')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
final tarjeta = find.byKey(const ValueKey('search-no-results-card'));
|
|
expect(tarjeta, findsOneWidget);
|
|
expect(
|
|
find.descendant(
|
|
of: tarjeta,
|
|
matching: find.text(l10n.searchClearFiltersAction(1)),
|
|
),
|
|
findsOneWidget,
|
|
reason: 't4:667 the clear-filters pill sits INSIDE the card',
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('Item 25 -- reconnect card (audit 13.2)', () {
|
|
testWidgets(
|
|
'shows the station name, "Reconectando...", and a stop button while '
|
|
'reconnecting',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final audio = FakeServicioAudio();
|
|
final estado = _crearEstado(audio: audio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
find.byIcon(Icons.stop_circle_rounded),
|
|
findsNothing,
|
|
reason: 'not reconnecting yet -- the card must not show',
|
|
);
|
|
|
|
audio.emitirEstado(EstadoReproduccion.reconectando);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(find.text('Radio Uno'), findsOneWidget);
|
|
final l10n = _l10nDe(tester);
|
|
expect(find.text(l10n.playbackStatusReconnecting), findsOneWidget);
|
|
expect(find.byIcon(Icons.stop_circle_rounded), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('tapping stop calls EstadoRadio.detenerReproduccion', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final audio = FakeServicioAudio();
|
|
final estado = _crearEstado(audio: audio);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
await audio.reproducir(emisoraDemo(uuid: 'r1', nombre: 'Radio Uno'));
|
|
|
|
await tester.pumpWidget(_conProviders(estado, _testApp()));
|
|
await _pumpStableFrame(tester);
|
|
|
|
audio.emitirEstado(EstadoReproduccion.reconectando);
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
await tester.tap(find.byIcon(Icons.stop_circle_rounded));
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
// `EstadoRadio.emisoraActual` deliberately keeps showing the last
|
|
// selected station even once stopped (an existing, unrelated
|
|
// property of `_emisoraSeleccionada`'s tracking) -- the real,
|
|
// user-visible effect of tapping stop is that this card disappears,
|
|
// since it only renders while `reconectando`.
|
|
expect(find.byKey(const ValueKey('tarjeta-reconectando')), findsNothing);
|
|
});
|
|
});
|
|
}
|
|
|
|
EstadoRadio _crearEstado({
|
|
FakeServicioAudio? audio,
|
|
FakeServicioFavoritos? favoritos,
|
|
FakeServicioRadio? radio,
|
|
Future<File> Function()? resolverArchivoCustom,
|
|
}) {
|
|
return EstadoRadio(
|
|
esPremium: () => true,
|
|
audio: audio ?? FakeServicioAudio(),
|
|
favoritos: favoritos ?? FakeServicioFavoritos(),
|
|
radio: radio ?? FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: resolverArchivoCustom ?? _archivoCustomVacio,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
}
|
|
|
|
Widget _conProviders(EstadoRadio estado, Widget child) {
|
|
return MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider<EstadoRadio>.value(value: estado),
|
|
ListenableProvider<EstadoEcualizador>.value(value: estado.ecualizador),
|
|
ListenableProvider<EstadoGrabacion>.value(value: estado.grabacion),
|
|
ListenableProvider<EstadoBusqueda>.value(value: estado.busqueda),
|
|
],
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Widget _testApp({Widget child = const PantallaBuscar()}) {
|
|
return MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: Scaffold(body: child),
|
|
);
|
|
}
|
|
|
|
AppLocalizations _l10nDe(WidgetTester tester) {
|
|
return AppLocalizations.of(tester.element(find.byType(PantallaBuscar)));
|
|
}
|
|
|
|
Future<void> _abrirYSeleccionarPais(WidgetTester tester, String label) async {
|
|
final l10n = _l10nDe(tester);
|
|
await tester.tap(find.text(l10n.searchFiltersLabel).first);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text(label).last);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<void> _abrirYSeleccionarCalidad(
|
|
WidgetTester tester,
|
|
String label,
|
|
) async {
|
|
final l10n = _l10nDe(tester);
|
|
await tester.tap(find.text(l10n.searchFiltersLabel).first);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text(label).last);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<void> _pumpStableFrame(WidgetTester tester) async {
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 100));
|
|
}
|
|
|
|
void _setLargeSurfaceSize(WidgetTester tester) {
|
|
tester.view.physicalSize = const Size(1440, 3200);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
}
|
|
|
|
Future<File> _crearArchivoCustom(List<dynamic> emisoras) async {
|
|
final nombre =
|
|
emisoras.isEmpty
|
|
? 'emisoras_custom_vacio.json'
|
|
: 'emisoras_custom_uno.json';
|
|
return File('${Directory.current.path}/test/fixtures/$nombre');
|
|
}
|
|
|
|
Future<File> _archivoCustomVacio() async => _crearArchivoCustom(const []);
|