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/live_sync.dart'; import 'package:jj_mkvmaker/app/settings.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/monitor_page.dart'; import 'package:jj_mkvmaker/ui/theme.dart'; import 'package:path/path.dart' as p; import 'support/temp_dir.dart'; /// 관리자 3a 조건 (10/20): 실시간 동기화의 지울지 묻는 창 - --delete-excluded 로 지워지는 거른 것을 따로 묶은 모양을 /// 크기 412 · 960, 언어 ko · en · ja 로 찍는다. 시험 폴더 안에서만 (확인 창은 [나중에] - 지우지 않음). /// 실행: flutter test integration_test\windows_live_excl_shot_test.dart -d windows (JJ_SHOT_DIR 에 PNG) void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final out = Platform.environment['JJ_SHOT_DIR'] ?? p.join(Directory.systemTemp.path, 'jj_shots_live_excl'); final rsync = p.join(Directory.current.path, 'third_party', 'rsync', 'windows', 'rsync.exe'); for (final lang in ['ko', 'en', 'ja']) { for (final (w, h) in [(412, 900), (960, 600)]) { testWidgets('지울지 묻는 창 $lang ${w}x$h', (t) async { if (!File(rsync).existsSync()) return markTestSkipped('앱에 든 rsync 가 없음'); 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_liveexcl_shot_'); final src = p.join(tmp.path, 's'), dst = p.join(tmp.path, 'd'); for (final (dir, rel) in [ (src, 'a.txt'), (dst, 'only.txt'), (dst, '오래된 메모.txt'), (dst, 'skip.tmp'), (dst, 'cache/x.tmp'), (dst, 'build/obj/very_long_intermediate_file_name.tmp'), ]) { File(p.join(dir, rel)) ..createSync(recursive: true) ..writeAsStringSync(rel); } final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); c.settings ..rsyncSource = 'custom' ..rsyncPath = rsync ..rsyncOptions = '-av --exclude=*.tmp --delete-excluded'; // 이미 --exclude 만으로 확인한 쌍에 --delete-excluded 를 더함 → 다시 묻는 창 (주황 이유 줄 · 거른 것 묶음) final pair = LiveSyncPair(src, dst, method: 'rsync', delete: true, deleteConfirmed: true, confirmedOptions: '-av --exclude=*.tmp'); c.settings.liveSyncPairs = [pair]; final live = LiveSync(c); addTearDown(live.dispose); await t.runAsync(() => live.syncNow(pair)); expect(live.toDeleteExcluded[LiveSync.keyOf(pair)], hasLength(3)); expect(live.reasked, contains(LiveSync.keyOf(pair))); final navKey = GlobalKey(); final shotKey = GlobalKey(); await t.pumpWidget(RepaintBoundary( key: shotKey, child: MaterialApp( theme: buildTheme(), debugShowCheckedModeBanner: false, navigatorKey: navKey, home: const Scaffold(body: SizedBox.expand())), )); unawaited(confirmLiveDeletes(navKey.currentContext!, live, pair)); for (var i = 0; i < 8; i++) { await t.runAsync(() => Future.delayed(const Duration(milliseconds: 60))); await t.pump(const Duration(milliseconds: 60)); } expect(t.takeException(), isNull, reason: '넘침'); 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, 'live_excl_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List()); navKey.currentState!.pop(); await t.pump(); expect(File(p.join(dst, 'skip.tmp')).existsSync(), isTrue, reason: '[나중에] - 지우지 않음'); }); } } }