first commit

This commit is contained in:
2026-08-11 13:16:33 +05:30
parent 18531bf46b
commit c8350563d9
388 changed files with 69656 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
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));
});
});
}