// Author: fruit.com.cn // 奖励中心设计图比例与关键布局视觉回归。 import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import 'package:fruitshorts/core/data/models/reward.dart'; import 'package:fruitshorts/core/data/remote/api_client.dart'; import 'package:fruitshorts/core/data/remote/auth_api.dart'; import 'package:fruitshorts/core/data/repositories/reward_repository.dart'; import 'package:fruitshorts/core/data/services/auth_controller.dart'; import 'package:fruitshorts/core/data/services/auth_session_store.dart'; import 'package:fruitshorts/core/data/services/reward_controller.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/reward/reward_page.dart'; import '../support/widget_capture.dart'; const bool _captureEnabled = bool.fromEnvironment('REWARD_CAPTURE'); const Size _screenSize = Size(393, 852); const Key _captureKey = Key('reward-visual-capture'); const String _capturePath = '/tmp/fruitshorts-reward-before.png'; const String _helpCapturePath = '/tmp/fruitshorts-reward-help-dialog.png'; void main() { testWidgets('should_render_reward_design_fixture_without_overflow', ( tester, ) async { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); await tester.binding.setSurfaceSize(_screenSize); addTearDown(() => tester.binding.setSurfaceSize(null)); if (_captureEnabled) await loadCaptureFonts(tester); final auth = _VisualAuthController(); final rewards = RewardController(_VisualRewardRepository()); await tester.pumpWidget(_host(auth: auth, rewards: rewards)); await tester.pump(const Duration(milliseconds: 2)); await tester.pumpAndSettle(); if (_captureEnabled) { await saveWidgetCapture(tester, _captureKey, _capturePath); } expect(find.byKey(const Key('reward-checkin-card')), findsOneWidget); expect(find.byKey(const Key('reward-task-card-INVITE')), findsOneWidget); expect(find.byKey(const Key('reward-task-card-NEWBIE')), findsOneWidget); expect(find.text('+0'), findsNothing); final action = tester.getSize( find.byKey(const Key('reward-checkin-action')), ); final actionContent = tester.getSize( find.byKey(const Key('reward-checkin-action-content')), ); expect(actionContent.width, lessThan(action.width * 0.8)); expect(_radius(tester, const Key('reward-checkin-card')), 14); expect(_radius(tester, const Key('reward-task-surface-INVITE')), 14); final backIcon = tester.widget( find.descendant( of: find.byKey(const Key('reward-back-button')), matching: find.byIcon(Icons.arrow_back_ios_new_rounded), ), ); expect(backIcon.size, 16); final helpButton = tester.getSize( find.byKey(const Key('reward-help-icon')), ); expect(helpButton.width, 24); expect(helpButton.height, 24); final helpHitTarget = tester.getSize( find.byKey(const Key('reward-help-button')), ); expect(helpHitTarget.width, 48); expect(helpHitTarget.height, 48); final taskButton = tester.getSize( find.byKey(const Key('reward-task-action-invite')), ); expect(taskButton.height, 36); expect(taskButton.width, 80); if (_captureEnabled) { await tester.tap(find.byKey(const Key('reward-help-button'))); await tester.pumpAndSettle(); await saveWidgetCapture( tester, const Key('reward-help-dialog'), _helpCapturePath, ); await tester.tap(find.byKey(const Key('reward-help-close'))); await tester.pumpAndSettle(); } expect(tester.takeException(), isNull); auth.dispose(); rewards.dispose(); }); } double _radius(WidgetTester tester, Key key) { final container = tester.widget(find.byKey(key)); final decoration = container.decoration! as BoxDecoration; return decoration.borderRadius!.resolve(TextDirection.ltr).topLeft.x; } Widget _host({ required AuthController auth, required RewardController rewards, }) { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: auth), ChangeNotifierProvider.value(value: rewards), ], child: MaterialApp( theme: AppTheme.dark(), locale: const Locale('zh'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: MediaQuery( data: const MediaQueryData( size: _screenSize, padding: EdgeInsets.only(top: 24, bottom: 24), ), child: const RepaintBoundary( key: _captureKey, child: Scaffold(body: RewardPage(active: true)), ), ), ), ); } class _VisualAuthController extends AuthController { _VisualAuthController() : super( AuthApi(ApiClient(baseUrl: 'http://127.0.0.1:8081')), AuthSessionStore(), ); @override bool get isSignedIn => false; } class _VisualRewardRepository implements RewardRepository { @override Future fetchOverview(String locale) async { return const RewardOverview( serverDate: '2026-11-09', timezone: 'Asia/Jakarta', coinBalance: 0, checkin: RewardCheckinState( taskId: 'checkin', checkedInToday: false, streak: 0, currentDay: 1, days: [ RewardCheckinDay( dayIndex: 1, rewardCoin: 55, checked: false, current: true, ), RewardCheckinDay( dayIndex: 2, rewardCoin: 35, checked: false, current: false, ), RewardCheckinDay( dayIndex: 3, rewardCoin: 35, checked: false, current: false, ), RewardCheckinDay( dayIndex: 4, rewardCoin: 35, checked: false, current: false, ), RewardCheckinDay( dayIndex: 5, rewardCoin: 35, checked: false, current: false, ), RewardCheckinDay( dayIndex: 6, rewardCoin: 55, checked: false, current: false, ), RewardCheckinDay( dayIndex: 7, rewardCoin: 55, checked: false, current: false, ), ], ), sections: [ RewardSection( code: 'INVITE', title: '邀请任务', tasks: [ RewardTask( id: 'invite', code: 'INVITE_FRIEND', type: 'INVITE_FRIEND', title: '领1小时免费看剧', subtitle: '邀请成功 +1小时', ctaText: '去完成', actionType: 'OPEN_URL', actionUrl: '', progressValue: 0, targetValue: 1, rewardCoin: 0, status: 'IN_PROGRESS', ), ], ), RewardSection( code: 'NEWBIE', title: '新手专属任务', tasks: [ RewardTask( id: 'facebook', code: 'FOLLOW_FACEBOOK', type: 'FOLLOW_SOCIAL', title: '关注 Facebook 账号', subtitle: '', ctaText: '去完成', actionType: 'OPEN_URL', actionUrl: '', progressValue: 0, targetValue: 1, rewardCoin: 50, status: 'IN_PROGRESS', ), RewardTask( id: 'youtube', code: 'FOLLOW_YOUTUBE', type: 'FOLLOW_SOCIAL', title: '关注 YouTube 账号', subtitle: '', ctaText: '去完成', actionType: 'OPEN_URL', actionUrl: '', progressValue: 0, targetValue: 1, rewardCoin: 50, status: 'IN_PROGRESS', ), ], ), ], ); } @override Future checkIn() async { return const RewardActionResult( rewardCoin: 55, coinBalance: 55, alreadyCompleted: false, ); } @override Future claim(String taskId) async { return const RewardActionResult( rewardCoin: 50, coinBalance: 50, alreadyCompleted: false, ); } }