Files
doormile_milderapp/test/card_cta_height_test.dart
2026-08-11 13:16:33 +05:30

163 lines
6.9 KiB
Dart
Raw Permalink 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:miler/views/helpers/constants/app_theme.dart';
/// The stop card's CTA is an `ElevatedButton.icon` inside a fixed-height
/// `SizedBox`, and the app theme styles `ElevatedButton` for full-width FORM
/// buttons: `padding: vertical 18` and a 56dp `minimumSize`.
///
/// Those two defaults are a trap for any button in a list row. `styleFrom`
/// replaces only the keys it is handed, so a card that overrides colour, shape
/// and elevation still inherits the form metrics. The padding then eats the
/// height from the inside: at 48dp the content band is 48 − 36 = **12px**, and
/// the label and icon are squeezed into it — glyphs cut through the middle,
/// icon painted at 12px instead of its declared 19px.
///
/// Two things make it worth a test. It is **silent** — no overflow exception,
/// nothing in the console, the button just renders half a word — and it is
/// invisible at 56dp, where the band is exactly 20px and one line happens to
/// fit. So the bug is dormant in any card using the theme's metrics and wakes
/// up the moment someone changes the height.
///
/// `PickupCard` is private to the `pickups.dart` library and cannot be
/// imported, so — as in `card_stripe_layout_test.dart` — these tests pin the
/// idiom rather than the widget.
void main() {
const iconSize = 19.0;
const labelFontSize = 14.5;
const label = 'Start Combined Stop';
/// The card's CTA, at the height the card gives it.
Widget cta({required bool withOverrides, double height = 48}) {
return MaterialApp(
theme: AppTheme.light,
home: Scaffold(
body: Center(
child: SizedBox(
width: 268, // CTA share of a 390pt-wide card, beside the ⋯ button
child: SizedBox(
height: height,
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.arrow_forward_rounded, size: iconSize),
label: const Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontWeight: FontWeight.w800, fontSize: labelFontSize),
),
style: withOverrides
? ElevatedButton.styleFrom(
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 12),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
)
: ElevatedButton.styleFrom(elevation: 0),
),
),
),
),
),
);
}
testWidgets('the themed form metrics squeeze a 48dp CTA', (tester) async {
await tester.pumpWidget(cta(withOverrides: false));
// Nothing is thrown — this is the failure mode that ships.
expect(tester.takeException(), isNull);
// 48 − (18 + 18) of inherited vertical padding leaves a 12px content band,
// and everything in the button is crushed into it. The icon is the clean
// witness: it asked for 19px and cannot have been drawn correctly at 12.
final content = tester.getRect(find.byIcon(Icons.arrow_forward_rounded));
expect(content.height, lessThan(iconSize),
reason: 'this is the squeeze that cut the button label in half');
expect(tester.getRect(find.text(label)).height, lessThan(labelFontSize),
reason: 'the label box is shorter than one line of its own text');
});
testWidgets('overriding padding and minimumSize lets the label fit',
(tester) async {
await tester.pumpWidget(cta(withOverrides: true));
expect(tester.takeException(), isNull);
final button = tester.getRect(find.byType(ElevatedButton));
final icon = tester.getRect(find.byIcon(Icons.arrow_forward_rounded));
final text = tester.getRect(find.text(label));
// The SizedBox is the single source of truth for the height.
expect(button.height, 48);
// Nothing is squeezed: the icon gets the size it asked for, and the label
// gets at least a full line box.
expect(icon.height, iconSize);
expect(text.height, greaterThanOrEqualTo(labelFontSize));
// And both sit fully inside the button rather than being clipped by it.
for (final part in [icon, text]) {
expect(part.top, greaterThanOrEqualTo(button.top));
expect(part.bottom, lessThanOrEqualTo(button.bottom));
expect(part.left, greaterThanOrEqualTo(button.left));
expect(part.right, lessThanOrEqualTo(button.right));
}
});
// ── The same trap, on OutlinedButton ──
//
// Not a hypothetical: an audit found `sheet.dart` giving a 48dp box to an
// `OutlinedButton.icon` whose style overrode only the side and the shape. The
// theme's `outlinedButtonTheme` carries `vertical: 16`, leaving a 16px content
// band, and the row's 18sp icon was measured painting at **16px**. Silently —
// this produces no overflow error, which is why it survived.
group('OutlinedButton inherits the same form metrics', () {
Widget outlined({required bool withOverrides, double height = 48}) {
return MaterialApp(
theme: AppTheme.light,
home: Scaffold(
body: Center(
child: SizedBox(
width: 220,
height: height,
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.close_rounded, size: 18),
label: const Text('Skip stop',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w700)),
style: withOverrides
? OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 14),
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
)
: null,
),
),
),
),
);
}
testWidgets('squeezes the icon without the overrides', (tester) async {
await tester.pumpWidget(outlined(withOverrides: false));
expect(tester.takeException(), isNull, reason: 'it fails silently');
expect(tester.getRect(find.byType(Icon)).height, lessThan(18),
reason: 'this is the shape that shrank the icon to 16px');
});
testWidgets('and does not with them', (tester) async {
await tester.pumpWidget(outlined(withOverrides: true));
expect(tester.takeException(), isNull);
final button = tester.getRect(find.byType(OutlinedButton));
final icon = tester.getRect(find.byType(Icon));
expect(button.height, 48);
expect(icon.height, 18, reason: 'the icon gets the size it asked for');
expect(icon.top, greaterThanOrEqualTo(button.top - 0.01));
expect(icon.bottom, lessThanOrEqualTo(button.bottom + 0.01));
});
});
}