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.
194 lines
6.4 KiB
Dart
194 lines
6.4 KiB
Dart
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('4–18 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,
|
||
);
|
||
},
|
||
);
|
||
});
|
||
}
|