diff --git a/lib/pantallas/pantalla_inicio.dart b/lib/pantallas/pantalla_inicio.dart index 4a0ecf6..b52608d 100644 --- a/lib/pantallas/pantalla_inicio.dart +++ b/lib/pantallas/pantalla_inicio.dart @@ -15,6 +15,7 @@ import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_layout.dart'; import '../widgets/pluri_premium_widgets.dart'; import '../widgets/pluri_sleep_timer_sheet.dart'; +import '../widgets/pluri_station_art_fallback.dart'; import '../widgets/visualizador_audio.dart'; import 'pantalla_reproductor.dart'; @@ -568,9 +569,9 @@ class _ArteEscuchar extends StatelessWidget { imageUrl: emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmerCuadrado(theme), - errorWidget: (_, __, ___) => _iconoFallback(theme), + errorWidget: (_, __, ___) => _iconoFallback(), ) - : _iconoFallback(theme), + : _iconoFallback(), ), ), ); @@ -582,14 +583,11 @@ class _ArteEscuchar extends StatelessWidget { child: Container(color: theme.colorScheme.surfaceContainerHighest), ); - Widget _iconoFallback(ThemeData theme) => Container( - color: theme.colorScheme.primaryContainer, - child: Icon( - Icons.radio_rounded, - size: 36, - color: theme.colorScheme.onPrimaryContainer, - ), - ); + // Issue 6 (feedback-pruebas): was a flat `primaryContainer` square with a + // bare `radio_rounded` icon — now the same shared fallback every other + // surface uses. + Widget _iconoFallback() => + PluriStationArtFallback(seed: emisora.uuid, iconSize: 36); } /// The hero's transport row — favorite / EQ toggle / stop / play-pause @@ -909,10 +907,10 @@ class _CeldaTusEmisoras extends StatelessWidget { imageUrl: emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmer(theme), - errorWidget: (_, __, ___) => _iconoFallback(theme), + errorWidget: (_, __, ___) => _iconoFallback(), ); } - return _iconoFallback(theme); + return _iconoFallback(); } Widget _shimmer(ThemeData theme) => shimmer.Shimmer.fromColors( @@ -921,12 +919,9 @@ class _CeldaTusEmisoras extends StatelessWidget { child: Container(color: theme.colorScheme.surfaceContainerHighest), ); - Widget _iconoFallback(ThemeData theme) => Container( - color: theme.colorScheme.primaryContainer, - child: Icon( - Icons.radio_rounded, - size: 20, - color: theme.colorScheme.onPrimaryContainer, - ), - ); + // Issue 6 (feedback-pruebas): was a flat `primaryContainer` square with a + // bare `radio_rounded` icon — now the same shared fallback every other + // surface uses. + Widget _iconoFallback() => + PluriStationArtFallback(seed: emisora.uuid, iconSize: 20); } diff --git a/lib/pantallas/pantalla_reproductor.dart b/lib/pantallas/pantalla_reproductor.dart index 78d210a..451f9a4 100644 --- a/lib/pantallas/pantalla_reproductor.dart +++ b/lib/pantallas/pantalla_reproductor.dart @@ -20,6 +20,7 @@ import '../widgets/ecualizador_widget.dart'; import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_premium_widgets.dart'; import '../widgets/pluri_push_scaffold.dart'; +import '../widgets/pluri_station_art_fallback.dart'; import '../widgets/visualizador_audio.dart'; /// WU14: restructured onto [PluriPushScaffold] (design ADR-2) — this screen @@ -291,10 +292,10 @@ class _ArteReproductor extends StatelessWidget { imageUrl: emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmer(theme), - errorWidget: (_, __, ___) => _iconoFallback(theme), + errorWidget: (_, __, ___) => _iconoFallback(), ) else - _iconoFallback(theme), + _iconoFallback(), if (cargando) Container( color: Colors.black45, @@ -333,14 +334,11 @@ class _ArteReproductor extends StatelessWidget { child: Container(color: theme.colorScheme.surfaceContainerHighest), ); - Widget _iconoFallback(ThemeData theme) => Container( - color: theme.colorScheme.primaryContainer, - child: Icon( - Icons.radio_rounded, - size: 80, - color: theme.colorScheme.onPrimaryContainer, - ), - ); + // Issue 6 (feedback-pruebas): was a flat `primaryContainer` square with a + // bare `radio_rounded` icon — now the same shared fallback every other + // surface uses. + Widget _iconoFallback() => + PluriStationArtFallback(seed: emisora.uuid, iconSize: 80); } /// Audit 2.2 (t4 lines 108-109): a full-bleed blurred backdrop of the diff --git a/lib/widgets/fila_emisora_plana.dart b/lib/widgets/fila_emisora_plana.dart index 3ef4615..2d5ee6f 100644 --- a/lib/widgets/fila_emisora_plana.dart +++ b/lib/widgets/fila_emisora_plana.dart @@ -8,6 +8,7 @@ import '../l10n/display_names.dart'; import '../l10n/gen/app_localizations.dart'; import '../modelos/emisora.dart'; import '../tema/pluriwave_tokens.dart'; +import 'pluri_station_art_fallback.dart'; /// Item 23 / audit 4.3 + 6.5 (t4:226-232, 302-306): a flat, background-less /// station row — square thumbnail, name, meta line, and a caller-supplied @@ -234,10 +235,10 @@ class _ArteFilaEmisora extends StatelessWidget { imageUrl: emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmer(theme), - errorWidget: (_, __, ___) => _iconoFallback(theme), + errorWidget: (_, __, ___) => _iconoFallback(), ); } - return _iconoFallback(theme); + return _iconoFallback(); } Widget _shimmer(ThemeData theme) => shimmer.Shimmer.fromColors( @@ -246,12 +247,9 @@ class _ArteFilaEmisora extends StatelessWidget { child: Container(color: theme.colorScheme.surfaceContainerHighest), ); - Widget _iconoFallback(ThemeData theme) => Container( - color: theme.colorScheme.primaryContainer, - child: Icon( - Icons.radio_rounded, - size: 22, - color: theme.colorScheme.onPrimaryContainer, - ), - ); + // Issue 6 (feedback-pruebas): was a flat `primaryContainer` square with a + // bare `radio_rounded` icon — now the same shared fallback every other + // surface uses. + Widget _iconoFallback() => + PluriStationArtFallback(seed: emisora.uuid, iconSize: 22); } diff --git a/lib/widgets/mini_reproductor.dart b/lib/widgets/mini_reproductor.dart index a7e126c..9a0fc2f 100644 --- a/lib/widgets/mini_reproductor.dart +++ b/lib/widgets/mini_reproductor.dart @@ -10,6 +10,7 @@ import '../modelos/emisora.dart'; import '../pantallas/pantalla_reproductor.dart'; import '../servicios/servicio_audio.dart'; import '../tema/pluriwave_theme.dart'; +import 'pluri_station_art_fallback.dart'; /// Barra inferior persistente con controles básicos de reproducción. /// Toca la barra para abrir PantallaReproductor completa. @@ -318,9 +319,9 @@ class _ArteMiniReproductor extends StatelessWidget { imageUrl: emisora.favicon!, fit: BoxFit.cover, placeholder: (_, __) => _shimmer(theme), - errorWidget: (_, __, ___) => _iconoFallback(theme), + errorWidget: (_, __, ___) => _iconoFallback(), ) - : _iconoFallback(theme), + : _iconoFallback(), ), ); } @@ -331,12 +332,9 @@ class _ArteMiniReproductor extends StatelessWidget { child: Container(color: theme.colorScheme.surfaceContainerHighest), ); - Widget _iconoFallback(ThemeData theme) => Container( - color: theme.colorScheme.primaryContainer, - child: Icon( - Icons.radio_rounded, - size: 20, - color: theme.colorScheme.onPrimaryContainer, - ), - ); + // Issue 6 (feedback-pruebas): was a flat `primaryContainer` square with a + // bare `radio_rounded` icon — now the same shared fallback every other + // surface uses. + Widget _iconoFallback() => + PluriStationArtFallback(seed: emisora.uuid, iconSize: 20); } diff --git a/lib/widgets/pluri_station_art_fallback.dart b/lib/widgets/pluri_station_art_fallback.dart new file mode 100644 index 0000000..f4cf67f --- /dev/null +++ b/lib/widgets/pluri_station_art_fallback.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; + +import '../l10n/gen/app_localizations.dart'; +import '../tema/pluriwave_theme.dart'; +import 'pluri_icon.dart'; + +/// Shared station-art fallback (feedback-pruebas issue 6). +/// +/// Before this widget existed, `TarjetaEmisora` had the only good fallback — +/// a deterministic pick from 4 bundled illustrations (`_fallbackArtFor`), +/// with a gradient + player glyph as a last resort if the asset itself ever +/// fails to decode. Every OTHER surface that can render a station without +/// artwork (`FilaEmisoraPlana`'s flat rows, the Escuchar hero, the "Tus +/// emisoras" grid cell, the mini player, the full player) had its own, +/// separate, much poorer copy: a flat `primaryContainer`-coloured square +/// with a bare `radio_rounded` icon. This widget is the ONE shared +/// implementation every one of those call sites now uses instead. +/// +/// The selection formula is pinned EXACTLY as `TarjetaEmisora` originally +/// had it (`seed.codeUnits.fold(0, (a, b) => a + b) % 4`, asset order +/// aurora/cosmic/pulse/nova) — `lib/servicios/navegacion_auto.dart` (a +/// protected, empty-diff file) independently mirrors this SAME formula for +/// Android Auto's own drawable-resource rotation +/// (`test/servicios/navegacion_auto_test.dart`'s `indiceArtePara`/ +/// `artUriPara` tests assert it inline, not by importing this widget) — +/// changing the order or the modulo here would silently desync the two +/// without either test suite noticing until an actual device compared them +/// side by side. +class PluriStationArtFallback extends StatelessWidget { + const PluriStationArtFallback({ + super.key, + required this.seed, + this.iconSize = 22, + }); + + /// Typically the station's `uuid` — the seed that deterministically picks + /// one of the 4 bundled arts below. + final String seed; + + /// Size of the centred player glyph drawn over the art. + final double iconSize; + + static const _arts = [ + 'assets/images/station_art_aurora.png', + 'assets/images/station_art_cosmic.png', + 'assets/images/station_art_pulse.png', + 'assets/images/station_art_nova.png', + ]; + + /// Exposed so other call sites (and tests) can assert which asset a given + /// seed resolves to without needing to render the widget. + static String artFor(String seed) { + final index = seed.codeUnits.fold(0, (a, b) => a + b) % _arts.length; + return _arts[index]; + } + + @override + Widget build(BuildContext context) { + return Stack( + fit: StackFit.expand, + children: [ + Image.asset( + artFor(seed), + fit: BoxFit.cover, + errorBuilder: + (_, __, ___) => DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + context.pluriTokens.deepViolet, + context.pluriTokens.electricMagenta.withValues( + alpha: 0.8, + ), + ], + ), + ), + ), + ), + Center( + child: PluriIcon( + glyph: PluriIconGlyph.player, + variant: PluriIconVariant.activeGlow, + size: iconSize, + semanticLabel: AppLocalizations.of(context).stationIconLabel, + ), + ), + ], + ); + } +} diff --git a/lib/widgets/tarjeta_emisora.dart b/lib/widgets/tarjeta_emisora.dart index 40af058..40a6e41 100644 --- a/lib/widgets/tarjeta_emisora.dart +++ b/lib/widgets/tarjeta_emisora.dart @@ -10,6 +10,7 @@ import '../modelos/emisora.dart'; import '../tema/pluriwave_theme.dart'; import 'pluri_glass_surface.dart'; import 'pluri_icon.dart'; +import 'pluri_station_art_fallback.dart'; /// Tarjeta compacta para mostrar una emisora en listas y grids. /// Incluye botón de favorito visible en ambos modos. @@ -302,51 +303,12 @@ class _TarjetaEmisoraState extends State { ); } + // Issue 6 (feedback-pruebas): extracted into the shared + // `PluriStationArtFallback` so every surface that can render a station + // without artwork shows the SAME fallback — see that widget's own doc + // comment for why the selection formula must never drift. Widget _iconoFallback(double size) { - final art = _fallbackArtFor(widget.emisora.uuid); - return Stack( - fit: StackFit.expand, - children: [ - Image.asset( - art, - fit: BoxFit.cover, - errorBuilder: - (_, __, ___) => DecoratedBox( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - context.pluriTokens.deepViolet, - context.pluriTokens.electricMagenta.withValues( - alpha: 0.8, - ), - ], - ), - ), - ), - ), - Center( - child: PluriIcon( - glyph: PluriIconGlyph.player, - variant: PluriIconVariant.activeGlow, - size: size, - semanticLabel: AppLocalizations.of(context).stationIconLabel, - ), - ), - ], - ); - } - - String _fallbackArtFor(String seed) { - const arts = [ - 'assets/images/station_art_aurora.png', - 'assets/images/station_art_cosmic.png', - 'assets/images/station_art_pulse.png', - 'assets/images/station_art_nova.png', - ]; - final index = seed.codeUnits.fold(0, (a, b) => a + b) % arts.length; - return arts[index]; + return PluriStationArtFallback(seed: widget.emisora.uuid, iconSize: size); } } diff --git a/test/pantallas/pantalla_inicio_test.dart b/test/pantallas/pantalla_inicio_test.dart index 038f1bd..a7a980d 100644 --- a/test/pantallas/pantalla_inicio_test.dart +++ b/test/pantallas/pantalla_inicio_test.dart @@ -11,6 +11,7 @@ import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/emisora.dart'; import 'package:pluriwave/pantallas/pantalla_inicio.dart'; import 'package:pluriwave/widgets/pluri_root_header.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; import 'package:pluriwave/widgets/visualizador_audio.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -698,6 +699,76 @@ void main() { ); }, ); + + testWidgets('issue 6 (feedback-pruebas): the Escuchar hero shows the shared ' + 'PluriStationArtFallback for a station with no favicon, not a flat ' + 'coloured square', (tester) async { + _setLargeSurfaceSize(tester); + final favoritos = FakeServicioFavoritos(); + final estado = EstadoRadio( + audio: FakeServicioAudio(), + favoritos: favoritos, + radio: FakeServicioRadio(), + servicioEcualizador: FakeServicioEcualizador(), + servicioGrabacion: FakeServicioGrabacionRadio(), + resolverArchivoCustom: _archivoCustomVacio, + iniciarAutomaticamente: false, + ); + addTearDown(estado.dispose); + await tester.runAsync(estado.inicializar); + final sonando = emisoraDemo(uuid: 'sin-arte', nombre: 'Sin Arte'); + await tester.runAsync(() => estado.reproducir(sonando)); + + await tester.pumpWidget( + _conProviders(estado, _testApp(const PantallaInicio())), + ); + await _pumpBounded(tester); + + expect( + find.byType(PluriStationArtFallback), + findsWidgets, + reason: + 'issue 6: the hero must reach the shared fallback, not its own ' + 'flat primaryContainer square', + ); + }); + + testWidgets( + 'issue 6 (feedback-pruebas): a "Tus emisoras" grid cell shows the shared ' + 'PluriStationArtFallback for a favourite with no favicon', + (tester) async { + _setLargeSurfaceSize(tester); + final favoritos = FakeServicioFavoritos(); + final estado = EstadoRadio( + audio: FakeServicioAudio(), + favoritos: favoritos, + radio: FakeServicioRadio(), + servicioEcualizador: FakeServicioEcualizador(), + servicioGrabacion: FakeServicioGrabacionRadio(), + resolverArchivoCustom: _archivoCustomVacio, + iniciarAutomaticamente: false, + ); + addTearDown(estado.dispose); + await tester.runAsync(estado.inicializar); + await favoritos.agregar( + emisoraDemo(uuid: 'grid-sin-arte', nombre: 'Grid Sin Arte'), + ); + await estado.cargarFavoritos(); + + await tester.pumpWidget( + _conProviders(estado, _testApp(const PantallaInicio())), + ); + await _pumpStableFrame(tester); + + expect( + find.byType(PluriStationArtFallback), + findsWidgets, + reason: + 'issue 6: the grid cell must reach the shared fallback, not its ' + 'own flat primaryContainer square', + ); + }, + ); } /// Mirrors the app.dart wiring: EstadoRadio owns the domain notifiers and diff --git a/test/pantallas/pantalla_reproductor_test.dart b/test/pantallas/pantalla_reproductor_test.dart index ab9d3fc..3d00439 100644 --- a/test/pantallas/pantalla_reproductor_test.dart +++ b/test/pantallas/pantalla_reproductor_test.dart @@ -12,6 +12,7 @@ import 'package:pluriwave/pantallas/pantalla_reproductor.dart'; import 'package:pluriwave/tema/pluriwave_tokens.dart'; import 'package:pluriwave/widgets/ecualizador_widget.dart'; import 'package:pluriwave/widgets/pluri_glass_surface.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; import 'package:pluriwave/widgets/visualizador_audio.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -704,4 +705,30 @@ void main() { }, ); }); + + group('issue 6 (feedback-pruebas): shared station-art fallback', () { + testWidgets( + 'a station with no favicon shows the shared PluriStationArtFallback, ' + 'not a flat coloured square', + (tester) async { + // `emisora` (the file-level fixture) never sets a favicon. + final estado = crearEstado(); + addTearDown(estado.dispose); + + await montarPantalla(tester, estado); + + final arte = find.byKey(const Key('player-hero-art')); + expect( + find.descendant( + of: arte, + matching: find.byType(PluriStationArtFallback), + ), + findsOneWidget, + reason: + 'issue 6: the full player must reach the shared fallback, ' + 'not its own flat primaryContainer square', + ); + }, + ); + }); } diff --git a/test/widgets/fila_emisora_plana_test.dart b/test/widgets/fila_emisora_plana_test.dart index dd9298a..2c18f35 100644 --- a/test/widgets/fila_emisora_plana_test.dart +++ b/test/widgets/fila_emisora_plana_test.dart @@ -4,6 +4,7 @@ import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/tema/pluriwave_tokens.dart'; import 'package:pluriwave/widgets/fila_emisora_plana.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'package:provider/provider.dart'; @@ -77,6 +78,32 @@ void main() { ); }); + testWidgets( + 'issue 6 (feedback-pruebas): a station with no favicon shows the shared ' + 'PluriStationArtFallback, not a flat coloured square', + (tester) async { + final estado = _estado(); + addTearDown(estado.dispose); + final emisora = emisoraDemo(uuid: 'sin-arte', nombre: 'Sin Arte'); + + await tester.pumpWidget( + _host(estado, FilaEmisoraPlana(emisora: emisora, meta: '')), + ); + await tester.pump(); + + expect( + find.descendant( + of: find.byKey(const ValueKey('fila-emisora-plana-arte')), + matching: find.byType(PluriStationArtFallback), + ), + findsOneWidget, + reason: + 'issue 6: the flat row must reach the shared fallback, not its ' + 'own flat primaryContainer square', + ); + }, + ); + testWidgets('omits the meta line entirely when empty (no stray gap)', ( tester, ) async { diff --git a/test/widgets/mini_reproductor_barra_test.dart b/test/widgets/mini_reproductor_barra_test.dart index bfa8fd0..d3c00bd 100644 --- a/test/widgets/mini_reproductor_barra_test.dart +++ b/test/widgets/mini_reproductor_barra_test.dart @@ -5,6 +5,7 @@ import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/tema/pluriwave_theme.dart'; import 'package:pluriwave/tema/pluriwave_tokens.dart'; import 'package:pluriwave/widgets/mini_reproductor.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; import 'package:pluriwave/widgets/visualizador_audio.dart'; import 'package:provider/provider.dart'; @@ -75,6 +76,33 @@ void main() { }, ); + testWidgets( + 'issue 6 (feedback-pruebas): a station with no favicon shows the shared ' + 'PluriStationArtFallback, not a flat coloured square', + (tester) async { + final estado = _estadoConEmisora(); + addTearDown(estado.dispose); + await estado.reproducir( + emisoraDemo(uuid: 'sin-arte', nombre: 'Sin Arte'), + ); + + await tester.pumpWidget(_hostFor(estado)); + await tester.pump(); + + final arte = find.byKey(const ValueKey('mini-reproductor-arte')); + expect( + find.descendant( + of: arte, + matching: find.byType(PluriStationArtFallback), + ), + findsOneWidget, + reason: + 'issue 6: the mini player must reach the shared fallback, not ' + 'its own flat primaryContainer square', + ); + }, + ); + testWidgets( 'is opaque -- no BackdropFilter -- unlike the former glass pill', (tester) async { diff --git a/test/widgets/pluri_station_art_fallback_test.dart b/test/widgets/pluri_station_art_fallback_test.dart new file mode 100644 index 0000000..2dddf39 --- /dev/null +++ b/test/widgets/pluri_station_art_fallback_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pluriwave/l10n/gen/app_localizations.dart'; +import 'package:pluriwave/widgets/pluri_icon.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; + +/// Issue 6 (feedback-pruebas): the shared station-art fallback extracted +/// from `TarjetaEmisora`'s original `_fallbackArtFor`. The formula and asset +/// order are pinned exactly — `navegacion_auto_test.dart`'s +/// `indiceArtePara`/`artUriPara` tests mirror the SAME formula independently +/// (protected file, empty diff vs main) and must keep agreeing with this +/// widget without either suite importing the other. +void main() { + Widget host(Widget child) { + return MaterialApp( + locale: const Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold(body: SizedBox(width: 60, height: 60, child: child)), + ); + } + + test('artFor reproduces the canonical aurora/cosmic/pulse/nova order ' + '(same formula as navegacion_auto.dart\'s indiceArtePara)', () { + // Single-letter seeds whose codeUnit % 4 covers all 4 indices, mirroring + // navegacion_auto_test.dart's own fixture exactly: 'd'(100)->0 aurora, + // 'a'(97)->1 cosmic, 'b'(98)->2 pulse, 'c'(99)->3 nova. + expect( + PluriStationArtFallback.artFor('d'), + 'assets/images/station_art_aurora.png', + ); + expect( + PluriStationArtFallback.artFor('a'), + 'assets/images/station_art_cosmic.png', + ); + expect( + PluriStationArtFallback.artFor('b'), + 'assets/images/station_art_pulse.png', + ); + expect( + PluriStationArtFallback.artFor('c'), + 'assets/images/station_art_nova.png', + ); + }); + + test('artFor is deterministic for a given seed (same station always picks ' + 'the same art)', () { + const seed = 'uuid-1234-abcd-real-looking'; + expect( + PluriStationArtFallback.artFor(seed), + PluriStationArtFallback.artFor(seed), + ); + }); + + testWidgets( + 'renders the deterministic station-art asset plus a centred player glyph', + (tester) async { + await tester.pumpWidget( + host(const PluriStationArtFallback(seed: 'd', iconSize: 22)), + ); + await tester.pump(); + + // Scoped by asset-name prefix, not `find.byType(Image).first` — the + // player glyph (`PluriIcon`) ALSO renders via its own `Image.asset` + // internally, so a bare type match would be ambiguous. + final arte = tester + .widgetList(find.byType(Image)) + .firstWhere( + (img) => + img.image is AssetImage && + (img.image as AssetImage).assetName.startsWith( + 'assets/images/station_art_', + ), + ); + expect( + (arte.image as AssetImage).assetName, + 'assets/images/station_art_aurora.png', + ); + + final icono = tester.widget(find.byType(PluriIcon)); + expect(icono.glyph, PluriIconGlyph.player); + expect(icono.variant, PluriIconVariant.activeGlow); + expect(icono.size, 22); + }, + ); +} diff --git a/test/widgets/tarjeta_emisora_thumbnail_test.dart b/test/widgets/tarjeta_emisora_thumbnail_test.dart index 34eda32..52f5dab 100644 --- a/test/widgets/tarjeta_emisora_thumbnail_test.dart +++ b/test/widgets/tarjeta_emisora_thumbnail_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_radio.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; +import 'package:pluriwave/widgets/pluri_station_art_fallback.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -73,6 +74,24 @@ void main() { expect(tester.getSize(thumbnailClip), const Size(48, 48)); }); + testWidgets( + 'issue 6 (feedback-pruebas): a station with no favicon renders via the ' + 'shared PluriStationArtFallback (extracted from this exact fallback)', + (tester) async { + await tester.pumpWidget( + host( + TarjetaEmisora( + emisora: emisoraDemo(uuid: 'sin-arte', nombre: 'Sin Arte'), + esCompacta: true, + ), + ), + ); + await tester.pump(); + + expect(find.byType(PluriStationArtFallback), findsOneWidget); + }, + ); + testWidgets('esCompacta shimmer placeholder is a square block, not a ' 'circle', (tester) async { await tester.pumpWidget(