Files
pluriwave/test/pantallas/root_header_wiring_test.dart
T
FreeTLab 97e38becfe fix(chrome): retire PluriScreenHeader, it has no equivalent in the prototype
PluriScreenHeader was a 38-radius glass hero: an aurora banner at 24%
opacity, a black-to-transparent scrim, two radial orbs, a 120px app-mark
watermark and a 56px tri-gradient glyph badge. None of it is in the
prototype (t4) -- every root's title is plain text in its own 56px
row, which PluriRootHeader (S1) already provides. Delete the class
(and its now-orphaned _Orb helper) and its four call sites (Buscar,
Favoritos x2, Alarmas, Ajustes landed with S8).

Two of those call sites carried the hero's only functional bit besides
the title, so PluriRootHeader gains an `actions` slot (rendered before
the shared bedtime button) to keep them reachable:
  - Alarmas' create-alarm button (FilledButton.tonalIcon, unchanged
    shape, just relocated)
  - Buscar's filters entry point (the same tappable PluriStatusPill,
    just relocated)
Every other retired trailing pill (Ajustes' "Secure" status,
Favoritos' collection-count badges, Alarmas' alarm-count badge) was
purely decorative and matches the prototype by simply disappearing.

S2, Tier 1 visual-fidelity pass (audit id 2521).
2026-07-29 21:29:49 +02:00

209 lines
7.3 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_alarmas.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_idioma.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/pantallas/pantalla_ajustes.dart';
import 'package:pluriwave/pantallas/pantalla_alarmas.dart';
import 'package:pluriwave/pantallas/pantalla_buscar.dart';
import 'package:pluriwave/pantallas/pantalla_favoritos.dart';
import 'package:pluriwave/pantallas/pantalla_inicio.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:pluriwave/widgets/pluri_root_header.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart';
import '../helpers/fakes_alarmas.dart';
/// S1 (Tier 1 visual fidelity): `app.dart`'s `PluriWaveScaffold(appBar:
/// AppBar(...))` is gone — every root now draws its own [PluriRootHeader]
/// instead, matching the prototype (no screen in `t4` shows a global
/// `AppBar`). [PluriRootHeader]'s own title text is asserted via
/// `find.descendant` throughout — see `pluri_screen_header_retired_test.dart`
/// for S2's separate assertion that the old glass-hero subtitle is gone.
///
/// Pre-existing project constraint (see `pantalla_ajustes_test.dart`):
/// PluriGlassSurface paints a background over ListTile's ink layer, which
/// Flutter flags as a warning-level assertion, not a correctness bug.
void _suppressListTileInkAssertion() {
final original = FlutterError.onError;
FlutterError.onError = (details) {
if (details.exceptionAsString().contains(
'ListTile background color or ink splashes may be invisible',
)) {
return;
}
original?.call(details);
};
addTearDown(() => FlutterError.onError = original);
}
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
Future<File> archivoCustomVacio() async => File(
'${Directory.current.path}/test/fixtures/emisoras_custom_vacio.json',
);
EstadoRadio crearEstadoRadio() => EstadoRadio(
audio: FakeServicioAudio(),
favoritos: FakeServicioFavoritos(),
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
resolverArchivoCustom: archivoCustomVacio,
iniciarAutomaticamente: false,
);
Widget testApp(EstadoRadio estado, Widget body, {EstadoAlarmas? alarmas}) {
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<EstadoIdioma>.value(value: EstadoIdioma()),
if (alarmas != null)
ChangeNotifierProvider<EstadoAlarmas>.value(value: alarmas),
],
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(body: body),
),
);
}
void setLargeSurface(WidgetTester tester) {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
}
Future<void> pumpStable(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
}
Finder titleInHeader(String title) => find.descendant(
of: find.byType(PluriRootHeader),
matching: find.text(title),
);
testWidgets(
'Escuchar draws its own PluriRootHeader with the tab title, no AppBar',
(tester) async {
_suppressListTileInkAssertion();
setLargeSurface(tester);
final estado = crearEstadoRadio();
addTearDown(estado.dispose);
await tester.pumpWidget(testApp(estado, const PantallaInicio()));
await pumpStable(tester);
expect(find.byType(PluriRootHeader), findsOneWidget);
expect(titleInHeader('Listen'), findsOneWidget);
expect(find.byType(AppBar), findsNothing);
},
);
testWidgets(
'Buscar draws its own PluriRootHeader with the search title, no AppBar',
(tester) async {
_suppressListTileInkAssertion();
setLargeSurface(tester);
final estado = crearEstadoRadio();
addTearDown(estado.dispose);
await tester.pumpWidget(testApp(estado, const PantallaBuscar()));
await pumpStable(tester);
expect(find.byType(PluriRootHeader), findsOneWidget);
expect(titleInHeader('Search signal'), findsOneWidget);
expect(find.byType(AppBar), findsNothing);
},
);
testWidgets(
'Favoritos (empty state) draws its own PluriRootHeader, no AppBar',
(tester) async {
_suppressListTileInkAssertion();
setLargeSurface(tester);
final estado = crearEstadoRadio();
addTearDown(estado.dispose);
await tester.pumpWidget(testApp(estado, const PantallaFavoritos()));
await pumpStable(tester);
expect(find.byType(PluriRootHeader), findsOneWidget);
expect(titleInHeader('Favorites'), findsOneWidget);
expect(find.byType(AppBar), findsNothing);
},
);
testWidgets('Alarmas draws its own PluriRootHeader, no AppBar', (
tester,
) async {
_suppressListTileInkAssertion();
setLargeSurface(tester);
final estado = crearEstadoRadio();
final alarmas = EstadoAlarmas(
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 1, 6, 0)),
android: FakePuertoAlarmasAndroid(),
iniciarAutomaticamente: false,
);
addTearDown(estado.dispose);
addTearDown(alarmas.dispose);
await tester.pumpWidget(
testApp(estado, const PantallaAlarmas(), alarmas: alarmas),
);
await pumpStable(tester);
expect(find.byType(PluriRootHeader), findsOneWidget);
expect(titleInHeader('Music wake-up'), findsOneWidget);
expect(find.byType(AppBar), findsNothing);
});
testWidgets(
'Ajustes draws its own PluriRootHeader, no AppBar, and its bedtime '
'action opens the sleep-timer sheet',
(tester) async {
_suppressListTileInkAssertion();
setLargeSurface(tester);
final estado = crearEstadoRadio();
addTearDown(estado.dispose);
await tester.pumpWidget(testApp(estado, const PantallaAjustes()));
await pumpStable(tester);
expect(find.byType(PluriRootHeader), findsOneWidget);
expect(titleInHeader('Settings'), findsOneWidget);
expect(find.byType(AppBar), findsNothing);
await tester.tap(find.byIcon(Icons.bedtime_outlined));
await pumpStable(tester);
// Not `find.text('Sleep timer')` — Ajustes' own "Sleep timer"
// FilaAjuste row (`l10n.timerSectionTitle`) coincidentally shares the
// exact same string as `l10n.sleepTimer`. The description line is
// unique to the sheet this action opens.
expect(
find.text('Smooth radio shutdown with an exact countdown.'),
findsOneWidget,
);
},
);
}