Files
lixi 694543647f
CI / flutter-gates (push) Failing after 1s
新增:首页问候语真实化 + 刻意保留的 demo 占位显式登记(T3.5-10,ADR-022)
- 「下午好,豆豆」改真实展示名(`nickname ?? username`,与资料页共用同一个
  ProfileController:一次 /me 供两个消费点,改昵称后两处一起变)。资料未到手
  时退化为不称名的「下午好 👋」,不编造假名。
- 按 ADR-022 决策 D3.5-1 钉死范围,**其余首页 demo 一律不动**,并在代码里逐项
  标注为「刻意保留的 demo 占位」:天气/位置(需接外部服务)、圈子=话题
  (ADR-018 剪出)、促销卡(M5 服务域)、搜索与本地服务段。
  widget 测试同时钉住「保留项仍在」,避免后续以「顺手清理」越出拍板范围。
- 新增 compose 真链路桌面实测脚本 `integration_test/profile_avatar_live_test.dart`
  (默认跳过,`PATBOND_PROFILE_LIVE=1 ... -d linux` 开启):UI 注册 → 登录 →
  资料真实化 → 设昵称 → 传用户头像 → Feed 作者名同步 → 宠物头像,逐步截图。
  只有选图与压缩两层是桌面替身,createUpload / 预签名 PUT / confirm / PATCH
  全为生产实现。两处实测坑写进注释:Feed 作者名有服务端 60s 缓存滞后;
  1×1 的极小图能上传能下载但 Flutter 解码器拒绝,会被误判成「没传上」。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-11 15:37:48 +08:00

145 lines
5.1 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<ProfileController> 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<String>.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);
});
}