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,7 +252,8 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Status LED pill
Container(
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: colors.surfaceCardSecondary,
@ -273,8 +274,11 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
),
),
const SizedBox(width: 8),
Text(
Flexible(
child: Text(
'SQUAD & CANYON FEED',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 11,
@ -283,9 +287,11 @@ class _SocialFeedScreenState extends ConsumerState<SocialFeedScreen> {
color: accentColor,
),
),
),
],
),
),
),
// Search toggle button
IconButton(
@ -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(
Expanded(
child: Text(
'GPX track exported for ${item.rideTitle}',
style: const TextStyle(color: Colors.white),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colors.textPrimary),
),
),
],
),
@ -1519,7 +1532,8 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
),
if (unit.isNotEmpty) ...[
const SizedBox(width: 4),
Text(
Flexible(
child: Text(
unit,
maxLines: 1,
overflow: TextOverflow.ellipsis,
@ -1530,6 +1544,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
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,8 +1604,11 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
color: Color(0xFF94A3B8),
),
SizedBox(width: 6),
Text(
Flexible(
child: Text(
'NO GPS TRACK RECORDED',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
@ -1598,6 +1617,7 @@ class _SocialFeedCardState extends ConsumerState<_SocialFeedCard>
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,
);
},
);
}
});
}