feat(auto): surface favorite groups as Android Auto sub-folders
Favoritos now renders non-empty custom groups as grupo:<id> sub-folders (hidden when empty) with ungrouped stations left as direct leaves, reusing the existing hijos() path so the zero-groups case stays byte-identical to today's flat list.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/modelos/emisora.dart';
|
||||
import 'package:pluriwave/modelos/grupo_favoritos.dart';
|
||||
import 'package:pluriwave/servicios/navegacion_auto.dart';
|
||||
|
||||
void main() {
|
||||
@@ -401,6 +402,168 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
group('ConstructorArbolAuto.esCarpetaGrupo', () {
|
||||
test('reconoce ids con el prefijo grupo:, rechaza el resto', () {
|
||||
final builder = ConstructorArbolAuto();
|
||||
|
||||
expect(builder.esCarpetaGrupo('grupo:g1'), isTrue);
|
||||
expect(builder.esCarpetaGrupo('favoritos'), isFalse);
|
||||
expect(builder.esCarpetaGrupo('emisora:x'), isFalse);
|
||||
expect(builder.esCarpetaGrupo(''), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('ConstructorArbolAuto.itemGrupo', () {
|
||||
test('mapea un GrupoFavoritos a una carpeta no reproducible con id '
|
||||
'grupo:<id>', () {
|
||||
final grupo = _grupo(id: 'g1', nombre: 'Rock');
|
||||
|
||||
final item = ConstructorArbolAuto().itemGrupo(grupo);
|
||||
|
||||
expect(item.id, 'grupo:g1');
|
||||
expect(item.playable, isFalse);
|
||||
expect(item.title, grupo.nombre);
|
||||
});
|
||||
});
|
||||
|
||||
group('ConstructorArbolAuto.carpetasFavoritos', () {
|
||||
test('sin grupos personalizados, el resultado es idéntico a hijos '
|
||||
'(idFavoritos, emisoras: favoritos) — regresión más importante de '
|
||||
'este cambio', () {
|
||||
final favoritos = [
|
||||
_emisora(uuid: 'uuid-1', nombre: 'Radio 1', bitrate: 128),
|
||||
_emisora(uuid: 'uuid-2', nombre: 'Radio 2', bitrate: 64),
|
||||
];
|
||||
final builder = ConstructorArbolAuto();
|
||||
|
||||
final resultado = builder.carpetasFavoritos(
|
||||
grupos: const [
|
||||
GrupoFavoritos(id: GrupoFavoritos.sinAsignarId, nombre: 'Sin asignar', orden: 0),
|
||||
],
|
||||
favoritos: favoritos,
|
||||
);
|
||||
final esperado = builder.hijos(
|
||||
ConstructorArbolAuto.idFavoritos,
|
||||
emisoras: favoritos,
|
||||
);
|
||||
|
||||
expect(resultado.map((i) => i.id).toList(), esperado.map((i) => i.id).toList());
|
||||
expect(resultado.length, esperado.length);
|
||||
for (var i = 0; i < resultado.length; i++) {
|
||||
expect(resultado[i].id, esperado[i].id);
|
||||
expect(resultado[i].playable, esperado[i].playable);
|
||||
expect(resultado[i].title, esperado[i].title);
|
||||
}
|
||||
});
|
||||
|
||||
test('con un grupo personalizado no vacío, devuelve la carpeta grupo:<id> '
|
||||
'seguida de las emisoras sin asignar como hojas', () {
|
||||
final rock = _grupo(id: 'g-rock', nombre: 'Rock', orden: 1);
|
||||
final sinAsignar = const GrupoFavoritos(
|
||||
id: GrupoFavoritos.sinAsignarId,
|
||||
nombre: 'Sin asignar',
|
||||
orden: 0,
|
||||
);
|
||||
final favoritos = [
|
||||
_emisora(uuid: 'uuid-rock', nombre: 'Radio Rock', grupoId: 'g-rock'),
|
||||
_emisora(uuid: 'uuid-suelta', nombre: 'Radio Suelta'),
|
||||
];
|
||||
|
||||
final resultado = ConstructorArbolAuto().carpetasFavoritos(
|
||||
grupos: [sinAsignar, rock],
|
||||
favoritos: favoritos,
|
||||
);
|
||||
|
||||
expect(resultado, hasLength(2));
|
||||
expect(resultado[0].id, 'grupo:g-rock');
|
||||
expect(resultado[0].playable, isFalse);
|
||||
expect(resultado[1].id, 'emisora:uuid-suelta');
|
||||
expect(resultado[1].playable, isTrue);
|
||||
});
|
||||
|
||||
test('un grupo personalizado vacío se omite; sin_asignar nunca aparece '
|
||||
'como carpeta propia aunque esté en la lista de grupos', () {
|
||||
final vacio = _grupo(id: 'g-vacio', nombre: 'Vacío', orden: 1);
|
||||
final sinAsignar = const GrupoFavoritos(
|
||||
id: GrupoFavoritos.sinAsignarId,
|
||||
nombre: 'Sin asignar',
|
||||
orden: 0,
|
||||
);
|
||||
final favoritos = [_emisora(uuid: 'uuid-1', nombre: 'Radio 1')];
|
||||
|
||||
final resultado = ConstructorArbolAuto().carpetasFavoritos(
|
||||
grupos: [sinAsignar, vacio],
|
||||
favoritos: favoritos,
|
||||
);
|
||||
|
||||
expect(resultado.map((i) => i.id), isNot(contains('grupo:g-vacio')));
|
||||
expect(
|
||||
resultado.map((i) => i.id),
|
||||
isNot(contains('grupo:${GrupoFavoritos.sinAsignarId}')),
|
||||
);
|
||||
expect(resultado.map((i) => i.id), contains('emisora:uuid-1'));
|
||||
});
|
||||
|
||||
test('más de 50 grupos elegibles no vacíos se truncan a 50', () {
|
||||
final favoritos = List.generate(
|
||||
60,
|
||||
(i) => _emisora(uuid: 'uuid-$i', nombre: 'Radio $i', grupoId: 'g-$i'),
|
||||
);
|
||||
final grupos = List.generate(
|
||||
60,
|
||||
(i) => _grupo(id: 'g-$i', nombre: 'Grupo $i', orden: i),
|
||||
);
|
||||
|
||||
final resultado = ConstructorArbolAuto().carpetasFavoritos(
|
||||
grupos: grupos,
|
||||
favoritos: favoritos,
|
||||
);
|
||||
|
||||
final carpetas = resultado.where((i) => i.playable != true).toList();
|
||||
expect(carpetas, hasLength(50));
|
||||
});
|
||||
});
|
||||
|
||||
group('ConstructorArbolAuto.hijosGrupo', () {
|
||||
test('filtra por grupoFavoritosId, ordena y capea a 50; id desconocido '
|
||||
'devuelve lista vacía', () {
|
||||
final favoritos = List.generate(
|
||||
60,
|
||||
(i) => _emisora(
|
||||
uuid: 'uuid-$i',
|
||||
nombre: 'Radio $i',
|
||||
bitrate: i,
|
||||
grupoId: 'g-rock',
|
||||
),
|
||||
);
|
||||
|
||||
final hijos = ConstructorArbolAuto().hijosGrupo(
|
||||
'grupo:g-rock',
|
||||
favoritos: favoritos,
|
||||
);
|
||||
|
||||
expect(hijos, hasLength(50));
|
||||
expect(hijos.first.id, 'emisora:uuid-59');
|
||||
expect(hijos.last.id, 'emisora:uuid-10');
|
||||
|
||||
final vacio = ConstructorArbolAuto().hijosGrupo(
|
||||
'grupo:g-inexistente',
|
||||
favoritos: favoritos,
|
||||
);
|
||||
expect(vacio, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('ConstructorArbolAuto.resolver: colisión con esquema grupo:', () {
|
||||
test('un id grupo:<id> nunca resuelve a una Emisora', () {
|
||||
final universo = [_emisora(uuid: 'g1', nombre: 'No debería matchear')];
|
||||
|
||||
final resultado = ConstructorArbolAuto().resolver('grupo:g1', universo);
|
||||
|
||||
expect(resultado, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('reproducirPorMediaId', () {
|
||||
test(
|
||||
'resuelve el id y delega a reproducir con un MediaItem con forma de '
|
||||
@@ -457,6 +620,7 @@ Emisora _emisora({
|
||||
String? favicon,
|
||||
String? codec,
|
||||
int? bitrate,
|
||||
String grupoId = GrupoFavoritos.sinAsignarId,
|
||||
}) {
|
||||
return Emisora(
|
||||
uuid: uuid,
|
||||
@@ -465,9 +629,18 @@ Emisora _emisora({
|
||||
favicon: favicon,
|
||||
codec: codec,
|
||||
bitrate: bitrate,
|
||||
grupoFavoritosId: grupoId,
|
||||
);
|
||||
}
|
||||
|
||||
GrupoFavoritos _grupo({
|
||||
required String id,
|
||||
required String nombre,
|
||||
int orden = 0,
|
||||
}) {
|
||||
return GrupoFavoritos(id: id, nombre: nombre, orden: orden);
|
||||
}
|
||||
|
||||
class _FakeFuenteEmisorasAuto implements FuenteEmisorasAuto {
|
||||
_FakeFuenteEmisorasAuto({required Map<String, Emisora> porUuidResultado})
|
||||
: _porUuidResultado = porUuidResultado;
|
||||
@@ -486,10 +659,14 @@ class _FakeFuenteEmisorasAuto implements FuenteEmisorasAuto {
|
||||
@override
|
||||
Future<Emisora?> porUuid(String uuid) async => _porUuidResultado[uuid];
|
||||
|
||||
@override
|
||||
Future<List<GrupoFavoritos>> grupos() async => const [];
|
||||
|
||||
@override
|
||||
void actualizarSnapshot({
|
||||
List<Emisora>? favoritos,
|
||||
List<Emisora>? misEmisoras,
|
||||
List<Emisora>? todas,
|
||||
List<GrupoFavoritos>? grupos,
|
||||
}) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user