Files
platforms/apps/user_app/tool/a1_visual_test.dart

198 lines
7.7 KiB
Dart
Raw Permalink Normal View History

// 功能描述:以独立 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();
});
}
}
}
}