新增:档案页共享组件三件 + TagPill 深变体映射(DEBT-1 偿还)
- PetAvatar 四尺寸档(96/64/44/32)收敛头像重复实现:3px 白描边 + 轻投影,编辑徽标底改 primaryStrong(白图标 4.49:1 达非文字 3:1), M2 无上传(ADR-010),url 缺省渲染本地占位形态 - RecordTypeDot 三尺寸档 + 五类记录类型的图标/三色映射唯一出口 (05 号规范 §2 表,供 T2-13/14 时间线复用) - EmptyStateIllustration 空态插画区(112 圆 + 标题 + 说明 + 可选 CTA) - TagPill 增可选 inkColor,缺省按 color 查深变体映射 (primary→primaryDark 8.74:1 / success→successInk 7.39:1 / accent→accentDark 7.07:1 / error→errorDark 5.78:1 / 未命中→ink), 既有调用零参数回归 - AppColors 新增 errorDark #B02C25、inkSoft #6B5A4A(05 号规范 D8) flutter test 141/141 全绿;analyze 0 问题(T2-12 第一部分) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:patbond_flutter/core/widgets/empty_state_illustration.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('渲染插画 / 标题 / 说明 / CTA,点击 CTA 回调', (tester) async {
|
||||
var taps = 0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: EmptyStateIllustration(
|
||||
icon: Icons.pets,
|
||||
title: '还没有宠物档案',
|
||||
description: '添加毛孩子,开始记录 TA 的健康点滴',
|
||||
ctaLabel: '添加宠物',
|
||||
onCtaPressed: () => taps++,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byIcon(Icons.pets), findsOneWidget);
|
||||
expect(find.text('还没有宠物档案'), findsOneWidget);
|
||||
expect(find.text('添加毛孩子,开始记录 TA 的健康点滴'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('添加宠物'));
|
||||
expect(taps, 1);
|
||||
});
|
||||
|
||||
testWidgets('无 CTA / 无说明时不渲染对应区块', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: EmptyStateIllustration(
|
||||
icon: Icons.event_note_outlined,
|
||||
title: '暂无体重记录',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('暂无体重记录'), findsOneWidget);
|
||||
expect(find.byType(FilledButton), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||||
import 'package:patbond_flutter/core/widgets/pet_avatar.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> pump(WidgetTester tester, Widget child) {
|
||||
return tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(body: Center(child: child)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('四档尺寸与规格一致(96/64/44/32)', (tester) async {
|
||||
expect(PetAvatarSize.xl.dimension, 96);
|
||||
expect(PetAvatarSize.lg.dimension, 64);
|
||||
expect(PetAvatarSize.md.dimension, 44);
|
||||
expect(PetAvatarSize.sm.dimension, 32);
|
||||
|
||||
await pump(tester, const PetAvatar(size: PetAvatarSize.xl));
|
||||
final size = tester.getSize(find.byType(PetAvatar));
|
||||
expect(size.width, 96);
|
||||
expect(size.height, 96);
|
||||
});
|
||||
|
||||
testWidgets('无 url 渲染本地占位形态(ADR-010:pets 图标)', (tester) async {
|
||||
await pump(tester, const PetAvatar(size: PetAvatarSize.lg));
|
||||
final icon = tester.widget<Icon>(find.byIcon(Icons.pets));
|
||||
expect(icon.color, AppColors.muted);
|
||||
expect(find.byType(Image), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('xl 档编辑徽标为 primaryStrong 底白图标(无障碍修订)', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
const PetAvatar(size: PetAvatarSize.xl, showEditBadge: true),
|
||||
);
|
||||
final badgeIcon = tester.widget<Icon>(find.byIcon(Icons.edit));
|
||||
expect(badgeIcon.color, Colors.white);
|
||||
final badge = tester.widget<Container>(
|
||||
find.ancestor(
|
||||
of: find.byIcon(Icons.edit),
|
||||
matching: find.byType(Container),
|
||||
),
|
||||
);
|
||||
final decoration = badge.decoration as BoxDecoration;
|
||||
expect(decoration.color, AppColors.primaryStrong);
|
||||
});
|
||||
|
||||
testWidgets('sm/md 档不渲染编辑徽标', (tester) async {
|
||||
await pump(
|
||||
tester,
|
||||
const PetAvatar(size: PetAvatarSize.sm, showEditBadge: true),
|
||||
);
|
||||
expect(find.byIcon(Icons.edit), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('onTap 生效且禁用态不响应', (tester) async {
|
||||
var taps = 0;
|
||||
await pump(tester, PetAvatar(size: PetAvatarSize.lg, onTap: () => taps++));
|
||||
await tester.tap(find.byType(PetAvatar));
|
||||
expect(taps, 1);
|
||||
|
||||
await pump(
|
||||
tester,
|
||||
PetAvatar(size: PetAvatarSize.lg, enabled: false, onTap: () => taps++),
|
||||
);
|
||||
await tester.tap(find.byType(PetAvatar));
|
||||
expect(taps, 1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||||
import 'package:patbond_flutter/core/widgets/record_type_dot.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('五类映射齐备:图标 + 双色(05 §2 表)', (tester) async {
|
||||
// 映射表唯一出口:五类各有图标、底色、图标色、文字色、文案。
|
||||
expect(recordTypeStyles.length, RecordType.values.length);
|
||||
expect(
|
||||
recordTypeStyles[RecordType.weight]!.iconColor,
|
||||
AppColors.primaryStrong,
|
||||
);
|
||||
expect(
|
||||
recordTypeStyles[RecordType.weight]!.inkColor,
|
||||
AppColors.primaryDark,
|
||||
);
|
||||
expect(
|
||||
recordTypeStyles[RecordType.vaccine]!.inkColor,
|
||||
AppColors.successInk,
|
||||
);
|
||||
expect(
|
||||
recordTypeStyles[RecordType.deworming]!.inkColor,
|
||||
AppColors.accentDark,
|
||||
);
|
||||
expect(recordTypeStyles[RecordType.medical]!.inkColor, AppColors.errorDark);
|
||||
expect(recordTypeStyles[RecordType.other]!.inkColor, AppColors.inkSoft);
|
||||
expect(recordTypeStyles[RecordType.medical]!.label, '就医');
|
||||
});
|
||||
|
||||
testWidgets('圆标渲染:尺寸档正确、图标为 dot 的 50%、底为基色 8% 淡染', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: RecordTypeDot(
|
||||
type: RecordType.vaccine,
|
||||
size: RecordTypeDotSize.md,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final size = tester.getSize(find.byType(RecordTypeDot));
|
||||
expect(size.width, 40);
|
||||
expect(size.height, 40);
|
||||
|
||||
final icon = tester.widget<Icon>(find.byIcon(Icons.vaccines_outlined));
|
||||
expect(icon.size, 20);
|
||||
expect(icon.color, AppColors.successInk);
|
||||
|
||||
final container = tester.widget<Container>(
|
||||
find.descendant(
|
||||
of: find.byType(RecordTypeDot),
|
||||
matching: find.byType(Container),
|
||||
),
|
||||
);
|
||||
final decoration = container.decoration as BoxDecoration;
|
||||
expect(decoration.color, AppColors.success.withAlpha(20));
|
||||
expect(decoration.shape, BoxShape.circle);
|
||||
});
|
||||
|
||||
testWidgets('三档尺寸 24/40/56', (tester) async {
|
||||
expect(RecordTypeDotSize.sm.dimension, 24);
|
||||
expect(RecordTypeDotSize.md.dimension, 40);
|
||||
expect(RecordTypeDotSize.lg.dimension, 56);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:patbond_flutter/core/theme/app_theme.dart';
|
||||
import 'package:patbond_flutter/widgets/common.dart';
|
||||
|
||||
/// DEBT-1 偿还回归(05 号规范 §5.3):TagPill 文字色改深变体映射,
|
||||
/// 底色维持 8% 淡染;既有零参数调用仅视觉变深、不改布局。
|
||||
void main() {
|
||||
Future<Text> pumpPill(WidgetTester tester, TagPill pill) async {
|
||||
await tester.pumpWidget(MaterialApp(home: Scaffold(body: pill)));
|
||||
return tester.widget<Text>(find.text(pill.label));
|
||||
}
|
||||
|
||||
testWidgets('默认色(primary)文字映射为 primaryDark', (tester) async {
|
||||
final text = await pumpPill(tester, const TagPill('标签'));
|
||||
expect(text.style?.color, AppColors.primaryDark);
|
||||
});
|
||||
|
||||
testWidgets('success → successInk、accent → accentDark、error → errorDark', (
|
||||
tester,
|
||||
) async {
|
||||
final success = await pumpPill(
|
||||
tester,
|
||||
const TagPill('成功', color: AppColors.success),
|
||||
);
|
||||
expect(success.style?.color, AppColors.successInk);
|
||||
|
||||
final accent = await pumpPill(
|
||||
tester,
|
||||
const TagPill('强调', color: AppColors.accent),
|
||||
);
|
||||
expect(accent.style?.color, AppColors.accentDark);
|
||||
|
||||
final error = await pumpPill(
|
||||
tester,
|
||||
const TagPill('错误', color: AppColors.error),
|
||||
);
|
||||
expect(error.style?.color, AppColors.errorDark);
|
||||
});
|
||||
|
||||
testWidgets('未命中映射的自定义色兜底为 ink', (tester) async {
|
||||
final text = await pumpPill(
|
||||
tester,
|
||||
const TagPill('自定义', color: Color(0xFF123456)),
|
||||
);
|
||||
expect(text.style?.color, AppColors.ink);
|
||||
});
|
||||
|
||||
testWidgets('inkColor 显式指定时优先于内置映射', (tester) async {
|
||||
final text = await pumpPill(
|
||||
tester,
|
||||
const TagPill('指定', color: AppColors.success, inkColor: AppColors.ink),
|
||||
);
|
||||
expect(text.style?.color, AppColors.ink);
|
||||
});
|
||||
|
||||
testWidgets('底色维持基色 8% 淡染不变', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(home: Scaffold(body: TagPill('底色'))),
|
||||
);
|
||||
final box = tester.widget<DecoratedBox>(
|
||||
find.ancestor(of: find.text('底色'), matching: find.byType(DecoratedBox)),
|
||||
);
|
||||
final decoration = box.decoration as BoxDecoration;
|
||||
expect(decoration.color, AppColors.primary.withAlpha(20));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user