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.
588 lines
19 KiB
Dart
588 lines
19 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:pluriwave/widgets/editor_hora_inline.dart';
|
|
import 'package:pluriwave/widgets/pluri_push_scaffold.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
import '../helpers/fakes_alarmas.dart';
|
|
|
|
class _Entorno {
|
|
_Entorno({required this.estadoAlarmas});
|
|
|
|
final EstadoAlarmas estadoAlarmas;
|
|
}
|
|
|
|
class _EntornoDosAlarmas {
|
|
_EntornoDosAlarmas({required this.estadoAlarmas, required this.android});
|
|
|
|
final EstadoAlarmas estadoAlarmas;
|
|
final FakePuertoAlarmasAndroid android;
|
|
}
|
|
|
|
/// WU8 fixture: two DAILY alarms ('r1' 07:00, 'r2' 08:00) under a FIXED clock
|
|
/// (06:00, before both) so 'r1' is deterministically the featured/soonest
|
|
/// alarm regardless of the wall-clock time the suite happens to run at.
|
|
Future<_EntornoDosAlarmas> _montarDosAlarmas(WidgetTester tester) 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, 1, 6, 0)),
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estadoAlarmas.dispose);
|
|
addTearDown(android.dispose);
|
|
await estadoAlarmas.guardarAlarma(
|
|
const AlarmaMusical(
|
|
id: 'r1',
|
|
nombre: 'Primera',
|
|
hora: 7,
|
|
minuto: 0,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
),
|
|
);
|
|
await estadoAlarmas.guardarAlarma(
|
|
const AlarmaMusical(
|
|
id: 'r2',
|
|
nombre: 'Segunda',
|
|
hora: 8,
|
|
minuto: 0,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
),
|
|
);
|
|
|
|
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.pumpAndSettle();
|
|
|
|
return _EntornoDosAlarmas(estadoAlarmas: estadoAlarmas, android: android);
|
|
}
|
|
|
|
Future<_Entorno> _abrirEditor(WidgetTester tester) async {
|
|
tester.view.physicalSize = const Size(1440, 3200);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
|
|
final favoritos = FakeServicioFavoritos();
|
|
await favoritos.agregar(emisoraDemo(uuid: 'alfa', nombre: 'Alfa FM'));
|
|
await favoritos.agregar(emisoraDemo(uuid: 'beta', nombre: 'Beta FM'));
|
|
final radio = EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: favoritos,
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(radio.dispose);
|
|
await radio.cargarFavoritos();
|
|
|
|
final android = FakePuertoAlarmasAndroid();
|
|
final estadoAlarmas = EstadoAlarmas(
|
|
servicio: ServicioAlarmas(reloj: DateTime.now),
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
addTearDown(estadoAlarmas.dispose);
|
|
addTearDown(android.dispose);
|
|
await estadoAlarmas.guardarAlarma(
|
|
const AlarmaMusical(
|
|
id: 'ed1',
|
|
nombre: 'Semanal',
|
|
hora: 7,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
|
|
diasSemana: [DateTime.monday],
|
|
),
|
|
);
|
|
|
|
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.pumpAndSettle();
|
|
|
|
// WU8: the always-visible "Edit" button is gone — tapping the simplified
|
|
// card itself now opens the editor.
|
|
final tarjeta = find.byKey(const ValueKey('tarjeta-alarma-ed1'));
|
|
await tester.ensureVisible(tarjeta);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(tarjeta);
|
|
await tester.pumpAndSettle();
|
|
return _Entorno(estadoAlarmas: estadoAlarmas);
|
|
}
|
|
|
|
String _textoPreview(WidgetTester tester) {
|
|
final texto = tester.widget<Text>(
|
|
find.descendant(
|
|
of: find.byKey(const ValueKey('next-trigger-preview')),
|
|
matching: find.byType(Text),
|
|
),
|
|
);
|
|
return texto.data ?? '';
|
|
}
|
|
|
|
void main() {
|
|
final l10n = lookupAppLocalizations(const Locale('es'));
|
|
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
testWidgets(
|
|
'muestra la proxima ejecucion y la actualiza al cambiar la recurrencia '
|
|
'(S2-R8)',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
expect(
|
|
find.byKey(const ValueKey('next-trigger-preview')),
|
|
findsOneWidget,
|
|
);
|
|
final antes = _textoPreview(tester);
|
|
expect(antes, isNot(l10n.alarmNoNextExecution));
|
|
|
|
// Lunes -> Martes: la fecha calculada SIEMPRE cambia, sea cual sea hoy.
|
|
//
|
|
// 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.descendant(
|
|
of: hojaEditor,
|
|
matching: find.text(l10n.weekdayShortMonday),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final despues = _textoPreview(tester);
|
|
expect(despues, isNot(l10n.alarmNoNextExecution));
|
|
expect(despues, isNot(antes));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'el selector de emisora abre un bottom sheet con buscador (S2-R9)',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
await tester.ensureVisible(
|
|
find.byKey(const ValueKey('alarm-station-field')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const ValueKey('alarm-station-field')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(SearchBar), findsOneWidget);
|
|
final lista = find.byType(ListView).last;
|
|
expect(
|
|
find.descendant(of: lista, matching: find.text('Alfa FM')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: lista, matching: find.text('Beta FM')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.enterText(find.byType(TextField).last, 'beta');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.descendant(of: lista, matching: find.text('Alfa FM')),
|
|
findsNothing,
|
|
);
|
|
expect(
|
|
find.descendant(of: lista, matching: find.text('Beta FM')),
|
|
findsOneWidget,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'tambien existe un selector para la emisora de respaldo (S2-R9)',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
// WU10 correction: the fallback-station field moved into the
|
|
// collapsed "Advanced" section (native-alarms delta — Alarm Editor
|
|
// Preserves Date, Fallback Station, and Sound Fields). It must be
|
|
// expanded first — a collapsed `ExpansionTile` does not build its
|
|
// children, so `find.byKey` would otherwise find nothing.
|
|
await tester.ensureVisible(find.text(l10n.alarmAdvancedSectionTitle));
|
|
await tester.tap(find.text(l10n.alarmAdvancedSectionTitle));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.ensureVisible(
|
|
find.byKey(const ValueKey('alarm-fallback-station-field')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(
|
|
find.byKey(const ValueKey('alarm-fallback-station-field')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(SearchBar), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('permite configurar la duracion del snooze (S2-R10)', (
|
|
tester,
|
|
) async {
|
|
final entorno = await _abrirEditor(tester);
|
|
|
|
await tester.ensureVisible(find.text(l10n.alarmSnoozeDurationTitle));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(SegmentedButton<int>), findsWidgets);
|
|
|
|
await tester.tap(find.text(l10n.alarmSnoozeOptionLabel(10)));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.ensureVisible(find.text(l10n.saveAlarmAction));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text(l10n.saveAlarmAction));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(entorno.estadoAlarmas.alarmas.single.snoozeMinutos, 10);
|
|
});
|
|
|
|
testWidgets('el slider de volumen permite bajar hasta 0.0 (S2-R11)', (
|
|
tester,
|
|
) async {
|
|
await _abrirEditor(tester);
|
|
|
|
final sliders = tester.widgetList<Slider>(find.byType(Slider));
|
|
final volumen = sliders.firstWhere((slider) => slider.max == 1.0);
|
|
expect(volumen.min, 0.0);
|
|
});
|
|
|
|
group('visual fidelity (audit 8.1/8.3/8.4, item 20)', () {
|
|
testWidgets(
|
|
'8.1: the sheet is an opaque, bottom-anchored surface (radius on the '
|
|
'top corners only) with a grab handle',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
final surface = tester.widget<DecoratedBox>(
|
|
find.byKey(const ValueKey('alarm-editor-sheet-surface')),
|
|
);
|
|
final decoracion = surface.decoration as BoxDecoration;
|
|
expect(
|
|
decoracion.color,
|
|
const Color(0xFF0D1B24),
|
|
reason: 't4 line 373: background:#0D1B24 (opaque, not glass)',
|
|
);
|
|
expect(
|
|
decoracion.borderRadius,
|
|
const BorderRadius.vertical(top: Radius.circular(30)),
|
|
reason:
|
|
't4 line 373: border-radius:30px 30px 0 0 — top corners '
|
|
'only, unlike a floating dialog',
|
|
);
|
|
|
|
final asa = tester.widget<Container>(
|
|
find.byKey(const ValueKey('alarm-editor-grab-handle')),
|
|
);
|
|
expect(asa.constraints?.maxWidth, 44);
|
|
expect(asa.constraints?.maxHeight, 4);
|
|
final asaDecoracion = asa.decoration as BoxDecoration;
|
|
expect(
|
|
asaDecoracion.borderRadius,
|
|
BorderRadius.circular(2),
|
|
reason: 't4 line 374: width:44px;height:4px;border-radius:2px',
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('8.3: the hour editor sits inside a framed card', (
|
|
tester,
|
|
) async {
|
|
await _abrirEditor(tester);
|
|
|
|
final tarjeta = tester.widget<DecoratedBox>(
|
|
find.ancestor(
|
|
of: find.byType(EditorHoraInline),
|
|
matching: find.byKey(const ValueKey('alarm-editor-hour-card')),
|
|
),
|
|
);
|
|
final decoracion = tarjeta.decoration as BoxDecoration;
|
|
expect(
|
|
decoracion.borderRadius,
|
|
BorderRadius.circular(22),
|
|
reason: 't4 line 378: border-radius:22px',
|
|
);
|
|
expect(
|
|
decoracion.color,
|
|
const Color(0xFF102532),
|
|
reason: 't4 line 378: background:#102532 (== listSurface token)',
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'8.4: the weekday selector is exactly 7 circular buttons, not chips',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
final fila = find.byKey(const ValueKey('alarm-weekday-circles'));
|
|
expect(fila, findsOneWidget);
|
|
expect(
|
|
find.descendant(of: fila, matching: find.byType(FilterChip)),
|
|
findsNothing,
|
|
reason: 't4 lines 383-390: 7 circles, not FilterChips',
|
|
);
|
|
final circulos = find.descendant(
|
|
of: fila,
|
|
matching: find.byWidgetPredicate(
|
|
(w) => w is Material && w.shape is CircleBorder,
|
|
),
|
|
);
|
|
expect(circulos, findsNWidgets(7));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'capacity guard: the date field, the fallback-station picker and the '
|
|
'sound dropdown all stay reachable behind Advanced',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
await tester.ensureVisible(find.text(l10n.alarmAdvancedSectionTitle));
|
|
await tester.tap(find.text(l10n.alarmAdvancedSectionTitle));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text(l10n.dateField), findsOneWidget);
|
|
expect(
|
|
find.byKey(const ValueKey('alarm-fallback-station-field')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byType(DropdownButtonFormField<SonidoInternoAlarma>),
|
|
findsOneWidget,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('8.5: the "REPETIR" eyebrow sits above the weekday circles', (
|
|
tester,
|
|
) async {
|
|
await _abrirEditor(tester);
|
|
|
|
expect(find.text(l10n.alarmRepeatSectionLabel), findsOneWidget);
|
|
final eyebrowY =
|
|
tester.getBottomLeft(find.text(l10n.alarmRepeatSectionLabel)).dy;
|
|
final circlesY =
|
|
tester
|
|
.getTopLeft(find.byKey(const ValueKey('alarm-weekday-circles')))
|
|
.dy;
|
|
expect(eyebrowY <= circlesY, isTrue);
|
|
});
|
|
|
|
testWidgets(
|
|
'8.6: the station field, volume, fade-in and vacation toggle share '
|
|
'ONE grouped card',
|
|
(tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
final tarjeta = find.byKey(const ValueKey('alarm-editor-grouped-card'));
|
|
expect(tarjeta, findsOneWidget);
|
|
expect(
|
|
find.descendant(
|
|
of: tarjeta,
|
|
matching: find.byKey(const ValueKey('alarm-station-field')),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: tarjeta, matching: find.byType(Slider)),
|
|
findsNWidgets(2),
|
|
reason: 'volume + fade-in sliders both live inside the card',
|
|
);
|
|
expect(
|
|
find.descendant(of: tarjeta, matching: find.byType(SwitchListTile)),
|
|
findsOneWidget,
|
|
reason: 'the vacation toggle lives inside the card',
|
|
);
|
|
// Snooze duration is a deliberate EXTRA (audit 8.8), kept OUTSIDE
|
|
// the prototype-mandated 4-row card.
|
|
expect(
|
|
find.descendant(
|
|
of: tarjeta,
|
|
matching: find.text(l10n.alarmSnoozeDurationTitle),
|
|
),
|
|
findsNothing,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('8.7: the volume row shows a live percentage label next to a '
|
|
'compact track', (tester) async {
|
|
await _abrirEditor(tester);
|
|
|
|
expect(find.text(l10n.alarmVolumeLabel), findsOneWidget);
|
|
expect(find.text('85%'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
group('WU8 — tarjeta de alarma simplificada', () {
|
|
testWidgets(
|
|
'tocar la tarjeta abre el editor precargado con los datos de esa '
|
|
'alarma',
|
|
(tester) async {
|
|
await _montarDosAlarmas(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('tarjeta-alarma-r2')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text(l10n.editAlarmTitle), findsOneWidget);
|
|
expect(find.text('Segunda'), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('deslizar la tarjeta pide confirmacion; cancelar la conserva y '
|
|
'confirmar la elimina', (tester) async {
|
|
final entorno = await _montarDosAlarmas(tester);
|
|
|
|
// Cancelar: la tarjeta se conserva.
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('tarjeta-alarma-r2')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(l10n.alarmDeleteConfirmTitle), findsOneWidget);
|
|
await tester.tap(find.text(l10n.cancelAction));
|
|
await tester.pumpAndSettle();
|
|
expect(entorno.estadoAlarmas.alarmas.length, 2);
|
|
expect(find.byKey(const ValueKey('tarjeta-alarma-r2')), findsOneWidget);
|
|
|
|
// Confirmar: la tarjeta se elimina.
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('tarjeta-alarma-r2')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(l10n.alarmDeleteConfirmTitle), findsOneWidget);
|
|
await tester.tap(find.text(l10n.deleteAction));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(entorno.estadoAlarmas.alarmas.length, 1);
|
|
expect(find.byKey(const ValueKey('tarjeta-alarma-r2')), findsNothing);
|
|
});
|
|
|
|
testWidgets(
|
|
'el banner destacado permite saltar la alarma mas proxima (accion '
|
|
'"Saltar")',
|
|
(tester) async {
|
|
final entorno = await _montarDosAlarmas(tester);
|
|
final proximaAntes = entorno.estadoAlarmas.proximaAlarma;
|
|
expect(proximaAntes?.id, 'r1');
|
|
|
|
await tester.tap(find.byKey(const ValueKey('hero-skip-next')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(SnackBar), findsOneWidget);
|
|
final r1 = entorno.estadoAlarmas.alarmas.firstWhere(
|
|
(a) => a.id == 'r1',
|
|
);
|
|
expect(r1.proximaEjecucion, isNot(proximaAntes!.proximaEjecucion));
|
|
},
|
|
);
|
|
|
|
testWidgets('la fila resumen de vacaciones empuja una pantalla al tocarla '
|
|
'(destino provisorio; WU9 lo reemplaza)', (tester) async {
|
|
await _montarDosAlarmas(tester);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('vacaciones-resumen')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byType(PluriPushScaffold), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'guardia de capacidad: editar (tap), saltar (hero) y eliminar (swipe) '
|
|
'siguen siendo alcanzables tras la simplificacion de la tarjeta',
|
|
(tester) async {
|
|
final entorno = await _montarDosAlarmas(tester);
|
|
|
|
// 1) Saltar, desde el banner destacado.
|
|
await tester.tap(find.byKey(const ValueKey('hero-skip-next')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(SnackBar), findsOneWidget);
|
|
|
|
// 2) Editar, tocando la tarjeta.
|
|
await tester.tap(find.byKey(const ValueKey('tarjeta-alarma-r2')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(l10n.editAlarmTitle), findsOneWidget);
|
|
await tester.tap(find.byIcon(Icons.close_rounded));
|
|
await tester.pumpAndSettle();
|
|
|
|
// 3) Eliminar, deslizando y confirmando.
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('tarjeta-alarma-r2')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text(l10n.deleteAction));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(entorno.estadoAlarmas.alarmas.length, 1);
|
|
},
|
|
);
|
|
});
|
|
}
|