Audit 6.2 (t4:292-293): active filter pills are brand-teal tinted with
a 15px close glyph and radius 10 -- was Material's own Chip theming.
Audit 6.3 (t4:294-295): an "Idioma" entry chip is now always reachable
once a search is active -- there was no standalone entry point for
language filtering before (only bundled inside "Filtros"). Opens the
same existing filter sheet rather than a new idioma-only picker.
Audit 6.4 (t4:299): the results-count line now uses eyebrowLabel
styling -- was labelLarge (14/w800).
Audit 13.3/13.4 (t4:649-651): the search-results loading skeleton
gains the missing "BUSCANDO EMISORAS..." eyebrow, and its row gap is
now 4px, not 10.
Audit 13.5/13.6 (t4:657-668): a new purpose-built _TarjetaSinResultados
replaces PluriEmptyState ONLY in the search-no-results branch (that
shared widget is left untouched for its other unrelated call sites --
favorites empty state, the discovery grid). The title now quotes the
typed query ("Sin resultados para <<jazzz>>"), and the clear-filters
pill sits INSIDE the card. New ARB key searchNoResultsForQueryTitle.
Item 6.1 (compact 60px active-search header replacing the persistent
glass pill) is NOT implemented: existing tests confirmed the quality
(bitrate) filter has NO entry point anywhere in the app besides the
header's "Filtros" pill (added first via _abrirYSeleccionarCalidad
while a country filter is ALREADY active, i.e. mid active-search).
Hiding PluriRootHeader during active search would make quality
filtering unreachable -- a real capability regression the task's own
"presentation changes, capability does not" principle forbids.
Re-plumbing where that filter lives is a bigger change than this
visual-fidelity pass should make unilaterally.
144 lines
5.6 KiB
Dart
144 lines
5.6 KiB
Dart
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/pantallas/pantalla_buscar.dart';
|
|
import 'package:pluriwave/widgets/tarjeta_emisora.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
/// S5-R6: the search loading state uses shimmer placeholders, not a bare
|
|
/// spinner, to stay consistent with the rest of the app.
|
|
///
|
|
/// WU6 correction: `PantallaBuscar` now also renders a discovery LANDING
|
|
/// state (task 6.5) that reads `EstadoRadio` directly, so any widget test
|
|
/// mounting it — this one included — must provide a full `EstadoRadio`
|
|
/// provider from the start, not just `EstadoBusqueda`. A non-empty query is
|
|
/// entered first so the screen leaves the landing state and reaches the
|
|
/// search-results branch this test actually targets.
|
|
///
|
|
/// Bugfix (surfaced while landing audit item 18's "Explorar por" grid):
|
|
/// on the FIRST pump here (landing state, empty `EstadoRadio`, default
|
|
/// narrow test viewport, no `_setLargeSurfaceSize`), `_gridEmisoras`
|
|
/// renders its empty-state branch — `PluriEmptyState`, in a fixed
|
|
/// `SizedBox(height: 260)`. That widget's own Column overflowed by 10px
|
|
/// at this width with real ARB copy (`pluri_premium_widgets.dart`,
|
|
/// fixed by wrapping it in a `SingleChildScrollView` — a no-op whenever
|
|
/// content already fits, which is every real device width). This test IS
|
|
/// the end-to-end regression guard for that fix: it renders the exact
|
|
/// scenario that surfaced it.
|
|
class _BusquedaCargando extends EstadoBusqueda {
|
|
_BusquedaCargando() : super(radio: FakeServicioRadio());
|
|
|
|
@override
|
|
bool get cargando => true;
|
|
}
|
|
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
testWidgets('PantallaBuscar muestra shimmer mientras carga', (tester) async {
|
|
final busqueda = _BusquedaCargando();
|
|
addTearDown(busqueda.dispose);
|
|
final estado = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: () async => throw UnimplementedError(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider<EstadoRadio>.value(value: estado),
|
|
ListenableProvider<EstadoEcualizador>.value(
|
|
value: estado.ecualizador,
|
|
),
|
|
ListenableProvider<EstadoGrabacion>.value(value: estado.grabacion),
|
|
// Overrides EstadoRadio's own (unused here) EstadoBusqueda with a
|
|
// fake pinned to cargando: true.
|
|
ListenableProvider<EstadoBusqueda>.value(value: busqueda),
|
|
],
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const Scaffold(body: PantallaBuscar()),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
|
|
// Still the landing state — no query entered yet.
|
|
expect(find.byType(TarjetaEmisoraShimmer), findsNothing);
|
|
|
|
await tester.enterText(find.byType(SearchBar), 'jazz');
|
|
await tester.pump();
|
|
|
|
expect(find.byType(TarjetaEmisoraShimmer), findsWidgets);
|
|
expect(find.byType(CircularProgressIndicator), findsNothing);
|
|
});
|
|
|
|
testWidgets('visual fidelity (audit 13.3/13.4): the "BUSCANDO EMISORAS..." '
|
|
'eyebrow renders above the loading rows, spaced 4px apart (t4:649-651)', (
|
|
tester,
|
|
) async {
|
|
final busqueda = _BusquedaCargando();
|
|
addTearDown(busqueda.dispose);
|
|
final estado = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: () async => throw UnimplementedError(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider<EstadoRadio>.value(value: estado),
|
|
ListenableProvider<EstadoEcualizador>.value(
|
|
value: estado.ecualizador,
|
|
),
|
|
ListenableProvider<EstadoGrabacion>.value(value: estado.grabacion),
|
|
ListenableProvider<EstadoBusqueda>.value(value: busqueda),
|
|
],
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const Scaffold(body: PantallaBuscar()),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
await tester.enterText(find.byType(SearchBar), 'jazz');
|
|
await tester.pump();
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaBuscar)),
|
|
);
|
|
expect(find.text(l10n.searchLoadingStationsLabel), findsOneWidget);
|
|
|
|
final filas = find.byType(TarjetaEmisoraShimmer);
|
|
expect(filas, findsWidgets);
|
|
final primeraAbajo = tester.getBottomLeft(filas.at(0)).dy;
|
|
final segundaArriba = tester.getTopLeft(filas.at(1)).dy;
|
|
expect(segundaArriba - primeraAbajo, 4);
|
|
});
|
|
}
|