Files
pluriwave/test/helpers/fakes_alarmas.dart
T
FreeTLab 049ab78acb feat(alarmas): replace one-line reliability button with full diagnostics screen
Surface all six DiagnosticoAlarmasAndroid fields instead of three: the
battery-optimization exemption and native pending-alarm count were
already collected but silently dropped by the old widget. Each failing
signal now offers a "Fix this" action that opens the right system
settings screen (exact alarms, notifications, full-screen intent,
battery optimization), guarded by SDK level and never crashing when a
ROM lacks that screen. Manufacturers known for aggressive background
killing (Xiaomi/Redmi/POCO, Huawei, Oppo, Vivo, OnePlus, Samsung) get
an honest explanation that Autostart must be enabled manually, since
there is no API to detect or grant it.

Notifications now deep-links straight to
ACTION_APP_NOTIFICATION_SETTINGS via a new openNotificationSettings
native method, instead of reusing the runtime permission popup meant
for first-time alarm creation.

New copy is added to all 13 ARB locales with real per-language
translations (not Spanish copies), verified by the ARB parity and
anti-copy tests plus the corruption scanner.
2026-07-31 19:10:52 +02:00

197 lines
6.3 KiB
Dart

import 'dart:async';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/servicios/servicio_alarmas_android.dart';
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
/// Shared fake of the Android alarm bridge for alarm-related tests.
class FakePuertoAlarmasAndroid implements PuertoAlarmasAndroid {
final programadas = <AlarmaMusical>[];
final canceladas = <String>[];
final detenidas = <String>[];
final ocultadas = <String>[];
final soloOcultadas = <String>[];
final ejecucionesNativas = <EjecucionAlarmaNativa>[];
final snoozesNativos = <EstadoSnoozeNativo>[];
final _eventos = StreamController<EventoAlarmaAndroid>.broadcast();
bool ignoraOptimizacionBateria = true;
int solicitudesExencionBateria = 0;
int aperturasConfiguracionNotificaciones = 0;
/// Extra diagnostico() fields (fix/alarmas-fiabilidad diagnostics screen).
/// Default values mirror the previous hardcoded literals in [diagnostico],
/// so every existing test that never sets these keeps seeing the exact
/// same snapshot as before.
bool puedeProgramarExactas = true;
bool notificacionesPermitidas = true;
bool puedeUsarPantallaCompleta = true;
int alarmasNativasPendientes = 0;
String fabricante = 'test';
int versionSdk = 35;
/// Test-only failure switch (diagnostics screen, "intent not resolving"
/// coverage): when true, every `abrir*`/`solicitar*` system-screen action
/// below reports failure (as a real device does when a ROM lacks that
/// settings screen), while still recording the attempt via its counter.
bool fallaAccionSistema = false;
/// Test-only failure switch (Design D7): when true, [programar] throws
/// instead of scheduling, enabling failure-path coverage that the fake
/// could not otherwise produce.
bool fallaProgramar = false;
/// Test-only failure switch: when true, [detenerSonidoActivo] reports an
/// unconfirmed/failed stop instead of a confirmed one.
bool fallaDetener = false;
/// Simulates [PluriWaveAlarmService.activeRingingId]: the id the fake
/// reports as currently ringing, or null if nothing rings.
String? alarmaSonandoIdValor;
/// Test-only failure switch (Finding 2, fail-toward-silence): when true,
/// [alarmaSonandoId] throws instead of returning a value, exercising the
/// [detenerSonidoNativo] fallback in `EstadoAlarmas._detenerSiEstaSonando`.
bool fallaConsultaSonando = false;
/// Every alarm id [detenerSonidoActivo] was invoked for, in call order.
final detencionesActivas = <String>[];
/// Test-only gate (RES-2 guard test): when set, [detenerSonidoActivo]
/// awaits it before resolving, letting tests exercise an overlapping
/// in-flight call.
Completer<void>? detenerActivoGate;
/// Simulates a native -> Flutter `alarmFired` MethodChannel event.
void emitirEvento(EventoAlarmaAndroid evento) => _eventos.add(evento);
@override
Stream<EventoAlarmaAndroid> get eventosAlarma => _eventos.stream;
@override
void configurarLocalizaciones(AppLocalizations l10n) {}
@override
Future<void> programar(AlarmaMusical alarma) async {
if (fallaProgramar) {
throw StateError('fake programar failure');
}
programadas.add(alarma);
}
@override
Future<void> cancelar(String alarmaId) async {
canceladas.add(alarmaId);
}
@override
Future<void> detenerSonidoNativo(String alarmaId) async {
detenidas.add(alarmaId);
}
@override
Future<String?> alarmaSonandoId() async {
if (fallaConsultaSonando) {
throw StateError('fake alarmaSonandoId failure');
}
return alarmaSonandoIdValor;
}
@override
Future<ResultadoDetencion> detenerSonidoActivo() async {
detencionesActivas.add(alarmaSonandoIdValor ?? '');
final gate = detenerActivoGate;
if (gate != null) await gate.future;
if (fallaDetener) {
return const ResultadoDetencion(detenido: false, estabaSonando: true);
}
return ResultadoDetencion(
detenido: true,
estabaSonando: alarmaSonandoIdValor != null,
alarmaId: alarmaSonandoIdValor,
);
}
@override
Future<void> ocultarNotificacionAlarma(String alarmaId) async {
ocultadas.add(alarmaId);
}
@override
Future<void> ocultarSoloNotificacion(String alarmaId) async {
soloOcultadas.add(alarmaId);
}
@override
Future<DiagnosticoAlarmasAndroid> diagnostico() async =>
DiagnosticoAlarmasAndroid(
puedeProgramarExactas: puedeProgramarExactas,
notificacionesPermitidas: notificacionesPermitidas,
puedeUsarPantallaCompleta: puedeUsarPantallaCompleta,
ignoraOptimizacionBateria: ignoraOptimizacionBateria,
alarmasNativasPendientes: alarmasNativasPendientes,
fabricante: fabricante,
versionSdk: versionSdk,
);
@override
Future<bool> solicitarExencionBateria() async {
solicitudesExencionBateria++;
return !fallaAccionSistema;
}
@override
Future<bool> abrirConfiguracionNotificaciones() async {
aperturasConfiguracionNotificaciones++;
return !fallaAccionSistema;
}
@override
Future<EventoAlarmaAndroid?> obtenerEventoInicial() async => null;
@override
Future<List<EjecucionAlarmaNativa>>
obtenerEjecucionesNativasGestionadas() async => ejecucionesNativas;
@override
Future<List<EstadoSnoozeNativo>> obtenerEstadoSnoozeNativo() async =>
List.of(snoozesNativos);
int solicitudesPermisoAlarmasExactas = 0;
int solicitudesPermisoPantallaCompleta = 0;
@override
Future<bool> solicitarPermisoAlarmasExactas() async {
solicitudesPermisoAlarmasExactas++;
return !fallaAccionSistema;
}
@override
Future<bool> solicitarPermisoNotificaciones() async => true;
@override
Future<bool> solicitarPermisoPantallaCompleta() async {
solicitudesPermisoPantallaCompleta++;
return !fallaAccionSistema;
}
Future<void> dispose() => _eventos.close();
}
/// Inactive recording service fake, safe for widget tests.
class FakeServicioGrabacionRadioInactiva extends ServicioGrabacionRadio {
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
@override
EstadoGrabacionRadio get estado => const EstadoGrabacionRadio.inactiva();
@override
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
@override
Future<void> inicializar() async {}
@override
Future<void> dispose() => _controller.close();
}