JJ_MKVMaker/integration_test/windows_xplore_tools_shot_t...

124 lines
5.7 KiB
Dart

import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/i18n_controller.dart';
import 'package:jj_mkvmaker/core/batch_rename.dart';
import 'package:jj_mkvmaker/core/file_ops.dart';
import 'package:jj_mkvmaker/core/text_codec.dart';
import 'package:jj_mkvmaker/ui/batch_rename_dialog.dart';
import 'package:jj_mkvmaker/ui/explorer_grid.dart';
import 'package:jj_mkvmaker/ui/storage_page.dart';
import 'package:jj_mkvmaker/ui/text_viewer.dart';
import 'package:jj_mkvmaker/ui/theme.dart';
import 'package:path/path.dart' as p;
/// X-plore 비교 g · h · i · P3 (관리자 지시 10/18): 격자 보기 · 글 뷰어 · 일괄 이름 (날짜) · 저장 공간 그림을 PNG 로
/// (JJ_SHOT_DIR, 없으면 임시 폴더). 크기 412 · 960, 언어 ko · en. 시험 파일은 임시 폴더에만.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_xptools');
for (final lang in ['ko', 'en']) {
for (final (w, h) in [(412, 900), (960, 600)]) {
testWidgets('X-plore 도구 $lang ${w}x$h', (t) async {
t.view.physicalSize = Size(w.toDouble(), h.toDouble());
t.view.devicePixelRatio = 1;
addTearDown(t.view.reset);
await t.runAsync(() => i18n.apply(lang, save: false));
addTearDown(() => i18n.apply('ko', save: false));
final tmp = Directory.systemTemp.createTempSync('jj_xptshot_');
addTearDown(() => tmp.deleteSync(recursive: true));
// 그림 (색이 다른 작은 그림) · 동영상 이름 · 글 (CP949)
final pics = <String>[];
for (var i = 0; i < 7; i++) {
final im = img.Image(width: 64, height: 48);
img.fill(im, color: img.ColorRgb8(40 + i * 30, 120, 200 - i * 20));
if (i == 0) im.exif.exifIfd['DateTimeOriginal'] = img.IfdValueAscii('2024:05:01 12:34:56');
final f = File(p.join(tmp.path, 'IMG_${1000 + i}.jpg'))..writeAsBytesSync(img.encodeJpg(im));
pics.add(f.path);
}
File(p.join(tmp.path, 'clip.mkv')).writeAsBytesSync(List.filled(10, 0));
final txt = File(p.join(tmp.path, 'memo.txt'))
..writeAsBytesSync(encodeText(List.generate(30, (i) => '${i + 1}번째 줄 - 한글 메모 line $i').join('\n'), 'CP949').bytes);
for (final (n, size) in [('Videos', 900000), ('Photos', 400000), ('Docs', 120000), ('Music', 60000)]) {
final d = Directory(p.join(tmp.path, 'disk', n))..createSync(recursive: true);
File(p.join(d.path, 'big.bin')).writeAsBytesSync(List.filled(size, 0));
}
File(p.join(tmp.path, 'disk', 'loose.bin')).writeAsBytesSync(List.filled(80000, 0));
final shotKey = GlobalKey();
final nav = GlobalKey<NavigatorState>();
await t.runAsync(() => t.pumpWidget(RepaintBoundary(
key: shotKey,
child: MaterialApp(navigatorKey: nav, theme: buildTheme(), debugShowCheckedModeBanner: false, home: const Scaffold()),
)));
Future<void> settle([int n = 12]) async {
for (var i = 0; i < n; i++) {
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 60)));
await t.pump();
}
}
Future<void> shot(String name) async {
await settle(4);
final r = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final image = await t.runAsync(() => r.toImage());
final bytes = await t.runAsync(() => image!.toByteData(format: ui.ImageByteFormat.png));
Directory(out).createSync(recursive: true);
File(p.join(out, 'xptools_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
}
// g 격자 보기
final entries = [
for (final x in [...pics, p.join(tmp.path, 'clip.mkv'), txt.path])
FileEntry(x, isDir: false, size: File(x).lengthSync(), modified: DateTime(2026, 10, 18)),
];
nav.currentState!.push(MaterialPageRoute<void>(
builder: (_) => ExplorerGridPage(
title: 'Photos',
entries: entries,
imageExts: const ['jpg'],
videoThumb: (_) async => null,
onOpen: (_) async {},
),
));
await settle(20);
await shot('grid');
nav.currentState!.pop();
await settle();
// h 글 뷰어 (CP949 를 스스로 알아봄)
nav.currentState!.push(MaterialPageRoute<void>(builder: (_) => TextViewerPage(path: txt.path)));
await settle(20);
await shot('text');
nav.currentState!.pop();
await settle();
// i 일괄 이름 바꾸기 - [D] (사진은 찍은 때)
showBatchRename(nav.currentContext!, pics.take(4).toList(),
isDir: (_) => false, initial: const RenameRule(template: '[D]_[T]_[C]'));
await settle(30);
await shot('rename_date');
await t.tap(find.text(lang == 'ko' ? '취소' : 'Cancel').last);
await settle();
// P3 저장 공간 그림
nav.currentState!.push(MaterialPageRoute<void>(builder: (_) => StorageAnalysisPage(root: p.join(tmp.path, 'disk'))));
await settle(30);
await t.tap(find.text(lang == 'ko' ? '칸 그림' : 'Treemap'));
await settle();
await shot('storage_chart');
nav.currentState!.pop();
await settle();
await t.pumpWidget(const SizedBox());
await settle(4);
});
}
}
}