diff --git a/lib/pantallas/ajustes/widgets/fila_ajuste.dart b/lib/pantallas/ajustes/widgets/fila_ajuste.dart index 75f52bf..964a629 100644 --- a/lib/pantallas/ajustes/widgets/fila_ajuste.dart +++ b/lib/pantallas/ajustes/widgets/fila_ajuste.dart @@ -93,6 +93,14 @@ class FilaAjuste extends StatelessWidget { /// "no accent" — the icon renders exactly as before. final Color? iconColor; + /// Issue 5 (feedback-pruebas): caps how much width the trailing current + /// value can claim. `ListTile` gives `trailing` as much width as it wants + /// before handing the title whatever is left — an unbounded value (e.g. a + /// real, arbitrarily long station name for "Emisora preferida") could + /// squeeze the title down to almost nothing, forcing it to wrap across + /// several lines that then get cut short by the row's fixed height. + static const _anchoMaximoValor = 108.0; + @override Widget build(BuildContext context) { final type = context.pluriType; @@ -106,17 +114,32 @@ class FilaAjuste extends StatelessWidget { // 14px/w700 (t4 line 514); cardTitle is 14.5/w700 — a one-off // override, not a new PluriWaveTypography style (mirrors the // precedent set for the ringing screen's station name, audit 9.7). - title: Text(titulo, style: type.cardTitle.copyWith(fontSize: 14)), + // Issue 5: constrained to one line, ellipsizing instead of wrapping — + // labels must wrap as little as possible and never render visibly + // truncated (a multi-line wrap inside this fixed-height row cuts the + // last line short, which reads as broken, not as intentional). + title: Text( + titulo, + style: type.cardTitle.copyWith(fontSize: 14), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (valorActual != null) ...[ - Text( - valorActual, - // bodyStrong is already 13/w600, matching the prototype's row - // value spec exactly — only the colour needs overriding. - style: type.bodyStrong.copyWith( - color: const Color(0xFFF2F7FA).withValues(alpha: 0.55), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: _anchoMaximoValor), + child: Text( + valorActual, + // bodyStrong is already 13/w600, matching the prototype's row + // value spec exactly — only the colour needs overriding. + style: type.bodyStrong.copyWith( + color: const Color(0xFFF2F7FA).withValues(alpha: 0.55), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.end, ), ), const SizedBox(width: 6), diff --git a/test/pantallas/ajustes/widgets/fila_ajuste_test.dart b/test/pantallas/ajustes/widgets/fila_ajuste_test.dart index 61a34b8..ae328a0 100644 --- a/test/pantallas/ajustes/widgets/fila_ajuste_test.dart +++ b/test/pantallas/ajustes/widgets/fila_ajuste_test.dart @@ -98,6 +98,75 @@ void main() { }, ); + testWidgets( + 'issue 5 (feedback-pruebas): a long trailing value does not squeeze the ' + 'title into wrapping across multiple lines -- the title stays on ONE ' + 'line, ellipsizing instead', + (tester) async { + const titulo = 'Emisora preferida'; + const valorLargo = 'Radio Nacional Clasica Internacional FM Stereo HD'; + + await tester.pumpWidget( + host( + SizedBox( + width: 360, + child: FilaAjuste( + icon: Icons.radio_rounded, + titulo: titulo, + valor: valorLargo, + onTap: () {}, + ), + ), + ), + ); + await tester.pump(); + + expect( + tester.takeException(), + isNull, + reason: 'a squeezed row must not overflow either', + ); + + final tituloWidget = tester.widget(find.text(titulo)); + expect( + tituloWidget.maxLines, + 1, + reason: 'issue 5: the title must be constrained to a single line', + ); + expect(tituloWidget.overflow, TextOverflow.ellipsis); + + // Measured, not just `find.text` (a wrapped-but-still-present Text + // would still satisfy a bare `find.text` match, per the known + // "find.text can't catch visual wrap" trap) -- compare the rendered + // height against the SAME style/width rendered with a title that is + // guaranteed to fit on one line. + final alturaConValorLargo = tester.getSize(find.text(titulo)).height; + + await tester.pumpWidget( + host( + SizedBox( + width: 360, + child: FilaAjuste( + icon: Icons.radio_rounded, + titulo: titulo, + onTap: () {}, + ), + ), + ), + ); + await tester.pump(); + final alturaReferencia = tester.getSize(find.text(titulo)).height; + + expect( + alturaConValorLargo, + closeTo(alturaReferencia, 1.0), + reason: + 'the title rendered taller with a long value present -- it ' + 'wrapped instead of staying on a single ellipsized line', + ); + }, + ); + testWidgets( 'visual fidelity (audit S10): GrupoAjustes insets its row divider by ' '47px, not full-bleed (t4 line 516)',