Files
pluriwave/test/pantallas/pantalla_diagnostico_alarmas_test.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

333 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_alarmas.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/pantallas/pantalla_diagnostico_alarmas.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes_alarmas.dart';
final _l10n = lookupAppLocalizations(const Locale('en'));
Future<EstadoAlarmas> _crearEstado({
required FakePuertoAlarmasAndroid android,
List<AlarmaMusical> alarmas = const [],
bool cargarDiagnostico = true,
}) async {
final estado = EstadoAlarmas(
servicio: ServicioAlarmas(),
android: android,
iniciarAutomaticamente: false,
);
for (final alarma in alarmas) {
await estado.guardarAlarma(alarma);
}
if (cargarDiagnostico) {
await estado.cargarDiagnostico();
}
return estado;
}
Widget _buildScreen(EstadoAlarmas estado) {
return ChangeNotifierProvider<EstadoAlarmas>.value(
value: estado,
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const PantallaDiagnosticoAlarmas(),
),
);
}
Future<void> _montarPantalla(WidgetTester tester, EstadoAlarmas estado) async {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(_buildScreen(estado));
await tester.pumpAndSettle();
}
AlarmaMusical _alarmaActiva() => const AlarmaMusical(
id: 'a1',
nombre: 'Despertar',
hora: 7,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
);
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets(
'caso todo OK: no muestra ningun boton "Fix" ni el estado de atencion, '
'y sin fabricante conocido no muestra la guia de autostart',
(tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
// A fresh fake defaults alarmasNativasPendientes to 0, which
// WOULD read as needs-attention once an alarm is active (that
// combination is exactly the diagnostic signal this screen
// exists to surface) -- give it a registered count so this
// specific scenario is genuinely all-OK.
..alarmasNativasPendientes = 1;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(find.text(_l10n.androidReliabilityTitle), findsOneWidget);
expect(find.text(_l10n.alarmDiagnosticsFixAction), findsNothing);
expect(
find.text(_l10n.alarmDiagnosticsNeedsAttentionStatus),
findsNothing,
);
expect(find.text(_l10n.alarmDiagnosticsAutostartTitle), findsNothing);
expect(find.text('Google'), findsOneWidget);
expect(find.text('35'), findsOneWidget);
},
);
testWidgets(
'alarmas exactas en atencion: muestra el boton Fix y lo invoca via '
'solicitarPermisoAlarmasExactas',
(tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..puedeProgramarExactas = false;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(find.text(_l10n.alarmDiagnosticsExactAlarmsTitle), findsOneWidget);
expect(find.text(_l10n.alarmDiagnosticsFixAction), findsOneWidget);
// guardarAlarma's own onboarding request
// (EstadoAlarmas._solicitarPermisosNecesariosParaAlarma) already fires
// once for this same failing field before the screen even mounts, so
// the assertion checks the DELTA the button tap itself caused.
final antes = android.solicitudesPermisoAlarmasExactas;
await tester.tap(find.text(_l10n.alarmDiagnosticsFixAction));
await tester.pumpAndSettle();
expect(android.solicitudesPermisoAlarmasExactas, antes + 1);
},
);
testWidgets('notificaciones en atencion: el boton Fix llama a '
'abrirConfiguracionNotificaciones (deep link a Settings, no el permiso '
'runtime)', (tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..notificacionesPermitidas = false;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
await tester.tap(find.text(_l10n.alarmDiagnosticsFixAction));
await tester.pumpAndSettle();
expect(android.aperturasConfiguracionNotificaciones, 1);
});
testWidgets('pantalla completa en atencion: el boton Fix llama a '
'solicitarPermisoPantallaCompleta', (tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..puedeUsarPantallaCompleta = false;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
// Same delta reasoning as the exact-alarms test above: the alarm's own
// onboarding request already fired once for this field before mount.
final antes = android.solicitudesPermisoPantallaCompleta;
await tester.tap(find.text(_l10n.alarmDiagnosticsFixAction));
await tester.pumpAndSettle();
expect(android.solicitudesPermisoPantallaCompleta, antes + 1);
});
testWidgets('optimizacion de bateria en atencion: el boton Fix llama a '
'solicitarExencionBateria', (tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..ignoraOptimizacionBateria = false;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
// Same delta reasoning as the exact-alarms test above: guardarAlarma's
// own onboarding request already fired once for this field before the
// screen mounts.
final antes = android.solicitudesExencionBateria;
await tester.tap(find.text(_l10n.alarmDiagnosticsFixAction));
await tester.pumpAndSettle();
expect(android.solicitudesExencionBateria, antes + 1);
});
testWidgets(
'accion de sistema que falla (ROM sin esa pantalla) muestra un aviso '
'en vez de fallar en silencio',
(tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..puedeProgramarExactas = false
..fallaAccionSistema = true;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
await tester.tap(find.text(_l10n.alarmDiagnosticsFixAction));
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
expect(
find.text(_l10n.alarmDiagnosticsIntentUnavailable),
findsOneWidget,
);
},
);
testWidgets(
'alarmas nativas pendientes: con una alarma activa y conteo 0 muestra '
'el aviso de atencion (la senal mas diagnostica del reporte)',
(tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..alarmasNativasPendientes = 0;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(
find.text(_l10n.alarmDiagnosticsNativeCountValue(0)),
findsOneWidget,
);
expect(
find.text(_l10n.alarmDiagnosticsNativeCountAttentionHint),
findsOneWidget,
);
},
);
testWidgets(
'alarmas nativas pendientes: con al menos una registrada no muestra '
'el aviso de atencion',
(tester) async {
final android =
FakePuertoAlarmasAndroid()
..fabricante = 'Google'
..alarmasNativasPendientes = 2;
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(
find.text(_l10n.alarmDiagnosticsNativeCountValue(2)),
findsOneWidget,
);
expect(
find.text(_l10n.alarmDiagnosticsNativeCountAttentionHint),
findsNothing,
);
},
);
testWidgets('fabricante Xiaomi muestra la guia de autostart con el nombre '
'interpolado', (tester) async {
final android = FakePuertoAlarmasAndroid()..fabricante = 'Xiaomi';
final estado = await _crearEstado(
android: android,
alarmas: [_alarmaActiva()],
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(find.text(_l10n.alarmDiagnosticsAutostartTitle), findsOneWidget);
expect(
find.text(_l10n.alarmDiagnosticsAutostartBody('Xiaomi')),
findsOneWidget,
);
});
testWidgets(
'diagnostico aun no disponible (null) no falla y muestra un aviso en '
'vez de romper la pantalla',
(tester) async {
// No alarms saved either: guardarAlarma() itself populates
// _diagnostico as a side effect of its own onboarding permission
// check, so reaching a genuinely null diagnostic requires an
// EstadoAlarmas that never called guardarAlarma or cargarDiagnostico.
final android = FakePuertoAlarmasAndroid();
final estado = await _crearEstado(
android: android,
cargarDiagnostico: false,
);
addTearDown(estado.dispose);
addTearDown(android.dispose);
await _montarPantalla(tester, estado);
expect(find.text(_l10n.alarmDiagnosticsUnavailableHint), findsOneWidget);
},
);
}