JJ_MKVMaker/integration_test/windows_ui2_shot_test.dart

114 lines
5.8 KiB
Dart
Raw Permalink 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:async';
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:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/i18n_controller.dart';
import 'package:jj_mkvmaker/app/subtitle_editor_controller.dart';
import 'package:jj_mkvmaker/core/encode_options.dart';
import 'package:jj_mkvmaker/core/languages.dart';
import 'package:jj_mkvmaker/core/models.dart';
import 'package:jj_mkvmaker/core/srt.dart';
import 'package:jj_mkvmaker/l10n/tr.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/subtitle_editor_page.dart';
import 'package:jj_mkvmaker/ui/theme.dart';
import 'package:jj_mkvmaker/ui/video_adjust_dialog.dart';
import 'package:path/path.dart' as p;
import 'support/temp_dir.dart';
/// 관리자 지시 (10/21, 28 찾음): ① 화면 · 색 보정 창 - 고른 비율 칩 ("세로 9:16") 은 ✓ 만 (그림 위에 체크가 겹치지 않게)
/// ② 자막 편집 화면 - 제목은 "자막 편집", 파일 이름은 그 아래 한 줄 (가운데를 줄여 끝 .srt 가 보이게). ko · ja × 412 · 960
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final out = Platform.environment['JJ_SHOT_DIR'] ?? '';
for (final lang in ['ko', 'ja']) {
for (final (w, h) in [(412, 900), (960, 600)]) {
testWidgets('화면 · 색 보정 칩 · 자막 편집 제목 $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 = testTempDir('jj_ui2shot_');
appTempInto(tmp);
final video = p.join(tmp.path, '아주 긴 이름의 시험용 동영상 파일 - 제1화 고화질 버전.mp4');
final ffmpeg = p.join(Directory.current.path, 'third_party', 'ffmpeg', 'windows', 'ffmpeg.exe');
final r = await t.runAsync(() => Process.run(ffmpeg, [
'-y', '-v', 'error', '-f', 'lavfi', '-i', 'testsrc2=size=640x360:rate=10:duration=2', //
'-c:v', 'libx264', '-pix_fmt', 'yuv420p', video,
]));
expect(r!.exitCode, 0, reason: '${r.stderr}');
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool.locate(), storage: DesktopStorageService()));
await t.runAsync(() async {
await c.init();
await c.addVideos([video]);
});
final shotKey = GlobalKey();
final navKey = GlobalKey<NavigatorState>();
await t.pumpWidget(MaterialApp(
theme: buildTheme(),
debugShowCheckedModeBanner: false,
navigatorKey: navKey,
builder: (context, child) => RepaintBoundary(key: shotKey, child: child),
home: const Scaffold(body: SizedBox.expand()),
));
Future<void> settle([int n = 10]) async {
for (var i = 0; i < n; i++) {
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 80)));
await t.pump();
}
}
Future<void> shot(String name) async {
await settle(3);
expect(t.takeException(), isNull, reason: '$name 넘침');
if (out.isEmpty) return;
final b = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final img = await t.runAsync(() => b.toImage());
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
Directory(out).createSync(recursive: true);
File(p.join(out, 'ui2_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
}
// ① 화면 · 색 보정 - "세로 9:16" 을 고른다
unawaited(showVideoAdjust(navKey.currentContext!, c));
await settle(20);
final portrait = find.text(FrameChoice.portrait.label);
await t.ensureVisible(portrait);
await t.tap(portrait);
await settle();
final chip = find.ancestor(of: portrait, matching: find.byType(ChoiceChip));
expect(t.widget<ChoiceChip>(chip).selected, isTrue);
expect(t.widget<ChoiceChip>(chip).showCheckmark, isFalse, reason: '그림 위에 체크가 겹치지 않게');
expect(find.descendant(of: chip, matching: find.byIcon(Icons.check)), findsOneWidget, reason: '✓ 만');
expect(find.descendant(of: chip, matching: find.byIcon(Icons.crop_portrait)), findsNothing);
await shot('1_adjust');
navKey.currentState!.popUntil((r) => r.isFirst);
await settle();
// ② 자막 편집 - 긴 파일 이름
final v = c.videos.single;
final s = SubtitleEntry.external(path: p.join(tmp.path, '아주 긴 이름의 시험용 동영상 파일 - 제1화 고화질 버전.ko.srt'), language: languageOf('ko'));
v.subtitles.add(s);
final editor = SubtitleEditorController(parseSrt('1\n00:00:01,000 --> 00:00:02,000\n안녕\n'), saveCharset: 'UTF-8');
unawaited(navKey.currentState!.push(MaterialPageRoute<void>(builder: (_) => SubtitleEditorPage(app: c, video: v, entry: s, editor: editor))));
await settle(12);
expect(find.text(tr('자막 편집')), findsOneWidget, reason: '제목은 "자막 편집" 만');
final name = find.textContaining('.srt');
expect(name, findsOneWidget, reason: '파일 이름은 아래 한 줄 - 끝 (.srt) 이 보임');
await shot('2_editor');
expect(t.widget<Text>(name).data!.endsWith('.srt'), isTrue, reason: '${t.widget<Text>(name).data}');
});
}
}
}