8 Commits
Author SHA1 Message Date
FreeTLab 4b89c9af07 fix(espaciados): complete the spacing review across every screen
Build & Deploy PluriWave / Análisis de código (push) Successful in 25s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m43s
Finishes the review the user asked for after on-device testing; Buscar
was fixed earlier in fdb7eb1.

- Favoritos' header sat 36px from the edge instead of 20 -- the list's
  own padding stacked on top of PluriRootHeader's inset, and it was the
  one root that did not match Alarmas and Ajustes
- Rows were card-tier (16) where the prototype uses row-tier (12)
- Settings group-to-group gap 12 -> 16; Grabaciones storage card -> rows
  12 -> 16; Paises language -> country list 16 -> 14
- Alarma sonando's snooze block had a non-uniform 10/14 gap pair
- The date line moved below the hero time, where the prototype puts it

Escuchar, Alarmas, Vacaciones, the 12 settings detail screens,
Reproductor and Bienvenida were checked and were already correct.

Tests: 926 -> 933.
2026-07-30 22:23:04 +02:00
FreeTLab f2b02c3ce2 merge: incorporate the CI version bump 2026-07-30 22:23:03 +02:00
FreeTLab db6f4a3a11 fix(alarma-sonando): put the date line below the hero time
The prototype's order is pill (t4:415-416), then 7:30 at 88px (t4:417),
then "Lunes, 3 de agosto" at 14px (t4:419). An earlier pass rendered the
date between the pill and the time and cited "t4 line 419" as its
justification -- but that line number is where the date SITS in the
source, which is exactly why it comes last.

Both the code and the test encoded the same misreading, so the test
passed while the screen was wrong.
2026-07-30 22:22:50 +02:00
FreeTLab 3bb92c0536 fix(grabaciones): correct the gap between the storage card and rows
Issue 3 (feedback-pruebas): t4:617 draws a 16px gap between the
storage usage card and the recordings list below it, not 12.
2026-07-30 22:14:51 +02:00
FreeTLab 9a75027d57 fix(paises): correct the gap between the language and country lists
Issue 3 (feedback-pruebas): t4:260 draws a 14px gap between "Tus
idiomas" and "Todos", not 16.
2026-07-30 22:13:43 +02:00
FreeTLab 93b7ec2af9 fix(alarma-sonando): make the snooze block's vertical gaps uniform
Issue 3 (feedback-pruebas): t4:427 wraps the POSPONER eyebrow, the
snooze tiles and the Stop button in a single flex column with a
uniform gap:12 -- this screen carried a 10/14 pair instead, matching
neither the prototype nor each other.

The dismiss-guard test (protected, untouched) only asserts behaviour
via find.text/find.byType, so this pure value change is safe against
it -- re-verified empty diff after this commit.
2026-07-30 22:12:24 +02:00
FreeTLab 5bbf750b63 fix(ajustes): correct the gap between stacked settings groups
Issue 3 (feedback-pruebas): the prototype (t4:523/534/541) draws a
16px gap between the AUDIO/STATIONS/RECORDINGS/APPLICATION cards, not
12 -- a plain unwired literal that happened to collide with the
sectionGap/panelGap tokens' own value without actually citing the
prototype.
2026-07-30 22:08:40 +02:00
FreeTLab 7faf56900f fix(favoritos): stop the header padding from doubling up
Issue 3 (feedback-pruebas): ReorderableListView.padding wrapped
header/rows/footer with a single horizontal value (16), which doubled
up on top of PluriRootHeader's own internal inset -- landing the
title at 36px instead of the 20px every other root uses -- while also
applying card-tier padding to the flat FilaEmisoraPlana rows (row
tier, matching the same widget's fix on Buscar) and leaving the
populated-state top gap at an unwired 4 that didn't match this same
screen's own empty state (0) or the footer CTA's prototype value (8).

Zeroes the list-level padding and gives the header, chip strip, rows,
and footer CTA their own correctly-tiered insets instead.
2026-07-30 22:06:36 +02:00
10 changed files with 338 additions and 53 deletions
+5 -3
View File
@@ -147,7 +147,9 @@ class _AjustesContent extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 12), // Issue 3 (feedback-pruebas): t4:523/534/541 all draw a 16px gap
// between these opaque, stacked settings groups -- not 12.
const SizedBox(height: 16, key: ValueKey('ajustes-group-gap-1')),
GrupoAjustes( GrupoAjustes(
titulo: l10n.settingsGroupStationsTitle, titulo: l10n.settingsGroupStationsTitle,
filas: [ filas: [
@@ -202,7 +204,7 @@ class _AjustesContent extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16, key: ValueKey('ajustes-group-gap-2')),
GrupoAjustes( GrupoAjustes(
titulo: l10n.settingsGroupRecordingsTitle, titulo: l10n.settingsGroupRecordingsTitle,
filas: [ filas: [
@@ -250,7 +252,7 @@ class _AjustesContent extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16, key: ValueKey('ajustes-group-gap-3')),
GrupoAjustes( GrupoAjustes(
titulo: l10n.settingsGroupApplicationTitle, titulo: l10n.settingsGroupApplicationTitle,
filas: [ filas: [
+28 -21
View File
@@ -225,25 +225,6 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
tokens: tokens, tokens: tokens,
), ),
const SizedBox(height: 22), const SizedBox(height: 22),
// Audit 9.4 (t4 line 419): "Lunes, 3 de agosto" between
// the pill and the hero time -- never rendered before.
// Purely additive: a new sibling Text, touching neither
// the pill above nor the hero time below.
Text(
fechaLargaConDiaSemana(
Localizations.localeOf(context).toString(),
DateTime.now(),
),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
const SizedBox(height: 6),
FittedBox( FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: Text( child: Text(
@@ -261,6 +242,28 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
), ),
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
// Audit 9.4: the date line goes BELOW the hero time. The
// prototype's order is pill (t4:415-416) -> 7:30 at 88px
// (t4:417) -> "Lunes, 3 de agosto" at 14px (t4:419). An
// earlier pass placed it between the pill and the time
// and cited "t4 line 419" for it — that line number is
// where the date SITS in the source, which is precisely
// why it comes last, not first.
Text(
fechaLargaConDiaSemana(
Localizations.localeOf(context).toString(),
DateTime.now(),
),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(
context,
).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
const SizedBox(height: 6),
Text( Text(
localizedAlarmName(l10n, alarma.nombre), localizedAlarmName(l10n, alarma.nombre),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -339,14 +342,18 @@ class _PantallaAlarmaSonandoState extends State<PantallaAlarmaSonando> {
], ],
), ),
), ),
const SizedBox(height: 10), // Issue 3 (feedback-pruebas): t4:427 wraps POSPONER's
// eyebrow, the snooze tiles and Stop in a `gap:12` flex
// column -- the same 12 on both sides, not the 10/14 pair
// this used to carry.
const SizedBox(height: 12),
_FilaSnoozeFija( _FilaSnoozeFija(
alarma: alarma, alarma: alarma,
l10n: l10n, l10n: l10n,
tokens: tokens, tokens: tokens,
onPosponer: _posponer, onPosponer: _posponer,
), ),
const SizedBox(height: 14), const SizedBox(height: 12),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: FilledButton.icon( child: FilledButton.icon(
+57 -20
View File
@@ -174,12 +174,18 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
return ReorderableListView( return ReorderableListView(
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
padding: const EdgeInsets.fromLTRB( // Issue 3 (feedback-pruebas): zero horizontal here, matching every
PluriLayout.horizontal, // other root's PluriLayout.pageListPadding convention (Alarmas,
4, // Ajustes, and this screen's OWN empty-state branch above).
PluriLayout.horizontal, // ReorderableListView.padding wraps header/children/footer UNIFORMLY,
PluriLayout.bottomChromeInset, // so a single horizontal value here can never be simultaneously right
), // for PluriRootHeader (self-padded, wants none), the reorderable rows
// (want row tier, applied per item below) and the footer CTA (wants
// card tier, applied on the footer's own Padding below). The previous
// `PluriLayout.horizontal` doubled up on top of PluriRootHeader's own
// internal inset, pushing "Favorites" in by 36px instead of the 20px
// every other root uses for its title.
padding: const EdgeInsets.only(bottom: PluriLayout.bottomChromeInset),
header: Padding( header: Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: Column( child: Column(
@@ -224,17 +230,39 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_FilaChipsGrupos( // Issue 3 (feedback-pruebas): t4:218 draws this chip strip at
grupos: gruposVisibles, // title-tier (20px) horizontal inset, directly on the page
favoritos: favoritos, // background -- it now needs its OWN inset since the list's
seleccionado: seleccionEfectiva, // padding no longer supplies one.
onSeleccionar: (id) => setState(() => _grupoSeleccionadoId = id), Padding(
padding: const EdgeInsets.symmetric(
horizontal: PluriLayout.titleHorizontal,
),
child: _FilaChipsGrupos(
grupos: gruposVisibles,
favoritos: favoritos,
seleccionado: seleccionEfectiva,
onSeleccionar:
(id) => setState(() => _grupoSeleccionadoId = id),
),
), ),
], ],
), ),
), ),
footer: Padding( footer: Padding(
padding: const EdgeInsets.only(top: 4), // Issue 3 (feedback-pruebas): card tier (16, matching every other
// screen's dashed CTA) now that the list's own padding no longer
// supplies it, plus t4:234's 8px gap above the CTA
// (PluriLayout.compactGap) instead of the previous unwired literal
// 4 -- the ONLY state of this screen with a nonzero top gap before
// its own content used a value that matched neither this screen's
// own empty-state branch nor the prototype.
padding: const EdgeInsets.fromLTRB(
PluriLayout.horizontal,
PluriLayout.compactGap,
PluriLayout.horizontal,
0,
),
child: _CtaEmisoraPersonalizada( child: _CtaEmisoraPersonalizada(
onTap: _abrirFormularioEmisoraPersonalizada, onTap: _abrirFormularioEmisoraPersonalizada,
), ),
@@ -244,14 +272,24 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
_onReorder(filtrados, favoritos, oldIndex, newIndex), _onReorder(filtrados, favoritos, oldIndex, newIndex),
children: [ children: [
for (var i = 0; i < filtrados.length; i++) for (var i = 0; i < filtrados.length; i++)
_FilaFavorito( // Issue 3 (feedback-pruebas): row tier (12), not card tier -- the
// key moves to this wrapper (ReorderableListView identifies each
// child by its own top-level key) since FilaEmisoraPlana rows are
// documented (audit 4.3) as flat, background-less rows, the same
// tier Buscar's results list already uses for the same widget.
Padding(
key: ValueKey(filtrados[i].uuid), key: ValueKey(filtrados[i].uuid),
index: i, padding: const EdgeInsets.symmetric(
emisora: filtrados[i], horizontal: PluriLayout.rowHorizontal,
grupos: gruposVisibles, ),
grupoActual: gruposVisibles.firstWhere( child: _FilaFavorito(
(g) => g.id == filtrados[i].grupoFavoritosId, index: i,
orElse: () => gruposVisibles.first, emisora: filtrados[i],
grupos: gruposVisibles,
grupoActual: gruposVisibles.firstWhere(
(g) => g.id == filtrados[i].grupoFavoritosId,
orElse: () => gruposVisibles.first,
),
), ),
), ),
], ],
@@ -343,7 +381,6 @@ class _FilaChipsGrupos extends StatelessWidget {
class _FilaFavorito extends StatelessWidget { class _FilaFavorito extends StatelessWidget {
const _FilaFavorito({ const _FilaFavorito({
super.key,
required this.index, required this.index,
required this.emisora, required this.emisora,
required this.grupos, required this.grupos,
+6 -1
View File
@@ -251,7 +251,12 @@ class _PantallaGrabacionesState extends State<PantallaGrabaciones> {
padding: PluriLayout.pageContentPadding, padding: PluriLayout.pageContentPadding,
children: [ children: [
_BarraDeAlmacenamiento(archivos: archivos), _BarraDeAlmacenamiento(archivos: archivos),
const SizedBox(height: 12), // Issue 3 (feedback-pruebas): t4:617 draws a 16px gap between
// the storage card and the rows below it, not 12.
const SizedBox(
height: 16,
key: ValueKey('grabaciones-storage-gap'),
),
if (snap.connectionState == ConnectionState.done && if (snap.connectionState == ConnectionState.done &&
archivos.isEmpty) archivos.isEmpty)
PluriEmptyState( PluriEmptyState(
+6 -1
View File
@@ -134,7 +134,12 @@ class _PantallaPaisesState extends State<PantallaPaises> {
), ),
if (query.isEmpty) ...[ if (query.isEmpty) ...[
_seccionTusIdiomas(context, estado.paises, l10n), _seccionTusIdiomas(context, estado.paises, l10n),
const SizedBox(height: 16), // Issue 3 (feedback-pruebas): t4:260 draws a 14px gap
// between "Tus idiomas" and "Todos", not 16.
const SizedBox(
height: 14,
key: ValueKey('paises-seccion-gap'),
),
_seccionTodos(context, estado.paises, l10n), _seccionTodos(context, estado.paises, l10n),
] else ] else
_seccionTodos(context, paisesFiltrados, l10n), _seccionTodos(context, paisesFiltrados, l10n),
+26
View File
@@ -306,6 +306,32 @@ void main() {
expect(find.text('RECORDINGS & MUSIC'), findsOneWidget); expect(find.text('RECORDINGS & MUSIC'), findsOneWidget);
expect(find.text('APPLICATION'), findsOneWidget); expect(find.text('APPLICATION'), findsOneWidget);
}); });
testWidgets(
'Issue 3 (feedback-pruebas): the gap between stacked settings groups '
'is 16, matching t4:523/534/541 -- not 12',
(tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
final estado = await crearEstado();
addTearDown(estado.dispose);
await tester.pumpWidget(buildAjustes(estado));
await pumpStable(tester);
for (final key in [
'ajustes-group-gap-1',
'ajustes-group-gap-2',
'ajustes-group-gap-3',
]) {
expect(
tester.getSize(find.byKey(ValueKey(key))).height,
16,
reason: 't4:523/534/541 all draw a 16px gap between stacked groups',
);
}
},
);
}); });
} }
@@ -99,8 +99,8 @@ void main() {
SharedPreferences.setMockInitialValues({}); SharedPreferences.setMockInitialValues({});
}); });
testWidgets('visual fidelity (audit 9.4): the date line renders between the ' testWidgets('visual fidelity (audit 9.4): the date line renders BELOW the '
'schedule pill and the hero time (t4:419)', (tester) async { 'hero time (t4:415-419: pill, then 7:30, then the date)', (tester) async {
await _montarPantalla(tester); await _montarPantalla(tester);
final localeTag = final localeTag =
@@ -111,16 +111,22 @@ void main() {
expect(find.text(esperado), findsOneWidget); expect(find.text(esperado), findsOneWidget);
// Order: pill above the date line, date line above the hero time. // Order: pill, then the hero time, then the date line. This test used
// to assert date-before-time and cited "t4:419" for it — but 419 is
// simply the source line the date occupies, and in the prototype it
// comes AFTER the 88px time on line 417. The citation refuted the
// assertion it was supporting.
final pillY = final pillY =
tester tester
.getBottomLeft(find.byKey(const ValueKey('ringing-schedule-pill'))) .getBottomLeft(find.byKey(const ValueKey('ringing-schedule-pill')))
.dy; .dy;
final dateY = tester.getTopLeft(find.text(esperado)).dy;
final timeY = final timeY =
tester.getTopLeft(find.byKey(const ValueKey('ringing-hero-time'))).dy; tester
expect(pillY <= dateY, isTrue); .getBottomLeft(find.byKey(const ValueKey('ringing-hero-time')))
expect(dateY <= timeY, isTrue); .dy;
final dateY = tester.getTopLeft(find.text(esperado)).dy;
expect(pillY <= timeY, isTrue, reason: 'pill sits above the time');
expect(timeY <= dateY, isTrue, reason: 'the date sits below the time');
// Regression guard: pumpAndSettle must still complete (purely // Regression guard: pumpAndSettle must still complete (purely
// additive static text, no new animation). // additive static text, no new animation).
@@ -159,4 +165,49 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
}, },
); );
testWidgets(
'Issue 3 (feedback-pruebas): the gap above the snooze tiles matches the '
'gap below them (t4:427 draws a uniform gap:12 flex column) -- the '
'previous 10/14 pair matched neither the prototype nor each other',
(tester) async {
await _montarPantalla(tester);
final l10n = AppLocalizations.of(
tester.element(find.byType(PantallaAlarmaSonando)),
);
final eyebrowBottom =
tester
.getBottomLeft(
find
.ancestor(
of: find.byIcon(Icons.snooze_rounded),
matching: find.byType(Row),
)
.first,
)
.dy;
final tileDestacado = find.ancestor(
of: find.text(l10n.alarmSnoozeOptionLabel(10)),
matching: find.byType(FilledButton),
);
final tilesTop = tester.getTopLeft(tileDestacado).dy;
final tilesBottom = tester.getBottomLeft(tileDestacado).dy;
final stopButtonTop =
tester
.getTopLeft(find.byKey(const ValueKey('ringing-stop-button')))
.dy;
expect(
tilesTop - eyebrowBottom,
12,
reason: 't4:427: gap:12 above the snooze tiles',
);
expect(
stopButtonTop - tilesBottom,
12,
reason: 't4:427: gap:12 below the snooze tiles, same as above',
);
},
);
} }
+102
View File
@@ -7,7 +7,9 @@ import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/pantallas/ajustes/pantalla_ajustes_grupos_favoritos.dart'; import 'package:pluriwave/pantallas/ajustes/pantalla_ajustes_grupos_favoritos.dart';
import 'package:pluriwave/pantallas/pantalla_favoritos.dart'; import 'package:pluriwave/pantallas/pantalla_favoritos.dart';
import 'package:pluriwave/widgets/fila_emisora_plana.dart'; import 'package:pluriwave/widgets/fila_emisora_plana.dart';
import 'package:pluriwave/widgets/pluri_layout.dart';
import 'package:pluriwave/widgets/pluri_push_scaffold.dart'; import 'package:pluriwave/widgets/pluri_push_scaffold.dart';
import 'package:pluriwave/widgets/pluri_root_header.dart';
import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -546,6 +548,106 @@ void main() {
expect(inactivo.backgroundColor, const Color(0xFF102532)); expect(inactivo.backgroundColor, const Color(0xFF102532));
}); });
}); });
group('Issue 3 (feedback-pruebas): spacing tiers', () {
testWidgets('the header title sits at title-tier inset (20px) -- '
'ReorderableListView.padding used to double up on top of '
"PluriRootHeader's own internal inset", (tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
final estado = await crearEstadoConFavoritos();
addTearDown(estado.dispose);
final l10n = lookupAppLocalizations(const Locale('en'));
await tester.pumpWidget(buildScreen(estado));
await pumpStable(tester);
final titulo = find.descendant(
of: find.byType(PluriRootHeader),
matching: find.text(l10n.favoritesTitle),
);
expect(
tester.getTopLeft(titulo).dx,
PluriLayout.titleHorizontal,
reason:
'PluriRootHeader already supplies its own 20px inset; the '
'previous ReorderableListView.padding of 16 doubled up on top '
'of it, landing the title at 36px instead of 20px -- the ONE '
"root screen whose header didn't match Alarmas/Ajustes",
);
});
testWidgets(
'the header sits at the SAME horizontal position whether the list is '
'empty or populated -- two mutually-exclusive states of the same '
'header must not read differently',
(tester) async {
setLargeSurface(tester);
final l10n = lookupAppLocalizations(const Locale('en'));
final vacio = await crearEstadoVacio();
addTearDown(vacio.dispose);
await tester.pumpWidget(buildScreen(vacio));
await pumpStable(tester);
final dxVacio =
tester
.getTopLeft(
find.descendant(
of: find.byType(PluriRootHeader),
matching: find.text(l10n.favoritesTitle),
),
)
.dx;
_suppressListTileInkAssertion();
final conFavoritos = await crearEstadoConFavoritos();
addTearDown(conFavoritos.dispose);
await tester.pumpWidget(buildScreen(conFavoritos));
await pumpStable(tester);
final dxConFavoritos =
tester
.getTopLeft(
find.descendant(
of: find.byType(PluriRootHeader),
matching: find.text(l10n.favoritesTitle),
),
)
.dx;
expect(
dxConFavoritos,
dxVacio,
reason:
'the empty and populated branches of this screen must render '
'the SAME header inset -- they previously did not (0 vs 16 '
'extra px of list-level padding)',
);
},
);
testWidgets(
'each favourite row uses row-tier horizontal inset (12), not the '
'card-tier constant a background-less row was never meant to carry',
(tester) async {
setLargeSurface(tester);
_suppressListTileInkAssertion();
final estado = await crearEstadoConFavoritos();
addTearDown(estado.dispose);
await tester.pumpWidget(buildScreen(estado));
await pumpStable(tester);
expect(
tester.getTopLeft(find.byType(FilaEmisoraPlana).first).dx,
PluriLayout.rowHorizontal,
reason:
'audit 4.3: background-less rows are row tier (12), matching '
'the same widget already fixed on Buscar -- not card tier '
'(16)',
);
},
);
});
} }
void setLargeSurface(WidgetTester tester) { void setLargeSurface(WidgetTester tester) {
@@ -216,6 +216,35 @@ void main() {
}, },
); );
testWidgets(
'Issue 3 (feedback-pruebas): the gap between the storage card and the '
'rows below is 16, matching t4:617 -- not 12',
(tester) async {
final estado = EstadoGrabacion(
servicio: _FakeServicioGrabacionConArchivos([
fijaA,
], maxBytesFijo: 200 * 1024 * 1024),
);
addTearDown(estado.dispose);
await tester.pumpWidget(
buildScreen(
estado: estado,
reproductor: _ReproductorGrabacionesFake(const {}),
),
);
await pumpStable(tester);
expect(
tester
.getSize(find.byKey(const ValueKey('grabaciones-storage-gap')))
.height,
16,
reason: 't4:617 draws a 16px gap here',
);
},
);
testWidgets('15.2-A: 3 recording fixtures render as 3 rows', (tester) async { testWidgets('15.2-A: 3 recording fixtures render as 3 rows', (tester) async {
final estado = EstadoGrabacion( final estado = EstadoGrabacion(
servicio: _FakeServicioGrabacionConArchivos([ servicio: _FakeServicioGrabacionConArchivos([
+21
View File
@@ -262,6 +262,27 @@ void main() {
expect(find.byIcon(Icons.search_rounded), findsOneWidget); expect(find.byIcon(Icons.search_rounded), findsOneWidget);
}); });
testWidgets('Issue 3 (feedback-pruebas): the gap between "Tus idiomas" and '
'"Todos" is 14, matching t4:260 -- not 16', (tester) async {
final estado = EstadoBusqueda(
radio: FakeServicioRadio(
paises: const [
PaisRadio(nombre: 'Spain', codigoIso: 'ES', numeroEmisoras: 482),
],
),
);
addTearDown(estado.dispose);
await tester.pumpWidget(buildScreen(estado));
await pumpEstable(tester);
expect(
tester.getSize(find.byKey(const ValueKey('paises-seccion-gap'))).height,
14,
reason: 't4:260 draws a 14px gap between the two eyebrow sections',
);
});
}); });
testWidgets( testWidgets(