Issue 3 (partial): the results area had no top gap against the filter row in one state and reused the horizontal constant for a vertical axis in another. Applies the 3-tier scale properly -- row tier for background-less placeholders, card tier for card states. The rest of the app's spacing review is still outstanding.
1128 lines
40 KiB
Dart
1128 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(
|
|
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 []);
|