MotorSignal-App/test/pro_screen_test.dart

280 lines
9.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:motorcycle_dash/features/entitlements/pro_screen.dart';
import 'package:motorcycle_dash/features/settings/widgets/current_plan_card.dart';
import 'package:motorcycle_dash/services/billing/billing_providers.dart';
import 'package:motorcycle_dash/services/billing/fake_billing_service.dart';
import 'package:motorcycle_dash/services/billing/product_catalog.dart';
import 'package:motorcycle_dash/services/entitlements/entitlement_providers.dart';
import 'package:motorcycle_dash/services/entitlements/entitlement_state.dart';
import 'package:motorcycle_dash/services/entitlements/entitlement_storage.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
Widget buildTestableWidget({
required Widget child,
List<Override> overrides = const [],
}) {
return ProviderScope(
overrides: overrides,
child: MaterialApp(home: child),
);
}
group('ProScreen UI and Flow Tests', () {
testWidgets(
'renders dynamic products with prices from FakeBillingService',
(tester) async {
final fakeBilling = FakeBillingService();
await tester.pumpWidget(
buildTestableWidget(
child: const ProScreen(),
overrides: [
billingServiceProvider.overrideWithValue(fakeBilling),
entitlementStorageProvider.overrideWithValue(
InMemoryEntitlementStorage(),
),
],
),
);
// Initial loading pump
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// Check header and titles
expect(find.text('MOTORSIGNAL'), findsOneWidget);
expect(
find.text('PRECISION TELEMETRY.\nUNLIMITED RIDE DATA.'),
findsOneWidget,
);
// Check dynamic product prices and plan card titles
expect(find.text('\$4.99/month'), findsOneWidget);
expect(find.text('\$49.99'), findsOneWidget);
expect(find.text('PRO MONTHLY'), findsOneWidget);
expect(find.text('PRO LIFETIME'), findsOneWidget);
// Check restore purchases button exists
expect(find.text('RESTORE'), findsOneWidget);
// Scroll to benefits list
final benefitsHeader = find.text('WHAT IS INCLUDED IN PRO');
await tester.scrollUntilVisible(benefitsHeader, 200);
expect(benefitsHeader, findsOneWidget);
final garageBenefit = find.text('Multiple Motorcycle Garage');
await tester.scrollUntilVisible(garageBenefit, 200);
expect(garageBenefit, findsOneWidget);
},
);
testWidgets('triggers purchase flow on monthly plan card selection', (
tester,
) async {
final fakeBilling = FakeBillingService();
await tester.pumpWidget(
buildTestableWidget(
child: const ProScreen(),
overrides: [
billingServiceProvider.overrideWithValue(fakeBilling),
entitlementStorageProvider.overrideWithValue(
InMemoryEntitlementStorage(),
),
],
),
);
await tester.pumpAndSettle();
// Tap on Pro Monthly card
final monthlyCard = find.text('PRO MONTHLY');
expect(monthlyCard, findsOneWidget);
await tester.tap(monthlyCard);
await tester.pumpAndSettle();
expect(fakeBilling.launchCount, 1);
expect(fakeBilling.lastPurchasedProductId, ProductCatalog.proMonthly);
});
testWidgets('triggers purchase flow on lifetime plan card selection', (
tester,
) async {
final fakeBilling = FakeBillingService();
await tester.pumpWidget(
buildTestableWidget(
child: const ProScreen(),
overrides: [
billingServiceProvider.overrideWithValue(fakeBilling),
entitlementStorageProvider.overrideWithValue(
InMemoryEntitlementStorage(),
),
],
),
);
await tester.pumpAndSettle();
// Tap on Pro Lifetime card
final lifetimeCard = find.text('PRO LIFETIME');
expect(lifetimeCard, findsOneWidget);
await tester.tap(lifetimeCard);
await tester.pumpAndSettle();
expect(fakeBilling.launchCount, 1);
expect(fakeBilling.lastPurchasedProductId, ProductCatalog.proLifetime);
});
testWidgets('handles empty product catalog with retry option', (
tester,
) async {
final fakeBilling = FakeBillingService()..available = false;
await tester.pumpWidget(
buildTestableWidget(
child: const ProScreen(),
overrides: [
billingServiceProvider.overrideWithValue(fakeBilling),
entitlementStorageProvider.overrideWithValue(
InMemoryEntitlementStorage(),
),
],
),
);
await tester.pumpAndSettle();
expect(find.text('Google Play Billing Unavailable'), findsOneWidget);
expect(find.text('RETRY CONNECTION'), findsOneWidget);
});
});
group('CurrentPlanCard Component Tests', () {
testWidgets('displays Free plan with Upgrade button', (tester) async {
final freeState = EntitlementState.free();
await tester.pumpWidget(
buildTestableWidget(
child: const Scaffold(body: CurrentPlanCard()),
overrides: [entitlementProvider.overrideWithValue(freeState)],
),
);
await tester.pumpAndSettle();
expect(find.text('MotorSignal Free'), findsOneWidget);
expect(find.text('FREE'), findsOneWidget);
expect(find.text('Upgrade to Pro'), findsOneWidget);
expect(
find.text('Basic telemetry & local recording active'),
findsOneWidget,
);
});
testWidgets('displays Active Pro Monthly with Manage Subscriptions link', (
tester,
) async {
final proState = EntitlementState.proMonthlyActive(
productId: ProductCatalog.proMonthly,
expiresAt: DateTime.now().add(const Duration(days: 20)),
);
await tester.pumpWidget(
buildTestableWidget(
child: const Scaffold(body: CurrentPlanCard()),
overrides: [entitlementProvider.overrideWithValue(proState)],
),
);
await tester.pumpAndSettle();
expect(find.text('MotorSignal Pro'), findsOneWidget);
expect(find.text('Manage Subscription'), findsOneWidget);
expect(find.text('MONTHLY'), findsOneWidget);
});
testWidgets(
'displays Lifetime with Lifetime License label and no renewal',
(tester) async {
final lifetimeState = EntitlementState.proLifetime(
productId: ProductCatalog.proLifetime,
);
await tester.pumpWidget(
buildTestableWidget(
child: const Scaffold(body: CurrentPlanCard()),
overrides: [entitlementProvider.overrideWithValue(lifetimeState)],
),
);
await tester.pumpAndSettle();
expect(find.text('MotorSignal Pro'), findsOneWidget);
expect(
find.text('Lifetime access • Permanent ownership'),
findsOneWidget,
);
expect(find.text('LIFETIME'), findsOneWidget);
},
);
testWidgets('displays Grace Period with warning badge', (tester) async {
final graceState = EntitlementState.normalized(
plan: MotorSignalPlan.proMonthly,
status: MotorSignalEntitlementStatus.proMonthlyGrace,
verificationStatus: EntitlementVerificationStatus.verified,
source: PurchaseSource.googlePlay,
productId: ProductCatalog.proMonthly,
expiresAt: DateTime.now().add(const Duration(days: 2)),
);
await tester.pumpWidget(
buildTestableWidget(
child: const Scaffold(body: CurrentPlanCard()),
overrides: [entitlementProvider.overrideWithValue(graceState)],
),
);
await tester.pumpAndSettle();
expect(find.text('GRACE PERIOD'), findsOneWidget);
expect(
find.text('Payment grace period • Action needed in Google Play'),
findsOneWidget,
);
expect(find.text('Manage Subscription'), findsOneWidget);
});
testWidgets('renders on narrow viewport without overflow', (tester) async {
tester.view.physicalSize = const Size(320, 640);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
final fakeBilling = FakeBillingService();
await tester.pumpWidget(
buildTestableWidget(
child: const ProScreen(),
overrides: [
billingServiceProvider.overrideWithValue(fakeBilling),
entitlementStorageProvider.overrideWithValue(
InMemoryEntitlementStorage(),
),
],
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(tester.takeException(), isNull);
expect(find.text('PRO LIFETIME'), findsOneWidget);
expect(find.text('BEST VALUE'), findsOneWidget);
});
});
}