import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:reelbox/app.dart'; import 'package:reelbox/data/api_service.dart'; import 'package:reelbox/data/models.dart'; import 'package:reelbox/state/app_state.dart'; import 'package:reelbox/ui/catalog.dart'; import 'package:shared_preferences/shared_preferences.dart'; class HomeBannerFixtureApi extends ApiService { bool fail = false; Completer? pending; dynamic bannerResponse = {'banners': []}; int bannerCalls = 0; @override Future get(String path, {Map? query}) async { if (path != 'banners/') return []; bannerCalls++; if (fail) throw const ApiException('Offline'); return pending == null ? bannerResponse : pending!.future; } @override Future initialize({bool discover = true}) async {} @override Future> home() async { if (fail) throw const ApiException('Offline'); return {'genres': [], 'actors': [], 'countries': []}; } @override Future>> actors({ int page = 1, String search = '', }) async => []; @override Future> countries() async => []; @override Future> genres() async => []; } Future mountBannerHome( WidgetTester tester, HomeBannerFixtureApi api, { String locale = 'en', }) async { SharedPreferences.setMockInitialValues({}); final state = AppState( api: api, storage: await SharedPreferences.getInstance(), ); await state.initialize(discover: false); await state.updateSettings( state.prefs.copyWith(locale: locale, introCompleted: true), ); await tester.pumpWidget(MyApp(state: state)); await tester.pumpAndSettle(); return state; } void main() { testWidgets( 'pending config keeps Telegram then labels bilingual ads and honors data saver', (tester) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final api = HomeBannerFixtureApi()..pending = Completer(); final state = await mountBannerHome(tester, api); expect(find.text('ReelBox updates'), findsOneWidget); expect(find.text('Advertisement'), findsNothing); api.pending!.complete({ 'banners': [ { 'title_en': 'Fixture sponsor', 'title_fa': 'حامی آزمایشی', 'target_url': 'https://sponsor.example/offer', 'image_url': 'https://sponsor.example/banner.jpg', }, ], }); await tester.pumpAndSettle(); expect(find.text('Advertisement'), findsOneWidget); expect(find.text('Fixture sponsor'), findsOneWidget); expect(find.byType(Image), findsOneWidget); expect(find.text('ReelBox updates'), findsOneWidget); final launches = []; const channel = MethodChannel('plugins.flutter.io/url_launcher'); tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( call, ) async { launches.add(call.arguments['url'] as String); return true; }); addTearDown( () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( channel, null, ), ); await tester.tap(find.byKey(const Key('home-banner-ad-1'))); await tester.pumpAndSettle(); expect(launches, ['https://sponsor.example/offer']); await state.updateSettings( state.prefs.copyWith(locale: 'fa', dataSaver: true), ); await tester.pumpAndSettle(); expect(find.text('تبلیغات'), findsOneWidget); expect(find.text('حامی آزمایشی'), findsOneWidget); expect(find.byType(Image), findsNothing); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox()); state.dispose(); }, ); testWidgets( 'failed external launch shows safe error and keeps official banner', (tester) async { const channel = MethodChannel('plugins.flutter.io/url_launcher'); tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( channel, (_) async => false, ); addTearDown( () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( channel, null, ), ); final state = await mountBannerHome(tester, HomeBannerFixtureApi()); await tester.tap(find.byKey(const Key('home-banner-telegram'))); await tester.pump(); expect(find.text('Could not open this link'), findsOneWidget); expect(find.text('ReelBox updates'), findsOneWidget); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox()); state.dispose(); }, ); testWidgets( 'home Telegram is immediately clickable through API failure in both languages', (tester) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final launches = []; const channel = MethodChannel('plugins.flutter.io/url_launcher'); tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( call, ) async { launches.add(call); return true; }); addTearDown( () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( channel, null, ), ); final api = HomeBannerFixtureApi()..fail = true; final state = await mountBannerHome(tester, api); expect(find.byType(DiscoverPage), findsOneWidget); expect(find.text('ReelBox updates'), findsOneWidget); expect(find.text('Updates and announcements'), findsOneWidget); expect(find.text('Offline'), findsWidgets); await tester.tap(find.byKey(const Key('home-banner-telegram'))); await tester.pumpAndSettle(); expect(launches.single.method, 'launch'); expect(launches.single.arguments['url'], 'https://t.me/ReelBoxApp'); expect(launches.single.arguments['useWebView'], false); await state.updateSettings(state.prefs.copyWith(locale: 'fa')); await tester.pumpAndSettle(); expect(find.text('تازه‌های ریل‌باکس'), findsOneWidget); expect(find.text('به‌روزرسانی‌ها و اطلاعیه‌ها'), findsOneWidget); expect( Directionality.of(tester.element(find.text('تازه‌های ریل‌باکس'))), TextDirection.rtl, ); expect(tester.takeException(), isNull); await tester.pumpWidget(const SizedBox()); state.dispose(); }, ); }