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:
parent
1820739a49
commit
eb4cd74f02
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue