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 _datos = {}; int escriturasString = 0; int lecturasString = 0; @override String? getString(String key) { lecturasString++; return _datos[key] as String?; } @override Future 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', ); }); }