import 'package:buzz/features/home/home_page.dart'; import 'package:buzz/features/channels/channels_page.dart'; import 'package:buzz/shared/theme/theme.dart'; import 'package:buzz/shared/l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { Future buildHome({ int unreadInboxCount = 0, bool disableAnimations = false, Gradient? topSectionGradient, }) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); return ProviderScope( overrides: [savedPrefsProvider.overrideWithValue(prefs)], child: MaterialApp( locale: const Locale('en'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: AppTheme.light(topSectionGradient: topSectionGradient), builder: (context, child) => MediaQuery( data: MediaQuery.of( context, ).copyWith(disableAnimations: disableAnimations), child: child!, ), home: HomePage( settingsPageBuilder: _buildSettingsPage, hasUnreadInbox: unreadInboxCount > 0, ), ), ); } testWidgets('shows icon-only navigation and an aligned quick action', ( tester, ) async { await tester.pumpWidget(await buildHome()); await tester.pump(); expect(find.text('Home'), findsNothing); expect(find.text('Activity'), findsNothing); expect(find.text('Search'), findsNothing); expect(find.bySemanticsLabel('Home'), findsOneWidget); expect(find.bySemanticsLabel('Activity'), findsOneWidget); expect(find.bySemanticsLabel('Search'), findsOneWidget); final quickAction = find.byTooltip('Create or start conversation'); expect(quickAction, findsOneWidget); final launcherSize = tester.getSize( find.byType(ChannelQuickActionsLauncher), ); expect(launcherSize.width, 800); expect(launcherSize.height, greaterThan(0)); final motionRect = tester.getRect( find.byKey(const Key('channel-quick-actions-motion')), ); expect(motionRect.width, const Size.square(56).width); expect(motionRect.left, greaterThanOrEqualTo(0)); expect(tester.getSize(quickAction), const Size.square(56)); final quickActionRect = tester.getRect(quickAction); expect(quickActionRect.left, greaterThanOrEqualTo(0)); expect(quickActionRect.top, greaterThanOrEqualTo(0)); expect(quickActionRect.right, lessThanOrEqualTo(800)); expect(quickActionRect.bottom, lessThanOrEqualTo(600)); final homeDestinationRect = tester.getRect(find.bySemanticsLabel('Home')); expect( quickActionRect.center.dy, closeTo(homeDestinationRect.center.dy, 0.01), ); }); testWidgets('keeps the Buzz backdrop behind the scalable Home screen', ( tester, ) async { const gradient = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.yellow, Colors.blue], ); await tester.pumpWidget(await buildHome(topSectionGradient: gradient)); await tester.pump(); final backdrop = find.byKey( const ValueKey('home-settings-transition-backdrop'), ); final decoration = tester.widget(backdrop).decoration as BoxDecoration; expect(decoration.gradient, gradient); expect( find.byKey(const ValueKey('home-settings-transition-scale')), findsOneWidget, ); expect( tester .widget( find.byKey(const ValueKey('home-settings-transition-scale')), ) .transform .getMaxScaleOnAxis(), 1, ); expect( tester .widget( find.byKey(const ValueKey('home-settings-transition-opacity')), ) .opacity, 1, ); }); testWidgets('gives selection haptics only when the tab changes', ( tester, ) async { final hapticCalls = []; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform, (call) async { if (call.method == 'HapticFeedback.vibrate') { hapticCalls.add(call); } return null; }); addTearDown( () => TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform, null), ); await tester.pumpWidget(await buildHome()); await tester.pump(); await tester.tap(find.byTooltip('Home')); await tester.pump(); expect(hapticCalls, isEmpty); await tester.tap(find.byTooltip('Activity')); await tester.pump(); expect(hapticCalls, hasLength(1)); expect(hapticCalls.single.arguments, 'HapticFeedbackType.selectionClick'); await tester.tap(find.byTooltip('Activity')); await tester.pump(); expect(hapticCalls, hasLength(1)); await tester.tap(find.byTooltip('Search')); await tester.pump(); expect(hapticCalls, hasLength(2)); }); testWidgets('gives a light impact when the Home quick action is pressed', ( tester, ) async { final hapticCalls = []; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform, (call) async { if (call.method == 'HapticFeedback.vibrate') { hapticCalls.add(call); } return null; }); addTearDown( () => TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler(SystemChannels.platform, null), ); await tester.pumpWidget(await buildHome()); await tester.pump(); await tester.tap(find.byTooltip('Create or start conversation')); await tester.pump(); expect(hapticCalls, hasLength(1)); expect(hapticCalls.single.arguments, 'HapticFeedbackType.lightImpact'); }); testWidgets('badges the Inbox tab when it has unread rows', (tester) async { await tester.pumpWidget(await buildHome(unreadInboxCount: 1)); await tester.pump(); expect( find.byKey(const ValueKey('activity-tab-unread-dot')), findsOneWidget, ); final badge = tester.widget( find.byKey(const ValueKey('activity-tab-unread-dot')), ); expect(badge.constraints?.maxWidth, 12); expect(badge.constraints?.maxHeight, 12); expect(find.bySemanticsLabel('Activity, unread'), findsOneWidget); AnimatedScale unreadDotScale() => tester.widget( find.byKey(const ValueKey('activity-tab-unread-dot-scale')), ); expect(unreadDotScale().scale, 1); expect(unreadDotScale().alignment, const Alignment(-0.5, 0.5)); expect(unreadDotScale().duration, const Duration(milliseconds: 220)); await tester.tap(find.byTooltip('Activity')); await tester.pump(); expect( find.byKey(const ValueKey('activity-tab-unread-dot')), findsOneWidget, ); expect(unreadDotScale().scale, 0); expect(find.bySemanticsLabel('Activity, unread'), findsNothing); await tester.tap(find.byTooltip('Home')); await tester.pump(); expect(unreadDotScale().scale, 1); }); testWidgets('fades and slides tab content in the selected direction', ( tester, ) async { await tester.pumpWidget(await buildHome()); await tester.pump(); Transform bodyTransform() => tester.widget( find.byKey(const ValueKey('frosted-scaffold-body-transition-transform')), ); Opacity bodyOpacity() => tester.widget( find.byKey(const ValueKey('frosted-scaffold-body-transition-opacity')), ); Transform appBarTransform() => tester.widget( find.byKey( const ValueKey('frosted-app-bar-content-transition-transform'), ), ); Opacity appBarOpacity() => tester.widget( find.byKey(const ValueKey('frosted-app-bar-content-transition-opacity')), ); double bodyOffset() => bodyTransform().transform.getTranslation().x; double appBarOffset() => appBarTransform().transform.getTranslation().x; expect(bodyOffset(), closeTo(0, 0.001)); expect(appBarOffset(), closeTo(0, 0.001)); expect(bodyOpacity().opacity, closeTo(1, 0.001)); expect(appBarOpacity().opacity, closeTo(1, 0.001)); await tester.tap(find.byTooltip('Activity')); await tester.pump(); expect(bodyOffset(), closeTo(24, 0.001)); expect(appBarOffset(), closeTo(24, 0.001)); expect(bodyOpacity().opacity, closeTo(0, 0.001)); expect(appBarOpacity().opacity, closeTo(0, 0.001)); expect( find.descendant( of: find.byKey(const ValueKey('frosted-app-bar-background')), matching: find.byKey( const ValueKey('frosted-app-bar-content-transition-transform'), ), ), findsOneWidget, ); await tester.pump(const Duration(milliseconds: 120)); expect(bodyOffset(), inExclusiveRange(0, 24)); expect(appBarOffset(), inExclusiveRange(0, 24)); expect(bodyOpacity().opacity, inExclusiveRange(0, 1)); expect(appBarOpacity().opacity, inExclusiveRange(0, 1)); await tester.pumpAndSettle(); await tester.tap(find.byTooltip('Home')); await tester.pump(); expect(bodyOffset(), closeTo(-24, 0.001)); expect(appBarOffset(), closeTo(-24, 0.001)); expect(bodyOpacity().opacity, closeTo(0, 0.001)); expect(appBarOpacity().opacity, closeTo(0, 0.001)); await tester.pumpAndSettle(); expect(bodyOffset(), closeTo(0, 0.001)); expect(appBarOffset(), closeTo(0, 0.001)); expect(bodyOpacity().opacity, closeTo(1, 0.001)); expect(appBarOpacity().opacity, closeTo(1, 0.001)); }); testWidgets('switches tab content instantly with reduced motion', ( tester, ) async { await tester.pumpWidget(await buildHome(disableAnimations: true)); await tester.pump(); await tester.tap(find.byTooltip('Activity')); await tester.pump(); final bodyTransform = tester.widget( find.byKey(const ValueKey('frosted-scaffold-body-transition-transform')), ); final bodyOpacity = tester.widget( find.byKey(const ValueKey('frosted-scaffold-body-transition-opacity')), ); final appBarTransform = tester.widget( find.byKey( const ValueKey('frosted-app-bar-content-transition-transform'), ), ); final appBarOpacity = tester.widget( find.byKey(const ValueKey('frosted-app-bar-content-transition-opacity')), ); expect(bodyTransform.transform.getTranslation().x, closeTo(0, 0.001)); expect(appBarTransform.transform.getTranslation().x, closeTo(0, 0.001)); expect(bodyOpacity.opacity, closeTo(1, 0.001)); expect(appBarOpacity.opacity, closeTo(1, 0.001)); }); testWidgets('scales and fades the quick action as tabs change', ( tester, ) async { await tester.pumpWidget(await buildHome()); await tester.pump(); double scale() => tester .widget(find.byKey(const Key('channel-quick-actions-scale'))) .transform .storage .first; double opacity() => tester .widget(find.byKey(const Key('channel-quick-actions-opacity'))) .opacity; expect(scale(), closeTo(1, 0.001)); expect(opacity(), closeTo(1, 0.001)); await tester.tap(find.byTooltip('Activity')); await tester.pump(); await tester.pump(const Duration(milliseconds: 110)); expect(scale(), inExclusiveRange(0.8, 1)); expect(opacity(), inExclusiveRange(0, 1)); await tester.pumpAndSettle(); expect(scale(), closeTo(0.8, 0.001)); expect(opacity(), closeTo(0, 0.001)); await tester.tap(find.byTooltip('Home')); await tester.pump(); await tester.pump(const Duration(milliseconds: 110)); expect(scale(), inExclusiveRange(0.8, 1)); expect(opacity(), inExclusiveRange(0, 1)); await tester.pumpAndSettle(); expect(scale(), closeTo(1, 0.001)); expect(opacity(), closeTo(1, 0.001)); }); } Widget _buildSettingsPage(BuildContext context) => const SizedBox.shrink();