feat(buscar): add discovery landing state, filter pills, counter, and sort

This commit is contained in:
2026-07-29 08:45:36 +02:00
parent 3a803ce2bf
commit 9bd828139f
29 changed files with 1789 additions and 655 deletions
+39
View File
@@ -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)',
+101
View File
@@ -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));
}
},
);
}