Files
pluriwave/test/servicios/servicio_audio_eq_custom_actions_test.dart
T
FreeTLab f19666508d fix(auto): remove the preset-cycling custom action, superseded by folder
The equalizer's preset-cycling custom action (eq_preset_siguiente) and
its ic_auto_eq_preset drawable are no longer needed now that the
"Ecualizador" folder lists all six presets directly: the folder replaces
what the cycle action did, and this frees a scarce Android Auto custom
action slot.

The on/off toggle is now the equalizer's only custom action.
2026-07-31 19:15:31 +02:00

201 lines
7.3 KiB
Dart

import 'dart:io';
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,
l10n: l10n,
);
expect(controles, isEmpty);
});
test('exactly 1 custom action when available: the on/off toggle -- '
'decision `auto/ecualizador-diseno` REMOVES the preset-cycling '
'action that used to sit alongside it; preset selection now lives '
'in the "Ecualizador" browsable folder instead (see '
'`itemsEcualizadorAuto`)', () {
final controles = controlesEcualizadorPersonalizados(
disponible: true,
activo: true,
l10n: l10n,
);
expect(controles, hasLength(1));
expect(controles.single.action, MediaAction.custom);
expect(controles.single.customAction?.name, accionEqToggle);
});
test('toggle label reflects ON -> shows "disable" action', () {
final controles = controlesEcualizadorPersonalizados(
disponible: true,
activo: true,
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,
l10n: l10n,
);
final toggle = controles.firstWhere(
(c) => c.customAction?.name == accionEqToggle,
);
expect(toggle.label, l10n.eqCustomActionEnableLabel);
});
test('toggle icon reflects EQ state: ON uses ic_auto_eq_on, OFF uses '
'ic_auto_eq_off -- a car head unit foregrounds the icon over the '
'label, so the icon itself must change, not just the text', () {
final activado = controlesEcualizadorPersonalizados(
disponible: true,
activo: true,
l10n: l10n,
).firstWhere((c) => c.customAction?.name == accionEqToggle);
final desactivado = controlesEcualizadorPersonalizados(
disponible: true,
activo: false,
l10n: l10n,
).firstWhere((c) => c.customAction?.name == accionEqToggle);
expect(activado.androidIcon, 'drawable/ic_auto_eq_on');
expect(desactivado.androidIcon, 'drawable/ic_auto_eq_off');
});
});
group(
'action name constants (item 4 -- collision-free with car-tree ids)',
() {
test('accionEqToggle is non-empty and does not collide with any '
'existing browse-tree media-id prefix -- accionEqPresetSiguiente '
'(decision `auto/ecualizador-diseno`: removed, superseded by the '
'"Ecualizador" browsable folder) no longer exists as a symbol at '
'all, which this file compiling proves on its own', () {
expect(accionEqToggle, isNotEmpty);
});
},
);
group('equalizer drawable assets on disk (on-device feedback follow-up: the '
'two custom actions used to share one drawable and were visually '
'indistinguishable)', () {
test('ic_auto_eq_on and ic_auto_eq_off exist under '
'android/app/src/main/res/drawable/ -- a missing drawable is not a '
'build error, it silently renders blank/default on the head unit, '
'so this is the only safety net that would have caught the '
'original duplication. ic_auto_eq_preset is deliberately NOT '
'checked here anymore -- decision `auto/ecualizador-diseno` '
'removes the preset-cycling action and its drawable', () {
for (final nombre in ['ic_auto_eq_on', 'ic_auto_eq_off']) {
final archivo = File('android/app/src/main/res/drawable/$nombre.xml');
expect(
archivo.existsSync(),
isTrue,
reason:
'$nombre.xml must exist under '
'android/app/src/main/res/drawable/',
);
}
expect(
File(
'android/app/src/main/res/drawable/ic_auto_eq_preset.xml',
).existsSync(),
isFalse,
reason:
'ic_auto_eq_preset.xml must be REMOVED -- decision '
'`auto/ecualizador-diseno` retires the preset-cycling '
'custom action it belonged to',
);
});
});
}