import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fuel_tax_tracker/widgets/onboarding_tour_overlay.dart'; /// Unit-level coverage of the tour's presentational overlay widget, /// independent of MainShell's step-sequencing/navigation logic (covered /// in main_shell_test.dart's onboarding-tour group instead). void main() { Widget wrap(Widget child) => MaterialApp(home: Scaffold(body: child)); testWidgets('shows the title, description, and step count', (tester) async { final targetKey = GlobalKey(); await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'Example Title', description: 'Example description text.', stepNumber: 2, totalSteps: 4, onNext: () {}, onSkip: () {}, ), )); expect(find.text('Example Title'), findsOneWidget); expect(find.text('Example description text.'), findsOneWidget); expect(find.text('Step 2 of 4'), findsOneWidget); }); testWidgets('shows "Next" before the last step and "Got It" on the last step', (tester) async { final targetKey = GlobalKey(); await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'T', description: 'D', stepNumber: 1, totalSteps: 3, onNext: () {}, onSkip: () {}, ), )); expect(find.widgetWithText(FilledButton, 'Next'), findsOneWidget); expect(find.widgetWithText(FilledButton, 'Got It'), findsNothing); await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'T', description: 'D', stepNumber: 3, totalSteps: 3, onNext: () {}, onSkip: () {}, ), )); expect(find.widgetWithText(FilledButton, 'Got It'), findsOneWidget); expect(find.widgetWithText(FilledButton, 'Next'), findsNothing); }); testWidgets('invokes onNext/onSkip when their buttons are tapped', (tester) async { var nextTapped = false; var skipTapped = false; final targetKey = GlobalKey(); await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'T', description: 'D', stepNumber: 1, totalSteps: 2, onNext: () => nextTapped = true, onSkip: () => skipTapped = true, ), )); await tester.tap(find.widgetWithText(TextButton, 'Skip Tour')); expect(skipTapped, isTrue); await tester.tap(find.widgetWithText(FilledButton, 'Next')); expect(nextTapped, isTrue); }); testWidgets( 'renders without crashing when the target key has no RenderObject yet — falls back to a ' 'centered card', (tester) async { final targetKey = GlobalKey(); // never attached to anything await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'T', description: 'D', stepNumber: 1, totalSteps: 1, onNext: () {}, onSkip: () {}, ), )); expect(find.text('T'), findsOneWidget); }); testWidgets('tapping the dimmed barrier does not invoke onNext or onSkip', (tester) async { var nextTapped = false; var skipTapped = false; final targetKey = GlobalKey(); await tester.pumpWidget(wrap( OnboardingTourOverlay( targetKeys: [targetKey], title: 'T', description: 'D', stepNumber: 1, totalSteps: 1, onNext: () => nextTapped = true, onSkip: () => skipTapped = true, ), )); // Top-left corner — away from the card, which sits mid/lower-screen. await tester.tapAt(const Offset(5, 5)); await tester.pump(); expect(nextTapped, isFalse); expect(skipTapped, isFalse); }); testWidgets('highlights every key in targetKeys, not just the first', (tester) async { final topKey = GlobalKey(); final bottomKey = GlobalKey(); Widget buildTree({required bool showOverlay}) => MaterialApp( home: Scaffold( body: Stack( children: [ Positioned(top: 0, left: 0, child: SizedBox(key: topKey, width: 40, height: 40)), Positioned( bottom: 0, left: 0, child: SizedBox(key: bottomKey, width: 40, height: 40)), if (showOverlay) OnboardingTourOverlay( targetKeys: [topKey, bottomKey], title: 'Both Highlighted', description: 'D', stepNumber: 1, totalSteps: 1, onNext: () {}, onSkip: () {}, ), ], ), ), ); // Two frames, matching how MainShell actually uses this: the target // keys' RenderBoxes need to already exist and be laid out (from a // prior frame) before the overlay's own build() can find their real // position — querying them in the very same frame they're first // mounted finds nothing yet (layout hasn't run for that frame at // build time). await tester.pumpWidget(buildTree(showOverlay: false)); await tester.pumpWidget(buildTree(showOverlay: true)); // One highlight border per resolved key. expect(find.byType(DecoratedBox), findsNWidgets(2)); expect(find.text('Both Highlighted'), findsOneWidget); }); testWidgets('still renders correctly when only some of targetKeys resolve', (tester) async { final resolvedKey = GlobalKey(); final unresolvedKey = GlobalKey(); // never attached to anything Widget buildTree({required bool showOverlay}) => MaterialApp( home: Scaffold( body: Stack( children: [ SizedBox(key: resolvedKey, width: 40, height: 40), if (showOverlay) OnboardingTourOverlay( targetKeys: [resolvedKey, unresolvedKey], title: 'T', description: 'D', stepNumber: 1, totalSteps: 1, onNext: () {}, onSkip: () {}, ), ], ), ), ); await tester.pumpWidget(buildTree(showOverlay: false)); await tester.pumpWidget(buildTree(showOverlay: true)); expect(find.byType(DecoratedBox), findsNWidgets(1), reason: 'only the resolved key gets a border'); expect(find.text('T'), findsOneWidget); }); }