694543647f
CI / flutter-gates (push) Failing after 1s
- 「下午好,豆豆」改真实展示名(`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>
145 lines
5.1 KiB
Dart
145 lines
5.1 KiB
Dart
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);
|
||
});
|
||
}
|