Files
pluriwave/test/pantallas/pantalla_alarmas_tier5_test.dart
T
FreeTLab c7d137c82e fix(alarmas): New pill, banner layout, vacation pill, card recurrence
Audit 7.1 (t4:325): the "New" action is a solid brand-teal pill with a
plain add glyph -- was a tonal button with auto_awesome.

Audit 7.2 (t4:326-330): the next-alarm banner is warmCoral-tinted with
the Skip chip BESIDE the text on the same row -- was an opaque default
card with the skip action stacked below as an OutlinedButton.

Audit 7.3 (t4:332): the vacation row gains a trailing "d-d MON"
date-range pill for the active-or-next range, built from the existing
EstadoAlarmas.rangoVacacionesActivo/vacacionesProximas() accessors --
new formato_fechas.dart helper, no new state.

Audit 7.4 (t4:337-345): the alarm card now shows a recurrence label
next to the giant time (reusing the existing oneTimeOption/
dailyOption/weekdaysOption strings) and a themed station-icon slot
next to the station name. The real per-station favicon is NOT
rendered here -- same network-image hazard already documented for the
ringing screen's audit 9.2 (Emisora.favicon is a network URL;
Image.network hangs widget tests without a mocked HttpClient). The
custom switch shape (52x32/26px thumb) is also left as Switch.adaptive
-- a disclosed sub-gap, not a silent drop.

Item 7.6 (_AccesoDiagnostico, an Android-reliability debug row) is a
pre-approved addition not in the prototype -- informational only, no
action needed.
2026-07-30 16:30:20 +02:00

194 lines
6.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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/modelos/emisora.dart';
import 'package:pluriwave/pantallas/pantalla_alarmas.dart';
import 'package:pluriwave/servicios/servicio_alarmas.dart';
import 'package:pluriwave/tema/pluriwave_tokens.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../helpers/fakes.dart';
import '../helpers/fakes_alarmas.dart';
/// Tier 5 visual-fidelity closeout (items 7.1-7.4, audit id 2521) --
/// screens 4-14 batch.
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
Future<void> montar(
WidgetTester tester, {
required EstadoAlarmas estadoAlarmas,
EstadoRadio? radio,
}) async {
tester.view.physicalSize = const Size(1440, 3200);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
final estadoRadio =
radio ??
EstadoRadio(
audio: FakeServicioAudio(),
favoritos: FakeServicioFavoritos(),
radio: FakeServicioRadio(),
servicioEcualizador: FakeServicioEcualizador(),
servicioGrabacion: FakeServicioGrabacionRadioInactiva(),
iniciarAutomaticamente: false,
);
if (radio == null) addTearDown(estadoRadio.dispose);
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<EstadoRadio>.value(value: estadoRadio),
ChangeNotifierProvider<EstadoAlarmas>.value(value: estadoAlarmas),
],
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const Scaffold(body: PantallaAlarmas()),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
}
EstadoAlarmas crearEstado() {
final android = FakePuertoAlarmasAndroid();
final estado = EstadoAlarmas(
servicio: ServicioAlarmas(reloj: () => DateTime(2026, 6, 11, 6, 0)),
android: android,
iniciarAutomaticamente: false,
);
return estado;
}
group('visual fidelity (audit 7.1): "New" action', () {
testWidgets('is a solid brand-teal pill with an add glyph, not the tonal '
'auto_awesome button (t4:325)', (tester) async {
final estado = crearEstado();
addTearDown(estado.dispose);
await montar(tester, estadoAlarmas: estado);
expect(find.byIcon(Icons.auto_awesome_rounded), findsNothing);
expect(find.byIcon(Icons.add_rounded), findsOneWidget);
final l10n = lookupAppLocalizations(const Locale('en'));
final boton = tester.widget<FilledButton>(find.byType(FilledButton));
final resuelto = boton.style?.backgroundColor?.resolve(<WidgetState>{});
expect(resuelto, PluriWaveTokens.brand);
expect(find.text(l10n.createAlarmAction), findsOneWidget);
});
});
group('visual fidelity (audit 7.2): next-alarm banner', () {
testWidgets(
'is warmCoral-tinted and the Skip chip sits BESIDE the text, not '
'below it (t4:327-331)',
(tester) async {
final estado = crearEstado();
addTearDown(estado.dispose);
await estado.guardarAlarma(
const AlarmaMusical(
id: 'proxima',
nombre: 'Despertar',
hora: 7,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
),
);
await montar(tester, estadoAlarmas: estado);
final l10n = lookupAppLocalizations(const Locale('en'));
final banner = tester.widget<DecoratedBox>(
find.byKey(const ValueKey('next-alarm-banner')),
);
final decoration = banner.decoration as BoxDecoration;
expect(
decoration.color,
const Color(0xFFF4B860).withValues(alpha: 0.13),
);
final skipY = tester.getCenter(find.text(l10n.alarmHeroSkipAction)).dy;
final titleY = tester.getCenter(find.byIcon(Icons.alarm_on)).dy;
expect(
(skipY - titleY).abs() < 4,
isTrue,
reason: 'the Skip chip sits on the SAME row as the icon/text',
);
},
);
});
group('visual fidelity (audit 7.3): vacation row date pill', () {
testWidgets('shows the upcoming range as a "d-d MON" pill (t4:332)', (
tester,
) async {
final estado = crearEstado();
addTearDown(estado.dispose);
await estado.crearRangoVacaciones(
estado.servicio.crearRangoVacaciones(
inicio: DateTime(2026, 8, 4),
fin: DateTime(2026, 8, 18),
nombre: 'Summer',
),
);
await montar(tester, estadoAlarmas: estado);
expect(find.text('418 AUG'), findsOneWidget);
});
});
group('visual fidelity (audit 7.4): alarm card', () {
testWidgets(
'shows a recurrence label next to the time and a station icon next '
'to the station name (t4:337-345)',
(tester) async {
final estado = crearEstado();
addTearDown(estado.dispose);
await estado.guardarAlarma(
const AlarmaMusical(
id: 'tarjeta',
nombre: 'Despertar',
hora: 7,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: [],
emisora: Emisora(
uuid: 'e1',
nombre: 'Radio Uno',
url: 'https://radio.example/stream',
),
),
);
await montar(tester, estadoAlarmas: estado);
final l10n = lookupAppLocalizations(const Locale('en'));
expect(
find.descendant(
of: find.byKey(const ValueKey('tarjeta-alarma-tarjeta')),
matching: find.text(l10n.dailyOption),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byKey(const ValueKey('tarjeta-alarma-tarjeta')),
matching: find.byKey(const ValueKey('tarjeta-alarma-arte')),
),
findsOneWidget,
);
},
);
});
}