079e19f0ee
- Integrate audio_session (new servicio_audio_session.dart): incoming calls pause the radio and resume on end, headphone unplug pauses without auto-resume, permanent focus loss never auto-resumes, duck lowers volume - Add play-intent flag to ServicioAudio so interruption handling and future reconnect logic can distinguish user pause from system-driven stops - Eliminate read-modify-write race in ServicioAlarmas with an in-memory cache and single-writer queue across all mutations; recalcularTodas persists only when state actually changed - Convert ServicioAlarmasAndroid static StreamController/handler to injectable instance fields, restoring test isolation - Inject a single cached SharedPreferences from main.dart across services and state (removes 23 inline getInstance() calls) - Move configurarLocalizaciones out of MiniReproductor.build() (was running on every rebuild during playback) - Bound the alarm fire-dedup set (cap 200 entries, 24h pruning) - 12 new tests (89 total green), flutter analyze clean
108 lines
3.2 KiB
Dart
108 lines
3.2 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/modelos/alarma_musical.dart';
|
|
import 'package:pluriwave/servicios/servicio_alarmas.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// SharedPreferences spy: only the members ServicioAlarmas touches are
|
|
/// implemented; everything else throws via noSuchMethod.
|
|
class _PrefsEspia implements SharedPreferences {
|
|
final Map<String, Object> _datos = {};
|
|
int escriturasString = 0;
|
|
int lecturasString = 0;
|
|
|
|
@override
|
|
String? getString(String key) {
|
|
lecturasString++;
|
|
return _datos[key] as String?;
|
|
}
|
|
|
|
@override
|
|
Future<bool> setString(String key, String value) async {
|
|
escriturasString++;
|
|
_datos[key] = value;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
|
}
|
|
|
|
void main() {
|
|
AlarmaMusical alarmaDiaria(
|
|
ServicioAlarmas servicio,
|
|
String nombre,
|
|
int hora,
|
|
) {
|
|
return servicio.crearAlarma(
|
|
nombre: nombre,
|
|
hora: hora,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: const [],
|
|
);
|
|
}
|
|
|
|
test(
|
|
'recalcularTodas NO escribe cuando la agenda no cambio (S3-R5-A)',
|
|
() async {
|
|
final prefs = _PrefsEspia();
|
|
final reloj = DateTime(2026, 6, 11, 6, 0);
|
|
final servicio = ServicioAlarmas(prefs: prefs, reloj: () => reloj);
|
|
await servicio.guardarAlarma(alarmaDiaria(servicio, 'Sin cambios', 7));
|
|
final escriturasBase = prefs.escriturasString;
|
|
|
|
await servicio.recalcularTodas();
|
|
|
|
expect(
|
|
prefs.escriturasString,
|
|
escriturasBase,
|
|
reason: 'agenda identica => sin setString',
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'recalcularTodas escribe exactamente una vez cuando cambia (S3-R5-B)',
|
|
() async {
|
|
var ahora = DateTime(2026, 6, 11, 6, 0);
|
|
final prefs = _PrefsEspia();
|
|
final servicio = ServicioAlarmas(prefs: prefs, reloj: () => ahora);
|
|
await servicio.guardarAlarma(alarmaDiaria(servicio, 'Cambia', 7));
|
|
final escriturasBase = prefs.escriturasString;
|
|
|
|
// A day later the next execution moves, so the schedule changed.
|
|
ahora = DateTime(2026, 6, 12, 8, 0);
|
|
await servicio.recalcularTodas();
|
|
|
|
expect(prefs.escriturasString, escriturasBase + 1);
|
|
},
|
|
);
|
|
|
|
test('mutaciones concurrentes no pierden escrituras (S3-R7-A)', () async {
|
|
final prefs = _PrefsEspia();
|
|
final servicio = ServicioAlarmas(
|
|
prefs: prefs,
|
|
reloj: () => DateTime(2026, 6, 11, 6, 0),
|
|
);
|
|
final alarmaA = alarmaDiaria(servicio, 'Concurrente A', 7);
|
|
final alarmaB = alarmaDiaria(servicio, 'Concurrente B', 8);
|
|
final lecturasBase = prefs.lecturasString;
|
|
|
|
// Dispatched WITHOUT awaiting in between: without the cache + writer
|
|
// queue both read the same base config and the last write wins.
|
|
await Future.wait([
|
|
servicio.guardarAlarma(alarmaA),
|
|
servicio.guardarAlarma(alarmaB),
|
|
]);
|
|
|
|
final config = await servicio.cargar();
|
|
expect(config.alarmas.map((a) => a.id).toSet(), {alarmaA.id, alarmaB.id});
|
|
expect(
|
|
prefs.lecturasString - lecturasBase,
|
|
lessThanOrEqualTo(2),
|
|
reason:
|
|
'las mutaciones hidratan la cache UNA vez; la lectura extra es el cargar() final',
|
|
);
|
|
});
|
|
}
|