feat(ui): add premium PluriWave redesign
This commit is contained in:
@@ -171,6 +171,41 @@ void main() {
|
||||
expect(estado.presetEcualizador, principal);
|
||||
expect(audio.presetsAplicados.last, principal);
|
||||
});
|
||||
|
||||
test('reordenar favoritos reindexa de forma determinística', () async {
|
||||
final favoritos = FakeServicioFavoritos();
|
||||
await favoritos.agregar(emisoraDemo(uuid: 'a', nombre: 'A'));
|
||||
await favoritos.agregar(emisoraDemo(uuid: 'b', nombre: 'B'));
|
||||
await favoritos.agregar(emisoraDemo(uuid: 'c', nombre: 'C'));
|
||||
|
||||
await favoritos.reordenar('a', 2);
|
||||
final lista = await favoritos.obtenerTodos();
|
||||
|
||||
expect(lista.map((e) => e.uuid).toList(), equals(['b', 'c', 'a']));
|
||||
expect(lista.map((e) => e.orden).toList(), equals([0, 1, 2]));
|
||||
});
|
||||
|
||||
test('toggleFavorito refresca lista global y evita estado stale', () async {
|
||||
final favoritos = FakeServicioFavoritos();
|
||||
final emisora = emisoraDemo(uuid: 'fav-sync', nombre: 'Sync');
|
||||
final estado = EstadoRadio(
|
||||
audio: FakeServicioAudio(),
|
||||
favoritos: favoritos,
|
||||
radio: FakeServicioRadio(populares: [emisora]),
|
||||
servicioEcualizador: FakeServicioEcualizador(),
|
||||
resolverArchivoCustom: _archivoCustomVacio,
|
||||
iniciarAutomaticamente: false,
|
||||
);
|
||||
|
||||
await estado.inicializar();
|
||||
expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isFalse);
|
||||
|
||||
await estado.toggleFavorito(emisora);
|
||||
expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isTrue);
|
||||
|
||||
await estado.toggleFavorito(emisora);
|
||||
expect(estado.listaFavoritos.any((e) => e.uuid == emisora.uuid), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+26
-8
@@ -55,35 +55,53 @@ class FakeServicioAudio extends ServicioAudio {
|
||||
}
|
||||
|
||||
class FakeServicioFavoritos extends ServicioFavoritos {
|
||||
final Map<String, Emisora> _favoritos = {};
|
||||
final List<Emisora> _favoritos = [];
|
||||
int toggleCalls = 0;
|
||||
|
||||
@override
|
||||
Future<List<Emisora>> obtenerTodos() async => _favoritos.values.toList();
|
||||
Future<List<Emisora>> obtenerTodos() async =>
|
||||
_favoritos.map((e) => e.copyWith()).toList();
|
||||
|
||||
@override
|
||||
Future<void> agregar(Emisora emisora) async {
|
||||
_favoritos[emisora.uuid] = emisora;
|
||||
_favoritos.removeWhere((e) => e.uuid == emisora.uuid);
|
||||
_favoritos.add(emisora.copyWith(orden: _favoritos.length));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> eliminar(String uuid) async {
|
||||
_favoritos.remove(uuid);
|
||||
_favoritos.removeWhere((e) => e.uuid == uuid);
|
||||
for (var i = 0; i < _favoritos.length; i++) {
|
||||
_favoritos[i] = _favoritos[i].copyWith(orden: i);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> esFavorito(String uuid) async => _favoritos.containsKey(uuid);
|
||||
Future<bool> esFavorito(String uuid) async =>
|
||||
_favoritos.any((e) => e.uuid == uuid);
|
||||
|
||||
@override
|
||||
Future<bool> toggleFavorito(Emisora emisora) async {
|
||||
toggleCalls += 1;
|
||||
if (_favoritos.containsKey(emisora.uuid)) {
|
||||
_favoritos.remove(emisora.uuid);
|
||||
if (_favoritos.any((e) => e.uuid == emisora.uuid)) {
|
||||
await eliminar(emisora.uuid);
|
||||
return false;
|
||||
}
|
||||
_favoritos[emisora.uuid] = emisora;
|
||||
await agregar(emisora);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> reordenar(String uuid, int nuevoOrden) async {
|
||||
final oldIndex = _favoritos.indexWhere((e) => e.uuid == uuid);
|
||||
if (oldIndex == -1 || _favoritos.isEmpty) return;
|
||||
final targetIndex = nuevoOrden.clamp(0, _favoritos.length - 1);
|
||||
final moved = _favoritos.removeAt(oldIndex);
|
||||
_favoritos.insert(targetIndex, moved);
|
||||
for (var i = 0; i < _favoritos.length; i++) {
|
||||
_favoritos[i] = _favoritos[i].copyWith(orden: i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeServicioRadio extends ServicioRadio {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/tema/pluriwave_theme.dart';
|
||||
import 'package:pluriwave/tema/pluriwave_tokens.dart';
|
||||
import 'package:pluriwave/widgets/pluri_glass_surface.dart';
|
||||
import 'package:pluriwave/widgets/pluri_icon.dart';
|
||||
import 'package:pluriwave/widgets/pluri_wave_scaffold.dart';
|
||||
|
||||
void main() {
|
||||
test('PluriWaveTokens.dark mantiene valores base esperados', () {
|
||||
expect(PluriWaveTokens.dark.deepViolet, const Color(0xFF24123D));
|
||||
expect(PluriWaveTokens.dark.radiusMd, 16);
|
||||
expect(PluriWaveTokens.dark.spacingMd, 16);
|
||||
});
|
||||
|
||||
testWidgets('PluriIcon expone semantics label', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: PluriWaveTheme.dark(),
|
||||
home: const Scaffold(
|
||||
body: PluriIcon(
|
||||
glyph: PluriIconGlyph.search,
|
||||
variant: PluriIconVariant.activeGlow,
|
||||
semanticLabel: 'Buscar emisoras',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.bySemanticsLabel('Buscar emisoras'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('PluriGlassSurface y PluriWaveScaffold se instancian', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: PluriWaveTheme.dark(),
|
||||
home: const PluriWaveScaffold(
|
||||
body: Center(
|
||||
child: PluriGlassSurface(
|
||||
child: Text('ok'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(PluriWaveScaffold), findsOneWidget);
|
||||
expect(find.byType(PluriGlassSurface), findsOneWidget);
|
||||
expect(find.text('ok'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:pluriwave/servicios/servicio_audio.dart';
|
||||
import 'package:pluriwave/widgets/visualizador_audio.dart';
|
||||
|
||||
void main() {
|
||||
group('VisualizadorAudio lifecycle', () {
|
||||
testWidgets('ignora eventos de estado después de dispose', (tester) async {
|
||||
final controller = StreamController<EstadoReproduccion>.broadcast();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: VisualizadorAudio(estadoStream: controller.stream),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
controller.add(EstadoReproduccion.reproduciendo);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
await controller.close();
|
||||
});
|
||||
|
||||
testWidgets('ignora eventos de estado después de dispose en indicador', (tester) async {
|
||||
final controller = StreamController<EstadoReproduccion>.broadcast();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: IndicadorReproduccion(estadoStream: controller.stream),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
controller.add(EstadoReproduccion.reproduciendo);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
await controller.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user