已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
197
apps/user_app/tool/a1_visual_test.dart
Normal file
197
apps/user_app/tool/a1_visual_test.dart
Normal file
@@ -0,0 +1,197 @@
|
||||
// 功能描述:以独立 Fixture 生成 A1 五页截图,验证多宽度与文本缩放无溢出。
|
||||
// 版本:1.0.0
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:user_app/app/dependencies.dart';
|
||||
import 'package:user_app/app/router.dart';
|
||||
import 'package:user_app/data/services/secure_session_store.dart';
|
||||
import 'package:user_app/data/services/repair_draft_store.dart';
|
||||
import 'package:user_app/ui/core/app_theme.dart';
|
||||
import '../test/support/a1_fixture.dart';
|
||||
|
||||
/// 仅截图使用的会话,不提供真实令牌或网络凭据。
|
||||
class VisualSession extends UserSession {
|
||||
VisualSession(this.signedIn) : super(SecureSessionStore());
|
||||
final bool signedIn;
|
||||
@override
|
||||
bool get isAuthenticated => signedIn;
|
||||
}
|
||||
|
||||
/// 视觉夹具不访问本机持久化,但保留生产页面的草稿入口。
|
||||
class VisualDraftStore implements RepairDraftStore {
|
||||
@override
|
||||
Future<Map<String, Object?>?> read(String owner) async => null;
|
||||
@override
|
||||
Future<void> write(String owner, Map<String, Object?> draft) async {}
|
||||
@override
|
||||
Future<void> delete(String owner) async {}
|
||||
}
|
||||
|
||||
/// 图片输出为新截图基线,不能单凭生成成功声称匹配设计稿。
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
setUpAll(() async {
|
||||
// 本文件是由 flutter test 执行的视觉测试,位置在tool目录。
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
PackageInfo.setMockInitialValues(
|
||||
appName: '瓶安芯',
|
||||
packageName: 'user_app',
|
||||
version: '1.0.0',
|
||||
buildNumber: '1',
|
||||
buildSignature: '',
|
||||
);
|
||||
final font = File('C:/Windows/Fonts/msyh.ttc');
|
||||
if (await font.exists()) {
|
||||
final loader = FontLoader('VisualChinese')
|
||||
..addFont(Future.value(ByteData.sublistView(await font.readAsBytes())));
|
||||
await loader.load();
|
||||
// Widget 测试默认使用 Ahem;组件主题中的独立文字样式也需真实字体。
|
||||
for (final family in ['Roboto', 'Ahem']) {
|
||||
final fallback = FontLoader(family)
|
||||
..addFont(Future.value(ByteData.sublistView(await font.readAsBytes())));
|
||||
await fallback.load();
|
||||
}
|
||||
}
|
||||
final icons = FontLoader('MaterialIcons')
|
||||
..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf'));
|
||||
await icons.load();
|
||||
});
|
||||
for (final (number, path) in [
|
||||
('01', '/login'),
|
||||
('03', '/home'),
|
||||
('05', '/repair'),
|
||||
('11', '/shop'),
|
||||
('12', '/products/p1'),
|
||||
('13', '/cart'),
|
||||
('14', '/cart/checkout'),
|
||||
('15', '/favorites'),
|
||||
('16', '/deposits'),
|
||||
('17', '/deposits/return?deposit=d1'),
|
||||
('18', '/gas/order'),
|
||||
('19', '/payment/gas/payment-1'),
|
||||
('20', '/orders'),
|
||||
('20-shop', '/orders?tab=shop'),
|
||||
('21', '/shop/orders/order-1'),
|
||||
('21-gas', '/gas/orders/gas-1'),
|
||||
('22', '/gas/orders/gas-1/delivery'),
|
||||
('23', '/gas/orders/gas-1/delivery/track'),
|
||||
('24', '/invoice/gas/gas-1'),
|
||||
('25', '/me'),
|
||||
('26', '/records'),
|
||||
('27', '/usage'),
|
||||
('28', '/wallet'),
|
||||
('29', '/addresses'),
|
||||
('30', '/messages'),
|
||||
('31', '/settings'),
|
||||
('31-payment', '/settings/payment-password'),
|
||||
('32', '/records/contracts'),
|
||||
('33', '/family'),
|
||||
('39', '/wallet/withdraw'),
|
||||
('40', '/wallet/banks'),
|
||||
('41', '/profile/edit'),
|
||||
('42', '/tickets/t1'),
|
||||
]) {
|
||||
for (final width in [320, 360, 390, 430]) {
|
||||
for (final scale in [1.0, 1.3]) {
|
||||
testWidgets('$number ${width}px 文本$scale', (tester) async {
|
||||
// 图30原稿是853×1873双倍像素,390宽归一化后高度约856像素。
|
||||
tester.view.physicalSize = Size(width.toDouble(), number == '30' ? 856 : 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final session = VisualSession(path != '/login');
|
||||
final router = createRouter(
|
||||
AppDependencies(
|
||||
session: session,
|
||||
repository: A1FixtureRepository(),
|
||||
repairDraftStore: VisualDraftStore(),
|
||||
),
|
||||
initialLocation: path,
|
||||
);
|
||||
final theme = AppTheme.light();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp.router(
|
||||
routerConfig: router,
|
||||
theme: theme.copyWith(
|
||||
textTheme: theme.textTheme.apply(fontFamily: 'VisualChinese'),
|
||||
appBarTheme: theme.appBarTheme.copyWith(
|
||||
titleTextStyle: theme.appBarTheme.titleTextStyle?.copyWith(
|
||||
fontFamily: 'VisualChinese',
|
||||
),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: theme.filledButtonTheme.style?.copyWith(
|
||||
textStyle: WidgetStatePropertyAll(
|
||||
theme.textTheme.labelLarge?.copyWith(fontFamily: 'VisualChinese'),
|
||||
),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: theme.textButtonTheme.style?.copyWith(
|
||||
textStyle: WidgetStatePropertyAll(
|
||||
theme.textTheme.labelLarge?.copyWith(fontFamily: 'VisualChinese'),
|
||||
),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: theme.outlinedButtonTheme.style?.copyWith(
|
||||
textStyle: WidgetStatePropertyAll(
|
||||
theme.textTheme.labelLarge?.copyWith(fontFamily: 'VisualChinese'),
|
||||
),
|
||||
),
|
||||
),
|
||||
chipTheme: theme.chipTheme.copyWith(
|
||||
labelStyle: theme.chipTheme.labelStyle?.copyWith(fontFamily: 'VisualChinese'),
|
||||
),
|
||||
),
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(scale)),
|
||||
child: RepaintBoundary(key: const ValueKey('capture'), child: child!),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
if (number == '17') {
|
||||
// 产品稿展示用户已逐项确认的状态,视觉样本先完成同样的真实交互。
|
||||
final checkboxCount = find.byType(Checkbox).evaluate().length;
|
||||
for (var index = 0; index < checkboxCount; index++) {
|
||||
await tester.tap(find.byType(Checkbox).at(index));
|
||||
await tester.pump();
|
||||
}
|
||||
}
|
||||
if (number == '18') {
|
||||
// 产品稿默认选择15kg一只;通过真实“增加”按钮进入相同视觉状态。
|
||||
await tester.tap(find.byTooltip('增加').last);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
if (number == '39') {
|
||||
await tester.enterText(find.byKey(const Key('withdrawal-amount')), '300');
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
await expectLater(
|
||||
find.byKey(const ValueKey('capture')),
|
||||
matchesGoldenFile(
|
||||
Uri.file(
|
||||
'${Directory.current.path}/../../docs/视觉验收/A1/${number}_${width}_$scale.png',
|
||||
),
|
||||
),
|
||||
);
|
||||
// 验证滚动到底部后仍无文字、按钮或导航溢出。
|
||||
final list = find.byType(Scrollable).first;
|
||||
if (list.evaluate().isNotEmpty) {
|
||||
await tester.drag(list, const Offset(0, -1600));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
router.dispose();
|
||||
session.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user