The prototype's order is pill (t4:415-416), then 7:30 at 88px (t4:417), then "Lunes, 3 de agosto" at 14px (t4:419). An earlier pass rendered the date between the pill and the time and cited "t4 line 419" as its justification -- but that line number is where the date SITS in the source, which is exactly why it comes last. Both the code and the test encoded the same misreading, so the test passed while the screen was wrong.
214 lines
7.4 KiB
Dart
214 lines
7.4 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 BELOW the '
|
|
'hero time (t4:415-419: pill, then 7:30, then the date)', (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, then the hero time, then the date line. This test used
|
|
// to assert date-before-time and cited "t4:419" for it — but 419 is
|
|
// simply the source line the date occupies, and in the prototype it
|
|
// comes AFTER the 88px time on line 417. The citation refuted the
|
|
// assertion it was supporting.
|
|
final pillY =
|
|
tester
|
|
.getBottomLeft(find.byKey(const ValueKey('ringing-schedule-pill')))
|
|
.dy;
|
|
final timeY =
|
|
tester
|
|
.getBottomLeft(find.byKey(const ValueKey('ringing-hero-time')))
|
|
.dy;
|
|
final dateY = tester.getTopLeft(find.text(esperado)).dy;
|
|
expect(pillY <= timeY, isTrue, reason: 'pill sits above the time');
|
|
expect(timeY <= dateY, isTrue, reason: 'the date sits below the time');
|
|
|
|
// 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',
|
|
);
|
|
},
|
|
);
|
|
}
|