254 lines
8.2 KiB
Dart
254 lines
8.2 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(isEvolution: true)),
|
|
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(isEvolution: false)),
|
|
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(isEvolution: true)),
|
|
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(isEvolution: true)),
|
|
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);
|
|
});
|
|
});
|
|
}
|