卡片标签硬编码「本月花费」,而服务端 summary.monthlyExpense 本就返回 month
(ISO year-month,按 tz 归月)。用户看不到实际月份,所以无法自证「我这条记录
到底落在哪个月」,把正确的 ¥0 当成统计故障。
已核实不改后端聚合:用户记录落在 2026-04-09、当天是 2026-09-10,
「本月花费 ¥0」是正确行为。本单只让客户端把口径亮出来。
- health_record_display 新增纯函数 monthlyExpenseCardLabel(month, {now}):
同年「9 月花费」(窄卡一行放得下,故不取「2026-09 花费」);跨年(服务端
归月年份 ≠ 设备当前年份)「2026/12 花费」补年份消歧;串非法退回
「本月花费」不崩不显示脏值。
- 可点提示:_SummaryCard 在 onTap 非空时右上角补 chevron_right。四张卡本都
可点进明细页却无任何视觉提示(用户反馈不知道能点),提示形态沿用项目既有
可点行/卡(宠物列表卡、健康提醒卡、资料页设置行)的 chevron_right,不自创。
- 整卡包 MergeSemantics:读屏一次读全「¥128.50,9 月花费,按钮」而非两段
孤立文字。没有用 excludeSemantics——那会连带丢掉 InkWell 的可激活性。
既有测试口径调整:pet_detail_page_test 两处断言改为实际月份,且样本
monthlyExpense.month 改用当月串使断言不随年份漂移(跨年格式由纯函数单测覆盖)。
新增 integration_test/client_ux_live_test.dart(环境变量门控,默认 skip,
沿用 M3 既有 live 测试形态):compose 六容器 + 真实 App 走完三项修复——
日期选择器全中文/品牌配色/手输录入/一键今天,以及花费卡实际月份 + chevron。
本机是 Wayland 会话、X11 import -window root 取不到根窗口,改为把整棵 App 包
一层 RepaintBoundary 后 toImage() 直出真实渲染像素落 build/ux-live/。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -277,4 +277,30 @@ void main() {
|
||||
expect(reminderStatusColor(dismissed, now), AppColors.muted);
|
||||
});
|
||||
});
|
||||
|
||||
group('M3.5-03 · monthlyExpenseCardLabel(花费卡展示实际月份)', () {
|
||||
test('同年只给月份,卡片一行放得下(不再硬编码「本月花费」)', () {
|
||||
final now = DateTime(2026, 9, 10);
|
||||
expect(monthlyExpenseCardLabel('2026-09', now: now), '9 月花费');
|
||||
// 用户误录场景:记录落在 4 月,卡片就该明说是 4 月,而不是「本月」。
|
||||
expect(monthlyExpenseCardLabel('2026-04', now: now), '4 月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-12', now: now), '12 月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-01', now: now), '1 月花费');
|
||||
});
|
||||
|
||||
test('跨年(服务端归月年份 ≠ 设备当前年份)补年份消歧', () {
|
||||
// 设备已跨到 2027-01,服务端 tz 窗口仍落在 2026-12。
|
||||
final now = DateTime(2027, 1, 1);
|
||||
expect(monthlyExpenseCardLabel('2026-12', now: now), '2026/12 花费');
|
||||
});
|
||||
|
||||
test('月份串非法退回「本月花费」(不崩、不显示脏值)', () {
|
||||
final now = DateTime(2026, 9, 10);
|
||||
expect(monthlyExpenseCardLabel('', now: now), '本月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-9', now: now), '本月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-13', now: now), '本月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-00', now: now), '本月花费');
|
||||
expect(monthlyExpenseCardLabel('2026-09-01', now: now), '本月花费');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,12 +188,24 @@ void main() {
|
||||
});
|
||||
|
||||
group('T2-13 · 数据卡行接 summary', () {
|
||||
testWidgets('四卡取数:最新体重 / 疫苗进度 / 下一针 / 本月花费(tz 透传)', (tester) async {
|
||||
testWidgets('四卡取数:最新体重 / 疫苗进度 / 下一针 / 当月花费(tz 透传)', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
String? capturedTz;
|
||||
// M3.5-03:花费卡标签展示服务端归月的实际月份,故样本月份取「本月」,
|
||||
// 断言不随年份漂移(跨年格式另由 monthlyExpenseCardLabel 单测覆盖)。
|
||||
final now = DateTime.now();
|
||||
final thisMonth = '${now.year}-${now.month.toString().padLeft(2, '0')}';
|
||||
repository.getPetSummaryHandler = (petId, tz) async {
|
||||
capturedTz = tz;
|
||||
return buildSummary();
|
||||
return buildSummary(
|
||||
overrides: {
|
||||
'monthlyExpense': {
|
||||
'month': thisMonth,
|
||||
'timezone': 'Asia/Shanghai',
|
||||
'amountCents': 12850,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
await pumpDetail(tester);
|
||||
@@ -208,12 +220,45 @@ void main() {
|
||||
expect(find.text('下一针·狂犬疫苗'), findsOneWidget);
|
||||
// T2-14:月度花费卡接 monthlyExpense(12850 分 → 元展示)。
|
||||
expect(find.text('¥128.50'), findsOneWidget);
|
||||
expect(find.text('本月花费'), findsOneWidget);
|
||||
// M3.5-03:不再硬编码「本月花费」,展示实际月份便于用户自查。
|
||||
expect(find.text('${now.month} 月花费'), findsOneWidget);
|
||||
expect(find.text('本月花费'), findsNothing);
|
||||
// T2-13 遗留③:tz 透传设备时区固定偏移(月度窗口随设备时区)。
|
||||
expect(capturedTz, tzOffsetQueryValue(DateTime.now().timeZoneOffset));
|
||||
expect(capturedTz, matches(RegExp(r'^[+-]\d{2}:\d{2}$')));
|
||||
});
|
||||
|
||||
testWidgets('M3.5-03 · 四张数据卡均有 chevron 可点提示', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
final now = DateTime.now();
|
||||
final thisMonth = '${now.year}-${now.month.toString().padLeft(2, '0')}';
|
||||
repository.getPetSummaryHandler = (petId, tz) async => buildSummary(
|
||||
overrides: {
|
||||
'monthlyExpense': {
|
||||
'month': thisMonth,
|
||||
'timezone': 'Asia/Shanghai',
|
||||
'amountCents': 0,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 四张 stat 卡都挂了 onTap,此前无任何视觉提示;现统一补
|
||||
// chevron_right(与宠物列表卡 / 健康提醒卡同款)。
|
||||
for (final label in ['最新体重', '疫苗进度', '下一针·狂犬疫苗', '${now.month} 月花费']) {
|
||||
expect(
|
||||
find.ancestor(
|
||||
of: find.text(label),
|
||||
matching: find.widgetWithIcon(Card, Icons.chevron_right),
|
||||
),
|
||||
findsOneWidget,
|
||||
reason: '「$label」卡缺可点提示',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('null 语义:无登记显示空态而非 0/0', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
repository.getPetSummaryHandler = (petId, tz) async => buildSummary(
|
||||
@@ -302,12 +347,22 @@ void main() {
|
||||
});
|
||||
|
||||
group('T2-14 · 时间线入口', () {
|
||||
testWidgets('点本月花费卡 → 健康时间线页;返回后重拉摘要', (tester) async {
|
||||
testWidgets('点当月花费卡 → 健康时间线页;返回后重拉摘要', (tester) async {
|
||||
repository.getPetHandler = (petId) async => buildPet('p-1');
|
||||
var summaryCalls = 0;
|
||||
final now = DateTime.now();
|
||||
final thisMonth = '${now.year}-${now.month.toString().padLeft(2, '0')}';
|
||||
repository.getPetSummaryHandler = (petId, tz) async {
|
||||
summaryCalls++;
|
||||
return buildSummary();
|
||||
return buildSummary(
|
||||
overrides: {
|
||||
'monthlyExpense': {
|
||||
'month': thisMonth,
|
||||
'timezone': 'Asia/Shanghai',
|
||||
'amountCents': 12850,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
repository.listHealthEventsHandler = (petId, limit, cursor) async =>
|
||||
const CursorPage(items: [], nextCursor: null, hasMore: false);
|
||||
@@ -315,7 +370,7 @@ void main() {
|
||||
await pumpDetail(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('本月花费'));
|
||||
await tester.tap(find.text('${now.month} 月花费'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(HealthEventsPage), findsOneWidget);
|
||||
|
||||
Reference in New Issue
Block a user