Files
pluriwave/test/pantallas/pantalla_alarmas_fecha_test.dart
FreeTLab d81fabbe27 refactor(iap): make esPremium a required constructor parameter
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.
2026-08-10 22:06:36 +02:00

223 lines
7.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:pluriwave/estado/estado_alarmas.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/formato_fechas.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/pantallas/pantalla_alarmas.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:pluriwave/servicios/servicio_anuncios.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart';
import '../helpers/fakes_alarmas.dart';
/// S5-R4: short dates must follow the active locale, not a hardcoded
/// DD/MM/YYYY pattern.
void main() {
test('en-US usa el orden mes/día, no el formato fijo DD/MM/YYYY', () {
final fecha = DateTime(2026, 6, 11);
final resultado = fechaCortaLocalizada('en-US', fecha);
expect(resultado, DateFormat.yMd('en-US').format(fecha));
expect(resultado, isNot('11/06/2026'));
expect(resultado, '6/11/2026');
});
test('es usa el orden día/mes', () async {
await initializeDateFormatting('es');
final fecha = DateTime(2026, 6, 11);
expect(
fechaCortaLocalizada('es', fecha),
DateFormat.yMd('es').format(fecha),
);
});
group('WU10 — la seccion Avanzada del editor conserva fecha, respaldo y '
'sonido', () {
final l10n = lookupAppLocalizations(const Locale('es'));
setUp(() {
SharedPreferences.setMockInitialValues({});
});
Future<EstadoAlarmas> abrirEditorNuevo(WidgetTester tester) async {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final favoritos = FakeServicioFavoritos();
await favoritos.agregar(emisoraDemo(uuid: 'gamma', nombre: 'Gamma FM'));
final radio = EstadoRadio(
esPremium: () => true,
audio: FakeServicioAudio(),
favoritos: favoritos,
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
iniciarAutomaticamente: false,
);
addTearDown(radio.dispose);
await radio.cargarFavoritos();
final android = FakePuertoAlarmasAndroid();
final estadoAlarmas = EstadoAlarmas(
esPremium: () => true,
servicio: ServicioAlarmas(reloj: DateTime.now),
android: android,
iniciarAutomaticamente: false,
);
addTearDown(estadoAlarmas.dispose);
addTearDown(android.dispose);
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<EstadoRadio>.value(value: radio),
ChangeNotifierProvider<EstadoAlarmas>.value(value: estadoAlarmas),
Provider<ServicioAnuncios>(
create: (_) => ServicioAnuncios(esPremium: () => true),
),
],
child: MaterialApp(
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: PantallaAlarmas()),
),
),
);
await tester.pumpAndSettle();
await tester.tap(find.text(l10n.createAlarmAction));
await tester.pumpAndSettle();
return estadoAlarmas;
}
Future<void> expandirAvanzado(WidgetTester tester) async {
await tester.ensureVisible(find.text(l10n.alarmAdvancedSectionTitle));
await tester.tap(find.text(l10n.alarmAdvancedSectionTitle));
await tester.pumpAndSettle();
}
testWidgets(
'los circulos de dia de la semana son siempre visibles, incluso '
'cuando el tipo es Una vez (por defecto en una alarma nueva)',
(tester) async {
await abrirEditorNuevo(tester);
expect(find.text(l10n.weekdayShortMonday), findsOneWidget);
expect(find.text(l10n.weekdayShortSunday), findsOneWidget);
},
);
testWidgets(
'permite crear una alarma de fecha unica desde la seccion Avanzada '
'(colapsada por defecto)',
(tester) async {
final estadoAlarmas = await abrirEditorNuevo(tester);
// A brand-new alarm already defaults to "one time" — the date field
// is reachable as soon as Advanced is expanded, no mode switch
// needed first.
await expandirAvanzado(tester);
await tester.ensureVisible(find.text(l10n.dateField));
await tester.tap(find.text(l10n.dateField));
await tester.pumpAndSettle();
final okLabel =
MaterialLocalizations.of(
tester.element(find.text(l10n.dateField)),
).okButtonLabel;
await tester.tap(find.text(okLabel));
await tester.pumpAndSettle();
await tester.ensureVisible(find.text(l10n.saveAlarmAction));
await tester.tap(find.text(l10n.saveAlarmAction));
await tester.pumpAndSettle();
final alarma = estadoAlarmas.alarmas.single;
expect(alarma.tipoProgramacion, TipoProgramacionAlarma.unica);
expect(alarma.fechaUnica, isNotNull);
expect(alarma.diasSemana, isEmpty);
},
);
testWidgets(
'el selector de emisora de respaldo sigue alcanzable desde Avanzado '
'y persiste (S2-R9)',
(tester) async {
final estadoAlarmas = await abrirEditorNuevo(tester);
await expandirAvanzado(tester);
await tester.ensureVisible(
find.byKey(const ValueKey('alarm-fallback-station-field')),
);
await tester.tap(
find.byKey(const ValueKey('alarm-fallback-station-field')),
);
await tester.pumpAndSettle();
// Scoped to the just-opened picker sheet's list: the primary
// station field auto-selects the sole registered favorite too
// (`EstadoRadio.emisoraPreferida` falls back to the first
// favorite), so an unscoped `find.text('Gamma FM')` would match
// twice — once there, once in this sheet.
final lista = find.byType(ListView).last;
expect(
find.descendant(of: lista, matching: find.text('Gamma FM')),
findsOneWidget,
);
await tester.tap(
find.descendant(of: lista, matching: find.text('Gamma FM')),
);
await tester.pumpAndSettle();
await tester.ensureVisible(find.text(l10n.saveAlarmAction));
await tester.tap(find.text(l10n.saveAlarmAction));
await tester.pumpAndSettle();
final alarma = estadoAlarmas.alarmas.single;
expect(alarma.emisoraFallback?.nombre, 'Gamma FM');
},
);
testWidgets(
'el dropdown de sonido interno sigue alcanzable desde Avanzado y '
'persiste',
(tester) async {
final estadoAlarmas = await abrirEditorNuevo(tester);
await expandirAvanzado(tester);
await tester.ensureVisible(
find.byType(DropdownButtonFormField<SonidoInternoAlarma>),
);
await tester.tap(
find.byType(DropdownButtonFormField<SonidoInternoAlarma>),
);
await tester.pumpAndSettle();
await tester.tap(find.text(l10n.soundSoftBell).last);
await tester.pumpAndSettle();
await tester.ensureVisible(find.text(l10n.saveAlarmAction));
await tester.tap(find.text(l10n.saveAlarmAction));
await tester.pumpAndSettle();
final alarma = estadoAlarmas.alarmas.single;
expect(alarma.sonidoInterno, SonidoInternoAlarma.campanaSuave);
},
);
});
}