import 'dart:ui' show Locale; import 'package:audio_service/audio_service.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/l10n/gen/app_localizations.dart'; import 'package:pluriwave/modelos/preset_ecualizador.dart'; import 'package:pluriwave/servicios/servicio_audio.dart'; /// Item 4 (Android Auto: equalizer custom actions) — the pure, handler- /// independent half of the fix. `PluriWaveAudioHandler` cannot be /// instantiated in unit tests (a real `just_audio.AudioPlayer` requires /// platform MethodChannels), so the preset-cycling decision, the preset-name /// localization and the `MediaControl` list construction are extracted as /// pure top-level functions here. The handler's own `customAction` dispatch /// and `playbackState` wiring are static-review-only, same as the existing /// EQ re-apply/session-id wiring. void main() { final l10n = lookupAppLocalizations(const Locale('es')); group('presetSiguiente (item 4 — cycling presets)', () { test('advances to the next preset in order', () { expect( presetSiguiente(PresetEcualizador.flat), PresetEcualizador.rock, ); expect( presetSiguiente(PresetEcualizador.rock), PresetEcualizador.pop, ); }); test('wraps around after the last preset', () { expect( presetSiguiente(PresetEcualizador.presets.last), PresetEcualizador.presets.first, ); }); test( 'an unknown/custom preset (e.g. a user-tweaked "Personalizado" band ' 'set) starts from the FIRST preset instead of throwing', () { final personalizado = PresetEcualizador( nombre: 'Personalizado', bandas: [1.0, 2.0, 3.0, 4.0, 5.0], ); expect( presetSiguiente(personalizado), PresetEcualizador.presets.first, ); }, ); test('respects an injected presets list instead of the default 6', () { final propios = [PresetEcualizador.jazz, PresetEcualizador.voz]; expect( presetSiguiente(PresetEcualizador.jazz, presets: propios), PresetEcualizador.voz, ); expect( presetSiguiente(PresetEcualizador.voz, presets: propios), PresetEcualizador.jazz, ); }); }); group('nombrePresetVisible (item 4)', () { test('maps every factory preset name to its localized ARB string', () { expect(nombrePresetVisible(l10n, 'Flat'), l10n.equalizerPresetFlat); expect(nombrePresetVisible(l10n, 'Rock'), l10n.equalizerPresetRock); expect(nombrePresetVisible(l10n, 'Pop'), l10n.equalizerPresetPop); expect( nombrePresetVisible(l10n, 'Bass Boost'), l10n.equalizerPresetBassBoost, ); expect(nombrePresetVisible(l10n, 'Jazz'), l10n.equalizerPresetJazz); expect(nombrePresetVisible(l10n, 'Voz'), l10n.equalizerPresetVoice); expect( nombrePresetVisible(l10n, 'Personalizado'), l10n.equalizerPresetCustom, ); }); test('an unrecognized name falls through verbatim', () { expect(nombrePresetVisible(l10n, 'Mi Preset Guardado'), 'Mi Preset Guardado'); }); }); group('controlesEcualizadorPersonalizados (item 4)', () { test('empty when the equalizer is not available on this device', () { final controles = controlesEcualizadorPersonalizados( disponible: false, activo: true, presetActual: PresetEcualizador.flat, l10n: l10n, ); expect(controles, isEmpty); }); test( 'exactly 2 custom actions when available: on/off toggle + preset ' 'cycle -- Android Auto shows a limited number of custom actions, so ' 'this is deliberately NOT one action per preset', () { final controles = controlesEcualizadorPersonalizados( disponible: true, activo: true, presetActual: PresetEcualizador.rock, l10n: l10n, ); expect(controles, hasLength(2)); expect(controles.every((c) => c.action == MediaAction.custom), isTrue); }, ); test('toggle label reflects ON -> shows "disable" action', () { final controles = controlesEcualizadorPersonalizados( disponible: true, activo: true, presetActual: PresetEcualizador.flat, l10n: l10n, ); final toggle = controles.firstWhere( (c) => c.customAction?.name == accionEqToggle, ); expect(toggle.label, l10n.eqCustomActionDisableLabel); }); test('toggle label reflects OFF -> shows "enable" action', () { final controles = controlesEcualizadorPersonalizados( disponible: true, activo: false, presetActual: PresetEcualizador.flat, l10n: l10n, ); final toggle = controles.firstWhere( (c) => c.customAction?.name == accionEqToggle, ); expect(toggle.label, l10n.eqCustomActionEnableLabel); }); test('preset-cycle label shows the CURRENT preset localized name', () { final controles = controlesEcualizadorPersonalizados( disponible: true, activo: true, presetActual: PresetEcualizador.jazz, l10n: l10n, ); final ciclo = controles.firstWhere( (c) => c.customAction?.name == accionEqPresetSiguiente, ); expect( ciclo.label, l10n.eqCustomActionPresetLabel(l10n.equalizerPresetJazz), ); }); test('both actions reuse the bundled notification drawable (zero new ' 'native assets)', () { final controles = controlesEcualizadorPersonalizados( disponible: true, activo: true, presetActual: PresetEcualizador.flat, l10n: l10n, ); expect( controles.every((c) => c.androidIcon == 'drawable/ic_stat_pluriwave'), isTrue, ); }); }); group('action name constants (item 4 — collision-free with car-tree ids)', () { test('names are non-empty, distinct, and do not collide with any ' 'existing browse-tree media-id prefix', () { expect(accionEqToggle, isNotEmpty); expect(accionEqPresetSiguiente, isNotEmpty); expect(accionEqToggle, isNot(equals(accionEqPresetSiguiente))); }); }); }