fix(social): prevent horizontal overflow in GPX export snackbar (#52)

- Wrap GPX export snackbar message Text in Expanded with maxLines: 2 and TextOverflow.ellipsis

- Add ValueKey('gpx_export_button_<id>') for deterministic testing

- Wrap narrow feed header/metric/empty-state Row labels in Flexible to prevent overflow under 320px/360px at 1.5x text scale

- Add BUG-005 widget tests across 320x640, 360x800, and 800x480 viewports at 1.5x text scale
This commit is contained in:
Developer 2026-10-16 23:23:12 -04:00
parent 1820739a49
commit eb4cd74f02
2 changed files with 151 additions and 52 deletions

View File

@ -252,38 +252,44 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Status LED pill
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: colors.surfaceCardSecondary,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3),
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: colors.surfaceCardSecondary,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: accentColor.withValues(alpha: isLight ? 0.5 : 0.3),
),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: accentColor,
shape: BoxShape.circle,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: accentColor,
shape: BoxShape.circle,
),
),
),
const SizedBox(width: 8),
Text(
'SQUAD & CANYON FEED',
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 0.6,
color: accentColor,
const SizedBox(width: 8),
Flexible(
child: Text(
'SQUAD & CANYON FEED',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 11,
fontWeight: FontWeight.w700,
letterSpacing: 0.6,
color: accentColor,
),
),
),
),
],
],
),
),
),
@ -1358,19 +1364,26 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
// GPX Export Button
InkWell(
key: ValueKey('gpx_export_button_${item.id}'),
borderRadius: BorderRadius.circular(12),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: const Color(0xFF1D2025),
backgroundColor: colors.surfaceCardSecondary,
content: Row(
children: [
const Icon(Icons.download_done_rounded,
color: AppColors.electricCyan),
Icon(
Icons.download_done_rounded,
color: accentColor,
),
const SizedBox(width: 10),
Text(
'GPX track exported for ${item.rideTitle}',
style: const TextStyle(color: Colors.white),
Expanded(
child: Text(
'GPX track exported for ${item.rideTitle}',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colors.textPrimary),
),
),
],
),
@ -1519,15 +1532,17 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
),
if (unit.isNotEmpty) ...[
const SizedBox(width: 4),
Text(
unit,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
fontWeight: FontWeight.w700,
color: secondaryTextColor,
Flexible(
child: Text(
unit,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
fontWeight: FontWeight.w700,
color: secondaryTextColor,
),
),
),
],
@ -1568,6 +1583,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
child: Center(
child: Container(
key: ValueKey('feed_route_no_gps_state_${item.id}'),
margin: const EdgeInsets.symmetric(horizontal: 12),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
@ -1588,14 +1604,18 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
color: Color(0xFF94A3B8),
),
SizedBox(width: 6),
Text(
'NO GPS TRACK RECORDED',
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
color: Color(0xFFCBD5E1),
Flexible(
child: Text(
'NO GPS TRACK RECORDED',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
fontWeight: FontWeight.w700,
letterSpacing: 0.5,
color: Color(0xFFCBD5E1),
),
),
),
],

View File

@ -901,4 +901,83 @@ void main() {
);
}
});
group('BUG-005: GPX Export Snackbar Horizontal Overflow Tests', () {
for (final viewport in const [
Size(320, 640),
Size(360, 800),
Size(800, 480),
]) {
testWidgets(
'GPX export snackbar renders long rideTitle without RenderFlex overflow at ${viewport.width.toInt()}x${viewport.height.toInt()} (1.5x text scale)',
(tester) async {
await tester.binding.setSurfaceSize(viewport);
final service = SocialFeedService();
addTearDown(service.dispose);
const longRideTitle =
'Ultra Long Sunday Skyline Boulevard & Pescadero Creek Canyon Telemetry Endurance Carve Session With Full Squad Pack';
final post = await service.shareRecentRide(
authorUid: 'uid_overflow_test',
authorDisplayName: 'Marcus Vance',
authorHandle: 'ApexMarcus',
locationName: 'Skyline Blvd',
rideTitle: longRideTitle,
bikeModel: '2024 Ducati Streetfighter V4',
distanceMiles: 68.4,
durationSeconds: 4440,
maxLeanAngleDeg: 51.0,
);
await tester.pumpWidget(
ProviderScope(
overrides: [
socialFeedServiceProvider.overrideWithValue(service),
],
child: MediaQuery(
data: MediaQueryData(
size: viewport,
textScaler: const TextScaler.linear(1.5),
),
child: MaterialApp(
theme: ThemeData.dark().copyWith(
extensions: const [MotorSignalThemeColors.dark],
),
home: const SocialFeedScreen(),
),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
final gpxButton = find.byKey(
ValueKey('gpx_export_button_${post.id}'),
);
await tester.scrollUntilVisible(
gpxButton,
120.0,
scrollable: find.descendant(
of: find.byKey(const ValueKey('social_feed_list_view')),
matching: find.byType(Scrollable),
),
);
await tester.pumpAndSettle();
await tester.tap(gpxButton);
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
// Must not throw any RenderFlex overflow exception
expect(tester.takeException(), isNull);
expect(
find.text('GPX track exported for $longRideTitle'),
findsOneWidget,
);
},
);
}
});
}