JJ_MKVMaker/test/photo_info_test.dart

88 lines
4.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/core/photo_info.dart';
import 'package:jj_mkvmaker/core/reader_sources.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/reader_page.dart';
import 'package:path/path.dart' as p;
/// 도 · 분 · 초 (image 패키지가 Rational 을 내보내지 않아 IfdValueRational 하나씩 만들어 잇는다)
img.IfdValueRational _dms(int d, int m, int s) =>
img.IfdValueRational(d, 1)..value.addAll([...img.IfdValueRational(m, 1).value, ...img.IfdValueRational(s, 1).value]);
/// Exif 가 든 JPEG (찍은 날 · 기기 · 위치)
Uint8List _jpeg({bool gps = true, bool south = false}) {
final im = img.Image(width: 30, height: 20);
im.exif.imageIfd['Make'] = 'Canon';
im.exif.imageIfd['Model'] = 'Canon EOS 5D';
im.exif.exifIfd['DateTimeOriginal'] = '2024:05:01 12:30:00';
if (gps) {
im.exif.gpsIfd[1] = img.IfdValueAscii(south ? 'S' : 'N');
im.exif.gpsIfd[2] = _dms(37, 30, 0);
im.exif.gpsIfd[3] = img.IfdValueAscii('E');
im.exif.gpsIfd[4] = _dms(127, 0, 36);
}
return Uint8List.fromList(img.encodeJpg(im));
}
/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치 (누를 때만, 보내지 않음)
void main() {
test('Exif 읽기: 찍은 날 (2024-05-01) · 기기 (Make 가 Model 앞에 있으면 한 번만) · 크기 · 위치 (도 · 분 · 초 → 도, 남은 음수)', () {
final i = readPhotoInfo(_jpeg());
expect(i.width, 30);
expect(i.height, 20);
expect(i.taken, '2024-05-01 12:30:00');
expect(i.device, 'Canon EOS 5D');
expect(i.latitude, closeTo(37.5, 1e-6));
expect(i.longitude, closeTo(127.01, 1e-6));
expect(i.locationText, '37.500000, 127.010000');
expect(i.hasExif, isTrue);
expect(readPhotoInfo(_jpeg(south: true)).latitude, closeTo(-37.5, 1e-6));
expect(readPhotoInfo(_jpeg(gps: false)).hasLocation, isFalse);
});
test('Exif 가 없는 그림 (PNG) 은 크기만, 깨진 바이트는 아무것도 없이 (예외 없음)', () {
final png = readPhotoInfo(Uint8List.fromList(img.encodePng(img.Image(width: 7, height: 9))));
expect((png.width, png.height), (7, 9));
expect(png.hasExif, isFalse);
final bad = readPhotoInfo(Uint8List.fromList([1, 2, 3]));
expect(bad.width, isNull);
expect(bad.hasExif, isFalse);
});
testWidgets('보기 화면 [사진 정보 (Exif)]: 찍은 날 · 기기 · 크기, 위치는 [위치 보기] 를 눌러야 보임 (링크 없음)', (tester) async {
tester.view.physicalSize = const Size(1400, 1200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final tmp = Directory.systemTemp.createTempSync('jj_photoinfo_');
addTearDown(() => tmp.deleteSync(recursive: true));
final f = File(p.join(tmp.path, 'trip.jpg'))..writeAsBytesSync(_jpeg());
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: ImageFilesSource([f.path], tempDir: tmp.path, title: 'x'))));
await tester.pump();
await tester.tap(find.byTooltip('사진 정보 (Exif)'));
for (var k = 0; k < 10; k++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
await tester.pump();
}
expect(find.text('2024-05-01 12:30:00'), findsOneWidget);
expect(find.text('Canon EOS 5D'), findsOneWidget);
expect(find.textContaining('30 × 20 픽셀'), findsOneWidget);
expect(find.textContaining('37.5'), findsNothing, reason: '위치는 누를 때만');
await tester.tap(find.text('위치 보기'));
await tester.pump();
expect(find.text('37.500000, 127.010000'), findsOneWidget);
expect(find.textContaining('http'), findsNothing, reason: '지도 · 인터넷 링크 없음');
await tester.tap(find.text('닫기'));
await tester.pumpAndSettle();
await tester.pumpWidget(const SizedBox());
});
}