Audit S7 plus the open items on screens 1-3. Escuchar gains the prototype's own 52px "now listening" eyebrow header (t4:53), which is structurally different from the 56px title row every other root uses (t4:325) -- the earlier wiring test asserted PluriRootHeader on all five roots, an over-broad premise now corrected to guard what actually holds: no AppBar, and the sleep-timer action still reachable.
769 lines
27 KiB
Dart
769 lines
27 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_busqueda.dart';
|
|
import 'package:pluriwave/estado/estado_ecualizador.dart';
|
|
import 'package:pluriwave/estado/estado_grabacion.dart';
|
|
import 'package:pluriwave/estado/estado_navegacion.dart';
|
|
import 'package:pluriwave/estado/estado_radio.dart';
|
|
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/visualizador_audio.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
// WU6 correction: the 3 scenarios that used to live here ("muestra custom
|
|
// ... favorito usa flujo existente", "permite reintentar manualmente",
|
|
// "PantallaFavoritos muestra custom favorito tras recarga") all depended
|
|
// on `_gridEmisoras`/`_errorBanner`, which task 6.5 relocates to
|
|
// `PantallaBuscar` and DELETES from here (WU5's task 5.9 deferred this
|
|
// cleanup explicitly). Their coverage moved, adapted, to
|
|
// `pantalla_buscar_test.dart` — PantallaInicio no longer renders any
|
|
// station list of its own (only the hero + the "Tus emisoras" favorites
|
|
// preview), so there is nothing left on this screen for those scenarios
|
|
// to exercise.
|
|
testWidgets(
|
|
'WU5 ADR-7 anti-cache: the Escuchar hero reflects a station changed '
|
|
'from OUTSIDE the widget tree (e.g. Android Auto / a notification '
|
|
'action), proving it caches nothing of its own',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final audio = FakeServicioAudio();
|
|
final estado = EstadoRadio(
|
|
audio: audio,
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: _archivoCustomVacio,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
final estacionA = emisoraDemo(uuid: 'a', nombre: 'Estacion A');
|
|
final estacionB = emisoraDemo(uuid: 'b', nombre: 'Estacion B');
|
|
await estado.reproducir(estacionA);
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
// Bounded pump, not _pumpStableFrame/pumpAndSettle: a "reproduciendo"
|
|
// station makes VisualizadorAudio start an indeterminately-repeating
|
|
// AnimationController (visualizador_audio.dart:77, `_controller.repeat()`)
|
|
// for its animated-fallback waveform — the same class of hazard as an
|
|
// indeterminate spinner, just via animation. pumpAndSettle() would
|
|
// never return while it keeps scheduling frames.
|
|
await _pumpBounded(tester);
|
|
|
|
expect(find.text('Estacion A'), findsOneWidget);
|
|
|
|
// Mutates the underlying ServicioAudio DIRECTLY, bypassing
|
|
// EstadoRadio.reproducir() entirely — this is exactly the shape of
|
|
// navegacion_auto.dart's out-of-band mutation (Android Auto's
|
|
// playFromMediaId). EstadoRadio's own audio.estadoStream listener
|
|
// (not this test) is what is expected to pick this up and update
|
|
// emisoraActual.
|
|
await audio.reproducir(estacionB);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(find.text('Estacion B'), findsOneWidget);
|
|
expect(find.text('Estacion A'), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets('WU5: "Ver todas" switches to the Favoritos root via '
|
|
'EstadoNavegacionRaiz.irA, without pushing a route', (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: 'f1', nombre: 'Favorita Uno'));
|
|
await estado.cargarFavoritos();
|
|
|
|
final navegacion = EstadoNavegacionRaiz();
|
|
final observer = _RecordingNavigatorObserver();
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(
|
|
estado,
|
|
_testApp(const PantallaInicio(), observers: [observer]),
|
|
navegacion: navegacion,
|
|
),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
final pushesAntesDeTocar = observer.pushCount;
|
|
|
|
await tester.ensureVisible(find.text('Ver todas'));
|
|
await _pumpStableFrame(tester);
|
|
await tester.tap(find.text('Ver todas'));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(navegacion.actual, RaizPluriWave.favoritos);
|
|
expect(
|
|
observer.pushCount,
|
|
pushesAntesDeTocar,
|
|
reason: 'switches tabs — must NOT push a new route',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.10): Tus emisoras is a 2-column grid showing '
|
|
'up to 8 stations, not a 6-capped horizontal strip',
|
|
(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);
|
|
for (var i = 0; i < 8; i++) {
|
|
await favoritos.agregar(
|
|
emisoraDemo(uuid: 'grid-$i', nombre: 'Grid Estacion $i'),
|
|
);
|
|
}
|
|
await estado.cargarFavoritos();
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
for (var i = 0; i < 8; i++) {
|
|
expect(
|
|
find.text('Grid Estacion $i'),
|
|
findsOneWidget,
|
|
reason:
|
|
'prototype t4 lines 82-88: a grid of 8, the old strip capped '
|
|
'at 6',
|
|
);
|
|
}
|
|
|
|
final grid = tester.widget<GridView>(find.byType(GridView));
|
|
final delegate =
|
|
grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
|
expect(
|
|
delegate.crossAxisCount,
|
|
2,
|
|
reason: 'prototype t4 line 82: grid-template-columns:1fr 1fr',
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('visual fidelity (audit 1.3): the hero art is 132 square', (
|
|
tester,
|
|
) async {
|
|
// The redesign shipped this at 84 — the prototype draws 132 (t4 line
|
|
// 56). Nothing caught it because every existing test here asserts
|
|
// behaviour, never dimensions. This is the guard that would have.
|
|
_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: 'f1', nombre: 'Favorita Uno');
|
|
await favoritos.agregar(sonando);
|
|
await estado.cargarFavoritos();
|
|
// The hero only renders when a station is active — `emisoraActual` is
|
|
// what gates it. Without this the assertion below passes vacuously.
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
final cuadrados = tester
|
|
.widgetList<SizedBox>(find.byType(SizedBox))
|
|
.where((s) => s.width == 132 && s.height == 132);
|
|
|
|
expect(
|
|
cuadrados,
|
|
isNotEmpty,
|
|
reason: 'prototype t4 line 56 sizes the Escuchar hero artwork at 132',
|
|
);
|
|
});
|
|
|
|
testWidgets('visual fidelity (audit 1.6): the hero shows "genre · country · '
|
|
'bitrate kbps" built from the station model, nothing invented', (
|
|
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);
|
|
const completa = Emisora(
|
|
uuid: 'meta-1',
|
|
nombre: 'Radio Horizonte',
|
|
url: 'https://stream.demo/radio',
|
|
tags: 'Noticias',
|
|
pais: 'España',
|
|
bitrate: 128,
|
|
);
|
|
await favoritos.agregar(completa);
|
|
await estado.cargarFavoritos();
|
|
// The hero only renders when a station is active.
|
|
await tester.runAsync(() => estado.reproducir(completa));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(
|
|
find.text('Noticias · España · 128 kbps'),
|
|
findsOneWidget,
|
|
reason: 'prototype t4 line 62: "Noticias · España · 128 kbps"',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.7): the hero visualizer renders 30 discrete '
|
|
'bars, not the continuous waveform stroke',
|
|
(tester) async {
|
|
// Item 16 is exactly the widget the audit warned about
|
|
// (visualizador_audio.dart:77, `_controller.repeat()`) — bounded pump
|
|
// only, never pumpAndSettle.
|
|
_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: 'f1', nombre: 'Favorita Uno');
|
|
await favoritos.agregar(sonando);
|
|
await estado.cargarFavoritos();
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(
|
|
find.byKey(const Key('visualizador-barra-0')),
|
|
findsOneWidget,
|
|
reason: 'prototype t4 line 66-68: 30 discrete bottom-anchored bars',
|
|
);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byType(VisualizadorAudio),
|
|
matching: find.byType(CustomPaint),
|
|
),
|
|
findsNothing,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.6): a station missing every meta field omits '
|
|
'the line gracefully, no stray separators',
|
|
(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 desnuda = emisoraDemo(uuid: 'meta-2', nombre: 'Radio Desnuda');
|
|
await favoritos.agregar(desnuda);
|
|
await estado.cargarFavoritos();
|
|
await tester.runAsync(() => estado.reproducir(desnuda));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(find.byKey(const ValueKey('escuchar-hero-meta')), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets('visual fidelity (audit 1.11): a count badge sits next to "Tus '
|
|
'emisoras", showing the TOTAL favorite count, not the 8-capped grid '
|
|
'size', (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);
|
|
for (var i = 0; i < 11; i++) {
|
|
await favoritos.agregar(
|
|
emisoraDemo(uuid: 'badge-$i', nombre: 'Badge Estacion $i'),
|
|
);
|
|
}
|
|
await estado.cargarFavoritos();
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
find.text('11'),
|
|
findsOneWidget,
|
|
reason:
|
|
'prototype t4 line 80: the badge reflects how many favorites '
|
|
'exist, even though the grid below caps display at 8',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.12): "Ver todas" renders as plain brand-teal '
|
|
'text, not a Material TextButton',
|
|
(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: 'f1', nombre: 'Favorita Uno'));
|
|
await estado.cargarFavoritos();
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byType(TextButton), findsNothing);
|
|
final texto = tester.widget<Text>(find.text('Ver todas'));
|
|
expect(texto.style?.fontSize, 12);
|
|
expect(texto.style?.fontWeight, FontWeight.w800);
|
|
expect(texto.style?.color, const Color(0xFF21D4D9));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'S7 visual fidelity: "Tus emisoras" uses the in-page section heading '
|
|
'style (15/w800/ls-.2), not titleMedium(16)/w900',
|
|
(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: 'f1', nombre: 'Favorita Uno'));
|
|
await estado.cargarFavoritos();
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
final texto = tester.widget<Text>(find.text('Tus emisoras'));
|
|
expect(texto.style?.fontSize, 15, reason: 'prototype t4 line 80');
|
|
expect(texto.style?.fontWeight, FontWeight.w800);
|
|
expect(texto.style?.letterSpacing, -0.2);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.1): a full-bleed art/gradient/noise wash '
|
|
'sits behind the whole screen, keyed so it can be found',
|
|
(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: 'f1', nombre: 'Favorita Uno');
|
|
await favoritos.agregar(sonando);
|
|
await estado.cargarFavoritos();
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(
|
|
find.byKey(const ValueKey('escuchar-background-art')),
|
|
findsOneWidget,
|
|
reason:
|
|
'prototype t4 lines 49-51: full-bleed art at .5 opacity + a '
|
|
'gradient + a noise texture',
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.1): the background wash still renders with '
|
|
'nothing playing (gradient/noise only, no station art to show)',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: _archivoCustomVacio,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(
|
|
find.byKey(const ValueKey('escuchar-background-art')),
|
|
findsOneWidget,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('visual fidelity (audit 1.2): Escuchar has its own 52px "Now '
|
|
'listening" eyebrow header, not the shared 56px PluriRootHeader', (
|
|
tester,
|
|
) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: _archivoCustomVacio,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byType(PluriRootHeader), findsNothing);
|
|
expect(
|
|
find.text('Escuchando ahora'),
|
|
findsOneWidget,
|
|
reason: 'prototype t4 line 53: "ESCUCHANDO AHORA"',
|
|
);
|
|
expect(find.byIcon(Icons.bedtime_outlined), findsOneWidget);
|
|
|
|
final cabeceras = tester
|
|
.widgetList<SizedBox>(find.byType(SizedBox))
|
|
.where((s) => s.height == 52);
|
|
expect(cabeceras, isNotEmpty, reason: 'prototype t4 line 53: height:52px');
|
|
});
|
|
|
|
testWidgets(
|
|
"visual fidelity (audit 1.2): the header's bedtime icon still opens "
|
|
'the shared sleep-timer sheet',
|
|
(tester) async {
|
|
_setLargeSurfaceSize(tester);
|
|
final estado = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadio(),
|
|
resolverArchivoCustom: _archivoCustomVacio,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
await tester.runAsync(estado.inicializar);
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpStableFrame(tester);
|
|
|
|
await tester.tap(find.byIcon(Icons.bedtime_outlined));
|
|
await _pumpStableFrame(tester);
|
|
|
|
expect(find.byType(ActionChip), findsWidgets);
|
|
},
|
|
);
|
|
|
|
testWidgets('visual fidelity (audit 1.5): the station name is 24/w800/ls-.5/'
|
|
'height 1.12, not titleLarge(22) with no letter-spacing/height', (
|
|
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);
|
|
// Deliberately NOT added to favoritos: the "Tus emisoras" grid also
|
|
// renders a station's bare name, and this assertion targets the
|
|
// hero's OWN station-name Text specifically -- a favorited station
|
|
// with the same name would render twice and make `find.text` throw.
|
|
final sonando = emisoraDemo(uuid: 'f1', nombre: 'Radio Horizonte');
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
final texto = tester.widget<Text>(find.text('Radio Horizonte'));
|
|
expect(texto.style?.fontSize, 24, reason: 'prototype t4 line 60');
|
|
expect(texto.style?.fontWeight, FontWeight.w800);
|
|
expect(texto.style?.letterSpacing, -0.5);
|
|
expect(texto.style?.height, 1.12);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.8): the hero play button is 72x72 with a '
|
|
'38px icon, not 56x56 with a 28px icon',
|
|
(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: 'f1', nombre: 'Favorita Uno');
|
|
await favoritos.agregar(sonando);
|
|
await estado.cargarFavoritos();
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
final botones = tester
|
|
.widgetList<SizedBox>(find.byType(SizedBox))
|
|
.where((s) => s.width == 72 && s.height == 72);
|
|
expect(botones, isNotEmpty, reason: 'prototype t4 line 73');
|
|
|
|
final iconos = tester
|
|
.widgetList<Icon>(find.byType(Icon))
|
|
.where(
|
|
(i) =>
|
|
i.icon == Icons.play_arrow_rounded ||
|
|
i.icon == Icons.pause_rounded,
|
|
);
|
|
expect(iconos, isNotEmpty);
|
|
expect(iconos.first.size, 38);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 1.9): the EQ/record/quality pill is centred, '
|
|
'pill-shaped and uses expand_less at 17px -- not a left-aligned '
|
|
'OutlinedButton with tune_rounded',
|
|
(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: 'f1', nombre: 'Favorita Uno');
|
|
await favoritos.agregar(sonando);
|
|
await estado.cargarFavoritos();
|
|
await tester.runAsync(() => estado.reproducir(sonando));
|
|
|
|
await tester.pumpWidget(
|
|
_conProviders(estado, _testApp(const PantallaInicio())),
|
|
);
|
|
await _pumpBounded(tester);
|
|
|
|
expect(find.byType(OutlinedButton), findsNothing);
|
|
expect(
|
|
find.byIcon(Icons.expand_less_rounded),
|
|
findsOneWidget,
|
|
reason: 'prototype t4 line 78: expand_less at 17px',
|
|
);
|
|
final icono = tester.widget<Icon>(find.byIcon(Icons.expand_less_rounded));
|
|
expect(icono.size, 17);
|
|
|
|
final pildoras = tester
|
|
.widgetList<DecoratedBox>(find.byType(DecoratedBox))
|
|
.map((d) => d.decoration)
|
|
.whereType<BoxDecoration>()
|
|
.where((d) => d.borderRadius == BorderRadius.circular(999));
|
|
expect(
|
|
pildoras,
|
|
isNotEmpty,
|
|
reason: 'prototype t4 line 78: border-radius:999px pill',
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Mirrors the app.dart wiring: EstadoRadio owns the domain notifiers and
|
|
/// the providers only expose the instances (no dispose callbacks).
|
|
/// [navegacion] defaults to a fresh [EstadoNavegacionRaiz] — harmless to
|
|
/// include for every test, only exercised by the "Ver todas" scenarios.
|
|
Widget _conProviders(
|
|
EstadoRadio estado,
|
|
Widget child, {
|
|
EstadoNavegacionRaiz? navegacion,
|
|
}) {
|
|
return MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider<EstadoRadio>.value(value: estado),
|
|
ListenableProvider<EstadoEcualizador>.value(value: estado.ecualizador),
|
|
ListenableProvider<EstadoGrabacion>.value(value: estado.grabacion),
|
|
ListenableProvider<EstadoBusqueda>.value(value: estado.busqueda),
|
|
ChangeNotifierProvider<EstadoNavegacionRaiz>.value(
|
|
value: navegacion ?? EstadoNavegacionRaiz(),
|
|
),
|
|
],
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
Widget _testApp(Widget body, {List<NavigatorObserver> observers = const []}) {
|
|
return MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
navigatorObservers: observers,
|
|
home: Scaffold(body: body),
|
|
);
|
|
}
|
|
|
|
/// Counts route pushes so a test can assert "switched tabs, did not push".
|
|
class _RecordingNavigatorObserver extends NavigatorObserver {
|
|
int pushCount = 0;
|
|
|
|
@override
|
|
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
|
|
pushCount++;
|
|
}
|
|
}
|
|
|
|
Future<void> _pumpStableFrame(WidgetTester tester) async {
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 100));
|
|
}
|
|
|
|
/// WU5: bounded pump, safe when a "reproduciendo" station is rendered —
|
|
/// `VisualizadorAudio` starts a repeating `AnimationController` for its
|
|
/// animated-fallback waveform in that case, which `pumpAndSettle` (used by
|
|
/// `_pumpStableFrame`) would wait on forever.
|
|
Future<void> _pumpBounded(WidgetTester tester) async {
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
}
|
|
|
|
void _setLargeSurfaceSize(WidgetTester tester) {
|
|
tester.view.physicalSize = const Size(1440, 3200);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
}
|
|
|
|
Future<File> _archivoCustomVacio() async =>
|
|
File('${Directory.current.path}/test/fixtures/emisoras_custom_vacio.json');
|