showPluriSleepTimerSheet already had a working countdown branch (ServicioTimer.tiempoRestanteStream), but every preset and the custom duration flow popped the sheet immediately after starting the timer -- so the countdown never rendered in the primary flow, only if the user happened to reopen the sheet afterwards. Stop popping the sheet on start; the existing Consumer<EstadoRadio> already reacts to iniciarTimerDuracion's notifyListeners and swaps to the countdown view live. Also make the sheet scroll-controlled: at a realistic phone width the countdown's title + description + headline- sized remaining-time text overflowed the default half-screen cap that never mattered while the sheet always closed before that view could render.
185 lines
6.5 KiB
Dart
185 lines
6.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/estado/estado_radio.dart';
|
|
import 'package:pluriwave/l10n/gen/app_localizations.dart';
|
|
import 'package:pluriwave/widgets/pluri_sleep_timer_sheet.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import '../helpers/fakes.dart';
|
|
import '../helpers/fakes_alarmas.dart';
|
|
|
|
/// Issue 2 (feedback-pruebas): "Timer de sueño" opened from Escuchar (and
|
|
/// every other root header) showed a bottom sheet with no visible
|
|
/// countdown. `ServicioTimer.tiempoRestanteStream`/`tiempoRestante` already
|
|
/// existed and this sheet already had a `StreamBuilder` countdown branch —
|
|
/// but every preset/custom-duration action popped the sheet immediately
|
|
/// after starting the timer, so the countdown never had a chance to render
|
|
/// in the primary flow. Zero test coverage existed for this file before.
|
|
EstadoRadio _estado() => EstadoRadio(
|
|
audio: FakeServicioAudio(),
|
|
favoritos: FakeServicioFavoritos(),
|
|
radio: FakeServicioRadio(),
|
|
servicioEcualizador: FakeServicioEcualizador(),
|
|
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
|
|
iniciarAutomaticamente: false,
|
|
);
|
|
|
|
Widget _host(EstadoRadio estado) {
|
|
return ChangeNotifierProvider<EstadoRadio>.value(
|
|
value: estado,
|
|
child: MaterialApp(
|
|
locale: const Locale('es'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder:
|
|
(context) => TextButton(
|
|
onPressed: () => showPluriSleepTimerSheet(context),
|
|
child: const Text('abrir'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
setUp(() {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
// The default 800x600 test viewport is shorter than a typical phone —
|
|
// same fix `pantalla_reproductor_test.dart`/`pantalla_inicio_test.dart`
|
|
// already established for other bottom-sheet/full-bleed content.
|
|
void ajustarSuperficieRealista(WidgetTester tester) {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
}
|
|
|
|
testWidgets(
|
|
'selecting a preset keeps the sheet open and switches it to the live '
|
|
'remaining-time countdown, instead of closing with no feedback',
|
|
(tester) async {
|
|
ajustarSuperficieRealista(tester);
|
|
final estado = _estado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_host(estado));
|
|
await tester.tap(find.text('abrir'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = AppLocalizations.of(tester.element(find.text('abrir')));
|
|
|
|
expect(estado.timer.activo, isFalse);
|
|
expect(find.byType(ActionChip), findsWidgets);
|
|
|
|
final chip = tester.widget<ActionChip>(
|
|
find.widgetWithText(ActionChip, l10n.durationMinutesOnly(5)),
|
|
);
|
|
chip.onPressed!();
|
|
await tester.pump();
|
|
|
|
expect(estado.timer.activo, isTrue);
|
|
expect(
|
|
find.byType(ActionChip),
|
|
findsNothing,
|
|
reason: 'issue 2: the picker is replaced by the countdown view',
|
|
);
|
|
expect(find.text(l10n.cancelTimer), findsOneWidget);
|
|
expect(
|
|
find.text(l10n.durationMinutesOnly(5)),
|
|
findsOneWidget,
|
|
reason: 'issue 2: the remaining time is now surfaced live',
|
|
);
|
|
|
|
// `ServicioTimer` starts a REAL periodic Timer -- `addTearDown`
|
|
// callbacks run too late to satisfy the "no pending timers" check
|
|
// (a well-known flutter_test ordering quirk), so it must be
|
|
// cancelled here, inside the test body, before it ends.
|
|
await estado.timer.cancelar();
|
|
},
|
|
);
|
|
|
|
testWidgets('reopening the sheet while a timer is already active shows the '
|
|
'countdown immediately, not the duration picker', (tester) async {
|
|
ajustarSuperficieRealista(tester);
|
|
final estado = _estado();
|
|
addTearDown(estado.dispose);
|
|
estado.iniciarTimerDuracion(const Duration(minutes: 10));
|
|
|
|
await tester.pumpWidget(_host(estado));
|
|
await tester.tap(find.text('abrir'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = AppLocalizations.of(tester.element(find.text('abrir')));
|
|
expect(find.byType(ActionChip), findsNothing);
|
|
expect(find.text(l10n.cancelTimer), findsOneWidget);
|
|
expect(find.text(l10n.durationMinutesOnly(10)), findsOneWidget);
|
|
|
|
// See the comment in the previous test — cancel before the body ends.
|
|
await estado.timer.cancelar();
|
|
});
|
|
|
|
testWidgets(
|
|
'cancelling from the countdown view stops the timer and closes the '
|
|
'sheet',
|
|
(tester) async {
|
|
ajustarSuperficieRealista(tester);
|
|
final estado = _estado();
|
|
addTearDown(estado.dispose);
|
|
estado.iniciarTimerDuracion(const Duration(minutes: 10));
|
|
|
|
await tester.pumpWidget(_host(estado));
|
|
await tester.tap(find.text('abrir'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = AppLocalizations.of(tester.element(find.text('abrir')));
|
|
final boton = tester.widget<FilledButton>(
|
|
find.widgetWithText(FilledButton, l10n.cancelTimer),
|
|
);
|
|
boton.onPressed!();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(estado.timer.activo, isFalse);
|
|
expect(find.text(l10n.cancelTimer), findsNothing);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'starting a custom duration ALSO keeps the sheet open on the countdown '
|
|
'view, not just the presets',
|
|
(tester) async {
|
|
ajustarSuperficieRealista(tester);
|
|
final estado = _estado();
|
|
addTearDown(estado.dispose);
|
|
|
|
await tester.pumpWidget(_host(estado));
|
|
await tester.tap(find.text('abrir'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final l10n = AppLocalizations.of(tester.element(find.text('abrir')));
|
|
await tester.tap(find.text(l10n.optionOther));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Confirm the custom-duration sub-sheet directly (default prefilled
|
|
// value is already "15" minutes -- see _TimerPersonalizadoSheetState).
|
|
final confirmar = tester.widget<FilledButton>(
|
|
find.widgetWithText(FilledButton, l10n.startTimer),
|
|
);
|
|
confirmar.onPressed!();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(estado.timer.activo, isTrue);
|
|
expect(find.byType(ActionChip), findsNothing);
|
|
expect(find.text(l10n.cancelTimer), findsOneWidget);
|
|
|
|
// See the comment in the first test — cancel before the body ends.
|
|
await estado.timer.cancelar();
|
|
},
|
|
);
|
|
}
|