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.
334 lines
10 KiB
Dart
334 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(
|
|
esPremium: () => true,
|
|
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);
|
|
},
|
|
);
|
|
}
|