Files
pluriwave/test/pantallas/pantalla_alarma_sonando_tier5_test.dart
T
FreeTLab 93b7ec2af9 fix(alarma-sonando): make the snooze block's vertical gaps uniform
Issue 3 (feedback-pruebas): t4:427 wraps the POSPONER eyebrow, the
snooze tiles and the Stop button in a single flex column with a
uniform gap:12 -- this screen carried a 10/14 pair instead, matching
neither the prototype nor each other.

The dismiss-guard test (protected, untouched) only asserts behaviour
via find.text/find.byType, so this pure value change is safe against
it -- re-verified empty diff after this commit.
2026-07-30 22:12:24 +02:00

208 lines
7.1 KiB
Dart

import 'dart:async';
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/formato_fechas.dart';
import 'package:pluriwave/l10n/gen/app_localizations.dart';
import 'package:pluriwave/modelos/alarma_musical.dart';
import 'package:pluriwave/modelos/emisora.dart';
import 'package:pluriwave/pantallas/pantalla_alarma_sonando.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:pluriwave/servicios/servicio_audio.dart';
import 'package:pluriwave/tema/pluriwave_theme.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart';
import '../helpers/fakes_alarmas.dart';
/// Closing batch (items 9.4, 9.10, audit id 2521) -- the ringing screen.
/// Mirrors `pantalla_alarma_sonando_tier4_test.dart`'s own harness (kept as
/// a local copy, not a shared import, matching this file's established
/// precedent of small helper duplication over cross-test-file coupling).
Future<void> _montarPantalla(WidgetTester tester) async {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final audio = FakeServicioAudio();
audio.emitirEstado(EstadoReproduccion.reproduciendo);
final radio = EstadoRadio(
audio: audio,
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, 7, 0)),
android: android,
iniciarAutomaticamente: false,
);
addTearDown(estadoAlarmas.dispose);
addTearDown(android.dispose);
await estadoAlarmas.guardarAlarma(
const AlarmaMusical(
id: 'tier5-1',
nombre: 'Despertar',
hora: 7,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
snoozeMinutos: 10,
emisora: Emisora(
uuid: 'e1',
nombre: 'Radio Uno',
url: 'https://radio.example/stream',
),
),
);
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,
theme: PluriWaveTheme.dark(),
home: const SizedBox.shrink(),
),
),
);
final navigator = tester.state<NavigatorState>(find.byType(Navigator));
unawaited(
navigator.push(
MaterialPageRoute<void>(
builder:
(_) => PantallaAlarmaSonando(alarma: estadoAlarmas.alarmas.single),
fullscreenDialog: true,
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
}
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
testWidgets('visual fidelity (audit 9.4): the date line renders between the '
'schedule pill and the hero time (t4:419)', (tester) async {
await _montarPantalla(tester);
final localeTag =
Localizations.localeOf(
tester.element(find.byType(PantallaAlarmaSonando)),
).toString();
final esperado = fechaLargaConDiaSemana(localeTag, DateTime.now());
expect(find.text(esperado), findsOneWidget);
// Order: pill above the date line, date line above the hero time.
final pillY =
tester
.getBottomLeft(find.byKey(const ValueKey('ringing-schedule-pill')))
.dy;
final dateY = tester.getTopLeft(find.text(esperado)).dy;
final timeY =
tester.getTopLeft(find.byKey(const ValueKey('ringing-hero-time'))).dy;
expect(pillY <= dateY, isTrue);
expect(dateY <= timeY, isTrue);
// Regression guard: pumpAndSettle must still complete (purely
// additive static text, no new animation).
await tester.pumpAndSettle();
});
testWidgets(
'visual fidelity (audit 9.10): the highlighted snooze tile carries a '
'"usual" qualifier ADDITIVELY -- the original flat label the dismiss '
'guard finds by text stays fully intact',
(tester) async {
await _montarPantalla(tester);
final l10n = AppLocalizations.of(
tester.element(find.byType(PantallaAlarmaSonando)),
);
// The ORIGINAL flat label must still resolve to exactly one Text --
// this IS the dismiss-guard's own finder contract.
expect(find.text(l10n.alarmSnoozeOptionLabel(10)), findsOneWidget);
// The additive qualifier renders too, but only on the destacado tile.
expect(find.text(l10n.alarmSnoozeUsualLabel), findsOneWidget);
expect(find.text(l10n.alarmSnoozeOptionLabel(3)), findsOneWidget);
expect(find.text(l10n.alarmSnoozeOptionLabel(5)), findsOneWidget);
// Regression guard: the exact hazard 9.9/9.10 share -- pumpAndSettle
// must still complete, and the flat label must remain the SAME
// widget the dismiss guard taps (still inside a FilledButton).
expect(
find.ancestor(
of: find.text(l10n.alarmSnoozeOptionLabel(10)),
matching: find.byType(FilledButton),
),
findsOneWidget,
);
await tester.pumpAndSettle();
},
);
testWidgets(
'Issue 3 (feedback-pruebas): the gap above the snooze tiles matches the '
'gap below them (t4:427 draws a uniform gap:12 flex column) -- the '
'previous 10/14 pair matched neither the prototype nor each other',
(tester) async {
await _montarPantalla(tester);
final l10n = AppLocalizations.of(
tester.element(find.byType(PantallaAlarmaSonando)),
);
final eyebrowBottom =
tester
.getBottomLeft(
find
.ancestor(
of: find.byIcon(Icons.snooze_rounded),
matching: find.byType(Row),
)
.first,
)
.dy;
final tileDestacado = find.ancestor(
of: find.text(l10n.alarmSnoozeOptionLabel(10)),
matching: find.byType(FilledButton),
);
final tilesTop = tester.getTopLeft(tileDestacado).dy;
final tilesBottom = tester.getBottomLeft(tileDestacado).dy;
final stopButtonTop =
tester
.getTopLeft(find.byKey(const ValueKey('ringing-stop-button')))
.dy;
expect(
tilesTop - eyebrowBottom,
12,
reason: 't4:427: gap:12 above the snooze tiles',
);
expect(
stopButtonTop - tilesBottom,
12,
reason: 't4:427: gap:12 below the snooze tiles, same as above',
);
},
);
}