refactor(state): extract recording and search state, scope screen rebuilds
- New EstadoGrabacion owns the recording service, subscription, directory/size preferences and open-file actions - New EstadoBusqueda owns search, nearby stations, pagination and the min-bitrate filter - New orden_emisoras.dart with the OrdenEmisoras enum, shared sorter and list identity memoization so context.select comparisons work on derived lists - Large screens (inicio, buscar, favoritos, ajustes, reproductor) consume scoped selects/dedicated notifiers instead of root context.watch<EstadoRadio>, so audio buffer events no longer rebuild whole screens - Remove all 15 TODO(S4b) compat members from EstadoRadio; consumers use the dedicated providers. EstadoRadio drops from ~1121 to 753 lines, keeping playback/stations/favorites orchestration - 8 new tests including a rebuild-scoping probe (110 total green), flutter analyze clean
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/estado/estado_grabacion.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
import 'package:pluriwave/servicios/servicio_grabacion_radio.dart';
|
||||
|
||||
import '../helpers/fakes.dart';
|
||||
|
||||
/// S4-R2: EstadoGrabacion owns the recording state previously in EstadoRadio
|
||||
/// and manages ServicioGrabacionRadio.
|
||||
void main() {
|
||||
test('notifica listeners cuando cambia el estado de grabación', () async {
|
||||
final servicio = _ServicioGrabacionControlado();
|
||||
final estado = EstadoGrabacion(servicio: servicio);
|
||||
addTearDown(estado.dispose);
|
||||
|
||||
var notificaciones = 0;
|
||||
estado.addListener(() => notificaciones++);
|
||||
|
||||
servicio.emitir(
|
||||
EstadoGrabacionRadio(
|
||||
tipo: EstadoGrabacionRadioTipo.grabando,
|
||||
emisora: emisoraDemo(uuid: 'rec-1', nombre: 'Grabable'),
|
||||
inicio: DateTime(2026, 6, 11, 10),
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(notificaciones, 1);
|
||||
expect(estado.activa, isTrue);
|
||||
expect(estado.estado.tipo, EstadoGrabacionRadioTipo.grabando);
|
||||
});
|
||||
|
||||
test('iniciar delega en el servicio con la emisora actual', () async {
|
||||
final servicio = _ServicioGrabacionControlado();
|
||||
final emisora = emisoraDemo(uuid: 'rec-2', nombre: 'Actual');
|
||||
final estado = EstadoGrabacion(
|
||||
servicio: servicio,
|
||||
emisoraActual: () => emisora,
|
||||
);
|
||||
addTearDown(estado.dispose);
|
||||
|
||||
await estado.iniciar(duracion: const Duration(minutes: 1));
|
||||
|
||||
expect(servicio.inicios, 1);
|
||||
expect(servicio.emisoraIniciada?.uuid, 'rec-2');
|
||||
expect(servicio.duracionIniciada, const Duration(minutes: 1));
|
||||
});
|
||||
|
||||
test(
|
||||
'iniciar sin emisora actual reporta error y no llama al servicio',
|
||||
() async {
|
||||
final servicio = _ServicioGrabacionControlado();
|
||||
final errores = <String>[];
|
||||
final estado = EstadoGrabacion(
|
||||
servicio: servicio,
|
||||
emisoraActual: () => null,
|
||||
alError: errores.add,
|
||||
);
|
||||
addTearDown(estado.dispose);
|
||||
|
||||
await estado.iniciar();
|
||||
|
||||
expect(servicio.inicios, 0);
|
||||
expect(errores, hasLength(1));
|
||||
},
|
||||
);
|
||||
|
||||
test('un estado de error del servicio se reporta vía alError', () async {
|
||||
final servicio = _ServicioGrabacionControlado();
|
||||
final errores = <String>[];
|
||||
final estado = EstadoGrabacion(servicio: servicio, alError: errores.add);
|
||||
addTearDown(estado.dispose);
|
||||
|
||||
servicio.emitir(
|
||||
const EstadoGrabacionRadio(
|
||||
tipo: EstadoGrabacionRadioTipo.error,
|
||||
error: 'HTTP 500',
|
||||
),
|
||||
);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(errores, hasLength(1));
|
||||
expect(errores.single, contains('HTTP 500'));
|
||||
});
|
||||
}
|
||||
|
||||
class _ServicioGrabacionControlado extends ServicioGrabacionRadio {
|
||||
final _controller = StreamController<EstadoGrabacionRadio>.broadcast();
|
||||
EstadoGrabacionRadio _estadoActual = const EstadoGrabacionRadio.inactiva();
|
||||
|
||||
int inicios = 0;
|
||||
Emisora? emisoraIniciada;
|
||||
Duration? duracionIniciada;
|
||||
|
||||
@override
|
||||
EstadoGrabacionRadio get estado => _estadoActual;
|
||||
|
||||
@override
|
||||
Stream<EstadoGrabacionRadio> get estadoStream => _controller.stream;
|
||||
|
||||
@override
|
||||
Future<void> inicializar() async {}
|
||||
|
||||
@override
|
||||
Future<void> iniciar(
|
||||
Emisora emisora, {
|
||||
Duration? duracion,
|
||||
String? directorio,
|
||||
}) async {
|
||||
inicios++;
|
||||
emisoraIniciada = emisora;
|
||||
duracionIniciada = duracion;
|
||||
}
|
||||
|
||||
void emitir(EstadoGrabacionRadio estado) {
|
||||
_estadoActual = estado;
|
||||
_controller.add(estado);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() => _controller.close();
|
||||
}
|
||||
Reference in New Issue
Block a user