EstadoAlarmas, EstadoGrabacion and EstadoRadio defaulted `esPremium` to `() => true`, so any construction site that forgot to wire entitlement compiled fine and silently ran ungated — failing OPEN to premium and disabling the paywall with no test able to catch it. The parameter is now required with no default. Production wiring in app.dart was already correct and is unchanged; the 184 pre-existing test call sites now pass `() => true` explicitly, which is exactly the old implicit default, so every assertion is untouched. EstadoRadio has no gate of its own but constructs EstadoGrabacion, so it inherits the same contract. The one test that existed to pin the old default is renamed to describe what it still covers (the premium path through iniciar() with no duracion); its assertions are unchanged.
184 lines
6.9 KiB
Dart
184 lines
6.9 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_entitlement.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/servicios/servicio_alarmas.dart';
|
|
import 'package:pluriwave/servicios/servicio_anuncios.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';
|
|
|
|
/// S2 (Tier 1 visual fidelity): `PluriScreenHeader` — a 38-radius glass
|
|
/// hero with an aurora banner, a black scrim, two radial orbs, a 120px
|
|
/// watermark and a tri-gradient glyph badge — is not in the prototype at
|
|
/// all (`t4` never draws it). It is retired from all 4 roots that used it;
|
|
/// each root's ONLY title chrome is now `PluriRootHeader` (S1). Its
|
|
/// subtitle text was the one thing the hero rendered that nothing else on
|
|
/// these screens does — its absence is this suite's signal that the hero
|
|
/// is really gone, since the class itself is deleted and can no longer be
|
|
/// referenced by type from a test.
|
|
///
|
|
/// 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(
|
|
esPremium: () => true,
|
|
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()),
|
|
ChangeNotifierProvider<EstadoEntitlement>(
|
|
create: (_) => EstadoEntitlement(prefs: null),
|
|
),
|
|
Provider<ServicioAnuncios>(
|
|
create: (_) => ServicioAnuncios(esPremium: () => true),
|
|
),
|
|
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));
|
|
}
|
|
|
|
testWidgets(
|
|
'Buscar: the retired hero subtitle is gone, PluriRootHeader is the '
|
|
'only header, and the filters entry point is still reachable',
|
|
(tester) async {
|
|
setLargeSurface(tester);
|
|
final estado = crearEstadoRadio();
|
|
addTearDown(estado.dispose);
|
|
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
|
|
|
await tester.pumpWidget(testApp(estado, const PantallaBuscar()));
|
|
await pumpStable(tester);
|
|
|
|
expect(find.text(l10n.searchScreenSubtitle), findsNothing);
|
|
expect(find.byType(PluriRootHeader), findsOneWidget);
|
|
expect(find.text(l10n.searchFiltersLabel), findsWidgets);
|
|
},
|
|
);
|
|
|
|
testWidgets('Favoritos (empty state): the retired hero subtitle is gone', (
|
|
tester,
|
|
) async {
|
|
setLargeSurface(tester);
|
|
final estado = crearEstadoRadio();
|
|
addTearDown(estado.dispose);
|
|
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
|
|
|
await tester.pumpWidget(testApp(estado, const PantallaFavoritos()));
|
|
await pumpStable(tester);
|
|
|
|
expect(find.text(l10n.favoritesHeaderSubtitle), findsNothing);
|
|
expect(find.byType(PluriRootHeader), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'Alarmas: the retired hero subtitle is gone, and the create-alarm '
|
|
'action is still reachable',
|
|
(tester) async {
|
|
setLargeSurface(tester);
|
|
final estado = crearEstadoRadio();
|
|
final alarmas = EstadoAlarmas(
|
|
esPremium: () => true,
|
|
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 1, 6, 0)),
|
|
android: FakePuertoAlarmasAndroid(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estado.dispose);
|
|
addTearDown(alarmas.dispose);
|
|
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
|
|
|
await tester.pumpWidget(
|
|
testApp(estado, const PantallaAlarmas(), alarmas: alarmas),
|
|
);
|
|
await pumpStable(tester);
|
|
|
|
expect(find.text(l10n.alarmScreenSubtitle), findsNothing);
|
|
expect(find.byType(PluriRootHeader), findsOneWidget);
|
|
expect(find.text(l10n.createAlarmAction), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('Ajustes: the retired hero subtitle is gone', (tester) async {
|
|
_suppressListTileInkAssertion();
|
|
setLargeSurface(tester);
|
|
final estado = crearEstadoRadio();
|
|
addTearDown(estado.dispose);
|
|
final l10n = await AppLocalizations.delegate.load(const Locale('en'));
|
|
|
|
await tester.pumpWidget(testApp(estado, const PantallaAjustes()));
|
|
await pumpStable(tester);
|
|
|
|
expect(find.text(l10n.settingsSubtitle), findsNothing);
|
|
expect(find.byType(PluriRootHeader), findsOneWidget);
|
|
});
|
|
}
|