// Headless generator for the App Store IAP review screenshot: renders the // Settings screen (where "광고 제거 / Remove Ads" is purchased) to a PNG. // // flutter test test/tool/generate_iap_review_screenshot_test.dart // // Output: docs/store/iap_review_remove_ads.png import 'dart:io'; import 'dart:ui' as ui; import 'package:block_seasons/data/save_repository.dart'; import 'package:block_seasons/l10n/gen/app_localizations.dart'; import 'package:block_seasons/services/iap_service.dart'; import 'package:block_seasons/state/providers.dart'; import 'package:block_seasons/ui/screens/settings_screen.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:shared_preferences/shared_preferences.dart'; // A stand-in IAP service that reports the product as available with a price, // so the Settings row renders exactly as a shopper sees it. initialize() is // never called, so no real store query happens. class _FakeIap extends IapService { _FakeIap() : super(onEntitlementGranted: _noop); static Future _noop() async {} @override bool get available => true; @override ProductDetails get product => ProductDetails( id: 'remove_ads', title: 'Remove Ads', description: 'Remove banner and interstitial ads', price: r'$0.99', rawPrice: 0.99, currencyCode: 'USD', ); } Future _loadFont() async { final bytes = File('/System/Library/Fonts/Supplemental/Arial.ttf').readAsBytesSync(); final loader = FontLoader('Arial') ..addFont(Future.value(ByteData.view(bytes.buffer))); await loader.load(); } Widget _wrap(ProviderContainer c, Widget screen, GlobalKey key) => UncontrolledProviderScope( container: c, child: RepaintBoundary( key: key, child: MaterialApp( debugShowCheckedModeBanner: false, locale: const Locale('en'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, theme: ThemeData( fontFamily: 'Arial', colorScheme: ColorScheme.fromSeed( seedColor: const Color(0xFF5B7FFF), brightness: Brightness.dark, ), useMaterial3: true, ), home: screen, ), ), ); void main() { testWidgets('generate IAP review screenshot', (tester) async { // in_app_purchase only registers a native platform for android/iOS/macOS; // on a desktop target it registers none, so constructing the real // IapService never makes a store channel call (which would throw async in // the headless binding). The fake overrides available/product anyway. debugDefaultTargetPlatformOverride = TargetPlatform.linux; await _loadFont(); const dpr = 3.0; tester.view.devicePixelRatio = dpr; tester.view.physicalSize = const Size(1242, 2688); // 6.5" — accepted size addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); SharedPreferences.setMockInitialValues({}); final repo = SaveRepository(await SharedPreferences.getInstance()); final key = GlobalKey(); final c = ProviderContainer(overrides: [ saveRepositoryProvider.overrideWithValue(repo), iapServiceProvider.overrideWithValue(_FakeIap()), ]); await tester.pumpWidget(_wrap(c, const SettingsScreen(), key)); await tester.pump(); await tester.pump(const Duration(milliseconds: 450)); var ro = key.currentContext!.findRenderObject()!; while (!ro.isRepaintBoundary) { ro = ro.parent!; } final layer = ro.debugLayer! as OffsetLayer; final bounds = ro.paintBounds; final bytes = await tester.runAsync(() async { final image = await layer.toImage(bounds, pixelRatio: dpr); final data = await image.toByteData(format: ui.ImageByteFormat.png); image.dispose(); return data; }); File('docs/store/iap_review_remove_ads.png') ..parent.createSync(recursive: true) ..writeAsBytesSync(bytes!.buffer.asUint8List()); c.dispose(); // Reset before the body ends so flutter_test's foundation-var invariant // check (which runs before addTearDown) passes. debugDefaultTargetPlatformOverride = null; expect( File('docs/store/iap_review_remove_ads.png').existsSync(), isTrue); }); }