Dos fallos reportados desde el uso real en el coche. Los grupos de favoritos no volvian al importar La exportacion nunca estuvo rota: los grupos viajaban desde siempre y la asignacion de cada emisora va dentro de cada favorito como grupo_id. El fallo estaba solo al importar. La restauracion reutilizaba ServicioFavoritos.agregar, que fuerza "sin asignar" a proposito, porque una emisora recien marcada como favorita no tiene grupo. Correcto para esa ruta, destructivo como primitiva de restauracion: los grupos volvian vacios y todo aterrizaba en Sin asignar. Se separa la ruta de restaurar, que respeta el grupo_id del backup y su orden. Ningun test lo detectaba porque el doble de pruebas era infiel: el fake conservaba el grupo que la implementacion real destruia, y no tenia restaurarGrupo, asi que esa ruta estaba sin cubrir. Los tests existentes lo esquivaban pasando siempre una lista de grupos vacia. Se corrige el doble. No hace falta subir la version del formato: el envoltorio ya llevaba todo. El coche perdia la lista al reconectar El contexto de reproduccion vivia solo en memoria, y el motor que arranca Android Auto es un proceso nuevo sin interfaz ni EstadoRadio, asi que al reconectar se reproducia la ultima emisora sin saber a que lista pertenecia y siguiente/anterior no hacian nada hasta entrar a favoritos a mano. Ahora se persiste el TIPO de contexto y, cuando aplica, el id del grupo, y se resuelve contra las listas vivas en cada salto: si el grupo cambia de contenido entre sesiones, el coche ve lo actual y no una foto vieja. Cadena de repliegue, decidida por el propietario: grupo vivo con la emisora dentro -> se recorre el grupo grupo vivo sin la emisora -> se permanece en el grupo, primera grupo vivo pero vacio -> se ensancha a favoritos grupo borrado -> favoritos sin favoritos -> comportamiento actual Honrar un grupo de una sola emisora exigia levantar dos barreras, no una: el resolutor y el propio _saltarEmisora, que se negaba a nombrar un grupo con menos de dos miembros. emisoraVecina queda intacta: su contrato de no saltar a ciegas es deliberado y se usa desde mas sitios, asi que el caso de "la emisora se salio del grupo" se trata en el flujo del salto. La puerta de entitlement no cambia: un conductor sin premium sigue recorriendo solo el conjunto gratuito, y un contexto congelado mientras pagaba se descarta en vez de recorrerse. Suite completa: 1501 pasan, 2 omitidos. flutter analyze mantiene los 5 avisos preexistentes.
318 lines
9.1 KiB
Dart
318 lines
9.1 KiB
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/contexto_reproduccion.dart';
|
|
import 'package:pluriwave/servicios/navegacion_auto.dart';
|
|
|
|
/// Requested: the Android Auto playback screen must offer previous/next for
|
|
/// stations too, not only for a local-music queue. Those buttons appear
|
|
/// because `skipToPrevious`/`skipToNext` are advertised in `systemActions` —
|
|
/// so they have to actually move, or the car shows two dead buttons.
|
|
void main() {
|
|
Emisora emisora(String uuid) =>
|
|
Emisora(uuid: uuid, nombre: uuid, url: 'https://example.com/$uuid');
|
|
|
|
final a = emisora('a');
|
|
final b = emisora('b');
|
|
final c = emisora('c');
|
|
|
|
group('emisoraVecina', () {
|
|
test('avanza y retrocede dentro de la lista', () {
|
|
expect(emisoraVecina(a, [a, b, c], haciaAtras: false), b);
|
|
expect(emisoraVecina(b, [a, b, c], haciaAtras: true), a);
|
|
});
|
|
|
|
test('da la vuelta en los dos extremos', () {
|
|
// A dead button at the end of a list reads as a broken app on a car
|
|
// screen, where there is no visible list position to explain it.
|
|
expect(emisoraVecina(c, [a, b, c], haciaAtras: false), a);
|
|
expect(emisoraVecina(a, [a, b, c], haciaAtras: true), c);
|
|
});
|
|
|
|
test('identifica por uuid, no por instancia: un snapshot refrescado trae '
|
|
'objetos distintos', () {
|
|
final copiaDeB = Emisora(
|
|
uuid: 'b',
|
|
nombre: 'otro nombre',
|
|
url: 'https://example.com/cambiada',
|
|
);
|
|
expect(emisoraVecina(copiaDeB, [a, b, c], haciaAtras: false), c);
|
|
});
|
|
|
|
test('no hace nada sin contexto suficiente', () {
|
|
expect(emisoraVecina(null, [a, b], haciaAtras: false), isNull);
|
|
expect(emisoraVecina(a, [a], haciaAtras: false), isNull);
|
|
expect(emisoraVecina(a, const [], haciaAtras: false), isNull);
|
|
expect(
|
|
emisoraVecina(emisora('fuera'), [a, b], haciaAtras: false),
|
|
isNull,
|
|
reason: 'una emisora que no está en la lista no debe saltar a ciegas',
|
|
);
|
|
});
|
|
});
|
|
|
|
group('listaParaSaltoEmisora', () {
|
|
test('favoritos gana: "siguiente" desde un favorito va al siguiente '
|
|
'favorito, no a la entrada 4318 del catálogo', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: a,
|
|
favoritos: [a, b],
|
|
misEmisoras: [a, c],
|
|
todas: [a, b, c],
|
|
),
|
|
[a, b],
|
|
);
|
|
});
|
|
|
|
test('mis emisoras cuando no es favorita', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a, b],
|
|
misEmisoras: [c, a],
|
|
todas: [a, b, c],
|
|
),
|
|
[c, a],
|
|
);
|
|
});
|
|
|
|
test('cae al catálogo completo para una emisora llegada por búsqueda', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b, c],
|
|
),
|
|
[a, b, c],
|
|
);
|
|
});
|
|
|
|
test('vacía si no está en ninguna: el salto queda en no-op', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: emisora('huerfana'),
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b],
|
|
),
|
|
isEmpty,
|
|
);
|
|
});
|
|
});
|
|
|
|
group('navegación por GRUPO de favoritos (pedido por el dueño)', () {
|
|
Emisora favorita(String uuid, String grupo) => Emisora(
|
|
uuid: uuid,
|
|
nombre: uuid,
|
|
url: 'https://example.com/$uuid',
|
|
grupoFavoritosId: grupo,
|
|
);
|
|
|
|
final rock1 = favorita('rock1', 'g-rock');
|
|
final rock2 = favorita('rock2', 'g-rock');
|
|
final jazz1 = favorita('jazz1', 'g-jazz');
|
|
final suelta = favorita('suelta', GrupoFavoritos.sinAsignarId);
|
|
|
|
test('sonando una favorita de un grupo, se recorre SOLO ese grupo', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: rock1,
|
|
favoritos: [rock1, jazz1, rock2, suelta],
|
|
misEmisoras: const [],
|
|
todas: [rock1, jazz1, rock2, suelta],
|
|
),
|
|
[rock1, rock2],
|
|
);
|
|
});
|
|
|
|
test('el grupo se lee del registro de FAVORITOS, no de lo que suena: la '
|
|
'emisora reconstruida desde el MediaItem no lleva grupo', () {
|
|
// emisoraDesdeMediaItem no puede saber el grupo -> llega "sin asignar".
|
|
final reconstruida = Emisora(
|
|
uuid: 'rock1',
|
|
nombre: 'Rock 1',
|
|
url: 'https://example.com/rock1',
|
|
);
|
|
expect(reconstruida.grupoFavoritosId, GrupoFavoritos.sinAsignarId);
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: reconstruida,
|
|
favoritos: [rock1, jazz1, rock2],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, rock2],
|
|
reason: 'si se leyera de `actual` caeríamos a todos los favoritos',
|
|
);
|
|
});
|
|
|
|
test('"sin asignar" NO es un grupo: recorre todos los favoritos', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: suelta,
|
|
favoritos: [rock1, suelta, jazz1],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, suelta, jazz1],
|
|
);
|
|
});
|
|
|
|
test('un grupo de UNA sola emisora cae a todos los favoritos, para no '
|
|
'dejar los dos botones muertos', () {
|
|
expect(
|
|
listaParaSaltoEmisora(
|
|
actual: jazz1,
|
|
favoritos: [rock1, rock2, jazz1],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
[rock1, rock2, jazz1],
|
|
);
|
|
});
|
|
});
|
|
|
|
group('contextoParaSaltoEmisora — la MISMA decisión, nombrada para poder '
|
|
'recordarla entre procesos', () {
|
|
Emisora favorita(String uuid, String grupo) => Emisora(
|
|
uuid: uuid,
|
|
nombre: uuid,
|
|
url: 'https://example.com/$uuid',
|
|
grupoFavoritosId: grupo,
|
|
);
|
|
|
|
final rock1 = favorita('rock1', 'g-rock');
|
|
final rock2 = favorita('rock2', 'g-rock');
|
|
final jazz1 = favorita('jazz1', 'g-jazz');
|
|
|
|
test('nombra el grupo cuando el salto se queda dentro del grupo', () {
|
|
expect(
|
|
contextoParaSaltoEmisora(
|
|
actual: rock1,
|
|
favoritos: [rock1, jazz1, rock2],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
const ContextoSalto.grupo('g-rock'),
|
|
);
|
|
});
|
|
|
|
test('nombra la lista de cada uno de los otros tres casos', () {
|
|
expect(
|
|
contextoParaSaltoEmisora(
|
|
actual: jazz1,
|
|
favoritos: [rock1, jazz1, rock2],
|
|
misEmisoras: const [],
|
|
todas: const [],
|
|
),
|
|
const ContextoSalto.favoritos(),
|
|
reason: 'un grupo de un solo miembro cae a todos los favoritos',
|
|
);
|
|
expect(
|
|
contextoParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a, b],
|
|
misEmisoras: [c, a],
|
|
todas: [a, b, c],
|
|
),
|
|
const ContextoSalto.misEmisoras(),
|
|
);
|
|
expect(
|
|
contextoParaSaltoEmisora(
|
|
actual: c,
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b, c],
|
|
),
|
|
const ContextoSalto.todas(),
|
|
);
|
|
});
|
|
|
|
test('null cuando la emisora no está en ninguna lista', () {
|
|
expect(
|
|
contextoParaSaltoEmisora(
|
|
actual: emisora('huerfana'),
|
|
favoritos: [a],
|
|
misEmisoras: [b],
|
|
todas: [a, b],
|
|
),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test('CONCUERDA con listaParaSaltoEmisora en todos los casos: son la '
|
|
'misma decisión y no pueden divergir', () {
|
|
final escenarios = <List<List<Emisora>>>[
|
|
[
|
|
[rock1],
|
|
[rock1, jazz1, rock2],
|
|
const [],
|
|
const [],
|
|
],
|
|
[
|
|
[jazz1],
|
|
[rock1, jazz1, rock2],
|
|
const [],
|
|
const [],
|
|
],
|
|
[
|
|
[c],
|
|
[a, b],
|
|
[c, a],
|
|
[a, b, c],
|
|
],
|
|
[
|
|
[c],
|
|
[a],
|
|
[b],
|
|
[a, b, c],
|
|
],
|
|
[
|
|
[emisora('huerfana')],
|
|
[a],
|
|
[b],
|
|
[a, b],
|
|
],
|
|
];
|
|
for (final escenario in escenarios) {
|
|
final actual = escenario[0].single;
|
|
final favoritos = escenario[1];
|
|
final misEmisoras = escenario[2];
|
|
final todas = escenario[3];
|
|
final contexto = contextoParaSaltoEmisora(
|
|
actual: actual,
|
|
favoritos: favoritos,
|
|
misEmisoras: misEmisoras,
|
|
todas: todas,
|
|
);
|
|
final porContexto =
|
|
contexto == null
|
|
? const <Emisora>[]
|
|
: resolverListaContexto(
|
|
contexto: contexto,
|
|
actual: actual,
|
|
favoritos: favoritos,
|
|
misEmisoras: misEmisoras,
|
|
todas: todas,
|
|
destacadas: const [],
|
|
grupos: const [
|
|
GrupoFavoritos(id: 'g-rock', nombre: 'Rock', orden: 1),
|
|
GrupoFavoritos(id: 'g-jazz', nombre: 'Jazz', orden: 2),
|
|
],
|
|
);
|
|
expect(
|
|
porContexto,
|
|
listaParaSaltoEmisora(
|
|
actual: actual,
|
|
favoritos: favoritos,
|
|
misEmisoras: misEmisoras,
|
|
todas: todas,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|