fix(buscar): build the 2x2 "Explorar por" grid, add Novedades
Audit 3.2 (t4 lines 162-171): the prototype groups Países, Géneros, Tendencias and the entirely-missing Novedades into one 2x2 entry-point grid. The build had them as three unrelated always-visible widgets (a Países ListTile, a Géneros FilterChip Wrap, a Tendencias ActionChip strip) and no Novedades entry at all. Replaces all three with a single grid section, capability-preserving: Países still pushes PantallaPaises; Géneros and Tendencias now open their exact existing content in a picker sheet instead of always-on- screen (Géneros auto-closes on selection, matching this screen's other single-choice filter sheets; Tendencias stays open, a browse list, not a filter). Novedades re-triggers the existing discovery refresh, since no distinct "new stations" feed exists anywhere in the domain. New ARB key set (exploreByTitle/exploreTrendingTitle/ exploreTrendingSubtitle/exploreNewTitle/exploreNewSubtitle) translated across all 13 locales with zero anti-copy allowlist entries needed. Also fixes a pre-existing PluriEmptyState overflow this restructure exposed (unrelated to the grid itself, confirmed via isolated repro): wraps its Column in a SingleChildScrollView so a too-tall title/ subtitle scrolls instead of throwing a hard RenderFlex overflow.
This commit is contained in:
@@ -21,6 +21,17 @@ import '../helpers/fakes.dart';
|
||||
/// 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());
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ 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/pantallas/pantalla_favoritos.dart';
|
||||
import 'package:pluriwave/pantallas/pantalla_paises.dart';
|
||||
import 'package:pluriwave/tema/pluriwave_theme.dart';
|
||||
import 'package:pluriwave/tema/pluriwave_tokens.dart';
|
||||
import 'package:pluriwave/widgets/tarjeta_emisora.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -43,8 +45,14 @@ void main() {
|
||||
|
||||
final l10n = _l10nDe(tester);
|
||||
expect(find.text(l10n.nearYou), findsOneWidget);
|
||||
expect(find.text(l10n.liveRadar), 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".
|
||||
@@ -434,6 +442,185 @@ void main() {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
'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));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
EstadoRadio _crearEstado({
|
||||
|
||||
Reference in New Issue
Block a user