import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:patbond_flutter/core/network/api_exception.dart'; import 'package:patbond_flutter/core/theme/app_theme.dart'; import 'package:patbond_flutter/core/widgets/inline_error_banner.dart'; import 'package:patbond_flutter/features/auth/auth_models.dart'; import 'package:patbond_flutter/features/community/community_models.dart'; import 'package:patbond_flutter/features/profile/profile_controller.dart'; import 'package:patbond_flutter/features/profile/profile_edit_page.dart'; import 'package:patbond_flutter/features/profile/profile_page.dart'; import 'package:patbond_flutter/state/app_state.dart'; import '../../helpers/auth_test_helpers.dart'; import '../../helpers/community_test_helpers.dart'; void main() { late FakeCommunityRepository community; setUp(() { community = FakeCommunityRepository(); community.onGetMyCommunityStats = () async => CommunityStats.fromJson(sampleCommunityStatsJson()); community.onGetFollowStats = (_) async => FollowStats.fromJson(sampleFollowStatsJson()); }); Future pumpProfile( WidgetTester tester, { required FakeAuthRepository auth, }) async { // 资料页头部 + 五行菜单 + 两个底部按钮,加高视口保证全在栏内。 tester.view.physicalSize = const Size(700, 2400); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.reset); final controller = ProfileController( authRepository: auth, communityRepository: community, ); addTearDown(controller.dispose); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: Scaffold( body: ProfilePage(appState: AppState(), controller: controller), ), ), ); return controller; } testWidgets('loading 态:居中转圈,无 demo 文案', (tester) async { final gate = Completer(); await pumpProfile( tester, auth: FakeAuthRepository(meHandler: () => gate.future), ); // 预取推到帧末,故先走一帧再断言。 await tester.pump(); expect(find.byType(CircularProgressIndicator), findsOneWidget); expect(find.text('萌宠新手(豆豆家长)'), findsNothing); expect(find.text('Patbond 社区创作达人'), findsNothing); gate.complete(buildProfile()); await tester.pumpAndSettle(); }); testWidgets('ready + 有昵称:展示昵称与 @username,四个数字来自服务端', (tester) async { await pumpProfile( tester, auth: FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx', nickname: '小柴'), ), ); await tester.pumpAndSettle(); expect(find.text('小柴'), findsOneWidget); expect(find.text('@llx'), findsOneWidget); // 24 关注我 / 7 我关注 / 128 获赞 / 12 作品(helpers 样本)。 expect(find.text('24'), findsOneWidget); expect(find.text('7'), findsOneWidget); expect(find.text('128'), findsOneWidget); expect(find.text('12'), findsOneWidget); expect(find.text('获赞'), findsOneWidget); expect(find.text('我的作品'), findsOneWidget); // demo 硬编码彻底退役。 expect(find.text('1.8k'), findsNothing); expect(find.text('萌宠新手(豆豆家长)'), findsNothing); }); testWidgets('ready + 无昵称:展示名回退 username(回退在客户端展示层)', (tester) async { await pumpProfile( tester, auth: FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx'), ), ); await tester.pumpAndSettle(); // 展示名与 @username 两处都是 llx。 expect(find.text('llx'), findsOneWidget); expect(find.text('@llx'), findsOneWidget); }); testWidgets('空数据:四个数字全 0(不是空白、不是错误态)', (tester) async { community.onGetMyCommunityStats = () async => CommunityStats.fromJson( sampleCommunityStatsJson(receivedLikeCount: 0, publishedPostCount: 0), ); community.onGetFollowStats = (_) async => FollowStats.fromJson( sampleFollowStatsJson(followerCount: 0, followingCount: 0), ); await pumpProfile(tester, auth: FakeAuthRepository()); await tester.pumpAndSettle(); expect(find.text('0'), findsNWidgets(4)); expect(find.text('统计加载失败'), findsNothing); }); testWidgets('error 态:横幅 + 重试;重试成功进 ready', (tester) async { var attempts = 0; await pumpProfile( tester, auth: FakeAuthRepository( meHandler: () async { attempts += 1; if (attempts == 1) throw const ApiNetworkException('断网'); return buildProfile(nickname: '小柴'); }, ), ); await tester.pumpAndSettle(); expect(find.byType(InlineErrorBanner), findsOneWidget); expect(find.text('网络异常,请检查网络后重试'), findsOneWidget); await tester.tap(find.text('重试')); await tester.pumpAndSettle(); expect(find.text('小柴'), findsOneWidget); expect(find.byType(InlineErrorBanner), findsNothing); }); testWidgets('统计块独立降级:资料照常显示,统计给「加载失败 + 重试」', (tester) async { var statsAttempts = 0; community.onGetMyCommunityStats = () async { statsAttempts += 1; if (statsAttempts == 1) throw const ApiNetworkException('断网'); return CommunityStats.fromJson(sampleCommunityStatsJson()); }; await pumpProfile( tester, auth: FakeAuthRepository( meHandler: () async => buildProfile(nickname: '小柴'), ), ); await tester.pumpAndSettle(); expect(find.text('小柴'), findsOneWidget, reason: '统计失败不该拖垮整页'); expect(find.text('统计加载失败'), findsOneWidget); await tester.tap(find.text('重试')); await tester.pumpAndSettle(); expect(find.text('128'), findsOneWidget); expect(find.text('统计加载失败'), findsNothing); }); testWidgets('点「编辑资料」进编辑页;保存成功回资料页并提示', (tester) async { final auth = FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx'), updateMeHandler: (_) async => buildProfile(username: 'llx', nickname: '小柴'), ); await pumpProfile(tester, auth: auth); await tester.pumpAndSettle(); await tester.tap(find.text('编辑资料')); await tester.pumpAndSettle(); expect(find.byType(ProfileEditPage), findsOneWidget); await tester.enterText(find.byType(TextFormField), '小柴'); await tester.pump(); await tester.tap(find.text('保存')); await tester.pumpAndSettle(); expect(find.byType(ProfileEditPage), findsNothing); expect(find.text('资料已更新'), findsOneWidget); // 头部随之更新为新昵称。 expect(find.text('小柴'), findsOneWidget); }); testWidgets('未装配上传能力时编辑页不渲染头像入口', (tester) async { await pumpProfile( tester, auth: FakeAuthRepository(meHandler: () async => buildProfile()), ); await tester.pumpAndSettle(); await tester.tap(find.text('编辑资料')); await tester.pumpAndSettle(); expect(find.text('更换头像'), findsNothing); expect(find.byType(TextFormField), findsOneWidget, reason: '昵称输入仍在'); }); }