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/features/auth/auth_models.dart'; import 'package:patbond_flutter/features/community/community_controller.dart'; import 'package:patbond_flutter/features/home/home_page.dart'; import 'package:patbond_flutter/features/profile/profile_controller.dart'; import 'package:patbond_flutter/state/app_state.dart'; import '../../helpers/auth_test_helpers.dart'; import '../../helpers/community_test_helpers.dart'; /// T3.5-10 首页问候语真实化。 /// /// ADR-022 决策 D3.5-1:本迭代**只做问候语**,天气 / 位置 / 圈子 / 促销卡 /// 刻意保留为 demo 占位——本文件同时钉住「保留项仍在」,避免后续有人以 /// 「顺手清理」为名越出拍板范围(真要动它们得先改 ADR)。 void main() { late FakeCommunityRepository community; late CommunityController feed; setUp(() { community = FakeCommunityRepository(); community.onFeed = (_, _) async => feedPage(const []); feed = CommunityController(repository: community); }); Future pumpHome( WidgetTester tester, { required FakeAuthRepository auth, }) async { tester.view.physicalSize = const Size(700, 1600); tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.reset); final profile = ProfileController( authRepository: auth, communityRepository: community, ); addTearDown(profile.dispose); await tester.pumpWidget( MaterialApp( theme: buildAppTheme(), home: Scaffold( body: HomePage( appState: AppState(), communityController: feed, profileController: profile, onOpenServices: (_) {}, onOpenCompose: () {}, ), ), ), ); return profile; } /// 当前时段问候前缀(与页面实现同一套阈值;避免测试跟着挂钟漂移)。 String greetingPrefix([DateTime? now]) { final hour = (now ?? DateTime.now()).hour; if (hour < 6) return '夜深了'; if (hour < 11) return '早上好'; if (hour < 14) return '中午好'; if (hour < 18) return '下午好'; return '晚上好'; } testWidgets('有昵称:问候语用昵称,demo 宠物名「豆豆」不再出现', (tester) async { final profile = await pumpHome( tester, auth: FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx', nickname: '小柴'), ), ); await profile.refresh(); await tester.pumpAndSettle(); expect(find.text('${greetingPrefix()},小柴 👋'), findsOneWidget); expect(find.textContaining('豆豆'), findsNothing); }); testWidgets('无昵称:问候语回退 username(与资料页同规则)', (tester) async { final profile = await pumpHome( tester, auth: FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx'), ), ); await profile.refresh(); await tester.pumpAndSettle(); expect(find.text('${greetingPrefix()},llx 👋'), findsOneWidget); }); testWidgets('资料未到手:只问候不称名(不编造假名)', (tester) async { await pumpHome( tester, auth: FakeAuthRepository( meHandler: () async => throw const ApiNetworkException('断网'), ), ); await tester.pumpAndSettle(); expect(find.text('${greetingPrefix()} 👋'), findsOneWidget); // 不出现「问候,某某」形态(更不会退回 demo 宠物名)。 expect(find.textContaining('${greetingPrefix()},'), findsNothing); }); testWidgets('改昵称后首页同步(与资料页共用同一控制器,无需手动刷新)', (tester) async { final auth = FakeAuthRepository( meHandler: () async => buildProfile(username: 'llx'), updateMeHandler: (_) async => buildProfile(username: 'llx', nickname: '小柴'), ); final profile = await pumpHome(tester, auth: auth); await profile.refresh(); await tester.pumpAndSettle(); expect(find.text('${greetingPrefix()},llx 👋'), findsOneWidget); await profile.save( const UpdateMeRequest(nickname: PatchField.value('小柴')), ); await tester.pumpAndSettle(); expect(find.text('${greetingPrefix()},小柴 👋'), findsOneWidget); }); testWidgets('刻意保留的 demo 占位仍在(ADR-022 钉死的范围)', (tester) async { final profile = await pumpHome( tester, auth: FakeAuthRepository(meHandler: () async => buildProfile()), ); await profile.refresh(); await tester.pumpAndSettle(); // 天气 / 位置(外部服务未接)。 expect(find.text('北京 · 朝阳区'), findsOneWidget); expect(find.text('28°C 晴'), findsOneWidget); // 圈子 = 话题(ADR-018 剪出)。 expect(find.text('柴犬圈'), findsOneWidget); // 促销卡(M5 服务域)。 expect(find.text('新用户首单立减 ¥20'), findsOneWidget); }); }