MO-Fuel-Tax-Back/test/onboarding_tour_overlay_test.dart

203 lines
6.4 KiB
Dart

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);
});
}