refactor(state): extract export/import service and equalizer state from EstadoRadio
- New ServicioExportImport owns the v2 backup envelope, pretty JSON encode and graceful decode; byte-compatible with existing exports, locked by a round-trip test - pantalla_ajustes delegates backup serialization to the service (inline jsonDecode/jsonEncode removed) - New EstadoEcualizador ChangeNotifier owns all EQ state and persistence (principal/current/per-station presets, active flag), exposed via its own provider so EQ changes no longer rebuild EstadoRadio consumers - EstadoRadio slims down ~210 lines and keeps 15 delegating compat members marked TODO(S4b) for the next slice to remove - Player EQ toggle rewired to the new provider to avoid going stale - 4 new tests (103 total green), flutter analyze clean
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/estado/estado_ecualizador.dart';
|
||||
import 'package:pluriwave/estado/estado_radio.dart';
|
||||
import 'package:pluriwave/modelos/preset_ecualizador.dart';
|
||||
|
||||
import '../helpers/fakes.dart';
|
||||
|
||||
void main() {
|
||||
group('EstadoEcualizador (S4-R1)', () {
|
||||
test(
|
||||
'cambiarPreset notifies EstadoEcualizador listeners (S4-R1-A)',
|
||||
() async {
|
||||
final audio = FakeServicioAudio();
|
||||
final eq = EstadoEcualizador(
|
||||
audio: audio,
|
||||
servicio: FakeServicioEcualizador(),
|
||||
);
|
||||
var avisos = 0;
|
||||
eq.addListener(() => avisos++);
|
||||
|
||||
await eq.cambiarPreset(PresetEcualizador.jazz);
|
||||
|
||||
expect(avisos, greaterThanOrEqualTo(1));
|
||||
expect(eq.presetActual, PresetEcualizador.jazz);
|
||||
expect(audio.presetsAplicados, contains(PresetEcualizador.jazz));
|
||||
eq.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test('EQ preset change does NOT rebuild EstadoRadio listeners '
|
||||
'(S4-R1-A, S4-R5)', () async {
|
||||
final estado = EstadoRadio(
|
||||
audio: FakeServicioAudio(),
|
||||
favoritos: FakeServicioFavoritos(),
|
||||
radio: FakeServicioRadio(),
|
||||
servicioEcualizador: FakeServicioEcualizador(),
|
||||
iniciarAutomaticamente: false,
|
||||
);
|
||||
var avisosRadio = 0;
|
||||
var avisosEq = 0;
|
||||
estado.addListener(() => avisosRadio++);
|
||||
estado.ecualizador.addListener(() => avisosEq++);
|
||||
|
||||
await estado.ecualizador.cambiarPreset(PresetEcualizador.jazz);
|
||||
|
||||
expect(avisosEq, greaterThanOrEqualTo(1));
|
||||
expect(avisosRadio, 0);
|
||||
estado.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
import 'package:pluriwave/modelos/grupo_favoritos.dart';
|
||||
import 'package:pluriwave/modelos/preset_ecualizador.dart';
|
||||
import 'package:pluriwave/servicios/servicio_export_import.dart';
|
||||
|
||||
void main() {
|
||||
const servicio = ServicioExportImport();
|
||||
|
||||
group('ServicioExportImport (S4-R4)', () {
|
||||
test('v2 round-trip: exportar then importar yields a deep-equal config '
|
||||
'(S4-R4-A, S6-R2 test #4)', () {
|
||||
final config = servicio.construirExportacion(
|
||||
gruposFavoritos: const [
|
||||
GrupoFavoritos(id: 'g1', nombre: 'Rock', orden: 1),
|
||||
GrupoFavoritos(
|
||||
id: GrupoFavoritos.sinAsignarId,
|
||||
nombre: 'Sin asignar',
|
||||
orden: 0,
|
||||
protegido: true,
|
||||
),
|
||||
],
|
||||
favoritos: const [
|
||||
Emisora(
|
||||
uuid: 'fav-1',
|
||||
nombre: 'Radio Uno',
|
||||
url: 'https://uno.example/stream',
|
||||
grupoFavoritosId: 'g1',
|
||||
bitrate: 192,
|
||||
),
|
||||
],
|
||||
emisorasCustom: const [
|
||||
Emisora(
|
||||
uuid: 'custom-1',
|
||||
nombre: 'Mi Radio',
|
||||
url: 'https://mia.example/stream',
|
||||
),
|
||||
],
|
||||
presetPrincipal: PresetEcualizador.jazz,
|
||||
presetsPorEmisora: {'fav-1': PresetEcualizador.rock},
|
||||
// Raw alarm block exactly as ServicioAlarmas persists it (alarms +
|
||||
// vacations + exceptions) — must pass through untouched.
|
||||
alarmas: const {
|
||||
'alarmas': [
|
||||
{
|
||||
'id': 'a1',
|
||||
'hora': 7,
|
||||
'minuto': 30,
|
||||
'activa': true,
|
||||
'snoozeMinutos': 5,
|
||||
},
|
||||
],
|
||||
'vacaciones': [
|
||||
{'desde': '2026-07-01', 'hasta': '2026-07-15'},
|
||||
],
|
||||
},
|
||||
emisoraPreferidaUuid: 'fav-1',
|
||||
ordenListas: 'calidad',
|
||||
timerSuenoPresetsSegundos: const [300, 600, 1800],
|
||||
);
|
||||
|
||||
final json = servicio.exportar(config);
|
||||
final importado = servicio.importar(json);
|
||||
|
||||
expect(importado, isNotNull);
|
||||
expect(importado, equals(config));
|
||||
expect(importado!['version'], 2);
|
||||
// Alarm raw-JSON passthrough survives the round-trip intact.
|
||||
expect(importado['alarmas'], equals(config['alarmas']));
|
||||
// The protected "sin asignar" group is never exported.
|
||||
final grupos = importado['gruposFavoritos'] as List;
|
||||
expect(grupos, hasLength(1));
|
||||
expect((grupos.single as Map)['id'], 'g1');
|
||||
});
|
||||
|
||||
test('malformed JSON returns null without throwing (S4-R4)', () {
|
||||
expect(servicio.importar('{not valid json'), isNull);
|
||||
expect(servicio.importar(''), isNull);
|
||||
// Valid JSON but not an object — also rejected gracefully.
|
||||
expect(servicio.importar('[1, 2, 3]'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user