Files
patbond-flutter/test/helpers/pet_test_helpers.dart
T
lixi 97a1f46e3f
CI / flutter-gates (push) Successful in 1m31s
重构:宠物列表/详情/编辑页接入真实数据(T2-12,四态齐备)
- 档案 Tab 替换为真实宠物列表(05 号规范 P1):PetsController 四态
  驱动(loading/empty/error+重试/ready),空态插画 + 建档 CTA,
  下拉刷新,虚线添加卡;页面一律走 T2-11 数据层、不直连 ApiClient
- 新增 PetDetailPage(P2 档案信息部分):内存副本首屏 + getPet 刷新,
  40401 防枚举 → 不存在态返回列表并刷新;仅 owner 显示编辑入口
  (40300 语义);体重/疫苗/时间线区块留待 T2-13/14 接摘要与记录接口
- 新增 PetFormPage(建档/编辑):品种目录接口 + 自定义品种互斥
  (目录失败回落自定义 + 重试)、性别契约必填、生日+估算、芯片号;
  失焦+提交双校验、错误三层模型沿用登录纵切
- 40902 版本冲突:明确提示 + 自动拉取最新 version 重提路径(有测试);
  40903 芯片号冲突字段级报错(有测试)
- 埋点挂接:pet_create_started(表单首次输入、每次进入一次)/
  succeeded / failed 三事件接线;建宠表单路由名 pet_form、详情页
  pet_detail 进入既有 RouteObserver 采集;档案 Tab 页名 pet_archive
  改报 pet_list(字典 v2);编辑表单不带路由名(漏斗到达段不掺编辑)
- app.dart 装配:pet 服务 ApiClient(:8083)共享 TokenRefresher;
  登出 reset 控制器防跨账号泄漏;PetsController.loadBreeds 按物种缓存
- AppState 清理:pets 页原 demo 消费方移除,vaccines 及其模型/持久化
  删除;pet demo 仅存首页问候/创作页/主壳头像(后续工单收敛)
- 头像按 ADR-010 本地占位,不做上传
- 测试 145 → 177 全绿(列表/详情/表单四态与冲突路径 widget 测试、
  控制器 loadBreeds/reset、展示纯函数);analyze 0;format 无 diff

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

164 lines
4.5 KiB
Dart
Raw 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.
/// pets 域测试样本(契约 openapi.yaml v1.2.0 各 schema 全字段 JSON
/// 与共享假仓库。
library;
import 'package:patbond_flutter/features/pets/pet_models.dart';
import 'package:patbond_flutter/features/pets/pets_repository.dart';
Map<String, Object?> okListEnvelope(List<Object?> data) => {
'code': 0,
'message': 'ok',
'data': data,
};
Map<String, dynamic> samplePetJson({
Map<String, Object?> overrides = const {},
}) => {
'id': 'p-1',
'name': '豆豆',
'species': 'dog',
'breedId': 'b-1',
'breedDisplayName': '柴犬',
'customBreedName': null,
'sex': 'male',
'birthDate': '2024-03-15',
'birthDateEstimated': false,
'personality': null,
'microchipNo': null,
'sterilizedOn': null,
'status': 'active',
'myRole': 'owner',
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-02T10:00:00+08:00',
'version': 3,
...overrides,
};
Map<String, dynamic> sampleWeightJson() => {
'id': 'w-1',
'petId': 'p-1',
'weightKg': 4.35,
'measuredAt': '2026-09-07T09:00:00+08:00',
'source': 'manual',
'note': null,
'createdAt': '2026-09-07T09:01:00+08:00',
};
Map<String, dynamic> sampleVaccinationJson() => {
'id': 'vx-1',
'petId': 'p-1',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'seriesKey': 'rabies-initial',
'doseNo': 1,
'doseLabel': null,
'status': 'scheduled',
'plannedOn': '2026-10-01',
'administeredOn': null,
'nextDueOn': null,
'manufacturer': null,
'batchNo': null,
'notes': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleHealthEventJson() => {
'id': 'e-1',
'petId': 'p-1',
'eventType': 'medical',
'occurredAt': '2026-09-05T14:00:00+08:00',
'title': '皮肤检查',
'notes': null,
'amountCents': 12850,
'createdByUserId': 'u-1',
'createdAt': '2026-09-05T14:05:00+08:00',
'updatedAt': '2026-09-05T14:05:00+08:00',
'version': 1,
};
Map<String, dynamic> sampleCareReminderJson() => {
'id': 'r-1',
'petId': 'p-1',
'reminderType': 'checkup',
'title': '年度体检',
'dueAt': '2026-10-01T00:00:00+08:00',
'status': 'pending',
'completedAt': null,
'createdAt': '2026-09-01T10:00:00+08:00',
'updatedAt': '2026-09-01T10:00:00+08:00',
};
Map<String, dynamic> samplePetSummaryJson() => {
'petId': 'p-1',
'latestWeight': {'weightKg': 4.35, 'measuredAt': '2026-09-07T09:00:00+08:00'},
'vaccinationProgress': {'completedDoses': 2, 'totalDoses': 3},
'nextVaccination': {
'vaccinationId': 'vx-3',
'vaccineId': 'v-1',
'vaccineName': '狂犬疫苗',
'doseNo': 3,
'doseLabel': null,
'dueOn': '2026-08-01',
'source': 'planned',
},
'monthlyExpense': {
'month': '2026-09',
'timezone': 'Asia/Shanghai',
'amountCents': 12850,
},
};
Map<String, dynamic> sampleBreedJson({
String id = 'b-1',
String species = 'dog',
String code = 'shiba',
String displayName = '柴犬',
}) => {'id': id, 'species': species, 'code': code, 'displayName': displayName};
/// 快速构造 Petwidget/controller 测试共用)。
Pet buildPet(
String id, {
String name = '豆豆',
int version = 1,
Map<String, Object?> overrides = const {},
}) => Pet.fromJson(
samplePetJson(
overrides: {'id': id, 'name': name, 'version': version, ...overrides},
),
);
/// 假仓库:各方法可注入行为;listPets 默认空列表,其余未注入的方法抛
/// UnimplementedError22 号报告 §7widget 测试注入假仓库的共享实现)。
class FakePetsRepository implements PetsRepository {
Future<List<Pet>> Function()? listPetsHandler;
Future<Pet> Function(CreatePetRequest)? createPetHandler;
Future<Pet> Function(String)? getPetHandler;
Future<Pet> Function(String, UpdatePetRequest)? updatePetHandler;
Future<List<Breed>> Function(PetSpecies?)? listBreedsHandler;
@override
Future<List<Pet>> listPets() =>
listPetsHandler?.call() ?? Future.value(const []);
@override
Future<Pet> createPet(CreatePetRequest request) => createPetHandler!(request);
@override
Future<Pet> getPet(String petId) => getPetHandler!(petId);
@override
Future<Pet> updatePet(String petId, UpdatePetRequest request) =>
updatePetHandler!(petId, request);
@override
Future<List<Breed>> listBreeds({PetSpecies? species}) =>
listBreedsHandler?.call(species) ??
Future.value([Breed.fromJson(sampleBreedJson())]);
@override
dynamic noSuchMethod(Invocation invocation) =>
throw UnimplementedError('${invocation.memberName}');
}