fix(buscar): filter pills, Idioma chip, results eyebrow, states screen

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.
This commit is contained in:
2026-07-30 17:04:44 +02:00
parent f61b0b9163
commit 42d35a2541
3 changed files with 368 additions and 39 deletions
@@ -89,4 +89,55 @@ void main() {
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);
});
}