Vacaciones ranges could be created but never edited or removed -- EstadoAlarmas already had crearRangoVacaciones/eliminarRangoVacaciones with no UI affordance reaching them, and no update path at all. Add EstadoAlarmas.editarRangoVacaciones and wire tap-to-edit / swipe-to-delete (with confirmation) onto every range card, mirroring the alarm list's own Dismissible + confirm-dialog pattern exactly. This covers the active-range hero too: a freshly created range is active immediately and only ever renders there, never in the scheduled/past lists, so it needed the same affordances or a user's very first range could never be fixed.
618 lines
20 KiB
Dart
618 lines
20 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_alarmas.dart';
|
|
import 'package:pluriwave/l10n/formato_fechas.dart';
|
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
|
import 'package:pluriwave/modelos/alarma_musical.dart';
|
|
import 'package:pluriwave/pantallas/pantalla_vacaciones.dart';
|
|
import 'package:pluriwave/servicios/servicio_alarmas.dart';
|
|
import 'package:pluriwave/widgets/pluri_push_scaffold.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes_alarmas.dart';
|
|
|
|
/// WU9, `alarm-vacation-ranges` spec — the real Vacaciones manager screen
|
|
/// that WU8's summary row pushes to (replacing WU8's own temporary
|
|
/// placeholder). Covers: active-range hero (name + days-remaining countdown
|
|
/// + per-alarm impact line), the "PROGRAMADOS" upcoming list, the "Añadir
|
|
/// rango" CTA, and the "Rangos pasados" history section.
|
|
///
|
|
/// Clock note: `PantallaVacaciones` calls `EstadoAlarmas.rangoVacacionesActivo()`
|
|
/// etc. with NO `ahora` argument (production code always uses real
|
|
/// `DateTime.now()` — design ADR-6 explicitly rejects a testable Clock
|
|
/// abstraction as out of scope for this redesign). So fixtures here are
|
|
/// built RELATIVE to `DateTime.now()`, never as hardcoded calendar dates —
|
|
/// the `ServicioAlarmas`'s own `reloj` (used only for alarm SCHEDULING
|
|
/// maths, irrelevant to the vacation-query assertions below) is left at its
|
|
/// real-clock default too, for consistency.
|
|
DateTime get _hoy => DateTime.now();
|
|
DateTime get _hoyDia => DateTime(_hoy.year, _hoy.month, _hoy.day);
|
|
|
|
Future<EstadoAlarmas> _crearEstado({
|
|
List<AlarmaMusical> alarmas = const [],
|
|
List<RangoVacaciones> vacaciones = const [],
|
|
}) async {
|
|
final android = FakePuertoAlarmasAndroid();
|
|
final estado = EstadoAlarmas(
|
|
servicio: ServicioAlarmas(),
|
|
android: android,
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
for (final alarma in alarmas) {
|
|
await estado.guardarAlarma(alarma);
|
|
}
|
|
if (vacaciones.isNotEmpty) {
|
|
await estado.guardarVacaciones(vacaciones);
|
|
}
|
|
return estado;
|
|
}
|
|
|
|
Widget _buildScreen(EstadoAlarmas estado) {
|
|
return ChangeNotifierProvider<EstadoAlarmas>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const PantallaVacaciones(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _pumpEstable(WidgetTester tester) async {
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
testWidgets(
|
|
'renders inside a PluriPushScaffold; con un rango activo muestra su '
|
|
'nombre, los dias restantes, y la linea de impacto por alarma',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
alarmas: [
|
|
const AlarmaMusical(
|
|
id: 'p1',
|
|
nombre: 'Pausada 1',
|
|
hora: 7,
|
|
minuto: 30,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
sonarEnVacaciones: false,
|
|
),
|
|
const AlarmaMusical(
|
|
id: 'p2',
|
|
nombre: 'Pausada 2',
|
|
hora: 13,
|
|
minuto: 45,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
sonarEnVacaciones: false,
|
|
),
|
|
const AlarmaMusical(
|
|
id: 'n1',
|
|
nombre: 'Sigue',
|
|
hora: 8,
|
|
minuto: 15,
|
|
tipoProgramacion: TipoProgramacionAlarma.diaria,
|
|
diasSemana: [],
|
|
),
|
|
],
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'v1',
|
|
nombre: 'Julio activo',
|
|
inicio: _hoyDia.subtract(const Duration(days: 3)),
|
|
fin: _hoyDia.add(const Duration(days: 5)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.byType(PluriPushScaffold), findsOneWidget);
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text('Julio activo'), findsOneWidget);
|
|
expect(find.text(l10n.vacationSummaryActiveCountdown(5)), findsOneWidget);
|
|
expect(
|
|
find.text(l10n.vacationImpactPausedLabel('07:30, 13:45')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.text(l10n.vacationImpactContinuesLabel('08:15')),
|
|
findsOneWidget,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('sin rango activo, muestra la pista en su lugar', (tester) async {
|
|
final estado = await _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text(l10n.vacationNoActiveRangeHint), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('los rangos futuros aparecen bajo el encabezado PROGRAMADOS', (
|
|
tester,
|
|
) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'f1',
|
|
nombre: 'Rango lejano',
|
|
inicio: _hoyDia.add(const Duration(days: 120)),
|
|
fin: _hoyDia.add(const Duration(days: 125)),
|
|
),
|
|
RangoVacaciones(
|
|
id: 'f2',
|
|
nombre: 'Rango cercano',
|
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
|
fin: _hoyDia.add(const Duration(days: 25)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text(l10n.vacationUpcomingSectionTitle), findsOneWidget);
|
|
expect(find.text('Rango lejano'), findsOneWidget);
|
|
expect(find.text('Rango cercano'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'los rangos pasados aparecen bajo el encabezado "Rangos pasados" tras '
|
|
'expandir la fila (audit 9b.7: colapsada por defecto, t4:489)',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'pa1',
|
|
nombre: 'Rango viejo',
|
|
inicio: _hoyDia.subtract(const Duration(days: 30)),
|
|
fin: _hoyDia.subtract(const Duration(days: 20)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text(l10n.vacationPastSectionTitle), findsOneWidget);
|
|
|
|
await tester.tap(find.text(l10n.vacationPastSectionTitle));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.text('Rango viejo'), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('el CTA "Añadir rango" abre el formulario de alta existente', (
|
|
tester,
|
|
) async {
|
|
final estado = await _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
await tester.tap(find.text(l10n.addVacationRangeCta));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.text(l10n.newVacationRangeTitle), findsOneWidget);
|
|
});
|
|
|
|
// Item 21 / audit 9b.4-9b.5 (t4:451-462, 469-479): the screen's signature
|
|
// element is a start/end date pair, never a determinate progress bar.
|
|
testWidgets(
|
|
'el rango activo muestra el par de fechas, no una barra de progreso',
|
|
(tester) async {
|
|
final inicio = _hoyDia.subtract(const Duration(days: 3));
|
|
final fin = _hoyDia.add(const Duration(days: 5));
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'v1',
|
|
nombre: 'Julio activo',
|
|
inicio: inicio,
|
|
fin: fin,
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(
|
|
find.byType(LinearProgressIndicator),
|
|
findsNothing,
|
|
reason:
|
|
't4:451-462 replaces the progress bar with a start/end date '
|
|
'pair — the screen\'s signature element',
|
|
);
|
|
|
|
expect(find.text(diaMesLocalizado('es', inicio)), findsOneWidget);
|
|
expect(find.text(diaMesLocalizado('es', fin)), findsOneWidget);
|
|
expect(
|
|
find.text(nombreDiaSemanaLocalizado('es', inicio)),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.text(nombreDiaSemanaLocalizado('es', fin)), findsOneWidget);
|
|
|
|
final fechaTexto = tester.widget<Text>(
|
|
find.text(diaMesLocalizado('es', inicio)),
|
|
);
|
|
expect(
|
|
fechaTexto.style?.fontSize,
|
|
26,
|
|
reason: 't4:459 the date digits are 26px/w800/ls-1',
|
|
);
|
|
expect(fechaTexto.style?.fontWeight, FontWeight.w800);
|
|
|
|
final diaSemanaTexto = tester.widget<Text>(
|
|
find.text(nombreDiaSemanaLocalizado('es', inicio)),
|
|
);
|
|
expect(
|
|
diaSemanaTexto.style?.fontSize,
|
|
11,
|
|
reason: 't4:459 the weekday caption is 11px/w700/50%',
|
|
);
|
|
expect(diaSemanaTexto.style?.fontWeight, FontWeight.w700);
|
|
|
|
final regla = tester.widget<DecoratedBox>(
|
|
find.byKey(const ValueKey('vacaciones-regla-activo')),
|
|
);
|
|
final decoration = regla.decoration as BoxDecoration;
|
|
expect(
|
|
decoration.gradient,
|
|
isNotNull,
|
|
reason:
|
|
't4:460 the active range connector is a brand-teal gradient, '
|
|
'not the flat rgba(255,255,255,.14) used by scheduled rows',
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'un rango programado usa el mismo par de fechas, con una regla plana y '
|
|
'una fila de etiqueta con el nombre del rango',
|
|
(tester) async {
|
|
final inicio = _hoyDia.add(const Duration(days: 20));
|
|
final fin = _hoyDia.add(const Duration(days: 25));
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(id: 'f2', nombre: 'Verano', inicio: inicio, fin: fin),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(
|
|
find.descendant(
|
|
of: find.byType(PantallaVacaciones),
|
|
matching: find.byType(ListTile),
|
|
),
|
|
findsNothing,
|
|
reason: '9b.5 (t4:469-479) replaces the ListTile row with a card',
|
|
);
|
|
expect(find.text(diaMesLocalizado('es', inicio)), findsOneWidget);
|
|
expect(find.text(diaMesLocalizado('es', fin)), findsOneWidget);
|
|
expect(
|
|
find.text('Verano'),
|
|
findsOneWidget,
|
|
reason:
|
|
't4:478 the label row shows the range name next to a '
|
|
'"label" icon',
|
|
);
|
|
|
|
final regla = tester.widget<DecoratedBox>(
|
|
find.byKey(const ValueKey('vacaciones-regla-f2')),
|
|
);
|
|
final decoration = regla.decoration as BoxDecoration;
|
|
expect(decoration.gradient, isNull);
|
|
expect(
|
|
decoration.color,
|
|
Colors.white.withValues(alpha: 0.14),
|
|
reason:
|
|
't4:474 scheduled/past rows use a flat rgba(255,255,255,.14) '
|
|
'connector, not the active gradient',
|
|
);
|
|
},
|
|
);
|
|
|
|
group('visual fidelity (audit 9b.1/9b.2/9b.6/9b.7)', () {
|
|
testWidgets(
|
|
'9b.1: a header "Add" action is reachable and opens the same form '
|
|
'as the CTA (t4:446)',
|
|
(tester) async {
|
|
final estado = await _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
final appBar = tester.widget<AppBar>(find.byType(AppBar));
|
|
expect(appBar.actions, isNotNull);
|
|
expect(appBar.actions, isNotEmpty);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('vacation-add-header')));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.text(l10n.newVacationRangeTitle), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'9b.2: the explanatory info banner is always visible (t4:448)',
|
|
(tester) async {
|
|
final estado = await _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text(l10n.vacationExplainerBanner), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'9b.6: the bottom CTA has a dashed border and a date_range icon '
|
|
'(t4:487)',
|
|
(tester) async {
|
|
final estado = await _crearEstado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.byIcon(Icons.date_range_rounded), findsOneWidget);
|
|
// Exactly ONE add_rounded on the whole screen -- the header's OWN
|
|
// "Add" action (audit 9b.1). The bottom CTA no longer uses it.
|
|
expect(find.byIcon(Icons.add_rounded), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'9b.7: "Rangos pasados" is collapsed by default, showing a count, '
|
|
'and expands on tap (t4:489)',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'pa1',
|
|
nombre: 'Rango viejo',
|
|
inicio: _hoyDia.subtract(const Duration(days: 30)),
|
|
fin: _hoyDia.subtract(const Duration(days: 20)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
expect(find.text(l10n.vacationPastSectionTitle), findsOneWidget);
|
|
expect(find.text('1'), findsOneWidget);
|
|
expect(
|
|
find.text('Rango viejo'),
|
|
findsNothing,
|
|
reason:
|
|
'collapsed by default -- matches the prototype count-only row',
|
|
);
|
|
|
|
await tester.tap(find.text(l10n.vacationPastSectionTitle));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.text('Rango viejo'), findsOneWidget);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('issue 1 (feedback-pruebas): editar y eliminar rangos', () {
|
|
testWidgets(
|
|
'tocar la tarjeta de un rango programado abre el editor precargado '
|
|
'con su nombre y fechas',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'f2',
|
|
nombre: 'Verano',
|
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
|
fin: _hoyDia.add(const Duration(days: 25)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2')));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
|
// Scoped to the TextField specifically -- the original card's OWN
|
|
// "Verano" label is still (offstage, behind the modal) in the tree,
|
|
// so a bare `find.text('Verano')` would ambiguously match both.
|
|
expect(find.widgetWithText(TextField, 'Verano'), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'guardar el editor abierto por tap actualiza el rango existente (no '
|
|
'crea uno nuevo)',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'f2',
|
|
nombre: 'Verano',
|
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
|
fin: _hoyDia.add(const Duration(days: 25)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2')));
|
|
await _pumpEstable(tester);
|
|
|
|
await tester.enterText(find.byType(TextField), 'Verano renombrado');
|
|
final boton = tester.widget<FilledButton>(
|
|
find.widgetWithText(FilledButton, l10n.saveRangeAction),
|
|
);
|
|
boton.onPressed!();
|
|
await _pumpEstable(tester);
|
|
|
|
expect(estado.vacaciones, hasLength(1));
|
|
expect(estado.vacaciones.single.id, 'f2');
|
|
expect(estado.vacaciones.single.nombre, 'Verano renombrado');
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'deslizar la tarjeta de un rango pide confirmacion; cancelar la '
|
|
'conserva y confirmar la elimina',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'f2',
|
|
nombre: 'Verano',
|
|
inicio: _hoyDia.add(const Duration(days: 20)),
|
|
fin: _hoyDia.add(const Duration(days: 25)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
|
|
// Cancelar: el rango se conserva.
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('vacaciones-tarjeta-f2')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await _pumpEstable(tester);
|
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
|
await tester.tap(find.text(l10n.cancelAction));
|
|
await _pumpEstable(tester);
|
|
expect(estado.vacaciones, hasLength(1));
|
|
|
|
// Confirmar: el rango se elimina.
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('vacaciones-tarjeta-f2')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await _pumpEstable(tester);
|
|
expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget);
|
|
await tester.tap(find.text(l10n.deleteAction));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(estado.vacaciones, isEmpty);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'el rango ACTIVO (mostrado en el hero) tambien se puede editar (tap) '
|
|
'y eliminar (swipe) -- un rango recien creado siempre esta activo y '
|
|
'nunca aparece en las listas programado/pasado',
|
|
(tester) async {
|
|
final estado = await _crearEstado(
|
|
vacaciones: [
|
|
RangoVacaciones(
|
|
id: 'v1',
|
|
nombre: 'Julio activo',
|
|
inicio: _hoyDia.subtract(const Duration(days: 3)),
|
|
fin: _hoyDia.add(const Duration(days: 5)),
|
|
),
|
|
],
|
|
);
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_buildScreen(estado));
|
|
await _pumpEstable(tester);
|
|
final l10n = AppLocalizations.of(
|
|
tester.element(find.byType(PantallaVacaciones)),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-v1')));
|
|
await _pumpEstable(tester);
|
|
expect(find.text(l10n.editVacationRangeTitle), findsOneWidget);
|
|
// Scoped to the TextField specifically -- the hero's OWN "Julio
|
|
// activo" label is still (offstage, behind the modal) in the tree.
|
|
expect(find.widgetWithText(TextField, 'Julio activo'), findsOneWidget);
|
|
// Dismiss the editor sheet (no explicit close button -- same as the
|
|
// pre-existing "Anadir rango" sheet, dismissible via the standard
|
|
// modal-bottom-sheet Navigator.pop) before interacting with the
|
|
// list underneath it.
|
|
Navigator.of(tester.element(find.byType(PantallaVacaciones))).pop();
|
|
await _pumpEstable(tester);
|
|
|
|
await tester.drag(
|
|
find.byKey(const ValueKey('vacaciones-tarjeta-v1')),
|
|
const Offset(-600, 0),
|
|
);
|
|
await _pumpEstable(tester);
|
|
await tester.tap(find.text(l10n.deleteAction));
|
|
await _pumpEstable(tester);
|
|
|
|
expect(estado.vacaciones, isEmpty);
|
|
expect(find.text(l10n.vacationNoActiveRangeHint), findsOneWidget);
|
|
},
|
|
);
|
|
});
|
|
}
|