Files
pluriwave/test/pantallas/pantalla_alarmas_recurrencia_test.dart
T
FreeTLab 4168dc5019 fix(alarmas): show which days a weekday alarm actually fires on
The alarms list showed a generic "Días" label for a diasSemana alarm
instead of its actual configured days. Render the real recurrence (e.g.
"Lun, Mié, Vie") by reusing the SAME per-day abbreviation the editor's own
day-picker circles already use -- no new formatting scheme, no new ARB
keys for the days themselves.

Also surface fade/volume/vacation-pause state on the card, each only when
it is a genuinely useful deviation from the common case: a fade badge when
fadeInSegundos > 0 (reusing the existing alarmFadeInLabel key), a volume
percentage when it differs from the 85% default, and a vacation-paused
badge when the alarm is both configured to pause and a vacation range is
currently active (mirrors the exact predicate ServicioProgramacionAlarmas
already uses). One compact line, not a badge per field.

Fixes a text-collision regression in pantalla_alarmas_editor_test.dart:
opening the editor for an alarm whose own day now renders on its card
(e.g. "Lun") made a bare find.text(weekday) ambiguous against the editor's
day-picker circle with the same label -- scoped that finder to the
BottomSheet subtree.
2026-07-31 01:05:46 +02:00

331 lines
9.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pluriwave/estado/estado_alarmas.dart';
import 'package:pluriwave/estado/estado_radio.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/pantallas/pantalla_alarmas.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart';
import '../helpers/fakes_alarmas.dart';
/// Item 5: the alarm list must show which days a `diasSemana` alarm
/// actually fires on (e.g. "Lun, Mié, Vie"), not the generic "Días" label,
/// plus surface fade/volume/vacation-pause state when they are genuinely
/// informative -- without cluttering the row.
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
Future<(EstadoRadio, EstadoAlarmas)> montar(
WidgetTester tester, {
required AlarmaMusical alarma,
List<RangoVacaciones> vacaciones = const [],
}) async {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final radio = EstadoRadio(
audio: FakeServicioAudio(),
favoritos: FakeServicioFavoritos(),
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
iniciarAutomaticamente: false,
);
addTearDown(radio.dispose);
final android = FakePuertoAlarmasAndroid();
final estadoAlarmas = EstadoAlarmas(
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 11, 6, 0)),
android: android,
iniciarAutomaticamente: false,
);
addTearDown(estadoAlarmas.dispose);
addTearDown(android.dispose);
await estadoAlarmas.guardarAlarma(alarma);
if (vacaciones.isNotEmpty) {
await estadoAlarmas.guardarVacaciones(vacaciones);
}
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<EstadoRadio>.value(value: radio),
ChangeNotifierProvider<EstadoAlarmas>.value(value: estadoAlarmas),
],
child: MaterialApp(
locale: const Locale('es'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: PantallaAlarmas()),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
return (radio, estadoAlarmas);
}
testWidgets(
'diasSemana alarm shows the ACTUAL configured days (Lun, Mié, Vie), '
'not the generic "Días" label',
(tester) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-dias',
nombre: 'Entre semana',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
diasSemana: [DateTime.monday, DateTime.wednesday, DateTime.friday],
),
);
expect(find.text('Lun, Mié, Vie'), findsOneWidget);
expect(find.text('Días'), findsNothing);
},
);
testWidgets('daily alarm still shows "Diaria" (unaffected)', (
tester,
) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-diaria',
nombre: 'Todos los días',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
),
);
expect(find.text('Diaria'), findsOneWidget);
});
testWidgets('one-time alarm still shows "Una vez" (unaffected)', (
tester,
) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-unica',
nombre: 'Una sola vez',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.unica,
diasSemana: [],
fechaUnica: null,
),
);
expect(find.text('Una vez'), findsOneWidget);
});
testWidgets(
'a diasSemana alarm with an (invalid/legacy) empty diasSemana falls '
'back to the generic label instead of showing nothing',
(tester) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-dias-vacio',
nombre: 'Corrupta',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
diasSemana: [],
),
);
expect(find.text('Días'), findsOneWidget);
},
);
testWidgets('a configured fade-in shows a compact "Fade-in Ns" detail', (
tester,
) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-fade',
nombre: 'Con fade',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
fadeInSegundos: 8,
),
);
expect(find.textContaining('Fade-in 8s'), findsOneWidget);
});
testWidgets('no fade-in (0s, the default) shows no fade detail', (
tester,
) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-sin-fade',
nombre: 'Sin fade',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
fadeInSegundos: 0,
),
);
expect(find.textContaining('Fade-in'), findsNothing);
});
testWidgets(
'a non-default volume shows a compact percentage detail',
(tester) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-vol',
nombre: 'Volumen bajo',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
volumen: 0.5,
),
);
expect(find.textContaining('50%'), findsOneWidget);
},
);
testWidgets('the default volume (85%) shows no volume detail', (
tester,
) async {
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-vol-default',
nombre: 'Volumen default',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
),
);
expect(find.textContaining('85%'), findsNothing);
});
testWidgets(
'an alarm paused by a CURRENTLY active vacation range shows a '
'vacation-paused detail',
(tester) async {
final l10n = lookupAppLocalizations(const Locale('es'));
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-vacaciones',
nombre: 'Pausada',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
sonarEnVacaciones: false,
),
vacaciones: [
// Wide, real-wall-clock-safe range (rangoVacacionesActivo()
// defaults to the REAL DateTime.now(), not this file's injected
// `reloj`) -- deliberately spans many years so the test stays
// valid regardless of exactly when it runs.
RangoVacaciones(
id: 'v1',
nombre: 'Verano',
inicio: DateTime(2020, 1, 1),
fin: DateTime(2030, 12, 31),
),
],
);
expect(
find.textContaining(l10n.alarmCardVacationPausedBadge),
findsOneWidget,
);
},
);
testWidgets(
'an alarm that DOES sound during vacations shows NO vacation-paused '
'detail even with an active range',
(tester) async {
final l10n = lookupAppLocalizations(const Locale('es'));
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-suena-vacaciones',
nombre: 'Suena igual',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
sonarEnVacaciones: true,
),
vacaciones: [
// Wide, real-wall-clock-safe range (rangoVacacionesActivo()
// defaults to the REAL DateTime.now(), not this file's injected
// `reloj`) -- deliberately spans many years so the test stays
// valid regardless of exactly when it runs.
RangoVacaciones(
id: 'v1',
nombre: 'Verano',
inicio: DateTime(2020, 1, 1),
fin: DateTime(2030, 12, 31),
),
],
);
expect(
find.textContaining(l10n.alarmCardVacationPausedBadge),
findsNothing,
);
},
);
testWidgets(
'sonarEnVacaciones:false with NO currently-active vacation range shows '
'no vacation-paused detail (nothing to be paused BY right now)',
(tester) async {
final l10n = lookupAppLocalizations(const Locale('es'));
await montar(
tester,
alarma: const AlarmaMusical(
id: 'a-sin-rango-activo',
nombre: 'Sin vacaciones activas',
hora: 7,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
sonarEnVacaciones: false,
),
);
expect(
find.textContaining(l10n.alarmCardVacationPausedBadge),
findsNothing,
);
},
);
}