- VaccinationFormPage:目录按宠物物种过滤(四态)、seriesKey 目录 code
预填、scheduled/completed 状态-日期规则结构化 + 纯函数双重前端拦截,
42201/40904 后端兜底横幅(均有测试)
- VaccinationRecordsPage:按系列分组、三态 TagPill(含 cancelled 留痕)、
四态齐备;viewer 隐藏登记入口
- 档案页数据卡行接 GET /pets/{id}/summary 实时聚合:最新体重/疫苗进度/
下一针三卡,null 语义为空态文案而非 0/0;从记录页返回即重拉摘要
- health_record 埋点装配(app→shell→pets→detail→记录页面族)
- 测试 205 → 224(+19)全绿;analyze 0 问题;format 无 diff
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import 'package:patbond_flutter/features/pets/pet_exceptions.dart';
|
||||
import 'package:patbond_flutter/features/pets/pet_form_page.dart';
|
||||
import 'package:patbond_flutter/features/pets/pet_models.dart';
|
||||
import 'package:patbond_flutter/features/pets/pets_controller.dart';
|
||||
import 'package:patbond_flutter/features/pets/vaccination_records_page.dart';
|
||||
import 'package:patbond_flutter/features/pets/weight_records_page.dart';
|
||||
|
||||
import '../../helpers/pet_test_helpers.dart';
|
||||
|
||||
@@ -22,6 +24,10 @@ void main() {
|
||||
});
|
||||
|
||||
Future<void> pumpDetail(WidgetTester tester, {String petId = 'p-1'}) async {
|
||||
// 详情页自 T2-13 增加健康数据卡行,加高视口保证底部按钮在栏内。
|
||||
tester.view.physicalSize = const Size(700, 1800);
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: buildAppTheme(),
|
||||
@@ -176,4 +182,114 @@ void main() {
|
||||
expect(find.text('编辑宠物资料'), findsOneWidget);
|
||||
expect(find.text('豆豆'), findsOneWidget);
|
||||
});
|
||||
|
||||
group('T2-13 · 数据卡行接 summary', () {
|
||||
testWidgets('三卡取数:最新体重 / 疫苗进度 / 下一针(含疫苗名)', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
String? capturedTz;
|
||||
repository.getPetSummaryHandler = (petId, tz) async {
|
||||
capturedTz = tz;
|
||||
return buildSummary();
|
||||
};
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('健康数据'), findsOneWidget);
|
||||
expect(find.text('4.35 kg'), findsOneWidget);
|
||||
expect(find.text('最新体重'), findsOneWidget);
|
||||
expect(find.text('2/3 针'), findsOneWidget);
|
||||
expect(find.text('疫苗进度'), findsOneWidget);
|
||||
expect(find.text('2026-08-01'), findsOneWidget);
|
||||
expect(find.text('下一针·狂犬疫苗'), findsOneWidget);
|
||||
// 本单不消费 monthlyExpense(T2-14),tz 不传走服务端缺省 UTC。
|
||||
expect(capturedTz, isNull);
|
||||
});
|
||||
|
||||
testWidgets('null 语义:无登记显示空态而非 0/0', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
repository.getPetSummaryHandler = (petId, tz) async => buildSummary(
|
||||
overrides: {
|
||||
'latestWeight': null,
|
||||
'vaccinationProgress': null,
|
||||
'nextVaccination': null,
|
||||
},
|
||||
);
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('暂无记录'), findsOneWidget);
|
||||
expect(find.text('未登记'), findsOneWidget);
|
||||
expect(find.text('暂无安排'), findsOneWidget);
|
||||
expect(find.textContaining('0/0'), findsNothing);
|
||||
expect(find.text('下一针'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('摘要加载失败:档案主链路不受阻,行内重试恢复', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
var calls = 0;
|
||||
repository.getPetSummaryHandler = (petId, tz) async {
|
||||
calls++;
|
||||
if (calls == 1) throw const ApiNetworkException('断网');
|
||||
return buildSummary();
|
||||
};
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 基本资料照常渲染,摘要行内错误 + 重试。
|
||||
expect(find.text('豆豆'), findsOneWidget);
|
||||
expect(find.text('健康数据加载失败'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('重试'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('4.35 kg'), findsOneWidget);
|
||||
expect(find.text('健康数据加载失败'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('点体重卡 → 体重历史页;返回后重拉摘要', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
var summaryCalls = 0;
|
||||
repository.getPetSummaryHandler = (petId, tz) async {
|
||||
summaryCalls++;
|
||||
return buildSummary();
|
||||
};
|
||||
repository.listWeightsHandler = (petId, limit, cursor) async =>
|
||||
const CursorPage(items: [], nextCursor: null, hasMore: false);
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('最新体重'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(WeightRecordsPage), findsOneWidget);
|
||||
expect(find.text('体重记录'), findsOneWidget);
|
||||
|
||||
await tester.pageBack();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(summaryCalls, 2);
|
||||
});
|
||||
|
||||
testWidgets('点疫苗卡 → 疫苗记录页(viewer 权限透传隐藏登记入口)', (tester) async {
|
||||
repository.getPetHandler = (petId) async =>
|
||||
buildPet('p-1', overrides: {'myRole': 'viewer'});
|
||||
repository.getPetSummaryHandler = (petId, tz) async => buildSummary();
|
||||
repository.listVaccinationsHandler = (petId) async => const [];
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('疫苗进度'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(VaccinationRecordsPage), findsOneWidget);
|
||||
// viewer:空态无 CTA、AppBar 无添加入口。
|
||||
expect(find.text('登记第一针'), findsNothing);
|
||||
expect(find.byIcon(Icons.add), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user