Audit 10.2 (t4:511): GrupoAjustes's eyebrow now sits OUTSIDE the PluriGlassSurface, at title-tier (20px) padding -- it used to share the card's own 16px content padding. Audit 10.5 (t4:514/526): FilaAjuste gains an optional iconColor. Wired on the two rows the prototype actually accents: AUDIO's Ecualizador (brand cyan) and STATIONS' Grupos de favoritos (warmCoral). Item 10.10 (two extra AUDIO rows -- "Calidad de streaming" and "Reproduccion sin interrupciones") is NOT implemented: neither concept has any backing state or service anywhere in this app today. Building them for real means inventing two new persisted settings, and a genuine streaming-quality preference would plausibly need to touch the protected servicio_audio.dart to actually affect playback -- out of scope for a visual-fidelity pass. Decorative rows that open nothing would be dead UI, which is worse than leaving the gap disclosed.
204 lines
5.8 KiB
Dart
204 lines
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pluriwave/pantallas/ajustes/widgets/fila_ajuste.dart';
|
|
import 'package:pluriwave/tema/pluriwave_theme.dart';
|
|
import 'package:pluriwave/widgets/pluri_glass_surface.dart';
|
|
|
|
/// S8 (Tier 1 visual fidelity): the prototype puts a trailing "current
|
|
/// value" on nearly every settings row, 13px `rgba(242,247,250,.55)` (t4
|
|
/// lines 512-539, 625 — "Voz clara", "Alta", "3 guardados", "Alfabético",
|
|
/// "7 · 84 MB", "Español", "Hoy, 08:12", "200 MB"). `FilaAjuste` used to
|
|
/// accept only `icon`/`titulo`/`onTap` — no value slot at all.
|
|
/// Pre-existing project constraint (see `pantalla_ajustes_test.dart`):
|
|
/// `PluriGlassSurface` paints a background over `ListTile`'s ink layer,
|
|
/// which Flutter flags as a warning-level assertion, not a correctness bug.
|
|
/// Only needed by the `GrupoAjustes` test below — the bare `FilaAjuste`
|
|
/// tests above never wrap a `ListTile` inside that surface.
|
|
void _suppressListTileInkAssertion() {
|
|
final original = FlutterError.onError;
|
|
FlutterError.onError = (details) {
|
|
if (details.exceptionAsString().contains(
|
|
'ListTile background color or ink splashes may be invisible',
|
|
)) {
|
|
return;
|
|
}
|
|
original?.call(details);
|
|
};
|
|
addTearDown(() => FlutterError.onError = original);
|
|
}
|
|
|
|
void main() {
|
|
Widget host(Widget child) {
|
|
return MaterialApp(
|
|
theme: PluriWaveTheme.dark(),
|
|
home: Scaffold(body: child),
|
|
);
|
|
}
|
|
|
|
testWidgets('renders the trailing value before the chevron when provided', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
host(
|
|
FilaAjuste(
|
|
icon: Icons.language_rounded,
|
|
titulo: 'Language',
|
|
valor: 'English',
|
|
onTap: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('English'), findsOneWidget);
|
|
expect(find.byIcon(Icons.chevron_right_rounded), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('renders no trailing value text when valor is omitted '
|
|
'(unchanged pre-S8 behaviour)', (tester) async {
|
|
await tester.pumpWidget(
|
|
host(
|
|
FilaAjuste(
|
|
icon: Icons.info_outline_rounded,
|
|
titulo: 'Info',
|
|
onTap: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Info'), findsOneWidget);
|
|
expect(find.byIcon(Icons.chevron_right_rounded), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 10.6/10.8/10.9): leading icon is 21px, the '
|
|
'title is 14px, and the chevron is 19px at 40% opacity (t4 lines '
|
|
'514-515)',
|
|
(tester) async {
|
|
await tester.pumpWidget(
|
|
host(
|
|
FilaAjuste(
|
|
icon: Icons.equalizer_rounded,
|
|
titulo: 'Ecualizador base',
|
|
onTap: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
final icon = tester.widget<Icon>(find.byIcon(Icons.equalizer_rounded));
|
|
expect(icon.size, 21);
|
|
|
|
final titulo = tester.widget<Text>(find.text('Ecualizador base'));
|
|
expect(titulo.style?.fontSize, 14);
|
|
|
|
final chevron = tester.widget<Icon>(
|
|
find.byIcon(Icons.chevron_right_rounded),
|
|
);
|
|
expect(chevron.size, 19);
|
|
expect(chevron.color, const Color(0xFFF2F7FA).withValues(alpha: 0.4));
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit S10): GrupoAjustes insets its row divider by '
|
|
'47px, not full-bleed (t4 line 516)',
|
|
(tester) async {
|
|
_suppressListTileInkAssertion();
|
|
await tester.pumpWidget(
|
|
host(
|
|
GrupoAjustes(
|
|
titulo: 'AUDIO',
|
|
filas: [
|
|
FilaAjuste(
|
|
icon: Icons.equalizer_rounded,
|
|
titulo: 'Uno',
|
|
onTap: () {},
|
|
),
|
|
FilaAjuste(icon: Icons.hd_rounded, titulo: 'Dos', onTap: () {}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final divider = tester.widget<Divider>(find.byType(Divider));
|
|
expect(divider.indent, 47);
|
|
},
|
|
);
|
|
|
|
testWidgets('visual fidelity (audit 10.5): an explicit iconColor tints the '
|
|
'leading icon (t4 lines 514/516/522 -- equalizer/hd/folder accents)', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
host(
|
|
FilaAjuste(
|
|
icon: Icons.equalizer_rounded,
|
|
titulo: 'Ecualizador base',
|
|
iconColor: const Color(0xFF21D4D9),
|
|
onTap: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
final icon = tester.widget<Icon>(find.byIcon(Icons.equalizer_rounded));
|
|
expect(icon.color, const Color(0xFF21D4D9));
|
|
});
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 10.5): omitting iconColor keeps the default '
|
|
'(unchanged pre-10.5 behaviour)',
|
|
(tester) async {
|
|
await tester.pumpWidget(
|
|
host(
|
|
FilaAjuste(
|
|
icon: Icons.language_rounded,
|
|
titulo: 'Idioma',
|
|
onTap: () {},
|
|
),
|
|
),
|
|
);
|
|
|
|
final icon = tester.widget<Icon>(find.byIcon(Icons.language_rounded));
|
|
expect(icon.color, isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'visual fidelity (audit 10.2): the group eyebrow sits OUTSIDE the '
|
|
'card, at title-tier (20px) padding -- not inside the '
|
|
'PluriGlassSurface (t4 line 511)',
|
|
(tester) async {
|
|
_suppressListTileInkAssertion();
|
|
await tester.pumpWidget(
|
|
host(
|
|
GrupoAjustes(
|
|
titulo: 'AUDIO',
|
|
filas: [
|
|
FilaAjuste(
|
|
icon: Icons.equalizer_rounded,
|
|
titulo: 'Uno',
|
|
onTap: () {},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
find.ancestor(
|
|
of: find.text('AUDIO'),
|
|
matching: find.byType(PluriGlassSurface),
|
|
),
|
|
findsNothing,
|
|
reason: 'the eyebrow must not be a descendant of the card',
|
|
);
|
|
|
|
final eyebrowLeft = tester.getTopLeft(find.text('AUDIO')).dx;
|
|
expect(
|
|
eyebrowLeft,
|
|
20,
|
|
reason: 'S5 title tier -- PluriLayout.titleHorizontal',
|
|
);
|
|
},
|
|
);
|
|
}
|