水印相机前端
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user