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