feat(buscar): add discovery landing state, filter pills, counter, and sort
This commit is contained in:
@@ -373,6 +373,45 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('EstadoRadio orden de listas — persistencia (WU6)', () {
|
||||
// Correction applied at apply time: cambiarOrdenListas'/_cargarOrdenListas
|
||||
// round-trip had zero test coverage before WU6 added OrdenEmisoras.
|
||||
// popularidad — a latent gap similar to WU5's androidAudioSessionIdStream
|
||||
// discovery. Adding a new enum case without covering the persistence
|
||||
// switch would have shipped a silent revert-to-calidad-on-restart bug.
|
||||
test(
|
||||
'popularidad persiste y sobrevive a una nueva instancia (reinicio)',
|
||||
() async {
|
||||
final estadoUno = EstadoRadio(
|
||||
audio: FakeServicioAudio(),
|
||||
favoritos: FakeServicioFavoritos(),
|
||||
radio: FakeServicioRadio(),
|
||||
servicioEcualizador: FakeServicioEcualizador(),
|
||||
resolverArchivoCustom: _archivoCustomVacio,
|
||||
iniciarAutomaticamente: false,
|
||||
);
|
||||
await estadoUno.inicializar();
|
||||
|
||||
await estadoUno.cambiarOrdenListas(OrdenEmisoras.popularidad);
|
||||
expect(estadoUno.ordenListas, OrdenEmisoras.popularidad);
|
||||
|
||||
// Fresh instance, same (mocked) SharedPreferences-backed store —
|
||||
// simulates an app restart re-reading the persisted preference.
|
||||
final estadoDos = EstadoRadio(
|
||||
audio: FakeServicioAudio(),
|
||||
favoritos: FakeServicioFavoritos(),
|
||||
radio: FakeServicioRadio(),
|
||||
servicioEcualizador: FakeServicioEcualizador(),
|
||||
resolverArchivoCustom: _archivoCustomVacio,
|
||||
iniciarAutomaticamente: false,
|
||||
);
|
||||
await estadoDos.inicializar();
|
||||
|
||||
expect(estadoDos.ordenListas, OrdenEmisoras.popularidad);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group(
|
||||
'EstadoRadio — emisoras custom: lectura tolerante y guardia de '
|
||||
'degradacion (persistence-resilience)',
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/estado/orden_emisoras.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
|
||||
/// WU6: every criterion the Buscar "Ordenar" control can render must map to
|
||||
/// a real, tested [OrdenEmisoras] case (`station-discovery-browse` spec,
|
||||
/// "Client-Side Search Sort Only" — "the system MUST NOT render an option
|
||||
/// that does not actually sort").
|
||||
void main() {
|
||||
Emisora conMetricas({
|
||||
required String uuid,
|
||||
required String nombre,
|
||||
int? bitrate,
|
||||
int votes = 0,
|
||||
int clickcount = 0,
|
||||
}) => Emisora(
|
||||
uuid: uuid,
|
||||
nombre: nombre,
|
||||
url: 'https://stream.demo/$uuid',
|
||||
bitrate: bitrate,
|
||||
votes: votes,
|
||||
clickcount: clickcount,
|
||||
);
|
||||
|
||||
group('OrdenEmisoras.nombre', () {
|
||||
test('ordena alfabéticamente sin distinguir mayúsculas', () {
|
||||
final emisoras = [
|
||||
conMetricas(uuid: 'z', nombre: 'zeta fm'),
|
||||
conMetricas(uuid: 'a', nombre: 'Alfa FM'),
|
||||
conMetricas(uuid: 'm', nombre: 'medio fm'),
|
||||
];
|
||||
|
||||
final resultado = ordenarEmisoras(emisoras, OrdenEmisoras.nombre);
|
||||
|
||||
expect(resultado.map((e) => e.uuid), ['a', 'm', 'z']);
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdenEmisoras.calidad', () {
|
||||
test('ordena por bitrate descendente', () {
|
||||
final emisoras = [
|
||||
conMetricas(uuid: 'baja', nombre: 'Baja', bitrate: 64),
|
||||
conMetricas(uuid: 'alta', nombre: 'Alta', bitrate: 320),
|
||||
conMetricas(uuid: 'media', nombre: 'Media', bitrate: 128),
|
||||
];
|
||||
|
||||
final resultado = ordenarEmisoras(emisoras, OrdenEmisoras.calidad);
|
||||
|
||||
expect(resultado.map((e) => e.uuid), ['alta', 'media', 'baja']);
|
||||
});
|
||||
});
|
||||
|
||||
group('OrdenEmisoras.popularidad', () {
|
||||
test('ordena por votos descendente', () {
|
||||
final emisoras = [
|
||||
conMetricas(uuid: 'pocos', nombre: 'Pocos', votes: 3),
|
||||
conMetricas(uuid: 'muchos', nombre: 'Muchos', votes: 900),
|
||||
conMetricas(uuid: 'medio', nombre: 'Medio', votes: 40),
|
||||
];
|
||||
|
||||
final resultado = ordenarEmisoras(emisoras, OrdenEmisoras.popularidad);
|
||||
|
||||
expect(resultado.map((e) => e.uuid), ['muchos', 'medio', 'pocos']);
|
||||
});
|
||||
|
||||
test('en empate de votos, desempata por clickcount descendente', () {
|
||||
final emisoras = [
|
||||
conMetricas(
|
||||
uuid: 'menos-clicks',
|
||||
nombre: 'Menos clicks',
|
||||
votes: 10,
|
||||
clickcount: 5,
|
||||
),
|
||||
conMetricas(
|
||||
uuid: 'mas-clicks',
|
||||
nombre: 'Mas clicks',
|
||||
votes: 10,
|
||||
clickcount: 500,
|
||||
),
|
||||
];
|
||||
|
||||
final resultado = ordenarEmisoras(emisoras, OrdenEmisoras.popularidad);
|
||||
|
||||
expect(resultado.map((e) => e.uuid), ['mas-clicks', 'menos-clicks']);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'todos los valores del enum tienen un caso de ordenamiento implementado '
|
||||
'(regresión: nunca ofrecer una opción decorativa que no ordene)',
|
||||
() {
|
||||
for (final criterio in OrdenEmisoras.values) {
|
||||
// Must not throw and must return a same-length list for every case.
|
||||
final resultado = ordenarEmisoras([
|
||||
conMetricas(uuid: 'x', nombre: 'X'),
|
||||
], criterio);
|
||||
expect(resultado, hasLength(1));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user