Files
Aq-Accounting-Flutter/test/watermark_layout_test.dart
T
2026-09-28 09:59:43 +08:00

168 lines
5.5 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:qiangbao_accounting/api/geo_api.dart';
import 'package:qiangbao_accounting/api/photo_api.dart';
import 'package:qiangbao_accounting/pages/watermark/watermark_camera_page.dart';
import 'package:qiangbao_accounting/pages/watermark/watermark_edit_page.dart';
/// 水印相机的页面布局回归测试。
///
/// 价值在之前抓过真 bug:记账首页顶部一行在 393 宽的常规手机上溢出 32px,
/// 光看代码看不出来。
///
/// 这里只断言「不溢出、不抛异常」,具体样式靠人眼验收。
///
/// 两个测试上的坑(踩过):
/// 1. 页面上的加载圈是**无限动画**,`pumpAndSettle` 永远等不到静止 → 超时。
/// 所以编辑页只能手动 pump。
/// 2. 读文件/解码图片是**真实异步 I/O**,得用 `tester.runAsync` 才跑得完。
/// 假定位:不发网络请求,直接返回一份完整快照
class _FakeGeoApi extends GeoApi {
@override
Future<GeoSnapshot> resolve() async => const GeoSnapshot(
latitude: 25.041234,
longitude: 102.712345,
address: '云南省昆明市五华区莲花池正街某小区某栋某单元',
weather: '晴',
temperature: '25℃',
);
}
/// 定位失败:页面仍应可用,只是少了地点那一行
class _FailingGeoApi extends GeoApi {
@override
Future<GeoSnapshot> resolve() async => const GeoSnapshot(error: '没有定位权限');
}
/// 假上传:立刻成功,避免测试里真的打网络
class _FakePhotoApi extends PhotoApi {
_FakePhotoApi() : super(Dio());
@override
Future<int?> upload({
required String filePath,
double? latitude,
double? longitude,
String? address,
String? weather,
String? remark,
DateTime? takenTime,
}) async =>
1;
}
void main() {
const sizes = [
('iPhone SE 窄屏', Size(320, 568)),
('常见安卓机', Size(393, 873)),
('大屏', Size(480, 1000)),
];
void useSize(WidgetTester tester, Size size) {
tester.view.physicalSize = size * 3;
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.reset);
}
/// 造一张真实的小图(编辑页要读它的文件头拿尺寸)
File makePhoto(WidgetTester tester) {
final dir = Directory.systemTemp.createTempSync('wm_layout_test');
addTearDown(() {
// Image.file 可能还占着文件句柄,删不掉就算了,别让清理把测试搞挂
try {
dir.deleteSync(recursive: true);
} catch (_) {}
});
return File('${dir.path}/photo.png')
..writeAsBytesSync(img.encodePng(img.Image(width: 120, height: 90)));
}
/// 等真实 I/O 跑完再渲染一帧
Future<void> settleIo(WidgetTester tester) async {
await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 300));
});
await tester.pump();
await tester.pump();
}
for (final (label, size) in sizes) {
testWidgets('入口页 $label 不溢出', (tester) async {
useSize(tester, size);
await tester.pumpWidget(
const ProviderScope(child: MaterialApp(home: WatermarkCameraPage())),
);
await tester.pumpAndSettle();
expect(find.text('水印相机'), findsOneWidget);
expect(find.text('拍照'), findsOneWidget);
expect(find.text('从相册选'), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('编辑页 $label 不溢出', (tester) async {
useSize(tester, size);
final photo = makePhoto(tester);
await tester.pumpWidget(
ProviderScope(
overrides: [
geoApiProvider.overrideWithValue(_FakeGeoApi()),
photoApiProvider.overrideWithValue(_FakePhotoApi()),
],
child: MaterialApp(home: WatermarkEditPage(imagePath: photo.path)),
),
);
await settleIo(tester);
expect(find.text('加水印'), findsOneWidget);
expect(find.text('保存'), findsOneWidget);
// 假定位的地址应该显示出来了(顺带验证异步链路是通的)
expect(find.textContaining('昆明市'), findsOneWidget);
expect(tester.takeException(), isNull);
});
}
testWidgets('设置弹层在窄屏也不溢出', (tester) async {
useSize(tester, const Size(320, 568));
await tester.pumpWidget(
const ProviderScope(child: MaterialApp(home: WatermarkCameraPage())),
);
await tester.pumpAndSettle();
// 点右上角进设置
await tester.tap(find.byIcon(Icons.tune));
await tester.pumpAndSettle();
expect(find.text('水印设置'), findsOneWidget);
expect(find.text('天气温度'), findsOneWidget);
expect(tester.takeException(), isNull);
});
testWidgets('定位失败时编辑页仍可用(只是少一行地点)', (tester) async {
useSize(tester, const Size(393, 873));
final photo = makePhoto(tester);
await tester.pumpWidget(
ProviderScope(
overrides: [
geoApiProvider.overrideWithValue(_FailingGeoApi()),
photoApiProvider.overrideWithValue(_FakePhotoApi()),
],
child: MaterialApp(home: WatermarkEditPage(imagePath: photo.path)),
),
);
await settleIo(tester);
expect(find.textContaining('没有定位权限'), findsOneWidget);
expect(tester.takeException(), isNull);
});
}