209 lines
6.5 KiB
Dart
209 lines
6.5 KiB
Dart
|
|
/// 封面/宣传图裁剪页 widget 测试(crop_your_image 回调式 API)。
|
|||
|
|
///
|
|||
|
|
/// 真实异步边界(源文件读取 + 包内 parse/crop 的 compute + 编码落盘
|
|||
|
|
/// Isolate.run)在 testWidgets 的 FakeAsync 下不会自行推进——参照
|
|||
|
|
/// crop_your_image 自身测试的驱动方式:tap + 等待都包进 tester.runAsync,
|
|||
|
|
/// 用真实 Future.delayed 等 isolate 完成。
|
|||
|
|
library;
|
|||
|
|
|
|||
|
|
import 'dart:io';
|
|||
|
|
|
|||
|
|
import 'package:flutter/material.dart';
|
|||
|
|
import 'package:flutter_test/flutter_test.dart';
|
|||
|
|
import 'package:image/image.dart' as img;
|
|||
|
|
import 'package:photo_live/l10n/app_localizations.dart';
|
|||
|
|
import 'package:photo_live/pages/image_crop_page.dart';
|
|||
|
|
|
|||
|
|
void main() {
|
|||
|
|
late final Directory tmp;
|
|||
|
|
late final String fixtureJpg;
|
|||
|
|
late final String fixtureGif;
|
|||
|
|
|
|||
|
|
setUpAll(() {
|
|||
|
|
tmp = Directory.systemTemp.createTempSync('image_crop_page_test');
|
|||
|
|
// 320x240(4:3)JPG 夹具:运行时生成,避免仓库二进制资产。
|
|||
|
|
final source = img.Image(width: 320, height: 240, numChannels: 3);
|
|||
|
|
img.fill(source, color: img.ColorRgb8(80, 120, 200));
|
|||
|
|
fixtureJpg = '${tmp.path}${Platform.pathSeparator}fixture.jpg';
|
|||
|
|
File(fixtureJpg).writeAsBytesSync(img.encodeJpg(source, quality: 90));
|
|||
|
|
// GIF 夹具:包内 formatDetector 兜底按 png 解码必然失败挂起,
|
|||
|
|
// 页面预检须直接判不支持(防 never-ready)。
|
|||
|
|
fixtureGif = '${tmp.path}${Platform.pathSeparator}fixture.gif';
|
|||
|
|
File(fixtureGif).writeAsBytesSync(img.encodeGif(source));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
tearDownAll(() {
|
|||
|
|
tmp.deleteSync(recursive: true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/// 打开裁剪页(zh 本地化);onPopped 接住 pop 结果。
|
|||
|
|
Future<void> openCropPage(
|
|||
|
|
WidgetTester tester,
|
|||
|
|
String sourcePath, {
|
|||
|
|
required double aspectRatio,
|
|||
|
|
int? maxOutputWidth,
|
|||
|
|
void Function(String? value)? onPopped,
|
|||
|
|
}) async {
|
|||
|
|
await tester.pumpWidget(
|
|||
|
|
MaterialApp(
|
|||
|
|
locale: const Locale('zh'),
|
|||
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|||
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|||
|
|
home: Scaffold(
|
|||
|
|
body: Builder(
|
|||
|
|
builder: (context) => Center(
|
|||
|
|
child: FloatingActionButton(
|
|||
|
|
onPressed: () async {
|
|||
|
|
final value = await Navigator.of(context).push<String>(
|
|||
|
|
MaterialPageRoute(
|
|||
|
|
builder: (_) => ImageCropPage(
|
|||
|
|
sourcePath: sourcePath,
|
|||
|
|
aspectRatio: aspectRatio,
|
|||
|
|
maxOutputWidth: maxOutputWidth,
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
onPopped?.call(value);
|
|||
|
|
},
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
),
|
|||
|
|
);
|
|||
|
|
await tester.runAsync(() async {
|
|||
|
|
await tester.tap(find.byType(FloatingActionButton));
|
|||
|
|
await tester.pump();
|
|||
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|||
|
|
// 真实异步必须「等→pump→再等→再 pump」:读取完成后经 pump 才构建
|
|||
|
|
// Crop(parse compute 此刻才启动),需再等真实时间让 isolate 回复落地。
|
|||
|
|
await Future.delayed(const Duration(seconds: 2));
|
|||
|
|
await tester.pump();
|
|||
|
|
await Future.delayed(const Duration(seconds: 2));
|
|||
|
|
await tester.pump();
|
|||
|
|
});
|
|||
|
|
await tester.pump();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
testWidgets('取消 → pop(null)', (tester) async {
|
|||
|
|
String? popped = 'sentinel';
|
|||
|
|
await openCropPage(
|
|||
|
|
tester,
|
|||
|
|
fixtureJpg,
|
|||
|
|
aspectRatio: 16 / 9,
|
|||
|
|
onPopped: (value) => popped = value,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await tester.runAsync(() async {
|
|||
|
|
await tester.tap(find.text('取消'));
|
|||
|
|
await tester.pump();
|
|||
|
|
});
|
|||
|
|
await tester.pumpAndSettle();
|
|||
|
|
|
|||
|
|
expect(popped, isNull);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
testWidgets('确认 → pop 出存在的 .jpg 路径', (tester) async {
|
|||
|
|
String? popped;
|
|||
|
|
await openCropPage(
|
|||
|
|
tester,
|
|||
|
|
fixtureJpg,
|
|||
|
|
aspectRatio: 16 / 9,
|
|||
|
|
maxOutputWidth: 1920,
|
|||
|
|
onPopped: (value) => popped = value,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await tester.runAsync(() async {
|
|||
|
|
await tester.tap(find.text('完成'));
|
|||
|
|
await tester.pump();
|
|||
|
|
// crop(compute)→ 编码落盘(Isolate.run)→ pop。
|
|||
|
|
await Future.delayed(const Duration(seconds: 5));
|
|||
|
|
await tester.pump();
|
|||
|
|
});
|
|||
|
|
await tester.pumpAndSettle();
|
|||
|
|
|
|||
|
|
expect(popped, isNotNull);
|
|||
|
|
expect(popped!.endsWith('.jpg'), isTrue);
|
|||
|
|
expect(File(popped!).existsSync(), isTrue);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
testWidgets('确认 → 输出尊重 maxOutputWidth 且保持纵横比', (tester) async {
|
|||
|
|
String? popped;
|
|||
|
|
await openCropPage(
|
|||
|
|
tester,
|
|||
|
|
fixtureJpg,
|
|||
|
|
aspectRatio: 16 / 9,
|
|||
|
|
maxOutputWidth: 160,
|
|||
|
|
onPopped: (value) => popped = value,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await tester.runAsync(() async {
|
|||
|
|
await tester.tap(find.text('完成'));
|
|||
|
|
await tester.pump();
|
|||
|
|
await Future.delayed(const Duration(seconds: 5));
|
|||
|
|
await tester.pump();
|
|||
|
|
});
|
|||
|
|
await tester.pumpAndSettle();
|
|||
|
|
|
|||
|
|
expect(popped, isNotNull);
|
|||
|
|
final decoded = img.decodeImage(File(popped!).readAsBytesSync());
|
|||
|
|
expect(decoded, isNotNull);
|
|||
|
|
expect(decoded!.width, 160);
|
|||
|
|
expect(decoded.height, 90);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
testWidgets('非图片源 → 提示不支持且不 pop', (tester) async {
|
|||
|
|
final bogus = File('${tmp.path}${Platform.pathSeparator}bogus.jpg');
|
|||
|
|
bogus.writeAsStringSync('definitely not an image');
|
|||
|
|
|
|||
|
|
final popped = <String?>[];
|
|||
|
|
await openCropPage(
|
|||
|
|
tester,
|
|||
|
|
bogus.path,
|
|||
|
|
aspectRatio: 3 / 1,
|
|||
|
|
onPopped: popped.add,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await tester.pump();
|
|||
|
|
expect(find.text('暂不支持该图片格式,请选择 JPG/PNG'), findsOneWidget);
|
|||
|
|
expect(popped, isEmpty);
|
|||
|
|
// 完成不可用(无裁剪可做)。
|
|||
|
|
final done = find.text('完成');
|
|||
|
|
expect(
|
|||
|
|
tester
|
|||
|
|
.widget<FilledButton>(
|
|||
|
|
find.ancestor(of: done, matching: find.byType(FilledButton)),
|
|||
|
|
)
|
|||
|
|
.enabled,
|
|||
|
|
isFalse,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
bogus.deleteSync();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
testWidgets('GIF 源 → 预检判不支持(防包内 png 兜底解码挂起)且不 pop',
|
|||
|
|
(tester) async {
|
|||
|
|
final popped = <String?>[];
|
|||
|
|
await openCropPage(
|
|||
|
|
tester,
|
|||
|
|
fixtureGif,
|
|||
|
|
aspectRatio: 16 / 9,
|
|||
|
|
onPopped: popped.add,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
await tester.pump();
|
|||
|
|
expect(find.text('暂不支持该图片格式,请选择 JPG/PNG'), findsOneWidget);
|
|||
|
|
expect(popped, isEmpty);
|
|||
|
|
// 完成不可用:包内若按 png 兜底解析 GIF,将永远到不了 ready。
|
|||
|
|
final done = find.text('完成');
|
|||
|
|
expect(
|
|||
|
|
tester
|
|||
|
|
.widget<FilledButton>(
|
|||
|
|
find.ancestor(of: done, matching: find.byType(FilledButton)),
|
|||
|
|
)
|
|||
|
|
.enabled,
|
|||
|
|
isFalse,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
}
|