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.
This commit is contained in:
@@ -190,9 +190,26 @@ void main() {
|
||||
expect(antes, isNot(l10n.alarmNoNextExecution));
|
||||
|
||||
// Lunes -> Martes: la fecha calculada SIEMPRE cambia, sea cual sea hoy.
|
||||
await tester.tap(find.text(l10n.weekdayShortTuesday));
|
||||
//
|
||||
// Item 5: the alarm CARD underneath now also renders the real day
|
||||
// abbreviation ("Lun") for a diasSemana alarm, so a bare
|
||||
// `find.text(...)` for a weekday letter is ambiguous while the
|
||||
// editor sheet is open on top of the list — scope to the sheet's own
|
||||
// BottomSheet subtree to target the day-picker circle specifically.
|
||||
final hojaEditor = find.byType(BottomSheet);
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: hojaEditor,
|
||||
matching: find.text(l10n.weekdayShortTuesday),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(l10n.weekdayShortMonday));
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: hojaEditor,
|
||||
matching: find.text(l10n.weekdayShortMonday),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final despues = _textoPreview(tester);
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user